diff --git a/evaluator/builtins.go b/evaluator/builtins.go index f028c05..065e210 100644 --- a/evaluator/builtins.go +++ b/evaluator/builtins.go @@ -3,6 +3,7 @@ package evaluator import ( "fmt" "monkey/object" + "unicode/utf8" ) var builtins = map[string]*object.Builtin{ @@ -16,7 +17,7 @@ var builtins = map[string]*object.Builtin{ case *object.Array: return &object.Integer{Value: int64(len(arg.Elements))} case *object.String: - return &object.Integer{Value: int64(len(arg.Value))} + return &object.Integer{Value: int64(utf8.RuneCountInString(arg.Value))} default: return newError("argument to `len` not supported, got %s", args[0].Type()) @@ -30,7 +31,7 @@ var builtins = map[string]*object.Builtin{ return newError("wrong number of arguments. got=%d, want=1", len(args)) } if args[0].Type() != object.ARRAY_OBJ { - return newError("argument to `first` must be Array, got %s", args[0].Type()) + return newError("argument to `first` must be ARRAY, got %s", args[0].Type()) } arr := args[0].(*object.Array) @@ -48,7 +49,7 @@ var builtins = map[string]*object.Builtin{ return newError("wrong number of arguments. got=%d, want=1", len(args)) } if args[0].Type() != object.ARRAY_OBJ { - return newError("argument to `last` must be Array, got %s", args[0].Type()) + return newError("argument to `last` must be ARRAY, got %s", args[0].Type()) } arr := args[0].(*object.Array) @@ -64,36 +65,18 @@ var builtins = map[string]*object.Builtin{ "rest": &object.Builtin{ Fn: func(args ...object.Object) object.Object { if len(args) != 1 { - return newError("wrong number of arguments. got=%d, want=1", len(args)) + return newError("wrong number of arguments. got=%d, want=1", + len(args)) } if args[0].Type() != object.ARRAY_OBJ { - return newError("argument to `rest` must be Array, got %s", args[0].Type()) - } - - arr := args[0].(*object.Array) - length := len(arr.Elements) - - newElements := make([]object.Object, length+1) - copy(newElements, arr.Elements) - newElements[length] = args[1] - - return &object.Array{Elements: newElements} - }, - }, - - "push": &object.Builtin{ - Fn: func(args ...object.Object) object.Object { - if len(args) != 1 { - return newError("wrong number of arguments. got=%d, want=1", len(args)) - } - if args[0].Type() != object.ARRAY_OBJ { - return newError("argument to `push` must be Array, got %s", args[0].Type()) + return newError("argument to `rest` must be ARRAY, got %s", + args[0].Type()) } arr := args[0].(*object.Array) length := len(arr.Elements) if length > 0 { - newElements := make([]object.Object, length-1) + newElements := make([]object.Object, length-1, length-1) copy(newElements, arr.Elements[1:length]) return &object.Array{Elements: newElements} } @@ -102,6 +85,28 @@ var builtins = map[string]*object.Builtin{ }, }, + "push": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", + len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `push` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + + newElements := make([]object.Object, length+1, length+1) + copy(newElements, arr.Elements) + newElements[length] = args[1] + + return &object.Array{Elements: newElements} + }, + }, + "puts": &object.Builtin{ Fn: func(args ...object.Object) object.Object { for _, arg := range args { diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index a69fb31..2e35f8b 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -330,7 +330,7 @@ func TestStringConcatenation(t *testing.T) { } } -func TestBuiltinFunction(t *testing.T) { +func TestBuiltinFunctions(t *testing.T) { tests := []struct { input string expected interface{} @@ -340,6 +340,20 @@ func TestBuiltinFunction(t *testing.T) { {`len("hello world")`, 11}, {`len(1)`, "argument to `len` not supported, got INTEGER"}, {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + {`len("∑")`, 1}, + {`len([1, 2, 3])`, 3}, + {`len([])`, 0}, + {`first([1, 2, 3])`, 1}, + {`first([])`, nil}, + {`first(1)`, "argument to `first` must be ARRAY, got INTEGER"}, + {`last([1, 2, 3])`, 3}, + {`last([])`, nil}, + {`last(1)`, "argument to `last` must be ARRAY, got INTEGER"}, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, nil}, + {`push([], 1)`, []int{1}}, + {`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"}, + {`puts("Hello World")`, nil}, } for _, tt := range tests { @@ -351,11 +365,13 @@ func TestBuiltinFunction(t *testing.T) { case string: errObj, ok := evaluated.(*object.Error) if !ok { - t.Errorf("object is not Error. got=%T (%+v)", evaluated, evaluated) + t.Errorf("object is not Error. got=%T (%+v)", + evaluated, evaluated) continue } if errObj.Message != expected { - t.Errorf("wrong error message. expected=%q, got=%q", expected, errObj.Message) + t.Errorf("wrong error message. expected=%q, got=%q", + expected, errObj.Message) } } }