Parses infix expressions.
Signed-off-by: jmug <u.g.a.mariano@gmail.com>
This commit is contained in:
parent
f286a88039
commit
5fa7b2481a
4 changed files with 201 additions and 4 deletions
|
|
@ -131,7 +131,7 @@ func (il *IntegerLiteral) String() string {
|
|||
}
|
||||
|
||||
type PrefixExpression struct {
|
||||
Token token.Token
|
||||
Token token.Token // The operator token
|
||||
Operator string
|
||||
Right Expression
|
||||
}
|
||||
|
|
@ -143,3 +143,18 @@ func (pe *PrefixExpression) TokenLiteral() string {
|
|||
func (pe *PrefixExpression) String() string {
|
||||
return "(" + pe.Operator + pe.Right.String() + ")"
|
||||
}
|
||||
|
||||
type InfixExpression struct {
|
||||
Token token.Token // The operator token
|
||||
Operator string
|
||||
Left Expression
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (ie *InfixExpression) expressionNode() {}
|
||||
func (ie *InfixExpression) TokenLiteral() string {
|
||||
return ie.Token.Literal
|
||||
}
|
||||
func (ie *InfixExpression) String() string {
|
||||
return "(" + ie.Left.String() + " " + ie.Operator + " " + ie.Right.String() + ")"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,10 +30,20 @@ func New(l *lexer.Lexer) *Parser {
|
|||
prefixParseFns: map[token.TokenType]prefixParseFn{},
|
||||
infixParseFns: map[token.TokenType]infixParseFn{},
|
||||
}
|
||||
// Prefix registrations
|
||||
p.registerPrefix(token.IDENT, p.parseIdentifier)
|
||||
p.registerPrefix(token.INT, p.parseIntegerLiteral)
|
||||
p.registerPrefix(token.MINUS, p.parsePrefixExpression)
|
||||
p.registerPrefix(token.BANG, p.parsePrefixExpression)
|
||||
// Infix registrations
|
||||
p.registerInfix(token.PLUS, p.parseInfixExpression)
|
||||
p.registerInfix(token.MINUS, p.parseInfixExpression)
|
||||
p.registerInfix(token.ASTERISK, p.parseInfixExpression)
|
||||
p.registerInfix(token.SLASH, p.parseInfixExpression)
|
||||
p.registerInfix(token.GT, p.parseInfixExpression)
|
||||
p.registerInfix(token.LT, p.parseInfixExpression)
|
||||
p.registerInfix(token.EQ, p.parseInfixExpression)
|
||||
p.registerInfix(token.NOT_EQ, p.parseInfixExpression)
|
||||
// TODO: figure out why this can't be done from `parseProgram`
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
|
|
@ -111,9 +121,16 @@ func (p *Parser) parseExpression(precedence int) ast.Expression {
|
|||
p.noPrefixParseFnError(p.curToken.Type)
|
||||
return nil
|
||||
}
|
||||
leftExpr := prefix()
|
||||
return leftExpr
|
||||
|
||||
curExpr := prefix()
|
||||
for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() {
|
||||
infix := p.infixParseFns[p.peekToken.Type]
|
||||
if infix == nil {
|
||||
return curExpr
|
||||
}
|
||||
p.nextToken()
|
||||
curExpr = infix(curExpr)
|
||||
}
|
||||
return curExpr
|
||||
}
|
||||
|
||||
func (p *Parser) parseIdentifier() ast.Expression {
|
||||
|
|
@ -141,6 +158,18 @@ func (p *Parser) parsePrefixExpression() ast.Expression {
|
|||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression {
|
||||
exp := &ast.InfixExpression{
|
||||
Token: p.curToken,
|
||||
Operator: p.curToken.Literal,
|
||||
Left: left,
|
||||
}
|
||||
precedence := p.curPrecedence()
|
||||
p.nextToken()
|
||||
exp.Right = p.parseExpression(precedence)
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) curTokenIs(typ token.TokenType) bool {
|
||||
return p.curToken.Type == typ
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,6 +200,130 @@ func TestParsingPrefixExpressions(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// parser/parser_test.go
|
||||
|
||||
func TestParsingInfixExpressions(t *testing.T) {
|
||||
infixTests := []struct {
|
||||
input string
|
||||
leftValue int64
|
||||
operator string
|
||||
rightValue int64
|
||||
}{
|
||||
{"5 + 5;", 5, "+", 5},
|
||||
{"5 - 5;", 5, "-", 5},
|
||||
{"5 * 5;", 5, "*", 5},
|
||||
{"5 / 5;", 5, "/", 5},
|
||||
{"5 > 5;", 5, ">", 5},
|
||||
{"5 < 5;", 5, "<", 5},
|
||||
{"5 == 5;", 5, "==", 5},
|
||||
{"5 != 5;", 5, "!=", 5},
|
||||
}
|
||||
|
||||
for _, tt := range infixTests {
|
||||
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.InfixExpression)
|
||||
if !ok {
|
||||
t.Fatalf("exp is not ast.InfixExpression. got=%T", stmt.Expression)
|
||||
}
|
||||
|
||||
if !testIntegerLiteral(t, exp.Left, tt.leftValue) {
|
||||
return
|
||||
}
|
||||
|
||||
if exp.Operator != tt.operator {
|
||||
t.Fatalf("exp.Operator is not '%s'. got=%s",
|
||||
tt.operator, exp.Operator)
|
||||
}
|
||||
|
||||
if !testIntegerLiteral(t, exp.Right, tt.rightValue) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperatorPrecedenceParsing(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"-a * b",
|
||||
"((-a) * b)",
|
||||
},
|
||||
{
|
||||
"!-a",
|
||||
"(!(-a))",
|
||||
},
|
||||
{
|
||||
"a + b + c",
|
||||
"((a + b) + c)",
|
||||
},
|
||||
{
|
||||
"a + b - c",
|
||||
"((a + b) - c)",
|
||||
},
|
||||
{
|
||||
"a * b * c",
|
||||
"((a * b) * c)",
|
||||
},
|
||||
{
|
||||
"a * b / c",
|
||||
"((a * b) / c)",
|
||||
},
|
||||
{
|
||||
"a + b / c",
|
||||
"(a + (b / c))",
|
||||
},
|
||||
{
|
||||
"a + b * c + d / e - f",
|
||||
"(((a + (b * c)) + (d / e)) - f)",
|
||||
},
|
||||
{
|
||||
"3 + 4; -5 * 5",
|
||||
"(3 + 4)((-5) * 5)",
|
||||
},
|
||||
{
|
||||
"5 > 4 == 3 < 4",
|
||||
"((5 > 4) == (3 < 4))",
|
||||
},
|
||||
{
|
||||
"5 < 4 != 3 > 4",
|
||||
"((5 < 4) != (3 > 4))",
|
||||
},
|
||||
{
|
||||
"3 + 4 * 5 == 3 * 1 + 4 * 5",
|
||||
"((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
l := lexer.New(tt.input)
|
||||
p := New(l)
|
||||
program := p.ParseProgram()
|
||||
checkParserErrors(t, p)
|
||||
|
||||
actual := program.String()
|
||||
if actual != tt.expected {
|
||||
t.Errorf("expected=%q, got=%q", tt.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool {
|
||||
integ, ok := il.(*ast.IntegerLiteral)
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"code.jmug.me/jmug/interpreter-in-go/pkg/token"
|
||||
)
|
||||
|
||||
const (
|
||||
_ int = iota
|
||||
LOWEST
|
||||
|
|
@ -10,3 +14,28 @@ const (
|
|||
PREFIX // -X or !X
|
||||
CALL // myFunction(X)
|
||||
)
|
||||
|
||||
var precedences = map[token.TokenType]int{
|
||||
token.EQ: EQUALS,
|
||||
token.NOT_EQ: EQUALS,
|
||||
token.GT: LESSGREATER,
|
||||
token.LT: LESSGREATER,
|
||||
token.PLUS: SUM,
|
||||
token.MINUS: SUM,
|
||||
token.ASTERISK: PRODUCT,
|
||||
token.SLASH: PRODUCT,
|
||||
}
|
||||
|
||||
func (p *Parser) peekPrecedence() int {
|
||||
if pr, ok := precedences[p.peekToken.Type]; ok {
|
||||
return pr
|
||||
}
|
||||
return LOWEST
|
||||
}
|
||||
|
||||
func (p *Parser) curPrecedence() int {
|
||||
if pr, ok := precedences[p.curToken.Type]; ok {
|
||||
return pr
|
||||
}
|
||||
return LOWEST
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue