commit c197eb43b8f485f0c9874a208e2eaf8d91eae6bd
parent b9ce320fafd72790fdc6a5d95b92f6f3587edb96
Author: bsandro <email@bsandro.tech>
Date: Mon, 27 Jun 2022 01:21:20 +0300
"len" builtin function (strings)
Diffstat:
4 files changed, 75 insertions(+), 9 deletions(-)
diff --git a/eval/builtins.go b/eval/builtins.go
@@ -0,0 +1,19 @@
+package eval
+
+import "interp/object"
+
+var builtins = map[string]*object.Builtin{
+ "len": &object.Builtin{
+ Fn: func(args ...object.Object) object.Object {
+ if len(args) != 1 {
+ return newError("wrong number of arguments (%d instead of %d)", len(args), 1)
+ }
+ switch arg := args[0].(type) {
+ case *object.String:
+ return &object.Integer{Value: int64(len(arg.Value))}
+ default:
+ return newError("unsupported `len` argument, got %s", args[0].Type())
+ }
+ },
+ },
+}
diff --git a/eval/eval.go b/eval/eval.go
@@ -235,11 +235,15 @@ func evalBlockStatement(block *ast.BlockStatement, ctx *object.Context) object.O
}
func evalIdentifier(node *ast.Identifier, ctx *object.Context) object.Object {
- value, ok := ctx.Get(node.Value)
- if !ok {
- return newError("identifier not found: " + node.Value)
+ if value, ok := ctx.Get(node.Value); ok {
+ return value
}
- return value
+
+ if builtin, ok := builtins[node.Value]; ok {
+ return builtin
+ }
+
+ return newError("identifier not found: " + node.Value)
}
func evalExpressions(expressions []ast.Expression, ctx *object.Context) []object.Object {
@@ -255,13 +259,18 @@ func evalExpressions(expressions []ast.Expression, ctx *object.Context) []object
}
func applyFunction(fn object.Object, args []object.Object) object.Object {
- function, ok := fn.(*object.Function)
- if !ok {
+ switch function := fn.(type) {
+ case *object.Function:
+ childCtx := childFunctionContext(function, args)
+ evaluated := Eval(function.Body, childCtx)
+ return unwrapReturnValue(evaluated)
+
+ case *object.Builtin:
+ return function.Fn(args...)
+
+ default:
return newError("not a function: %s", fn.Type())
}
- childCtx := childFunctionContext(function, args)
- evaluated := Eval(function.Body, childCtx)
- return unwrapReturnValue(evaluated)
}
func childFunctionContext(fn *object.Function, args []object.Object) *object.Context {
diff --git a/eval/eval_test.go b/eval/eval_test.go
@@ -253,3 +253,32 @@ func TestStringsConcat(t *testing.T) {
t.Fatalf("string value is wrong: %s", str.Value)
}
}
+
+func TestBuiltinFunctions(t *testing.T) {
+ tests := []struct {
+ input string
+ expected interface{}
+ }{
+ {`len("")`, 0},
+ {`len("four")`, 4},
+ {`len(1)`, "unsupported `len` argument, got INTEGER"},
+ {`len("one", "two")`, "wrong number of arguments (2 instead of 1)"},
+ }
+
+ for _, tt := range tests {
+ evaluated := testEval(tt.input)
+ switch expected := tt.expected.(type) {
+ case int:
+ testIntegerObject(t, evaluated, int64(expected))
+ case string:
+ errObj, ok := evaluated.(*object.Error)
+ if !ok {
+ t.Errorf("object is not Error")
+ continue
+ }
+ if errObj.Message != expected {
+ t.Errorf("wrong error message")
+ }
+ }
+ }
+}
diff --git a/object/object.go b/object/object.go
@@ -8,6 +8,7 @@ import (
)
type ObjectType string
+type BuiltinFunction func(args ...Object) Object
const (
INTEGER_OBJ = "INTEGER"
@@ -17,6 +18,7 @@ const (
ERROR_OBJ = "ERROR"
FUNCTION_OBJ = "FUNCTION"
STRING_OBJ = "STRING"
+ BUILTIN_OBJ = "BUILTIN"
)
type Object interface {
@@ -84,3 +86,10 @@ type String struct {
func (s *String) Type() ObjectType { return STRING_OBJ }
func (s *String) Inspect() string { return s.Value }
+
+type Builtin struct {
+ Fn BuiltinFunction
+}
+
+func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ }
+func (b *Builtin) Inspect() string { return "builtin function" }