diff --git a/evaluator/builtins.go b/evaluator/builtins.go index bd48dff..4c34536 100644 --- a/evaluator/builtins.go +++ b/evaluator/builtins.go @@ -3,7 +3,11 @@ package evaluator import "github.com/kitasuke/monkey-go/object" const ( - BuiltinFuncNameLen = "len" + BuiltinFuncNameLen = "len" + BuiltinFuncNameFirst = "first" + BuiltinFuncNameLast = "last" + BuiltinFuncNameRest = "rest" + BuiltinFuncNamePush = "push" ) var builtins = map[string]*object.Builtin{ @@ -14,11 +18,84 @@ var builtins = map[string]*object.Builtin{ } switch arg := args[0].(type) { + case *object.Array: + return &object.Integer{Value: int64(len(arg.Elements))} case *object.String: return &object.Integer{Value: int64(len(arg.Value))} default: - return newError("argument to `len` not supported, got %s", args[0].Type()) + return newError("argument to %q not supported, got %s", BuiltinFuncNameLen, args[0].Type()) } }, }, + BuiltinFuncNameFirst: { + 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.ArrayObj { + return newError("argument to %q must be %s, got %s", BuiltinFuncNameFirst, object.ArrayObj, args[0].Type()) + } + + arr := args[0].(*object.Array) + if len(arr.Elements) > 0 { + return arr.Elements[0] + } + return Null + }, + }, + BuiltinFuncNameLast: { + 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.ArrayObj { + return newError("argument to %q must be %s, got %s", BuiltinFuncNameLast, object.ArrayObj, args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + return arr.Elements[length-1] + } + return Null + }, + }, + BuiltinFuncNameRest: { + 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.ArrayObj { + return newError("argument to %q must be %s, got %s", BuiltinFuncNameRest, object.ArrayObj, args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + newElements := make([]object.Object, length-1, length-1) + copy(newElements, arr.Elements[1:length]) + return &object.Array{Elements: newElements} + } + return Null + }, + }, + BuiltinFuncNamePush: { + 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.ArrayObj { + return newError("argument to %q must be %s, got %s", BuiltinFuncNamePush, object.ArrayObj, 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} + }, + }, } diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index bea6039..8a1adb4 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -269,8 +269,23 @@ func TestBuiltinFunctions(t *testing.T) { {`len("")`, 0}, {`len("four")`, 4}, {`len("hello world")`, 11}, - {`len(1)`, fmt.Sprintf("argument to `len` not supported, got %s", object.IntegerObj)}, + {`len(1)`, fmt.Sprintf("argument to %q not supported, got %s", BuiltinFuncNameLen, object.IntegerObj)}, {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + {`first([1, 2, 3])`, 1}, + {`first([])`, nil}, + {`first(1)`, fmt.Sprintf("argument to %q must be %s, got %s", BuiltinFuncNameFirst, object.ArrayObj, object.IntegerObj)}, + {`first(1, 2)`, "wrong number of arguments. got=2, want=1"}, + {`last([1, 2, 3])`, 3}, + {`last([])`, nil}, + {`last(1)`, fmt.Sprintf("argument to %q must be %s, got %s", BuiltinFuncNameLast, object.ArrayObj, object.IntegerObj)}, + {`last(1, 2)`, "wrong number of arguments. got=2, want=1"}, + {`rest([1, 2, 3])`, []int{2, 3}}, + {`rest([])`, nil}, + {`rest(1)`, fmt.Sprintf("argument to %q must be %s, got %s", BuiltinFuncNameRest, object.ArrayObj, object.IntegerObj)}, + {`rest(1, 2)`, "wrong number of arguments. got=2, want=1"}, + {`push([], 1)`, []int{1}}, + {`push(1, 2)`, fmt.Sprintf("argument to %q must be %s, got %s", BuiltinFuncNamePush, object.ArrayObj, object.IntegerObj)}, + {`push(1)`, "wrong number of arguments. got=1, want=2"}, } for _, tt := range tests { @@ -279,6 +294,8 @@ func TestBuiltinFunctions(t *testing.T) { switch expected := tt.expected.(type) { case int: testIntegerObject(t, evaluated, int64(expected)) + case nil: + testNullObject(t, evaluated) case string: errObj, ok := evaluated.(*object.Error) if !ok { @@ -289,6 +306,21 @@ func TestBuiltinFunctions(t *testing.T) { if errObj.Message != expected { t.Errorf("wrong error message. expected=%q, got=%q", expected, errObj.Message) } + case []int: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not %s. got=%T (%+v)", object.ArrayObj, evaluated, evaluated) + continue + } + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + continue + } + + for i, element := range expected { + testIntegerObject(t, array.Elements[i], int64(element)) + } } } }