summaryrefslogtreecommitdiff
path: root/parser
diff options
context:
space:
mode:
authorMichael Tews <michael@tews.dev>2024-12-13 07:16:00 +0100
committerMichael Tews <michael@tews.dev>2026-04-12 11:11:02 +0200
commit68ff1414130b7bdd9cbfe0e5c1fc60278f9a48fd (patch)
tree5baeea833552b3f60f2c9f5dbf7d82492e066358 /parser
parent0f51131eedb73d371bcf4d4868cb784f3592d08c (diff)
feat(parser): expressions, literalsHEADmain
Signed-off-by: Michael Tews <michael@tews.dev>
Diffstat (limited to 'parser')
-rw-r--r--parser/parser.go111
-rw-r--r--parser/parser_test.go171
2 files changed, 280 insertions, 2 deletions
diff --git a/parser/parser.go b/parser/parser.go
index 936d58a..a9c381a 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -2,18 +2,46 @@ package parser
import (
"fmt"
+ "strconv"
"github.com/mewsen/interpreter/ast"
"github.com/mewsen/interpreter/lexer"
"github.com/mewsen/interpreter/token"
)
+const (
+ _ int = iota
+ LOWEST
+ EQUALS
+ LESSGREATER
+ SUM
+ PRODUCT
+ PREFIX
+ CALL
+)
+
+type (
+ prefixParseFn func() ast.Expression
+ infixParseFn func(ast.Expression) ast.Expression
+)
+
type Parser struct {
l *lexer.Lexer
errors []string
curToken token.Token
peekToken token.Token
+
+ prefixParseFns map[token.TokenType]prefixParseFn
+ infixParseFns map[token.TokenType]infixParseFn
+}
+
+func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) {
+ p.prefixParseFns[tokenType] = fn
+}
+
+func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) {
+ p.infixParseFns[tokenType] = fn
}
func (p *Parser) peekError(t token.TokenType) {
@@ -34,12 +62,35 @@ func (p *Parser) Errors() []string {
func New(l *lexer.Lexer) *Parser {
p := &Parser{l: l, errors: []string{}}
+ p.prefixParseFns = make(map[token.TokenType]prefixParseFn)
+ p.registerPrefix(token.IDENT, p.parseIdentifier)
+ p.registerPrefix(token.INT, p.parseIntegerLiteral)
+ p.registerPrefix(token.BANG, p.parsePrefixExpression)
+ p.registerPrefix(token.MINUS, p.parsePrefixExpression)
+
p.NextToken()
p.NextToken()
return p
}
+func (p *Parser) parsePrefixExpression() ast.Expression {
+ expression := &ast.PrefixExpression{
+ Token: p.curToken,
+ Operator: p.curToken.Literal,
+ }
+
+ p.NextToken()
+
+ expression.Right = p.parseExpression(PREFIX)
+
+ return expression
+}
+
+func (p *Parser) parseIdentifier() ast.Expression {
+ return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
+}
+
func (p *Parser) ParseProgram() *ast.Program {
program := &ast.Program{}
program.Statements = []ast.Statement{}
@@ -59,9 +110,25 @@ func (p *Parser) parseStatement() ast.Statement {
switch p.curToken.Type {
case token.LET:
return p.parseLetStatement()
+ case token.RETURN:
+ return p.parseReturnStatement()
default:
- return nil
+ return p.parseExpressionStatement()
+ }
+}
+
+func (p *Parser) parseReturnStatement() *ast.ReturnStatement {
+ stmt := &ast.ReturnStatement{Token: p.curToken}
+
+ p.NextToken()
+
+ // TODO: We're skipping the expressions until
+ // we encounter a semicolon
+ for !p.curTokenIs(token.SEMICOLON) {
+ p.NextToken()
}
+
+ return stmt
}
func (p *Parser) parseLetStatement() *ast.LetStatement {
@@ -85,6 +152,43 @@ func (p *Parser) parseLetStatement() *ast.LetStatement {
return stmt
}
+func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement {
+ stmt := &ast.ExpressionStatement{Token: p.curToken}
+
+ stmt.Expression = p.parseExpression(LOWEST)
+
+ if p.peekTokenIs(token.SEMICOLON) {
+ p.NextToken()
+ }
+
+ return stmt
+}
+
+func (p *Parser) parseExpression(precedence int) ast.Expression {
+ prefix := p.prefixParseFns[p.curToken.Type]
+ if prefix == nil {
+ p.noPrefixParseFnError(p.curToken.Type)
+ return nil
+ }
+ leftExp := prefix()
+ return leftExp
+}
+
+func (p *Parser) parseIntegerLiteral() ast.Expression {
+ lit := &ast.IntegerLiteral{Token: p.curToken}
+
+ value, err := strconv.ParseInt(p.curToken.Literal, 0, 64)
+ if err != nil {
+ msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal)
+ p.errors = append(p.errors, msg)
+ return nil
+ }
+
+ lit.Value = value
+
+ return lit
+}
+
func (p *Parser) curTokenIs(t token.TokenType) bool {
return p.curToken.Type == t
}
@@ -101,3 +205,8 @@ func (p *Parser) expectPeek(t token.TokenType) bool {
return false
}
}
+
+func (p *Parser) noPrefixParseFnError(t token.TokenType) {
+ msg := fmt.Sprintf("no prefix parse function for %s found", t)
+ p.errors = append(p.errors, msg)
+}
diff --git a/parser/parser_test.go b/parser/parser_test.go
index f7158bf..be49bf5 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -1,9 +1,11 @@
package parser
import (
+ "fmt"
+ "testing"
+
"github.com/mewsen/interpreter/ast"
"github.com/mewsen/interpreter/lexer"
- "testing"
)
func TestLetStatements(t *testing.T) {
@@ -64,3 +66,170 @@ func testLetStatement(t *testing.T, s ast.Statement, name string) bool {
return true
}
+
+func TestReturnStatement(t *testing.T) {
+ input := `
+return 5;
+return 10;
+return 993322;
+ `
+
+ l := lexer.New(input)
+ p := New(l)
+
+ program := p.ParseProgram()
+
+ checkParserErrors(t, p)
+
+ if len(program.Statements) != 3 {
+ t.Fatalf("program.Statements does not contain 3 statement. got=%d", len(program.Statements))
+ }
+
+ for _, stmt := range program.Statements {
+ returnStmt, ok := stmt.(*ast.ReturnStatement)
+ if !ok {
+ t.Errorf("stmt not *ast.returnStatement, got=%T", stmt)
+ continue
+ }
+ if returnStmt.TokenLiteral() != "return" {
+ t.Errorf("returnStmt.TokenLiteral not 'return', got=%q", returnStmt.TokenLiteral())
+ }
+ }
+}
+
+func TestIdentifierExpression(t *testing.T) {
+ input := "foobar;"
+
+ l := lexer.New(input)
+ p := New(l)
+ program := p.ParseProgram()
+
+ checkParserErrors(t, p)
+
+ if len(program.Statements) != 1 {
+ t.Fatalf("program has not enough statements. got=%d",
+ len(program.Statements))
+ }
+
+ stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
+ if !ok {
+ t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T",
+ program.Statements[0])
+ }
+
+ ident, ok := stmt.Expression.(*ast.Identifier)
+ if !ok {
+ t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression)
+ }
+
+ if ident.Value != "foobar" {
+ t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value)
+ }
+
+ if ident.TokenLiteral() != "foobar" {
+ t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar",
+ ident.TokenLiteral())
+ }
+}
+
+func checkParserErrors(t *testing.T, p *Parser) {
+ for _, err := range p.errors {
+ t.Fatal(err)
+ }
+}
+
+func TestIntegerLiteralExpression(t *testing.T) {
+ input := "5;"
+
+ l := lexer.New(input)
+ p := New(l)
+ program := p.ParseProgram()
+ checkParserErrors(t, p)
+
+ if len(program.Statements) != 1 {
+ t.Fatalf("program has not enough statements. got=%d",
+ len(program.Statements))
+ }
+ stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
+ if !ok {
+ t.Fatalf("exp not *ast.IntergerLiteral. got=%T", program.Statements[0])
+ }
+
+ literal, ok := stmt.Expression.(*ast.IntegerLiteral)
+ if !ok {
+ t.Fatalf("literal.Value not %d, got=%d", 5, stmt.Expression)
+ }
+
+ if literal.Value != 5 {
+ t.Errorf("literal.Value not %d. got=%d", 5, literal.Value)
+ }
+
+ if literal.TokenLiteral() != "5" {
+ t.Errorf("literal.TokenLiteral not %s. got=%s", "5",
+ literal.TokenLiteral())
+ }
+}
+
+func TestParsingPrefixExpressions(t *testing.T) {
+ prefixTests := []struct {
+ input string
+ operator string
+ integerValue int64
+ }{
+ {"!5", "!", 5},
+ {"-15", "-", 15},
+ }
+
+ for _, tt := range prefixTests {
+ l := lexer.New(tt.input)
+ p := New(l)
+ program := p.ParseProgram()
+ checkParserErrors(t, p)
+
+ if len(program.Statements) != 1 {
+ t.Fatalf("program.Statements does not contain %d statements. got=%d\n",
+ 1, len(program.Statements))
+ }
+
+ stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
+ if !ok {
+ t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T",
+ program.Statements[0])
+ }
+
+ exp, ok := stmt.Expression.(*ast.PrefixExpression)
+ if !ok {
+ t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression)
+ }
+
+ if exp.Operator != tt.operator {
+ t.Fatalf("exp.Operator is not %s. got=%s",
+ tt.operator, exp.Operator)
+ }
+
+ if !testIntegerLiteral(t, exp.Right, tt.integerValue) {
+ return
+ }
+ }
+}
+
+func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool {
+ integ, ok := il.(*ast.IntegerLiteral)
+ if !ok {
+ t.Errorf("il not *ast.IntegerLiteral. got=%T", il)
+ return false
+ }
+
+ if integ.Value != value {
+ t.Errorf("integ.Value not %d. got=%d", value, integ.Value)
+ return false
+ }
+
+ if integ.TokenLiteral() != fmt.Sprintf("%d", value) {
+ t.Errorf("integ.TokenLiteral not %d. got=%s", value,
+ integ.TokenLiteral())
+ return false
+ }
+
+ return true
+}