Skip to content

Commit 0dcf09b

Browse files
committed
2.6 Pratt Parser (infix)
1 parent 5e371a7 commit 0dcf09b

File tree

3 files changed

+231
-0
lines changed

3 files changed

+231
-0
lines changed

ast/ast.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,29 @@ func (pe *PrefixExpression) String() string {
205205

206206
return out.String()
207207
}
208+
209+
// InfixExpression represents an infix expression node.
210+
type InfixExpression struct {
211+
Token token.Token // The operator token, e.g. +
212+
Left Expression
213+
Operator string
214+
Right Expression
215+
}
216+
217+
func (oe *InfixExpression) expressionNode() {}
218+
219+
// TokenLiteral prints the literal value of the token associated with this node.
220+
func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal }
221+
222+
// String returns a stringified version of the expression node.
223+
func (oe *InfixExpression) String() string {
224+
var out bytes.Buffer
225+
226+
out.WriteString("(")
227+
out.WriteString(oe.Left.String())
228+
out.WriteString(" " + oe.Operator + " ")
229+
out.WriteString(oe.Right.String())
230+
out.WriteString(")")
231+
232+
return out.String()
233+
}

parser/parser.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ const (
2727
CALL // myFunction(X)
2828
)
2929

30+
// Precedence table.
31+
// It associates token types with their precedence.
32+
var precedences = map[token.TokenType]int{
33+
token.EQ: EQUALS,
34+
token.NOT_EQ: EQUALS,
35+
token.LT: LESSGREATER,
36+
token.GT: LESSGREATER,
37+
token.PLUS: SUM,
38+
token.MINUS: SUM,
39+
token.SLASH: PRODUCT,
40+
token.ASTERISK: PRODUCT,
41+
}
42+
3043
// Pratt parser's idea is the association of parsing functions with token types.
3144
// Whenever this token type is encountered, the parsing functions are called to
3245
// parse the appropriate expression and return an AST node that represents it.
@@ -68,6 +81,16 @@ func New(l *lexer.Lexer) *Parser {
6881
p.registerPrefix(token.BANG, p.parsePrefixExpression)
6982
p.registerPrefix(token.MINUS, p.parsePrefixExpression)
7083

84+
p.infixParseFns = make(map[token.TokenType]infixParseFn)
85+
p.registerInfix(token.PLUS, p.parseInfixExpression)
86+
p.registerInfix(token.MINUS, p.parseInfixExpression)
87+
p.registerInfix(token.SLASH, p.parseInfixExpression)
88+
p.registerInfix(token.ASTERISK, p.parseInfixExpression)
89+
p.registerInfix(token.EQ, p.parseInfixExpression)
90+
p.registerInfix(token.NOT_EQ, p.parseInfixExpression)
91+
p.registerInfix(token.LT, p.parseInfixExpression)
92+
p.registerInfix(token.GT, p.parseInfixExpression)
93+
7194
// Read two tokens, so curToken and peekToken are both set.
7295
p.nextToken()
7396
p.nextToken()
@@ -192,6 +215,21 @@ func (p *Parser) parseExpression(precedence int) ast.Expression {
192215

193216
leftExp := prefix()
194217

218+
// The heart of our Pratt parser.
219+
for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() {
220+
// Try to find infixParseFns for the next token.
221+
infix := p.infixParseFns[p.peekToken.Type]
222+
if infix == nil {
223+
return leftExp
224+
}
225+
226+
p.nextToken()
227+
228+
leftExp = infix(leftExp)
229+
230+
// Loop until it encounters a token that has a higher precedence.
231+
}
232+
195233
return leftExp
196234
}
197235

@@ -243,6 +281,21 @@ func (p *Parser) parsePrefixExpression() ast.Expression {
243281
return expression
244282
}
245283

284+
func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression {
285+
expression := &ast.InfixExpression{
286+
Token: p.curToken, // the operator of the infix expression
287+
Operator: p.curToken.Literal,
288+
Left: left,
289+
}
290+
291+
// Precedence of the operator token.
292+
precedence := p.curPrecedence()
293+
p.nextToken()
294+
expression.Right = p.parseExpression(precedence)
295+
296+
return expression
297+
}
298+
246299
// "assertion functions".
247300
// Enforce the correctness of the order of tokens by checking the type of the
248301
// next token.
@@ -272,3 +325,21 @@ func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) {
272325
func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) {
273326
p.infixParseFns[tokenType] = fn
274327
}
328+
329+
// Returns the precedence associated with the token type of peekToken.
330+
func (p *Parser) peekPrecedence() int {
331+
if p, ok := precedences[p.peekToken.Type]; ok {
332+
return p
333+
}
334+
335+
return LOWEST
336+
}
337+
338+
// Returns the precedence associated with the token type of curToken.
339+
func (p *Parser) curPrecedence() int {
340+
if p, ok := precedences[p.curToken.Type]; ok {
341+
return p
342+
}
343+
344+
return LOWEST
345+
}

