diff --git a/pkg/evaluator/builtins.go b/pkg/evaluator/builtins.go index 659524a..d85f02f 100644 --- a/pkg/evaluator/builtins.go +++ b/pkg/evaluator/builtins.go @@ -1,6 +1,10 @@ package evaluator -import "code.jmug.me/jmug/interpreter-in-go/pkg/object" +import ( + "os" + + "code.jmug.me/jmug/interpreter-in-go/pkg/object" +) var builtins = map[string]*object.Builtin{ "len": { @@ -13,10 +17,96 @@ var builtins = map[string]*object.Builtin{ switch arg := args[0].(type) { case *object.String: return &object.Integer{Value: int64(len(arg.Value))} + case *object.Array: + return &object.Integer{Value: int64(len(arg.Elements))} default: return newError("argument to `len` not supported, got %s", args[0].Type()) } }, }, + "first": { + 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 `first` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + if len(arr.Elements) > 0 { + return arr.Elements[0] + } + + return _NULL + }, + }, + "last": { + 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 `last` must be ARRAY, got %s", + args[0].Type()) + } + + arr := args[0].(*object.Array) + if len(arr.Elements) > 0 { + return arr.Elements[len(arr.Elements)-1] + } + return _NULL + }, + }, + "rest": { + 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 `rest` must be ARRAY, got %s", + args[0].Type()) + } + arr := args[0].(*object.Array).Elements + arrLen := len(arr) + if arrLen > 0 { + newArr := make([]object.Object, arrLen-1) + copy(newArr, arr[1:]) + return &object.Array{Elements: newArr} + } + return _NULL + }, + }, + "push": { + 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).Elements + arrLen := len(arr) + newArr := make([]object.Object, arrLen+1) + copy(newArr, arr) + newArr[arrLen] = args[1] + return &object.Array{Elements: newArr} + }, + }, + "exit": { + Fn: func(args ...object.Object) object.Object { + if len(args) != 0 { + return newError("exit takes no arguments...") + } + os.Exit(0) + return nil // Make the compiler happy. + }, + }, } diff --git a/pkg/evaluator/evaluator.go b/pkg/evaluator/evaluator.go index f910a3e..82edc36 100644 --- a/pkg/evaluator/evaluator.go +++ b/pkg/evaluator/evaluator.go @@ -77,10 +77,20 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return applyFunction(fn, args) case *ast.ArrayLiteral: els := evalExpressions(node.Elements, env) - if len(els) == 1 && isError(els[1]) { + if len(els) == 1 && isError(els[0]) { return els[0] } return &object.Array{Elements: els} + case *ast.IndexExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + index := Eval(node.Index, env) + if isError(index) { + return index + } + return evalIndexExpression(left, index) } return nil } @@ -237,6 +247,24 @@ func evalExpressions( return res } +func evalIndexExpression(left, index object.Object) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + default: + return newError("index operator not supported: %s", left.Type()) + } +} + +func evalArrayIndexExpression(arrayObj, indexObj object.Object) object.Object { + array := arrayObj.(*object.Array).Elements + index := indexObj.(*object.Integer).Value + if index < 0 || index >= int64(len(array)) { + return _NULL + } + return array[index] +} + func applyFunction(fnObj object.Object, args []object.Object) object.Object { switch fn := fnObj.(type) { case *object.Function: diff --git a/pkg/evaluator/evaluator_test.go b/pkg/evaluator/evaluator_test.go index 51c311c..938c47f 100644 --- a/pkg/evaluator/evaluator_test.go +++ b/pkg/evaluator/evaluator_test.go @@ -348,6 +348,64 @@ func TestArrayLiterals(t *testing.T) { testIntegerObject(t, result.Elements[2], 6) } +func TestArrayIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "[1, 2, 3][0]", + 1, + }, + { + "[1, 2, 3][1]", + 2, + }, + { + "[1, 2, 3][2]", + 3, + }, + { + "let i = 0; [1][i];", + 1, + }, + { + "[1, 2, 3][1 + 1];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[2];", + 3, + }, + { + "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + 6, + }, + { + "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + 2, + }, + { + "[1, 2, 3][3]", + nil, + }, + { + "[1, 2, 3][-1]", + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + func testNullObject(t *testing.T, obj object.Object) bool { if obj != _NULL { t.Errorf("object is not NULL. got=%T (%+v)", obj, obj) diff --git a/pkg/token/token.go b/pkg/token/token.go index 3864c3b..0ccca72 100644 --- a/pkg/token/token.go +++ b/pkg/token/token.go @@ -26,6 +26,7 @@ const ( // Delimiters COMMA = "," SEMICOLON = ";" + COLON = ":" LPAREN = "(" RPAREN = ")"