Skip to content

Commit

Permalink
array literal evaluation
Browse files Browse the repository at this point in the history
  • Loading branch information
RoBaertschi committed Mar 28, 2024
1 parent 4f19549 commit eb42661
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 1 deletion.
20 changes: 20 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,23 @@ func (al *ArrayLiteral) String() string {

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()
}
7 changes: 6 additions & 1 deletion evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,13 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
if len(args) == 1 && isError(args[0]) {
return args[0]
}

return applyFunction(function, args)
case *ast.ArrayLiteral:
elements := evalExpressions(node.Elements, env)
if len(elements) == 1 && isError(elements[0]) {
return elements[0]
}
return &object.Array{Elements: elements}
case *ast.Boolean:
return nativeBoolToBooleanObject(node.Value)
case *ast.IntegerLiteral:
Expand Down
19 changes: 19 additions & 0 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,25 @@ 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 Array. got=%T (%+v)", 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 testEval(input string) object.Object {
l := lexer.New(input)
p := parser.New(l)
Expand Down
21 changes: 21 additions & 0 deletions object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
INTEGER_OBJ = "INTEGER"
BOOLEAN_OBJ = "BOOLEAN"
STRING_OBJ = "STRING"
ARRAY_OBJ = "ARRAY"
NULL_OBJ = "NULL"
RETURN_VALUE_OBJ = "RETURN_VALUE"
ERROR_OBJ = "ERROR"
Expand Down Expand Up @@ -98,3 +99,23 @@ 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()
}
16 changes: 16 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
PRODUCT // *
PREFIX // -X or !X
CALL // myFunction(X)
INDEX // array[index]
)

var precedences = map[token.TokenType]int{
Expand All @@ -29,6 +30,7 @@ var precedences = map[token.TokenType]int{
token.SLASH: PRODUCT,
token.ASTERISK: PRODUCT,
token.LPAREN: CALL,
token.LBRACKET: INDEX,
}

type (
Expand Down Expand Up @@ -73,6 +75,7 @@ func New(l *lexer.Lexer) *Parser {
p.registerInfix(token.LT, p.parseInfixExpression)
p.registerInfix(token.GT, p.parseInfixExpression)
p.registerInfix(token.LPAREN, p.parseCallExpression)
p.registerInfix(token.LBRACKET, p.parseIndexExpression)

p.nextToken()
p.nextToken()
Expand Down Expand Up @@ -433,6 +436,19 @@ func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression {
return list
}

func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression {
exp := &ast.IndexExpression{Token: p.curToken, Left: left}

p.nextToken()
exp.Index = p.parseExpression(LOWEST)

if !p.expectPeek(token.RBRACKET) {
return nil
}

return exp
}

func (p *Parser) ParseProgram() *ast.Program {
program := &ast.Program{}
program.Statements = []ast.Statement{}
Expand Down
31 changes: 31 additions & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,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 {
Expand Down Expand Up @@ -775,3 +783,26 @@ func TestParsingArrayLiterals(t *testing.T) {
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 *ast.IndexExpression. got=%T", stmt.Expression)
}

if !testIdentifier(t, indexExp.Left, "myArray") {
return
}

if !testInfixExpression(t, indexExp.Index, 1, "+", 1) {
return
}
}

0 comments on commit eb42661

Please sign in to comment.