parser/parser_test.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,140 @@ func TestParsingPrefixExpression(t *testing.T) {
182182
}
183183
}
184184

185+
func TestParsingInfixExpressions(t *testing.T) {
186+
// Test parsing infix operators.
187+
// e.g.: `5 + 5;`
188+
// As with prefix operator expressions, we can use any expressions to the
189+
// left and right of the operator:
190+
// <expression> <infix operator> <expression>
191+
192+
infixTests := []struct {
193+
input string
194+
leftValue int64
195+
operator string
196+
rightValue int64
197+
}{
198+
{"5 + 5;", 5, "+", 5},
199+
{"5 - 5;", 5, "-", 5},
200+
{"5 * 5;", 5, "*", 5},
201+
{"5 / 5;", 5, "/", 5},
202+
{"5 > 5;", 5, ">", 5},
203+
{"5 < 5;", 5, "<", 5},
204+
{"5 == 5;", 5, "==", 5},
205+
{"5 != 5;", 5, "!=", 5},
206+
}
207+
208+
for _, tt := range infixTests {
209+
l := lexer.New(tt.input)
210+
p := New(l)
211+
program := p.ParseProgram()
212+
checkParserErrors(t, p)
213+
214+
if len(program.Statements) != 1 {
215+
t.Fatalf("program.Statements does not contain %d statements. got=%d\n",
216+
1, len(program.Statements))
217+
}
218+
219+
stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
220+
if !ok {
221+
t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T",
222+
program.Statements[0])
223+
}
224+
225+
exp, ok := stmt.Expression.(*ast.InfixExpression)
226+
if !ok {
227+
t.Fatalf("exp is not ast.InfixExpression. got=%T", stmt.Expression)
228+
}
229+
230+
if !testIntegerLiteral(t, exp.Left, tt.leftValue) {
231+
return
232+
}
233+
234+
if exp.Operator != tt.operator {
235+
t.Fatalf("exp.Operator is not '%s'. got=%s",
236+
tt.operator, exp.Operator)
237+
}
238+
239+
if !testIntegerLiteral(t, exp.Right, tt.rightValue) {
240+
return
241+
}
242+
}
243+
}
244+
245+
func TestOperatorPrecedenceParsing(t *testing.T) {
246+
// Tests that use multiple operators with different precedences and how the
247+
// AST in string form correctly represents this.
248+
tests := []struct {
249+
input string
250+
expected string
251+
}{
252+
{
253+
"-a * b",
254+
"((-a) * b)",
255+
},
256+
{
257+
"!-a",
258+
"(!(-a))",
259+
},
260+
{
261+
"a + b + c",
262+
"((a + b) + c)",
263+
},
264+
{
265+
"a + b - c",
266+
"((a + b) - c)",
267+
},
268+
{
269+
"a * b * c",
270+
"((a * b) * c)",
271+
},
272+
{
273+
"a * b / c",
274+
"((a * b) / c)",
275+
},
276+
{
277+
"a + b / c",
278+
"(a + (b / c))",
279+
},
280+
{
281+
"a + b * c + d / e - f",
282+
"(((a + (b * c)) + (d / e)) - f)",
283+
},
284+
{
285+
"3 + 4; -5 * 5",
286+
"(3 + 4)((-5) * 5)",
287+
},
288+
{
289+
"5 > 4 == 3 < 4",
290+
"((5 > 4) == (3 < 4))",
291+
},
292+
{
293+
"5 < 4 != 3 > 4",
294+
"((5 < 4) != (3 > 4))",
295+
},
296+
{
297+
"3 + 4 * 5 == 3 * 1 + 4 * 5",
298+
"((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))",
299+
},
300+
{
301+
"3 + 4 * 5 == 3 * 1 + 4 * 5",
302+
"((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))",
303+
},
304+
}
305+
306+
for _, tt := range tests {
307+
l := lexer.New(tt.input)
308+
p := New(l)
309+
program := p.ParseProgram()
310+
checkParserErrors(t, p)
311+
312+
actual := program.String()
313+
if actual != tt.expected {
314+
t.Errorf("expected=%q, got=%q", tt.expected, actual)
315+
}
316+
}
317+
}
318+
185319
func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool {
186320
integ, ok := il.(*ast.IntegerLiteral)
187321
if !ok {

0 commit comments

Comments
 (0)