diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 27538fa..a497112 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -27,7 +27,7 @@ func Eval(exp ast.Expression, env *object.Environment) object.Object { } return evalPrefixAtom(expt.Operator, right) case *ast.Symbol: - return evalSymbol(expt) + return evalSymbol(expt, env) case *ast.CommandObject: return evalCommandObject(expt, env) case *ast.IfExpression: @@ -102,12 +102,17 @@ func evalExclamationPrefix(right object.Object) object.Object { } } -func evalSymbol(symbol *ast.Symbol) object.Object { +func evalSymbol(symbol *ast.Symbol, env *object.Environment) object.Object { builtintFunc, ok := builtins[symbol.Value] if ok { return builtintFunc } + obj, ok := env.Get(symbol.Value) + if ok { + return obj + } + return newError("symbol not found: %s", symbol.Value) } diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index eb2b2cb..d432a72 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -399,3 +399,61 @@ func TestSetExpression(t *testing.T) { }) } } + +func TestMultipleExpressions(t *testing.T) { + tests := []struct { + name string + input string + expected int64 + }{ + { + name: "multiple atoms", + input: ` + [ + 1, + 2 + ]`, + expected: 2, + }, + { + name: "multiple commands", + input: ` + [ + { + "command": { + "symbol": "+", + "args": [1, 2] + } + }, + { + "command": { + "symbol": "-", + "args": [1, 2] + } + } + ]`, + expected: -1, + }, + { + name: "multiple commands with set expression", + input: ` + [ + { + "set": { + "var": "$x", + "val": 10 + } + }, + "$x" + ]`, + expected: 10, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + evaluated := testEval(t, tt.input) + testIntegerObject(t, evaluated, tt.expected) + }) + } +} diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go index bdb0ec5..d0c875d 100644 --- a/lexer/lexer_test.go +++ b/lexer/lexer_test.go @@ -533,3 +533,194 @@ func TestSingleProgram(t *testing.T) { }) } } + +func TestMultiplePrograms(t *testing.T) { + tests := []struct { + name string + input string + expected []token.Token + }{ + { + name: "multiple atoms", + input: ` + [ + 1, + 2 + ] + `, + expected: []token.Token{ + {Type: token.LBRACKET, Literal: "["}, + {Type: token.INT, Literal: "1"}, + {Type: token.COMMA, Literal: ","}, + {Type: token.INT, Literal: "2"}, + {Type: token.RBRACKET, Literal: "]"}, + {Type: token.EOF, Literal: ""}, + }, + }, + { + name: "multiple commands", + input: ` + [ + { + "command": { + "symbol": "+", + "args": [1, 2] + } + }, + { + "command": { + "symbol": "-", + "args": [3, 4] + } + } + ] + `, + expected: []token.Token{ + {Type: token.LBRACKET, Literal: "["}, + {Type: token.LBRACE, Literal: "{"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COMMAND, Literal: "command"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COLON, Literal: ":"}, + {Type: token.LBRACE, Literal: "{"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.SYMBOLKEY, Literal: "symbol"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COLON, Literal: ":"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.SYMBOL, Literal: "+"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COMMA, Literal: ","}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.ARGS, Literal: "args"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COLON, Literal: ":"}, + {Type: token.LBRACKET, Literal: "["}, + {Type: token.INT, Literal: "1"}, + {Type: token.COMMA, Literal: ","}, + {Type: token.INT, Literal: "2"}, + {Type: token.RBRACKET, Literal: "]"}, + {Type: token.RBRACE, Literal: "}"}, + {Type: token.RBRACE, Literal: "}"}, + {Type: token.COMMA, Literal: ","}, + {Type: token.LBRACE, Literal: "{"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COMMAND, Literal: "command"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COLON, Literal: ":"}, + {Type: token.LBRACE, Literal: "{"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.SYMBOLKEY, Literal: "symbol"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COLON, Literal: ":"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.SYMBOL, Literal: "-"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COMMA, Literal: ","}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.ARGS, Literal: "args"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COLON, Literal: ":"}, + {Type: token.LBRACKET, Literal: "["}, + {Type: token.INT, Literal: "3"}, + {Type: token.COMMA, Literal: ","}, + {Type: token.INT, Literal: "4"}, + {Type: token.RBRACKET, Literal: "]"}, + {Type: token.RBRACE, Literal: "}"}, + {Type: token.RBRACE, Literal: "}"}, + {Type: token.RBRACKET, Literal: "]"}, + {Type: token.EOF, Literal: ""}, + }, + }, + { + name: "read symbol from environment", + input: ` + [ + { + "set": { + "var": "$x", + "val": { + "command": { + "symbol": "+", + "args": [1, 2] + } + } + } + }, + "$x" + ] + `, + expected: []token.Token{ + {Type: token.LBRACKET, Literal: "["}, + {Type: token.LBRACE, Literal: "{"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.SET, Literal: "set"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COLON, Literal: ":"}, + {Type: token.LBRACE, Literal: "{"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.VAR, Literal: "var"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COLON, Literal: ":"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.DOLLAR, Literal: "$"}, + {Type: token.SYMBOL, Literal: "x"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COMMA, Literal: ","}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.VAL, Literal: "val"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COLON, Literal: ":"}, + {Type: token.LBRACE, Literal: "{"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COMMAND, Literal: "command"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COLON, Literal: ":"}, + {Type: token.LBRACE, Literal: "{"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.SYMBOLKEY, Literal: "symbol"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COLON, Literal: ":"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.SYMBOL, Literal: "+"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COMMA, Literal: ","}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.ARGS, Literal: "args"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.COLON, Literal: ":"}, + {Type: token.LBRACKET, Literal: "["}, + {Type: token.INT, Literal: "1"}, + {Type: token.COMMA, Literal: ","}, + {Type: token.INT, Literal: "2"}, + {Type: token.RBRACKET, Literal: "]"}, + {Type: token.RBRACE, Literal: "}"}, + {Type: token.RBRACE, Literal: "}"}, + {Type: token.RBRACE, Literal: "}"}, + {Type: token.RBRACE, Literal: "}"}, + {Type: token.COMMA, Literal: ","}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.DOLLAR, Literal: "$"}, + {Type: token.SYMBOL, Literal: "x"}, + {Type: token.DOUBLE_QUOTE, Literal: "\""}, + {Type: token.RBRACKET, Literal: "]"}, + {Type: token.EOF, Literal: ""}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := New(tt.input) + for i, expected := range tt.expected { + tok := l.NextToken() + if tok.Type != expected.Type { + t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q", i, expected.Type, tok.Type) + } + if tok.Literal != expected.Literal { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", i, expected.Literal, tok.Literal) + } + } + }) + } +} diff --git a/parser/parser.go b/parser/parser.go index d26e119..b92984c 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -44,7 +44,7 @@ func (p *Parser) Errors() []string { return out } -func (p *Parser) handleErr(err error) { +func (p *Parser) appendError(err error) { p.errors = errors.Join(p.errors, err) } @@ -54,21 +54,33 @@ func (p *Parser) nextToken() { } func (p *Parser) ParseProgram() (*ast.Program, error) { + if p.curTokenIs(token.EOF) { + return nil, nil + } + + var err error program := &ast.Program{ Expressions: []ast.Expression{}, } - for !p.curTokenIs(token.EOF) { + if p.curTokenIs(token.LBRACKET) { + program, err = p.parseMultipleExpressions() + if err != nil { + p.appendError(err) + } + } else { exp, err := p.parseExpression() if err != nil { - // run through the rest of the program to find all errors - p.handleErr(err) + p.appendError(err) } program.Expressions = append(program.Expressions, exp) } - // return all errors found after parsing the whole program + if !p.curTokenIs(token.EOF) { + p.appendError(fmt.Errorf("expected EOF, got %s instead", p.curToken.Type)) + } + if p.errors != nil { return nil, p.errors } @@ -76,10 +88,46 @@ func (p *Parser) ParseProgram() (*ast.Program, error) { return program, nil } +func (p *Parser) parseMultipleExpressions() (*ast.Program, error) { + if err := p.expectCurToken(token.LBRACKET); err != nil { + p.appendError(err) + } + + program := &ast.Program{ + Expressions: []ast.Expression{}, + } + + for { + exp, err := p.parseExpression() + if err != nil { + p.appendError(err) + } + + program.Expressions = append(program.Expressions, exp) + + if p.curTokenIs(token.COMMA) { + // if the program still has more expressions, skip the comma and continue + p.nextToken() + } else { + // if the program has no more expressions, break the loop + if err := p.expectCurToken(token.RBRACKET); err != nil { + p.appendError(err) + } + break + } + } + + return program, nil +} + func (p *Parser) curTokenIs(t token.TokenType) bool { return p.curToken.Type == t } +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + func (p *Parser) expectCurToken(t token.TokenType) error { if p.curTokenIs(t) { p.nextToken() @@ -101,36 +149,45 @@ func (p *Parser) expectTokens(tokens ...token.TokenType) error { } func (p *Parser) parseExpression() (ast.Expression, error) { - defer p.nextToken() - if p.curTokenIs(token.LBRACE) { return p.parseObject() } return p.parseAtom() } -func (p *Parser) parseObject() (ast.Expression, error) { +func (p *Parser) parseObject() (obj ast.Expression, err error) { if err := p.expectCurToken(token.LBRACE); err != nil { return nil, err } - // parse main key - if err := p.expectCurToken(token.DOUBLE_QUOTE); err != nil { - return nil, err - } - switch p.curToken.Type { + switch p.peekToken.Type { case token.COMMAND: - return p.parseCommand() + obj, err = p.parseCommand() case token.IF: - return p.parseIfExpression() + obj, err = p.parseIfExpression() case token.SET: - return p.parseSetExpression() + obj, err = p.parseSetExpression() default: - return nil, fmt.Errorf("unexpected token type %s", p.curToken.Type) + err = fmt.Errorf("unexpected token type %s", p.curToken.Type) + } + if err != nil { + return nil, err } + + if err := p.expectCurToken(token.RBRACE); err != nil { + return nil, err + } + + return obj, nil } func (p *Parser) parseCommand() (*ast.CommandObject, error) { + if err := p.expectCurToken(token.DOUBLE_QUOTE); err != nil { + return nil, err + } + if !p.curTokenIs(token.COMMAND) { + return nil, fmt.Errorf("expected command, got %s instead", p.curToken.Type) + } commandToken := p.curToken // skip to symbol @@ -143,20 +200,18 @@ func (p *Parser) parseCommand() (*ast.CommandObject, error) { token.SYMBOLKEY, token.DOUBLE_QUOTE, token.COLON, - token.DOUBLE_QUOTE, ); err != nil { return nil, err } // parse symbol - symbol, err := p.parseExpression() + symbol, err := p.parseSymbol() if err != nil { return nil, err } // skip to args if err := p.expectTokens( - token.DOUBLE_QUOTE, token.COMMA, token.DOUBLE_QUOTE, token.ARGS, @@ -212,6 +267,12 @@ func (p *Parser) parseArgs() ([]ast.Expression, error) { } func (p *Parser) parseIfExpression() (*ast.IfExpression, error) { + if err := p.expectCurToken(token.DOUBLE_QUOTE); err != nil { + return nil, err + } + if !p.curTokenIs(token.IF) { + return nil, fmt.Errorf("expected if, got %s instead", p.curToken.Type) + } ifToken := p.curToken // skip to condition @@ -267,7 +328,7 @@ func (p *Parser) parseIfExpression() (*ast.IfExpression, error) { return nil, err } - if err := p.expectTokens(token.RBRACE, token.RBRACE); err != nil { + if err := p.expectTokens(token.RBRACE); err != nil { return nil, err } @@ -279,7 +340,7 @@ func (p *Parser) parseIfExpression() (*ast.IfExpression, error) { }, nil } - if err := p.expectTokens(token.RBRACE, token.RBRACE); err != nil { + if err := p.expectTokens(token.RBRACE); err != nil { return nil, err } @@ -292,6 +353,12 @@ func (p *Parser) parseIfExpression() (*ast.IfExpression, error) { } func (p *Parser) parseSetExpression() (*ast.SetExpression, error) { + if err := p.expectCurToken(token.DOUBLE_QUOTE); err != nil { + return nil, err + } + if !p.curTokenIs(token.SET) { + return nil, fmt.Errorf("expected set, got %s instead", p.curToken.Type) + } setToken := p.curToken // skip to var @@ -304,8 +371,6 @@ func (p *Parser) parseSetExpression() (*ast.SetExpression, error) { token.VAR, token.DOUBLE_QUOTE, token.COLON, - token.DOUBLE_QUOTE, - token.DOLLAR, ); err != nil { return nil, err } @@ -322,7 +387,6 @@ func (p *Parser) parseSetExpression() (*ast.SetExpression, error) { // skip to val if err := p.expectTokens( - token.DOUBLE_QUOTE, token.COMMA, token.DOUBLE_QUOTE, token.VAL, @@ -338,7 +402,7 @@ func (p *Parser) parseSetExpression() (*ast.SetExpression, error) { return nil, err } - if err := p.expectTokens(token.RBRACE, token.RBRACE); err != nil { + if err := p.expectTokens(token.RBRACE); err != nil { return nil, err } @@ -355,14 +419,14 @@ func (p *Parser) parseAtom() (ast.Expression, error) { return p.parsePrefixAtom() case token.INT: return p.parseIntegerLiteral() - case token.TRUE: - return &ast.Boolean{Token: p.curToken, Value: true}, nil - case token.FALSE: - return &ast.Boolean{Token: p.curToken, Value: false}, nil - case token.SYMBOL: - return &ast.Symbol{Token: p.curToken, Value: p.curToken.Literal}, nil + case token.TRUE, token.FALSE: + return p.parseBoolean() + case token.DOUBLE_QUOTE: + return p.parseDoubleQuotedString() default: - return nil, fmt.Errorf("unexpected token type %s", p.curToken.Type) + err := fmt.Errorf("unexpected token type %s", p.curToken.Type) + p.nextToken() + return nil, err } } @@ -384,10 +448,68 @@ func (p *Parser) parsePrefixAtom() (*ast.PrefixAtom, error) { } func (p *Parser) parseIntegerLiteral() (*ast.IntegerLiteral, error) { + if !p.curTokenIs(token.INT) { + return nil, fmt.Errorf("expected integer, got %s instead", p.curToken.Type) + } + intValue, err := strconv.ParseInt(p.curToken.Literal, 0, 64) if err != nil { return nil, fmt.Errorf("could not parse %q as integer", p.curToken.Literal) } - return &ast.IntegerLiteral{Token: p.curToken, Value: intValue}, nil + result := &ast.IntegerLiteral{Token: p.curToken, Value: intValue} + + p.nextToken() + + return result, nil +} + +func (p *Parser) parseBoolean() (*ast.Boolean, error) { + if !p.curTokenIs(token.TRUE) && !p.curTokenIs(token.FALSE) { + return nil, fmt.Errorf("expected boolean, got %s instead", p.curToken.Type) + } + + var result *ast.Boolean + + switch p.curToken.Type { + case token.TRUE: + result = &ast.Boolean{Token: p.curToken, Value: true} + case token.FALSE: + result = &ast.Boolean{Token: p.curToken, Value: false} + } + + p.nextToken() + + return result, nil +} + +func (p *Parser) parseDoubleQuotedString() (ast.Expression, error) { + if p.peekTokenIs(token.DOLLAR) { + return p.parseSymbol() + } + + // TOOD: parse string literal + return nil, fmt.Errorf("unexpected token type %s", p.curToken.Type) +} + +func (p *Parser) parseSymbol() (*ast.Symbol, error) { + if err := p.expectCurToken(token.DOUBLE_QUOTE); err != nil { + return nil, err + } + + if p.curTokenIs(token.DOLLAR) { + p.nextToken() + } + + if !p.curTokenIs(token.SYMBOL) { + return nil, fmt.Errorf("expected symbol, got %s instead", p.curToken.Type) + } + + symbol := &ast.Symbol{Token: p.curToken, Value: p.curToken.Literal} + + if err := p.expectTokens(token.SYMBOL, token.DOUBLE_QUOTE); err != nil { + return nil, err + } + + return symbol, nil } diff --git a/parser/parser_test.go b/parser/parser_test.go index 4ba6823..a4b7a63 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -624,3 +624,139 @@ func TestSetExpression(t *testing.T) { }) } } + +func TestPrograms(t *testing.T) { + tests := []struct { + name string + input string + expected *ast.Program + }{ + { + name: "multiple atoms", + input: ` + [ + 1, + true + ] + `, + expected: &ast.Program{ + Expressions: []ast.Expression{ + &ast.IntegerLiteral{ + Token: token.Token{Type: token.INT, Literal: "1"}, + Value: 1, + }, + &ast.Boolean{ + Token: token.Token{Type: token.TRUE, Literal: "true"}, + Value: true, + }, + }, + }, + }, + { + name: "multiple objects", + input: ` + [ + { + "command": { + "symbol": "+", + "args": [1, 2] + } + }, + { + "if": { + "cond": true, + "conseq": 1 + } + } + ] + `, + expected: &ast.Program{ + Expressions: []ast.Expression{ + &ast.CommandObject{ + Token: token.Token{Type: token.COMMAND, Literal: "command"}, + Symbol: &ast.Symbol{ + Token: token.Token{Type: token.SYMBOL, Literal: "+"}, + Value: "+", + }, + Args: []ast.Expression{ + &ast.IntegerLiteral{ + Token: token.Token{Type: token.INT, Literal: "1"}, + Value: 1, + }, + &ast.IntegerLiteral{ + Token: token.Token{Type: token.INT, Literal: "2"}, + Value: 2, + }, + }, + }, + &ast.IfExpression{ + Token: token.Token{Type: token.IF, Literal: "if"}, + Condition: &ast.Boolean{ + Token: token.Token{Type: token.TRUE, Literal: "true"}, + Value: true, + }, + Consequence: &ast.IntegerLiteral{ + Token: token.Token{Type: token.INT, Literal: "1"}, + Value: 1, + }, + Alternative: nil, + }, + }, + }, + }, + { + name: "multiple objects with set expression", + input: ` + [ + { + "set": { + "var": "$x", + "val": 1 + } + }, + "$x" + ] + `, + expected: &ast.Program{ + Expressions: []ast.Expression{ + &ast.SetExpression{ + Token: token.Token{Type: token.SET, Literal: "set"}, + Name: &ast.Symbol{ + Token: token.Token{Type: token.SYMBOL, Literal: "x"}, + Value: "x", + }, + Value: &ast.IntegerLiteral{ + Token: token.Token{Type: token.INT, Literal: "1"}, + Value: 1, + }, + }, + &ast.Symbol{ + Token: token.Token{Type: token.SYMBOL, Literal: "x"}, + Value: "x", + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := lexer.New(tt.input) + p := New(l) + + program, err := p.ParseProgram() + if err != nil { + checkParserErrors(t, p) + } + if len(program.Expressions) != len(tt.expected.Expressions) { + t.Fatalf("program.Expressions does not contain %d expressions. got=%d", len(tt.expected.Expressions), len(program.Expressions)) + } + + for i, exp := range tt.expected.Expressions { + if program.Expressions[i].String() != exp.String() { + t.Fatalf("exp.String() not %q. got=%q", exp.String(), program.Expressions[i].String()) + } + } + }) + } +}