commit 0e082d47a6552b29857ab3fd1e54e60a73ebe61f
parent bfcf0bffcc71d9f795de9f86033b844b12931378
Author: bsandro <email@bsandro.tech>
Date: Tue, 28 Jun 2022 01:57:18 +0300
array indices operator support
Diffstat:
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
+ }
+}