Skip to content

Commit

Permalink
Support Array literal
Browse files Browse the repository at this point in the history
  • Loading branch information
kitasuke committed Nov 11, 2018
1 parent bf70adf commit 194cbbc
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 55 deletions.
42 changes: 42 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
38 changes: 38 additions & 0 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
76 changes: 76 additions & 0 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
7 changes: 7 additions & 0 deletions lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func TestNextToken(t *testing.T) {
10 != 9;
"foobar"
"foo bar"
[1, 2];
`

tests := []struct {
Expand Down Expand Up @@ -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, ""},
}

Expand Down
21 changes: 21 additions & 0 deletions object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
FunctionObj = "Function"
StringObj = "String"
BuiltinObj = "Builtin"
ArrayObj = "Array"
)

type Object interface {
Expand Down Expand Up @@ -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()
}
75 changes: 50 additions & 25 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
Product // *
Prefix // -X or !X
Call // myFunction(X)
Index // array[index]
)

var precedences = map[token.TokenType]int{
Expand All @@ -30,6 +31,7 @@ var precedences = map[token.TokenType]int{
token.Slash: Product,
token.Asterisk: Product,
token.LeftParen: Call,
token.LeftBracket: Index,
}

type (
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 194cbbc

Please # to comment.