diff --git a/ast/ast.go b/ast/ast.go index b495a40..f0480d8 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -267,3 +267,45 @@ func (fl *FunctionLiteral) String() string { return out.String() } + +type ArrayLiteral struct { + Token token.Token // the '[' token + Elements []Expression +} + +func (al *ArrayLiteral) expressionNode() {} +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } +func (al *ArrayLiteral) String() string { + var out bytes.Buffer + + var elements []string + for _, el := range al.Elements { + elements = append(elements, el.String()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type IndexExpression struct { + Token token.Token // The [ token + Left Expression + Index Expression +} + +func (ie *IndexExpression) expressionNode() {} +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IndexExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString("[") + out.WriteString(ie.Index.String()) + out.WriteString("])") + + return out.String() +} diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 87a61c1..4eee2fd 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -71,6 +71,17 @@ func Eval(node ast.Node, env *object.Environment) object.Object { } return applyFunction(function, args) + 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) case *ast.Identifier: return evalIdentifier(node, env) case *ast.FunctionLiteral: @@ -83,6 +94,12 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return &object.String{Value: node.Value} case *ast.Boolean: return nativeBoolToBooleanObject(node.Value) + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, env) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} } return nil @@ -238,6 +255,27 @@ func evalStringInfixExpression(operator string, left, right object.Object) objec return &object.String{Value: leftVal + rightVal} } +func evalIndexExpression(left, index object.Object) object.Object { + switch { + case left.Type() == object.ArrayObj && index.Type() == object.IntegerObj: + return evalArrayIndexExpression(left, index) + default: + return newError("index operator not supported: %s", left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return Null + } + + return arrayObject.Elements[idx] +} + func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object { if val, ok := env.Get(node.Value); ok { return val diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index cd32ebb..bea6039 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -293,6 +293,82 @@ func TestBuiltinFunctions(t *testing.T) { } } +func TestArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not %T. got=%T (%+v)", object.Array{}, evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("array has wrong num of elements. got=%d", len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 4) + 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 testEval(input string) object.Object { l := lexer.New(input) p := parser.New(l) diff --git a/lexer/lexer.go b/lexer/lexer.go index 2073869..0e57684 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -65,6 +65,10 @@ func (l *Lexer) NextToken() token.Token { tok = newToken(token.LeftBrace, l.ch) case '}': tok = newToken(token.RightBrace, l.ch) + case '[': + tok = newToken(token.LeftBracket, l.ch) + case ']': + tok = newToken(token.RightBracket, l.ch) case '"': tok.Type = token.String tok.Literal = l.readString() diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go index 19342c8..26423c4 100644 --- a/lexer/lexer_test.go +++ b/lexer/lexer_test.go @@ -28,6 +28,7 @@ func TestNextToken(t *testing.T) { 10 != 9; "foobar" "foo bar" + [1, 2]; ` tests := []struct { @@ -109,6 +110,12 @@ func TestNextToken(t *testing.T) { {token.Semicolon, ";"}, {token.String, "foobar"}, {token.String, "foo bar"}, + {token.LeftBracket, "["}, + {token.Int, "1"}, + {token.Comma, ","}, + {token.Int, "2"}, + {token.RightBracket, "]"}, + {token.Semicolon, ";"}, {token.EOF, ""}, } diff --git a/object/object.go b/object/object.go index 615a8c3..d997024 100644 --- a/object/object.go +++ b/object/object.go @@ -19,6 +19,7 @@ const ( FunctionObj = "Function" StringObj = "String" BuiltinObj = "Builtin" + ArrayObj = "Array" ) type Object interface { @@ -99,3 +100,23 @@ type Builtin struct { func (b *Builtin) Type() ObjectType { return BuiltinObj } func (b *Builtin) Inspect() string { return "builtin function" } + +type Array struct { + Elements []Object +} + +func (ao *Array) Type() ObjectType { return ArrayObj } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + var elements []string + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} diff --git a/parser/parser.go b/parser/parser.go index 2619e79..ac954e1 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -18,6 +18,7 @@ const ( Product // * Prefix // -X or !X Call // myFunction(X) + Index // array[index] ) var precedences = map[token.TokenType]int{ @@ -30,6 +31,7 @@ var precedences = map[token.TokenType]int{ token.Slash: Product, token.Asterisk: Product, token.LeftParen: Call, + token.LeftBracket: Index, } type ( @@ -62,6 +64,7 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.If, p.parseIfExpression) p.registerPrefix(token.Function, p.parseFunctionLiteral) p.registerPrefix(token.String, p.parseStringLiteral) + p.registerPrefix(token.LeftBracket, p.parseArrayLiteral) p.infixParseFns = make(map[token.TokenType]infixParseFn) p.registerInfix(token.Plus, p.parseInfixExpression) @@ -73,6 +76,7 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(token.LessThan, p.parseInfixExpression) p.registerInfix(token.GreaterThan, p.parseInfixExpression) p.registerInfix(token.LeftParen, p.parseCallExpression) + p.registerInfix(token.LeftBracket, p.parseIndexExpression) // Read two tokens, so currentToken and peekToken are both set p.nextToken() @@ -337,49 +341,70 @@ func (p *Parser) parseStringLiteral() ast.Expression { return &ast.StringLiteral{Token: p.currentToken, Value: p.currentToken.Literal} } -// Infix expressions - -func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { - expression := &ast.InfixExpression{ - Token: p.currentToken, - Operator: p.currentToken.Literal, - Left: left, - } +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.currentToken} - precedence := p.currentPrecedence() - p.nextToken() - expression.Right = p.parseExpression(precedence) - return expression -} + array.Elements = p.parseExpressionList(token.RightBracket) -func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { - exp := &ast.CallExpression{Token: p.currentToken, Function: function} - exp.Arguments = p.parseCallArguments() - return exp + return array } -func (p *Parser) parseCallArguments() []ast.Expression { - var args []ast.Expression +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + var list []ast.Expression - if p.peekTokenIs(token.RightParen) { + if p.peekTokenIs(end) { p.nextToken() - return args + return list } p.nextToken() - args = append(args, p.parseExpression(Lowest)) + list = append(list, p.parseExpression(Lowest)) for p.peekTokenIs(token.Comma) { p.nextToken() p.nextToken() - args = append(args, p.parseExpression(Lowest)) + list = append(list, p.parseExpression(Lowest)) } - if !p.setNextTokenIf(token.RightParen) { + if !p.setNextTokenIf(end) { return nil } - return args + return list +} + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.currentToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(Lowest) + + if !p.setNextTokenIf(token.RightBracket) { + return nil + } + + return exp +} + +// Infix expressions + +func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { + expression := &ast.InfixExpression{ + Token: p.currentToken, + Operator: p.currentToken.Literal, + Left: left, + } + + precedence := p.currentPrecedence() + p.nextToken() + expression.Right = p.parseExpression(precedence) + return expression +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.currentToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RightParen) + return exp } // Token diff --git a/parser/parser_test.go b/parser/parser_test.go index 8434f57..77e5ab8 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -58,7 +58,7 @@ func TestReturnStatement(t *testing.T) { for _, stmt := range program.Statements { returnStmt, ok := stmt.(*ast.ReturnStatement) if !ok { - t.Errorf("stmt not *ast.returnStatement. got=%T", stmt) + t.Errorf("stmt not %T. got=%T", &ast.ReturnStatement{}, stmt) continue } @@ -79,12 +79,12 @@ func TestIdentifierExpression(t *testing.T) { stmt, ok := program.Statements[0].(*ast.ExpressionStatement) if !ok { - t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", program.Statements[0]) + t.Fatalf("program.Statements[0] is not %T. got=%T", &ast.ExpressionStatement{}, program.Statements[0]) } identifier, ok := stmt.Expression.(*ast.Identifier) if !ok { - t.Fatalf("expression not *ast.Identifier. got=%T", stmt.Expression) + t.Fatalf("expression not %T. got=%T", &ast.Identifier{}, stmt.Expression) } if identifier.Value != "foobar" { @@ -106,12 +106,12 @@ func TestIntegerLiteralExpression(t *testing.T) { stmt, ok := program.Statements[0].(*ast.ExpressionStatement) if !ok { - t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", program.Statements[0]) + t.Fatalf("program.Statements[0] is not %T. got=%T", &ast.ExpressionStatement{}, program.Statements[0]) } literal, ok := stmt.Expression.(*ast.IntegerLiteral) if !ok { - t.Fatalf("exp not *ast.IntegerLiteral. got=%T", stmt.Expression) + t.Fatalf("exp not %T. got=%T", &ast.IntegerLiteral{}, stmt.Expression) } if literal.Value != 5 { @@ -144,12 +144,12 @@ func TestParsingPrefixExpressions(t *testing.T) { stmt, ok := program.Statements[0].(*ast.ExpressionStatement) if !ok { - t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", program.Statements[0]) + t.Fatalf("program.Statements[0] is not %T. got=%T", &ast.ExpressionStatement{}, program.Statements[0]) } exp, ok := stmt.Expression.(*ast.PrefixExpression) if !ok { - t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression) + t.Fatalf("stmt is not %T. got=%T", &ast.PrefixExpression{}, stmt.Expression) } if exp.Operator != tt.operator { t.Fatalf("exp.Operator is not '%s'. got=%s", tt.operator, exp.Operator) @@ -189,7 +189,7 @@ func TestParsingInfixExpressions(t *testing.T) { stmt, ok := program.Statements[0].(*ast.ExpressionStatement) if !ok { - t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", program.Statements[0]) + t.Fatalf("program.Statements[0] is not %T. got=%T", &ast.ExpressionStatement{}, program.Statements[0]) } if !testInfixExpression(t, stmt.Expression, tt.leftValue, tt.operator, tt.rightValue) { @@ -291,6 +291,14 @@ func TestOperatorPrecedenceParsing(t *testing.T) { "add(a + b + c * d / f + g)", "add((((a + b) + ((c * d) / f)) + g))", }, + { + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + }, + { + "add(a * b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + }, } for _, tt := range tests { @@ -314,12 +322,12 @@ func TestIfExpression(t *testing.T) { stmt, ok := program.Statements[0].(*ast.ExpressionStatement) if !ok { - t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", program.Statements[0]) + t.Fatalf("program.Statements[0] is not %T. got=%T", &ast.ExpressionStatement{}, program.Statements[0]) } exp, ok := stmt.Expression.(*ast.IfExpression) if !ok { - t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + t.Fatalf("stmt.Expression is not %T. got=%T", &ast.IfExpression{}, stmt.Expression) } if !testInfixExpression(t, exp.Condition, "x", "<", "y") { @@ -332,7 +340,7 @@ func TestIfExpression(t *testing.T) { consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) if !ok { - t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", exp.Consequence.Statements[0]) + t.Fatalf("Statements[0] is not %T. got=%T", &ast.ExpressionStatement{}, exp.Consequence.Statements[0]) } if !testIdentifier(t, consequence.Expression, "x") { @@ -356,13 +364,13 @@ func TestIfElseExpression(t *testing.T) { stmt, ok := program.Statements[0].(*ast.ExpressionStatement) if !ok { - t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + t.Fatalf("program.Statements[0] is not %T. got=%T", &ast.ExpressionStatement{}, program.Statements[0]) } exp, ok := stmt.Expression.(*ast.IfExpression) if !ok { - t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + t.Fatalf("stmt.Expression is not %T. got=%T", &ast.IfExpression{}, stmt.Expression) } if !testInfixExpression(t, exp.Condition, "x", "<", "y") { @@ -376,7 +384,7 @@ func TestIfElseExpression(t *testing.T) { consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) if !ok { - t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + t.Fatalf("Statements[0] is not %T. got=%T", &ast.ExpressionStatement{}, exp.Consequence.Statements[0]) } @@ -391,7 +399,7 @@ func TestIfElseExpression(t *testing.T) { alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) if !ok { - t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + t.Fatalf("Statements[0] is not %T. got=%T", &ast.ExpressionStatement{}, exp.Alternative.Statements[0]) } @@ -411,12 +419,12 @@ func TestFunctionLiteralParsing(t *testing.T) { stmt, ok := program.Statements[0].(*ast.ExpressionStatement) if !ok { - t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", program.Statements[0]) + t.Fatalf("program.Statements[0] is not %T. got=%T", &ast.ExpressionStatement{}, program.Statements[0]) } function, ok := stmt.Expression.(*ast.FunctionLiteral) if !ok { - t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", stmt.Expression) + t.Fatalf("stmt.Expression is not %T. got=%T", &ast.FunctionLiteral{}, stmt.Expression) } if len(function.Parameters) != 2 { @@ -432,7 +440,7 @@ func TestFunctionLiteralParsing(t *testing.T) { bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) if !ok { - t.Fatalf("function body stmt is not ast.ExpressionStatement. got=%T", function.Body.Statements[0]) + t.Fatalf("function body stmt is not %T. got=%T", &ast.ExpressionStatement{}, function.Body.Statements[0]) } testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") @@ -475,12 +483,12 @@ func TestCallExpressionParsing(t *testing.T) { stmt, ok := program.Statements[0].(*ast.ExpressionStatement) if !ok { - t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", program.Statements[0]) + t.Fatalf("stmt is not %T. got=%T", &ast.ExpressionStatement{}, program.Statements[0]) } exp, ok := stmt.Expression.(*ast.CallExpression) if !ok { - t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", stmt.Expression) + t.Fatalf("stmt.Expression is not %T. got=%T", &ast.CallExpression{}, stmt.Expression) } if !testIdentifier(t, exp.Function, "add") { @@ -507,7 +515,7 @@ func TestStringLiteralExpression(t *testing.T) { stmt := program.Statements[0].(*ast.ExpressionStatement) literal, ok := stmt.Expression.(*ast.StringLiteral) if !ok { - t.Fatalf("exp not *ast.StringLiteral. got=%T", stmt.Expression) + t.Fatalf("exp not %T. got=%T", &ast.StringLiteral{}, stmt.Expression) } if literal.Value != "hello world" { @@ -533,7 +541,7 @@ func testLetStatement(t *testing.T, s ast.Statement, name string) bool { letStmt, ok := s.(*ast.LetStatement) if !ok { - t.Errorf("s not *ast.LetStatement. got=%T", s) + t.Errorf("s not %T. got=%T", &ast.LetStatement{}, s) return false } @@ -550,10 +558,56 @@ func testLetStatement(t *testing.T, s ast.Statement, name string) bool { return true } +func TestParsingArrayLiteral(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("exp not %T. got=%T", &ast.ArrayLiteral{}, stmt.Expression) + } + + if len(array.Elements) != 3 { + t.Fatalf("len(array.Elements) not 3. got=%d", len(array.Elements)) + } + + testIntegerLiteral(t, array.Elements[0], 1) + testInfixExpression(t, array.Elements[1], 2, "*", 2) + testInfixExpression(t, array.Elements[2], 3, "+", 3) +} + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1 + 1]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("exp not %T. got=%T", &ast.IndexExpression{}, stmt.Expression) + } + + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + + if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { + return + } +} + func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { int, ok := il.(*ast.IntegerLiteral) if !ok { - t.Errorf("il not *ast.IntegerLiteral. got=%T", il) + t.Errorf("il not %T. got=%T", &ast.IntegerLiteral{}, il) return false } @@ -573,7 +627,7 @@ func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { identifier, ok := exp.(*ast.Identifier) if !ok { - t.Errorf("exp not *ast.Identifier. got=%T", exp) + t.Errorf("exp not %T. got=%T", &ast.Identifier{}, exp) return false } @@ -593,7 +647,7 @@ func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { bo, ok := exp.(*ast.Boolean) if !ok { - t.Errorf("exp not *ast.Boolean. got=%T", exp) + t.Errorf("exp not %T. got=%T", &ast.Boolean{}, exp) return false } @@ -628,7 +682,7 @@ func testLiteralExpression(t *testing.T, exp ast.Expression, expected interface{ func testInfixExpression(t *testing.T, exp ast.Expression, left interface{}, operator string, right interface{}) bool { opExp, ok := exp.(*ast.InfixExpression) if !ok { - t.Errorf("exp is not ast.OperatorExpression. got=%T('%s')", exp, exp) + t.Errorf("exp is not %T. got=%T('%s')", &ast.InfixExpression{}, exp, exp) return false } diff --git a/token/token.go b/token/token.go index cfc18a4..d70234b 100644 --- a/token/token.go +++ b/token/token.go @@ -33,10 +33,12 @@ const ( Comma = "," Semicolon = ";" - LeftParen = "(" - RightParen = ")" - LeftBrace = "{" - RightBrace = "}" + LeftParen = "(" + RightParen = ")" + LeftBrace = "{" + RightBrace = "}" + LeftBracket = "[" + RightBracket = "]" // Keywords Function = "Function"