umx_compiler

UMX virtual machine "Monkey" interpreter / bytecode compiler
git clone git://bsandro.tech/umx_compiler
Log | Files | Refs

commit c197eb43b8f485f0c9874a208e2eaf8d91eae6bd
parent b9ce320fafd72790fdc6a5d95b92f6f3587edb96
Author: bsandro <email@bsandro.tech>
Date:   Mon, 27 Jun 2022 01:21:20 +0300

"len" builtin function (strings)

Diffstat:
Aeval/builtins.go | 19+++++++++++++++++++
Meval/eval.go | 27++++++++++++++++++---------
Meval/eval_test.go | 29+++++++++++++++++++++++++++++
Mobject/object.go | 9+++++++++
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" }