Skip to content

Commit e30f785

Browse files
committed
[lexical-scoping] Parsing let-expression.
Signed-off-by: Springcomp <[email protected]>
1 parent 178a640 commit e30f785

File tree

5 files changed

+169
-15
lines changed

5 files changed

+169
-15
lines changed

pkg/parsing/astnodetype_string.go

Lines changed: 6 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/parsing/lexer.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ const (
8787
TOKExpref
8888
TOKAnd
8989
TOKNot
90+
TOKLet
91+
TOKIn
92+
TOKVarref
93+
TOKAssign
9094
TOKEOF
9195
)
9296

@@ -228,7 +232,7 @@ loop:
228232
t := lexer.matchOrElse(r, '=', TOKNE, TOKNot)
229233
tokens = append(tokens, t)
230234
} else if r == '=' {
231-
t := lexer.matchOrElse(r, '=', TOKEQ, TOKUnknown)
235+
t := lexer.matchOrElse(r, '=', TOKEQ, TOKAssign)
232236
tokens = append(tokens, t)
233237
} else if r == '&' {
234238
t := lexer.matchOrElse(r, '&', TOKAnd, TOKExpref)

pkg/parsing/parser.go

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ const (
3737
ASTSubexpression
3838
ASTSlice
3939
ASTValueProjection
40+
ASTLetExpression
41+
ASTVariable
42+
ASTBindings
43+
ASTBinding
4044
)
4145

4246
// ASTNode represents the abstract syntax tree of a JMESPath expression.
@@ -83,6 +87,7 @@ func (node ASTNode) PrettyPrint(indent int) string {
8387

8488
var bindingPowers = map[TokType]int{
8589
TOKEOF: 0,
90+
TOKVarref: 0,
8691
TOKUnquotedIdentifier: 0,
8792
TOKQuotedIdentifier: 0,
8893
TOKRbracket: 0,
@@ -93,6 +98,7 @@ var bindingPowers = map[TokType]int{
9398
TOKCurrent: 0,
9499
TOKExpref: 0,
95100
TOKColon: 0,
101+
TOKAssign: 1,
96102
TOKPipe: 1,
97103
TOKOr: 2,
98104
TOKAnd: 3,
@@ -140,6 +146,10 @@ func (p *Parser) Parse(expression string) (ASTNode, error) {
140146
if err != nil {
141147
return ASTNode{}, err
142148
}
149+
return p.parseTokens(tokens)
150+
}
151+
152+
func (p *Parser) parseTokens(tokens []token) (ASTNode, error) {
143153
p.tokens = tokens
144154
parsed, err := p.parseExpression(0)
145155
if err != nil {
@@ -303,16 +313,16 @@ func (p *Parser) led(tokenType TokType, node ASTNode) (ASTNode, error) {
303313
Value: tokenType,
304314
Children: []ASTNode{node, right},
305315
}, nil
306-
case TOKEQ, TOKNE, TOKGT, TOKGTE, TOKLT, TOKLTE:
307-
right, err := p.parseExpression(bindingPowers[tokenType])
308-
if err != nil {
309-
return ASTNode{}, err
316+
case TOKAssign:
317+
{
318+
right, err := p.parseExpression(bindingPowers[0])
319+
return ASTNode{
320+
NodeType: ASTBinding,
321+
Children: []ASTNode{node, right},
322+
}, err
310323
}
311-
return ASTNode{
312-
NodeType: ASTComparator,
313-
Value: tokenType,
314-
Children: []ASTNode{node, right},
315-
}, nil
324+
case TOKEQ, TOKNE, TOKGT, TOKGTE, TOKLT, TOKLTE:
325+
return p.parseComparatorExpression(node, tokenType)
316326
case TOKLbracket:
317327
tokenType := p.current()
318328
var right ASTNode
@@ -345,6 +355,44 @@ func (p *Parser) led(tokenType TokType, node ASTNode) (ASTNode, error) {
345355

346356
func (p *Parser) nud(token token) (ASTNode, error) {
347357
switch token.tokenType {
358+
case TOKLet:
359+
{
360+
var bindings []ASTNode
361+
for p.current() != TOKIn {
362+
binding, err := p.parseExpression(0)
363+
if err != nil {
364+
return ASTNode{}, err
365+
}
366+
if p.current() == TOKComma {
367+
if err := p.match(TOKComma); err != nil {
368+
return ASTNode{}, err
369+
}
370+
}
371+
bindings = append(bindings, binding)
372+
}
373+
if err := p.match(TOKIn); err != nil {
374+
return ASTNode{}, err
375+
}
376+
expression, err := p.parseExpression(0)
377+
if err != nil {
378+
return ASTNode{}, err
379+
}
380+
return ASTNode{
381+
NodeType: ASTLetExpression,
382+
Children: []ASTNode{
383+
{
384+
NodeType: ASTBindings,
385+
Children: bindings,
386+
},
387+
expression,
388+
},
389+
}, nil
390+
}
391+
case TOKVarref:
392+
return ASTNode{
393+
NodeType: ASTVariable,
394+
Value: token.value,
395+
}, nil
348396
case TOKJSONLiteral:
349397
var parsed interface{}
350398
err := json.Unmarshal([]byte(token.value), &parsed)
@@ -596,6 +644,18 @@ func (p *Parser) parseProjectionRHS(bindingPower int) (ASTNode, error) {
596644
}
597645
}
598646

647+
func (p *Parser) parseComparatorExpression(left ASTNode, tokenType TokType) (ASTNode, error) {
648+
right, err := p.parseExpression(bindingPowers[tokenType])
649+
if err != nil {
650+
return ASTNode{}, err
651+
}
652+
return ASTNode{
653+
NodeType: ASTComparator,
654+
Value: tokenType,
655+
Children: []ASTNode{left, right},
656+
}, nil
657+
}
658+
599659
func (p *Parser) lookahead(number int) TokType {
600660
return p.lookaheadToken(number).tokenType
601661
}

pkg/parsing/parser_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,88 @@ import (
77
"github.com/stretchr/testify/assert"
88
)
99

10+
func TestParsingVariable(t *testing.T) {
11+
assert := assert.New(t)
12+
tokens := []token{
13+
{tokenType: TOKVarref, value: "foo", position: 20, length: 3},
14+
{tokenType: TOKEOF, position: 19},
15+
}
16+
17+
var prettyPrintedLookup = `ASTVariable {
18+
value: "foo"
19+
}
20+
`
21+
p := NewParser()
22+
parsed, _ := p.parseTokens(tokens)
23+
assert.Equal(prettyPrintedLookup, parsed.PrettyPrint(0))
24+
}
25+
26+
func TestParsingVariableBinding(t *testing.T) {
27+
assert := assert.New(t)
28+
tokens := []token{
29+
{tokenType: TOKVarref, value: "foo", position: 4, length: 4},
30+
{tokenType: TOKAssign, value: "=", position: 9, length: 1},
31+
{tokenType: TOKUnquotedIdentifier, value: "foo", position: 11, length: 3},
32+
{tokenType: TOKEOF, position: 19},
33+
}
34+
35+
var prettyPrintedLookup = `ASTBinding {
36+
children: {
37+
ASTVariable {
38+
value: "foo"
39+
}
40+
ASTField {
41+
value: "foo"
42+
}
43+
}
44+
}
45+
`
46+
p := NewParser()
47+
parsed, _ := p.parseTokens(tokens)
48+
assert.Equal(prettyPrintedLookup, parsed.PrettyPrint(0))
49+
}
50+
51+
func TestParsingLetExpression(t *testing.T) {
52+
// let $foo = foo in @
53+
// 012345678901234567890123
54+
// 1 2
55+
assert := assert.New(t)
56+
tokens := []token{
57+
{tokenType: TOKLet, value: "let", position: 0, length: 3},
58+
{tokenType: TOKVarref, value: "foo", position: 4, length: 4},
59+
{tokenType: TOKAssign, value: "=", position: 9, length: 1},
60+
{tokenType: TOKUnquotedIdentifier, value: "foo", position: 11, length: 3},
61+
{tokenType: TOKIn, value: "in", position: 15, length: 2},
62+
{tokenType: TOKCurrent, value: "@", position: 18, length: 1},
63+
{tokenType: TOKEOF, position: 19},
64+
}
65+
66+
expected := `ASTLetExpression {
67+
children: {
68+
ASTBindings {
69+
children: {
70+
ASTBinding {
71+
children: {
72+
ASTVariable {
73+
value: "foo"
74+
}
75+
ASTField {
76+
value: "foo"
77+
}
78+
}
79+
}
80+
}
81+
}
82+
ASTCurrentNode {
83+
}
84+
}
85+
}
86+
`
87+
p := NewParser()
88+
parsed, _ := p.parseTokens(tokens)
89+
assert.Equal(expected, parsed.PrettyPrint(0))
90+
}
91+
1092
var parsingErrorTests = []struct {
1193
expression string
1294
msg string

pkg/parsing/toktype_string.go

Lines changed: 7 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)