Skip to content

Commit

Permalink
feat: add array literals and array indexing
Browse files Browse the repository at this point in the history
  • Loading branch information
vighnesh153 committed Jan 26, 2024
1 parent 667009b commit 1fe29aa
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 9 deletions.
37 changes: 37 additions & 0 deletions golang-tools/interpreters/monkey-lang/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,40 @@ type StringLiteral struct {
func (sl *StringLiteral) expressionNode() {}
func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal }
func (sl *StringLiteral) String() string { return sl.Token.Literal }

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
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()
}
35 changes: 35 additions & 0 deletions golang-tools/interpreters/monkey-lang/evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
}

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.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)
}

return nil
Expand Down Expand Up @@ -293,3 +309,22 @@ func unwrapReturnValue(obj object.Object) object.Object {
}
return obj
}

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]
}
77 changes: 77 additions & 0 deletions golang-tools/interpreters/monkey-lang/evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,83 @@ 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 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 testNullObject(t *testing.T, obj object.Object) bool {
if obj != NULL {
t.Errorf("object is not NULL. got=%T (%+v)", obj, obj)
Expand Down
4 changes: 4 additions & 0 deletions golang-tools/interpreters/monkey-lang/lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ func (l *Lexer) NextToken() token.Token {
t = token.NewToken(token.LEFT_CURLY_BRACE, l.currentCharacter)
case '}':
t = token.NewToken(token.RIGHT_CURLY_BRACE, l.currentCharacter)
case '[':
t = token.NewToken(token.LEFT_BRACKET, l.currentCharacter)
case ']':
t = token.NewToken(token.RIGHT_BRACKET, l.currentCharacter)
case '"':
t.Literal = l.readString()
t.Type = token.STRING
Expand Down
10 changes: 10 additions & 0 deletions golang-tools/interpreters/monkey-lang/lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ func TestNextToken(t *testing.T) {
10 == 10;
10 != 9;
"foobar"
"foo bar"
[1, 2];
`

tests := []token.Token{
Expand Down Expand Up @@ -115,6 +118,13 @@ func TestNextToken(t *testing.T) {
{Type: token.STRING, Literal: "foobar"},
{Type: token.STRING, Literal: "foo bar"},

{Type: token.LEFT_BRACKET, Literal: "["},
{Type: token.INTEGER, Literal: "1"},
{Type: token.COMMA, Literal: ","},
{Type: token.INTEGER, Literal: "2"},
{Type: token.RIGHT_BRACKET, Literal: "]"},
{Type: token.SEMICOLON, Literal: ";"},

{Type: token.EOF, Literal: ""},
}

Expand Down
18 changes: 18 additions & 0 deletions golang-tools/interpreters/monkey-lang/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"
FUNCTION_OBJ = "FUNCTION"
RETURN_VALUE_OBJ = "RETURN_VALUE"
Expand Down Expand Up @@ -94,3 +95,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()
}
42 changes: 33 additions & 9 deletions golang-tools/interpreters/monkey-lang/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
FUNCTION_CALL // myFunction(X)
INDEX // array[index] or string[index]
)

var precedences = map[token.TokenType]int{
Expand All @@ -29,6 +30,7 @@ var precedences = map[token.TokenType]int{
token.FORWARD_SLASH: PRODUCT,
token.ASTERISK: PRODUCT,
token.LEFT_PARENTHESIS: FUNCTION_CALL,
token.LEFT_BRACKET: INDEX,
}

type Parser struct {
Expand Down Expand Up @@ -60,6 +62,7 @@ func NewParser(l *lexer.Lexer) *Parser {
p.registerPrefixParseFn(token.IF, p.parseIfExpression)
p.registerPrefixParseFn(token.FUNCTION, p.parseFunctionLiteral)
p.registerPrefixParseFn(token.STRING, p.parseStringLiteral)
p.registerPrefixParseFn(token.LEFT_BRACKET, p.parseArrayLiteral)

p.infixParseFunctions = make(map[token.TokenType]infixParseFunction)
p.registerInfixParseFn(token.PLUS, p.parseInfixExpression)
Expand All @@ -71,6 +74,7 @@ func NewParser(l *lexer.Lexer) *Parser {
p.registerInfixParseFn(token.LESS_THAN, p.parseInfixExpression)
p.registerInfixParseFn(token.GREATER_THAN, p.parseInfixExpression)
p.registerInfixParseFn(token.LEFT_PARENTHESIS, p.parseCallExpression)
p.registerInfixParseFn(token.LEFT_BRACKET, p.parseIndexExpression)

// Read two tokens, so currentToken and peekToken are both set
p.nextToken()
Expand Down Expand Up @@ -409,34 +413,54 @@ func (p *Parser) parseFunctionParameters() []*ast.Identifier {

func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression {
exp := &ast.CallExpression{Token: p.currentToken, Function: function}
exp.Arguments = p.parseCallArguments()
exp.Arguments = p.parseExpressionList(token.RIGHT_PARENTHESIS)
return exp
}

func (p *Parser) parseCallArguments() []ast.Expression {
args := []ast.Expression{}
if p.peekTokenIs(token.RIGHT_PARENTHESIS) {
func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression {
list := []ast.Expression{}

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.expectPeek(token.RIGHT_PARENTHESIS) {
if !p.expectPeek(end) {
return nil
}

return args
return list
}

func (p *Parser) parseStringLiteral() ast.Expression {
return &ast.StringLiteral{Token: p.currentToken, Value: p.currentToken.Literal}
}

func (p *Parser) parseArrayLiteral() ast.Expression {
array := &ast.ArrayLiteral{Token: p.currentToken}
array.Elements = p.parseExpressionList(token.RIGHT_BRACKET)
return array
}

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.expectPeek(token.RIGHT_BRACKET) {
return nil
}

return exp
}
Loading

0 comments on commit 1fe29aa

Please sign in to comment.