aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2023-11-03 16:17:15 +0000
committerBobby <[email protected]>2023-11-03 16:17:15 +0000
commite8a6c163c9a58b69f6f96c373118ac6996637e25 (patch)
treed16ab60698adbd9491e5904809c67a4ecc41cd0e
parent15f9a15757322221d8bf9a3ecd5d8ce8490ebda6 (diff)
downloadmana-e8a6c163c9a58b69f6f96c373118ac6996637e25.tar.xz
mana-e8a6c163c9a58b69f6f96c373118ac6996637e25.zip
parser:init pratt parser. parser:add prefix, infix and `parseIntegerLiteral`
-rw-r--r--ast/ast.go14
-rw-r--r--parser/parser.go86
-rw-r--r--parser/parser_test.go70
3 files changed, 168 insertions, 2 deletions
diff --git a/ast/ast.go b/ast/ast.go
index fea8b60..ec53a8c 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -128,3 +128,17 @@ func (es *ExpressionStatement) String() string {
}
return ""
}
+
+// IntegerLiteral represents an integer literal.
+type IntegerLiteral struct {
+ Token tokens.Token // the token.INT token
+ Value int64
+}
+
+func (il *IntegerLiteral) expressionNode() {}
+func (il *IntegerLiteral) TokenLiteral() string {
+ return il.Token.Literal
+}
+func (il *IntegerLiteral) String() string {
+ return il.Token.Literal
+}
diff --git a/parser/parser.go b/parser/parser.go
index df15165..32bb4bc 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -5,16 +5,36 @@ import (
"mana/ast"
"mana/lexer"
"mana/tokens"
+ "strconv"
+)
+
+// Define the precedence of the operators.
+const (
+ _ int = iota
+ LOWEST
+ EQUALS // ==
+ LESSGREATER // > or <
+ SUM // +
+ PRODUCT // *
+ PREFIX // -X or !X
+ CALL // myFunction(X)
+)
+
+type (
+ prefixParseFn func() ast.Expression
+ infixParseFn func(ast.Expression) ast.Expression
)
// Parser represents a parser.
type Parser struct {
- l *lexer.Lexer
+ l *lexer.Lexer
+ errors []string
curToken tokens.Token
peekToken tokens.Token
- errors []string
+ prefixParseFns map[tokens.TokenType]prefixParseFn
+ infixParseFns map[tokens.TokenType]infixParseFn
}
// New returns a new Parser.
@@ -28,6 +48,11 @@ func New(l *lexer.Lexer) *Parser {
p.nextToken()
p.nextToken()
+ // Initialize the prefix parse functions.
+ p.prefixParseFns = make(map[tokens.TokenType]prefixParseFn)
+ p.registerPrefix(tokens.IDENT, p.parseIdentifier)
+ p.registerPrefix(tokens.INT, p.parseIntegerLiteral)
+
return p
}
@@ -37,6 +62,16 @@ func (p *Parser) nextToken() {
p.peekToken = p.l.NextToken()
}
+// registerPrefix registers a prefix parse function.
+func (p *Parser) registerPrefix(tokenType tokens.TokenType, fn prefixParseFn) {
+ p.prefixParseFns[tokenType] = fn
+}
+
+// registerInfix registers an infix parse function.
+func (p *Parser) registerInfix(tokenType tokens.TokenType, fn infixParseFn) {
+ p.infixParseFns[tokenType] = fn
+}
+
// ParseProgram parses a program.
func (p *Parser) ParseProgram() *ast.Program {
@@ -69,8 +104,29 @@ func (p *Parser) parseStatement() ast.Statement {
case tokens.RETURN:
return p.parseReturnStatement()
default:
+ return p.parseExpressionStatement()
+ }
+}
+
+// parseIdentifier parses an identifier.
+func (p *Parser) parseIdentifier() ast.Expression {
+ return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
+}
+
+// parseIntegerLiteral parses an integer literal.
+func (p *Parser) parseIntegerLiteral() ast.Expression {
+ var lit *ast.IntegerLiteral = &ast.IntegerLiteral{ Token: p.curToken }
+
+ var value, err = strconv.ParseInt(p.curToken.Literal, 0, 64)
+ if err != nil {
+ var msg string = fmt.Sprintf("could not parse %q as integer", p.curToken.Literal)
+ p.errors = append(p.errors, msg)
return nil
}
+
+ lit.Value = value
+
+ return lit
}
// parseLetStatement parses a let statement.
@@ -113,6 +169,32 @@ func (p *Parser) parseReturnStatement() *ast.ReturnStatement {
return stmt
}
+// parseExpressionStatement parses an expression statement.
+func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement {
+ var stmt *ast.ExpressionStatement = &ast.ExpressionStatement{Token: p.curToken}
+
+ stmt.Expression = p.parseExpression(LOWEST)
+
+ if p.peekTokenIs(tokens.SEMICOLON) {
+ p.nextToken()
+ }
+
+ return stmt
+}
+
+// parseExpression parses an expression.
+func (p *Parser) parseExpression(precedence int) ast.Expression {
+ var prefix = p.prefixParseFns[p.curToken.Type]
+
+ if prefix == nil {
+ return nil
+ }
+
+ var leftExp ast.Expression = prefix()
+
+ return leftExp
+}
+
// curTokenIs returns true if the current token is of the given type.
func (p *Parser) curTokenIs(t tokens.TokenType) bool {
return p.curToken.Type == t
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 8e06ecd..cd1d506 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -122,3 +122,73 @@ func TestReturnStatements(t *testing.T) {
}
}
}
+
+// Identifier expression tests.
+func TestIdentifierExpression(t *testing.T) {
+ var input string = "foobar;"
+
+ var l *lexer.Lexer = lexer.New(input)
+ var p *Parser = New(l)
+ var program *ast.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())
+ }
+}
+
+// Integer literal expression tests.
+func TestIntegerLiteralExpression(t *testing.T) {
+ var input string = "5;"
+
+ var l *lexer.Lexer = lexer.New(input)
+ var p *Parser = New(l)
+ var program *ast.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])
+ }
+
+ literal, ok := stmt.Expression.(*ast.IntegerLiteral)
+
+ if !ok {
+ t.Fatalf("exp not *ast.IntegerLiteral. got=%T", 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())
+ }
+}
+