Skip to content

Commit

Permalink
Merge pull request #55 from DustTheory/54-new-array-literal-syntax
Browse files Browse the repository at this point in the history
New array literals syntax
  • Loading branch information
DustTheory authored Oct 3, 2023
2 parents 0336100 + 5733880 commit adc23e9
Show file tree
Hide file tree
Showing 21 changed files with 173 additions and 103 deletions.
22 changes: 16 additions & 6 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ast

import (
"bytes"
"sort"
"strings"

"github.com/0xM-D/interpreter/token"
Expand Down Expand Up @@ -219,10 +220,10 @@ func (al *ArrayLiteral) String() string {
for _, a := range al.Elements {
elements = append(elements, a.String())
}

out.WriteString("[")
out.WriteString(al.Type.String())
out.WriteString("{")
out.WriteString(strings.Join(elements, ", "))
out.WriteString("]")
out.WriteString("}")

return out.String()
}
Expand Down Expand Up @@ -262,9 +263,18 @@ func (hl *HashLiteral) TokenValue() token.Token { return hl.Token }
func (hl *HashLiteral) String() string {
var out bytes.Buffer

keys := make([]Expression, 0, len(hl.Pairs))
for k := range hl.Pairs {
keys = append(keys, k)
}

sort.SliceStable(keys, func(i, j int) bool {
return hl.Pairs[keys[i]].String() < hl.Pairs[keys[j]].String()
})

pairs := []string{}
for key, value := range hl.Pairs {
pairs = append(pairs, key.String()+":"+value.String())
for _, key := range keys {
pairs = append(pairs, key.String()+":"+hl.Pairs[key].String())
}

out.WriteString("{")
Expand Down Expand Up @@ -358,7 +368,7 @@ func (at ArrayType) typeNode() {}
func (at ArrayType) TokenLiteral() string { return at.Token.Literal }
func (at ArrayType) TokenValue() token.Token { return at.Token }

func (at ArrayType) String() string { return at.ElementType.String() + "[]" }
func (at ArrayType) String() string { return "[]" + at.ElementType.String() }

func (nt NamedType) typeNode() {}
func (nt NamedType) TokenLiteral() string { return nt.Token.Literal }
Expand Down
2 changes: 2 additions & 0 deletions ast/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ type StringLiteral struct {
type ArrayLiteral struct {
Token token.Token
Elements []Expression
Type
}

type IndexExpression struct {
Expand All @@ -142,6 +143,7 @@ type HashLiteral struct {
}

type TypedDeclarationStatement struct {
Token token.Token
DeclarationStatement
}

Expand Down
2 changes: 1 addition & 1 deletion evaluator/tests/array_literals_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func TestArrayLiterals(t *testing.T) {
input := "[1, 2 * 2, 3 + 3]"
input := "[]int{1, 2 * 2, 3 + 3}"

evaluated := testEval(input)
result, ok := evaluated.(*object.Array)
Expand Down
2 changes: 1 addition & 1 deletion evaluator/tests/error_handling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func TestErrorHandling(t *testing.T) {
"Incorrect parameter count for function(int, int) -> int fun. expected=2, got=0",
},
{
`[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].deleteee(1, 3)`,
`[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}.deleteee(1, 3)`,
"Member deleteee does not exist on int[]",
},
}
Expand Down
22 changes: 11 additions & 11 deletions evaluator/tests/index_operator_exp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,47 @@ func TestArrayIndexExpressions(t *testing.T) {
expected interface{}
}{
{
"[1, 2, 3][0]",
"[]int{1, 2, 3}[0]",
1,
},
{
"[1, 2, 3][1]",
"[]int{1, 2, 3}[1]",
2,
},
{
"[1, 2, 3][2]",
"[]int{1, 2, 3}[2]",
3,
},
{
"let i = 0; [1][i];",
"let i = 0; []int{1}[i];",
1,
},
{
"[1, 2, 3][1 + 1];",
"[]int{1, 2, 3}[1 + 1];",
3,
},
{
"let myArray = [1, 2, 3]; myArray[2];",
"let myArray = []int{1, 2, 3}; myArray[2];",
3,
},
{
"let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];",
"let myArray = []int{1, 2, 3}; myArray[0] + myArray[1] + myArray[2];",
6,
},
{
"let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]",
"let myArray = []int{1, 2, 3}; let i = myArray[0]; myArray[i]",
2,
},
{
"[1, 2, 3][3]",
"[]int{1, 2, 3}[3]",
nil,
},
{
"[1, 2, 3][-1]",
"[]int{1, 2, 3}[-1]",
nil,
},
{
"let a = [1]; a[0] = 2; a[0]",
"let a = []int{1}; a[0] = 2; a[0]",
2,
},
}
Expand Down
30 changes: 15 additions & 15 deletions evaluator/tests/type_builtins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@ func TestTypeBuiltins(t *testing.T) {
input string
expected interface{}
}{
{"let arr = [1, 2, 3]; arr.size();", 3},
{"let arr = []; arr.size();", 0},
{"[1, 2, 3, 4, 5].size();", 5},
{"let arr = []int{1, 2, 3}; arr.size();", 3},
{"let arr = []int{}; arr.size();", 0},
{"[]int{1, 2, 3, 4, 5}.size();", 5},
{`str := "abcdef"; str.length();`, 6},
{`const string str = ""; str.length();`, 0},
{`"bleh".length()`, 4},
{"1.toString()", "1"},
{"(123*456).toString()", "56088"},
{"let arr = [1, 2, 3]; arr.push(4).size()", 4},
{"[].push(1).size();", 1},
{"[1, 2, 3].delete(1, 5).size();", 1},
{`["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"].delete(1, 3);`, []string{"1", "5", "6", "7", "8", "9", "10"}},
{`[].pushMultiple("0", 10)`, []string{"0", "0", "0", "0", "0", "0", "0", "0", "0", "0"}},
{`[1, 2, 3].pushMultiple(0, 10).size()`, 13},
{`[1, 2, 3].slice(0, 3).size()`, 3},
{`[1, 2, 3].slice(0, 2).size()`, 2},
{`[1, 2, 3].slice(1, 2).size()`, 2},
{`[1, 2, 3].slice(1, 100).size()`, 2},
{`[1, 2, 3].slice(2, 1).size()`, 1},
{`[1, 2, 3].slice(2, 0).size()`, 0},
{"let arr = []int{1, 2, 3}; arr.push(4).size()", 4},
{"[]int{}.push(1).size();", 1},
{"[]int{1, 2, 3}.delete(1, 5).size();", 1},
{`[]int{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}.delete(1, 3);`, []string{"1", "5", "6", "7", "8", "9", "10"}},
{`[]int{}.pushMultiple("0", 10)`, []string{"0", "0", "0", "0", "0", "0", "0", "0", "0", "0"}},
{`[]int{1, 2, 3}.pushMultiple(0, 10).size()`, 13},
{`[]int{1, 2, 3}.slice(0, 3).size()`, 3},
{`[]int{1, 2, 3}.slice(0, 2).size()`, 2},
{`[]int{1, 2, 3}.slice(1, 2).size()`, 2},
{`[]int{1, 2, 3}.slice(1, 100).size()`, 2},
{`[]int{1, 2, 3}.slice(2, 1).size()`, 1},
{`[]int{1, 2, 3}.slice(2, 0).size()`, 0},
}
for _, tt := range tests {
switch expected := tt.expected.(type) {
Expand Down
2 changes: 1 addition & 1 deletion evaluator/tests/typed_declaration_statement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestTypedDeclarationStatement(t *testing.T) {
}{
{"int a = 5; a;", 5},
{"string a = \"testmmm\"; a;", "testmmm"},
{"const int[] a = [1, 2, 3, 4]; let b = a; b;", []string{"1", "2", "3", "4"}},
{"const []int a = []int{1, 2, 3, 4}; let b = a; b;", []string{"1", "2", "3", "4"}},
{"bool a = true; let b = !a; b;", false},
{"const function(int, int)->int sum = fn(a: int, b: int) -> int { return a + b; }; sum", ExpectedFunction{
"fn(a, b) {" + "\n" +
Expand Down
17 changes: 13 additions & 4 deletions parser/parse_array_literal.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@ import (

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

array.Type = p.parseType()
if array.Type == nil {
return nil
}

return p.parseArrayLiteralRest(array)
}

func (p *Parser) parseEmptyArray() ast.Expression {
return &ast.ArrayLiteral{Token: p.curToken, Elements: []ast.Expression{}}
func (p *Parser) parseArrayLiteralRest(array *ast.ArrayLiteral) ast.Expression {
if !p.expectPeek(token.LBRACE) {
return nil
}
array.Elements = p.parseExpressionList(token.RBRACE)
return array
}
16 changes: 4 additions & 12 deletions parser/parse_assignment_declaration_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,9 @@ import (
)

func (p *Parser) parseAssignmentDeclarationStatement() *ast.AssignmentDeclarationStatement {
stmt := &ast.DeclarationStatement{Token: p.curToken}

stmt.Name = p.parseIdentifier().(*ast.Identifier)
p.nextToken()
p.nextToken()

stmt.Value = p.parseExpression(LOWEST)

if p.peekTokenIs(token.SEMICOLON) {
p.nextToken()
declStmt := p.parseDeclarationStatement(token.DECL_ASSIGN)
if declStmt == nil {
return nil
}

return &ast.AssignmentDeclarationStatement{DeclarationStatement: *stmt}
return &ast.AssignmentDeclarationStatement{DeclarationStatement: *declStmt}
}
28 changes: 28 additions & 0 deletions parser/parse_declaration_statement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package parser

import (
"github.com/0xM-D/interpreter/ast"
"github.com/0xM-D/interpreter/token"
)

func (p *Parser) parseDeclarationStatement(assignToken token.TokenType) *ast.DeclarationStatement {
stmt := &ast.DeclarationStatement{Token: p.curToken}

stmt.Name = p.parseIdentifier().(*ast.Identifier)

if !p.peekTokenIs(assignToken) {
p.newError(stmt, "invalid token in declaration statement. expected=%q got=%q", assignToken, p.peekToken.Literal)
return nil
}

p.nextToken()
p.nextToken()

stmt.Value = p.parseExpression(LOWEST)

if p.peekTokenIs(token.SEMICOLON) {
p.nextToken()
}

return stmt
}
5 changes: 4 additions & 1 deletion parser/parse_expression_statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement {

func (p *Parser) parseExpression(precedence int) ast.Expression {
prefix := p.prefixParseFns[p.curToken.Type]

if prefix == nil {
p.newError(&ast.ExpressionStatement{Token: p.curToken}, "no prefix parse function for %s found", p.curToken.Literal)
return nil
}
leftExp := prefix()

return p.parseExpressionRest(precedence, leftExp)
}

func (p *Parser) parseExpressionRest(precedence int, leftExp ast.Expression) ast.Expression {
for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() {
infix := p.infixParseFns[p.peekToken.Type]

Expand Down
38 changes: 34 additions & 4 deletions parser/parse_statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,47 @@ import (
)

func (p *Parser) parseStatement() ast.Statement {

if p.curToken.Type == token.ARRAY_TYPE {
startToken := p.curToken
arrayType := p.parseType()
if arrayType == nil {
return nil
}
if p.peekTokenIs(token.LBRACE) {
arrayLiteral := p.parseArrayLiteralRest(&ast.ArrayLiteral{Token: startToken, Type: arrayType})
if arrayLiteral == nil {
return nil
}
expr := p.parseExpressionRest(LOWEST, arrayLiteral)
if expr == nil {
return nil
}
exprStmt := &ast.ExpressionStatement{
Token: p.curToken,
Expression: expr,
}
if p.peekTokenIs(token.SEMICOLON) {
p.nextToken()
}
return exprStmt
} else {
p.nextToken()
return p.parseTypedDeclarationStatement(arrayType)
}
}

switch p.curToken.Type {
case token.LET:
return p.parseLetStatement()
case token.RETURN:
return p.parseReturnStatement()
case token.CONST:
return p.parseTypedDeclarationStatement()
return p.parseTypedDeclarationStatement(nil)
case token.MAP_TYPE:
return p.parseTypedDeclarationStatement()
return p.parseTypedDeclarationStatement(nil)
case token.FUNCTION_TYPE:
return p.parseTypedDeclarationStatement()
return p.parseTypedDeclarationStatement(nil)
case token.FOR:
return p.parseForStatement()
case token.IDENT:
Expand All @@ -28,7 +58,7 @@ func (p *Parser) parseStatement() ast.Statement {
case token.ARRAY_TYPE:
fallthrough
case token.LBRACE:
return p.parseTypedDeclarationStatement()
return p.parseTypedDeclarationStatement(nil)
}
fallthrough
default:
Expand Down
19 changes: 10 additions & 9 deletions parser/parse_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ func (p *Parser) parseType() ast.Type {

var result ast.Type

for p.curToken.Type == token.ARRAY_TYPE {
p.nextToken()
elementType := p.parseType()
if elementType == nil {
return nil
}
return ast.ArrayType{Token: p.curToken, ElementType: elementType}
}

switch p.curToken.Type {
case token.MAP_TYPE:
result = p.parseMapType()
Expand All @@ -17,17 +26,9 @@ func (p *Parser) parseType() ast.Type {
case token.IDENT:
typeIdentifier := p.parseIdentifier().(*ast.Identifier)
result = ast.NamedType{Token: p.curToken, TypeName: *typeIdentifier}
}

if result == nil {
default:
return nil
}

for p.peekToken.Type == token.ARRAY_TYPE {
p.nextToken()
result = ast.ArrayType{Token: p.curToken, ElementType: result}
}

return result
}

Expand Down
Loading

0 comments on commit adc23e9

Please sign in to comment.