umx_compiler

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

commit 0e082d47a6552b29857ab3fd1e54e60a73ebe61f
parent bfcf0bffcc71d9f795de9f86033b844b12931378
Author: bsandro <email@bsandro.tech>
Date:   Tue, 28 Jun 2022 01:57:18 +0300

array indices operator support

Diffstat:
Mast/ast.go | 20++++++++++++++++++++
Meval/eval.go | 35+++++++++++++++++++++++++++++++++++
Meval/eval_test.go | 41+++++++++++++++++++++++++++++++++++++++++
Mobject/object.go | 18++++++++++++++++++
Mparser/parser.go | 14++++++++++++++
Mparser/parser_test.go | 21+++++++++++++++++++++
6 files changed, 149 insertions(+), 0 deletions(-)

diff --git a/ast/ast.go b/ast/ast.go @@ -299,3 +299,23 @@ func (al *ArrayLiteral) String() string { out.WriteString("]") return out.String() } + +type IndexExpression struct { + Token token.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/eval/eval.go b/eval/eval.go @@ -91,6 +91,22 @@ func Eval(node ast.Node, ctx *object.Context) object.Object { return applyFunction(fn, args) case *ast.StringLiteral: return &object.String{Value: node.Value} + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, ctx) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} + case *ast.IndexExpression: + left := Eval(node.Left, ctx) + if isError(left) { + return left + } + index := Eval(node.Index, ctx) + if isError(index) { + return index + } + return evalIndexExpression(left, index) } return nil } @@ -296,3 +312,22 @@ func evalStringInfixExpression(operator string, left, right object.Object, ctx * rightVal := right.(*object.String).Value return &object.String{Value: leftVal + rightVal} } + +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(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] +} diff --git a/eval/eval_test.go b/eval/eval_test.go @@ -282,3 +282,44 @@ func TestBuiltinFunctions(t *testing.T) { } } } + +func TestArrayLiterals(t *testing.T) { + input := "[1,2*3,4*5]" + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("object is not Array, got %T (%+v)", evaluated, evaluated) + } + if len(result.Elements) != 3 { + t.Fatalf("array elements count is wrong") + } + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 6) + testIntegerObject(t, result.Elements[2], 20) +} + +func TestArrayIndexExpression(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"[1,2,3][0]", 1}, + {"[10,20,30][1]", 20}, + {"[1,2,3][2]", 3}, + {"let i=0;[1][i];", 1}, + {"[1,2,3][1+1]", 3}, + {"let myarray=[10,20,30]; myarray[1];", 20}, + {"let myarray=[10,20,30]; myarray[0]+myarray[1]+myarray[2];", 60}, + {"[10,20,30][5]", nil}, + {"[10,20,30][-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) + } + } +} diff --git a/object/object.go b/object/object.go @@ -19,6 +19,7 @@ const ( FUNCTION_OBJ = "FUNCTION" STRING_OBJ = "STRING" BUILTIN_OBJ = "BUILTIN" + ARRAY_OBJ = "ARRAY" ) type Object interface { @@ -93,3 +94,20 @@ type Builtin struct { func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } func (b *Builtin) Inspect() string { return "builtin function" } + +type Array struct { + Elements []Object +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + 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 @@ -17,6 +17,7 @@ const ( PRODUCT // * PREFIX // -X or !X CALL // myFunction(x) + INDEX // array[index] ) var precedences = map[token.TokenType]int{ @@ -29,6 +30,7 @@ var precedences = map[token.TokenType]int{ token.SLASH: PRODUCT, token.ASTERISK: PRODUCT, token.LPAREN: CALL, + token.LBRACKET: INDEX, } type prefixParseFn func() ast.Expression @@ -79,6 +81,8 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.STRING, p.parseStringLiteral) p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) + p.registerInfix(token.LBRACKET, p.parseIndexExpression) + return p } @@ -379,3 +383,13 @@ func (p *Parser) parseArrayLiteral() ast.Expression { array.Elements = p.parseExpressionList(token.RBRACKET) return array } + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + expr := &ast.IndexExpression{Token: p.curToken, Left: left} + p.nextToken() + expr.Index = p.parseExpression(LOWEST) + if !p.expectPeek(token.RBRACKET) { + return nil + } + return expr +} diff --git a/parser/parser_test.go b/parser/parser_test.go @@ -355,6 +355,8 @@ func TestOperatorPrecedenceParsing(t *testing.T) { {"a+add(b*c)+d", "((a + add((b * c))) + d)"}, {"add(a+b+c*d/f+g)", "add((((a + b) + ((c * d) / f)) + g))"}, {"add(a,b,1,2*3,4+5,add(6,7+8))", "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 + 8)))"}, + {"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 { @@ -606,3 +608,22 @@ func TestParsingArrayLiterals(t *testing.T) { testInfixExpression(t, array.Elements[1], 20, "*", 30) testInfixExpression(t, array.Elements[2], 40, "+", 50) } + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1+2]" + l := lexer.New(input) + p := New(l) + prg := p.ParseProgram() + checkParserErrors(t, p) + statement, ok := prg.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := statement.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("expression is not IndexExpression") + } + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + if !testInfixExpression(t, indexExp.Index, 1, "+", 2) { + return + } +}