Skip to content

Commit

Permalink
[lexical-scoping] Parsing let-expression.
Browse files Browse the repository at this point in the history
Signed-off-by: Springcomp <[email protected]>
  • Loading branch information
springcomp committed Mar 23, 2023
1 parent 3f054ab commit 9c3acd5
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 15 deletions.
8 changes: 6 additions & 2 deletions pkg/parsing/astnodetype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion pkg/parsing/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ const (
TOKExpref
TOKAnd
TOKNot
TOKLet
TOKIn
TOKVarref
TOKAssign
TOKEOF
)

Expand Down Expand Up @@ -228,7 +232,7 @@ loop:
t := lexer.matchOrElse(r, '=', TOKNE, TOKNot)
tokens = append(tokens, t)
} else if r == '=' {
t := lexer.matchOrElse(r, '=', TOKEQ, TOKUnknown)
t := lexer.matchOrElse(r, '=', TOKEQ, TOKAssign)
tokens = append(tokens, t)
} else if r == '&' {
t := lexer.matchOrElse(r, '&', TOKAnd, TOKExpref)
Expand Down
78 changes: 69 additions & 9 deletions pkg/parsing/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ const (
ASTSubexpression
ASTSlice
ASTValueProjection
ASTLetExpression
ASTVariable
ASTBindings
ASTBinding
)

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

var bindingPowers = map[TokType]int{
TOKEOF: 0,
TOKVarref: 0,
TOKUnquotedIdentifier: 0,
TOKQuotedIdentifier: 0,
TOKRbracket: 0,
Expand All @@ -93,6 +98,7 @@ var bindingPowers = map[TokType]int{
TOKCurrent: 0,
TOKExpref: 0,
TOKColon: 0,
TOKAssign: 1,
TOKPipe: 1,
TOKOr: 2,
TOKAnd: 3,
Expand Down Expand Up @@ -140,6 +146,10 @@ func (p *Parser) Parse(expression string) (ASTNode, error) {
if err != nil {
return ASTNode{}, err
}
return p.parseTokens(tokens)
}

func (p *Parser) parseTokens(tokens []token) (ASTNode, error) {
p.tokens = tokens
parsed, err := p.parseExpression(0)
if err != nil {
Expand Down Expand Up @@ -303,16 +313,16 @@ func (p *Parser) led(tokenType TokType, node ASTNode) (ASTNode, error) {
Value: tokenType,
Children: []ASTNode{node, right},
}, nil
case TOKEQ, TOKNE, TOKGT, TOKGTE, TOKLT, TOKLTE:
right, err := p.parseExpression(bindingPowers[tokenType])
if err != nil {
return ASTNode{}, err
case TOKAssign:
{
right, err := p.parseExpression(bindingPowers[0])
return ASTNode{
NodeType: ASTBinding,
Children: []ASTNode{node, right},
}, err
}
return ASTNode{
NodeType: ASTComparator,
Value: tokenType,
Children: []ASTNode{node, right},
}, nil
case TOKEQ, TOKNE, TOKGT, TOKGTE, TOKLT, TOKLTE:
return p.parseComparatorExpression(node, tokenType)
case TOKLbracket:
tokenType := p.current()
var right ASTNode
Expand Down Expand Up @@ -345,6 +355,44 @@ func (p *Parser) led(tokenType TokType, node ASTNode) (ASTNode, error) {

func (p *Parser) nud(token token) (ASTNode, error) {
switch token.tokenType {
case TOKLet:
{
var bindings []ASTNode
for p.current() != TOKIn {
binding, err := p.parseExpression(0)
if err != nil {
return ASTNode{}, err
}
if p.current() == TOKComma {
if err := p.match(TOKComma); err != nil {
return ASTNode{}, err
}
}
bindings = append(bindings, binding)
}
if err := p.match(TOKIn); err != nil {
return ASTNode{}, err
}
expression, err := p.parseExpression(0)
if err != nil {
return ASTNode{}, err
}
return ASTNode{
NodeType: ASTLetExpression,
Children: []ASTNode{
{
NodeType: ASTBindings,
Children: bindings,
},
expression,
},
}, nil
}
case TOKVarref:
return ASTNode{
NodeType: ASTVariable,
Value: token.value,
}, nil
case TOKJSONLiteral:
var parsed interface{}
err := json.Unmarshal([]byte(token.value), &parsed)
Expand Down Expand Up @@ -596,6 +644,18 @@ func (p *Parser) parseProjectionRHS(bindingPower int) (ASTNode, error) {
}
}

func (p *Parser) parseComparatorExpression(left ASTNode, tokenType TokType) (ASTNode, error) {
right, err := p.parseExpression(bindingPowers[tokenType])
if err != nil {
return ASTNode{}, err
}
return ASTNode{
NodeType: ASTComparator,
Value: tokenType,
Children: []ASTNode{left, right},
}, nil
}

func (p *Parser) lookahead(number int) TokType {
return p.lookaheadToken(number).tokenType
}
Expand Down
82 changes: 82 additions & 0 deletions pkg/parsing/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,88 @@ import (
"github.com/stretchr/testify/assert"
)

func TestParsingVariable(t *testing.T) {
assert := assert.New(t)
tokens := []token{
{tokenType: TOKVarref, value: "foo", position: 20, length: 3},
{tokenType: TOKEOF, position: 19},
}

var prettyPrintedLookup = `ASTVariable {
value: "foo"
}
`
p := NewParser()
parsed, _ := p.parseTokens(tokens)
assert.Equal(prettyPrintedLookup, parsed.PrettyPrint(0))
}

func TestParsingVariableBinding(t *testing.T) {
assert := assert.New(t)
tokens := []token{
{tokenType: TOKVarref, value: "foo", position: 4, length: 4},
{tokenType: TOKAssign, value: "=", position: 9, length: 1},
{tokenType: TOKUnquotedIdentifier, value: "foo", position: 11, length: 3},
{tokenType: TOKEOF, position: 19},
}

var prettyPrintedLookup = `ASTBinding {
children: {
ASTVariable {
value: "foo"
}
ASTField {
value: "foo"
}
}
}
`
p := NewParser()
parsed, _ := p.parseTokens(tokens)
assert.Equal(prettyPrintedLookup, parsed.PrettyPrint(0))
}

func TestParsingLetExpression(t *testing.T) {
// let $foo = foo in @
// 012345678901234567890123
// 1 2
assert := assert.New(t)
tokens := []token{
{tokenType: TOKLet, value: "let", position: 0, length: 3},
{tokenType: TOKVarref, value: "foo", position: 4, length: 4},
{tokenType: TOKAssign, value: "=", position: 9, length: 1},
{tokenType: TOKUnquotedIdentifier, value: "foo", position: 11, length: 3},
{tokenType: TOKIn, value: "in", position: 15, length: 2},
{tokenType: TOKCurrent, value: "@", position: 18, length: 1},
{tokenType: TOKEOF, position: 19},
}

expected := `ASTLetExpression {
children: {
ASTBindings {
children: {
ASTBinding {
children: {
ASTVariable {
value: "foo"
}
ASTField {
value: "foo"
}
}
}
}
}
ASTCurrentNode {
}
}
}
`
p := NewParser()
parsed, _ := p.parseTokens(tokens)
assert.Equal(expected, parsed.PrettyPrint(0))
}

var parsingErrorTests = []struct {
expression string
msg string
Expand Down
9 changes: 6 additions & 3 deletions pkg/parsing/toktype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 9c3acd5

Please sign in to comment.