Skip to content

Commit

Permalink
[lexical-scoping] Parsing let-expression.
Browse files Browse the repository at this point in the history
  • Loading branch information
springcomp committed Mar 23, 2023
1 parent 3f054ab commit 6c865ec
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 14 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.

3 changes: 3 additions & 0 deletions pkg/parsing/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ const (
TOKExpref
TOKAnd
TOKNot
TOKLet
TOKIn
TOKVarref
TOKEOF
)

Expand Down
80 changes: 71 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 Down Expand Up @@ -140,6 +145,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 +312,19 @@ 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 TOKEQ:
{
if node.NodeType != ASTVariable {
return p.parseComparatorExpression(node, tokenType)
}
right, err := p.parseExpression(bindingPowers[TOKEQ])
return ASTNode{
NodeType: ASTBinding,
Children: []ASTNode{node, right},
}, err
}
return ASTNode{
NodeType: ASTComparator,
Value: tokenType,
Children: []ASTNode{node, right},
}, nil
case TOKNE, TOKGT, TOKGTE, TOKLT, TOKLTE:
return p.parseComparatorExpression(node, tokenType)
case TOKLbracket:
tokenType := p.current()
var right ASTNode
Expand Down Expand Up @@ -345,6 +357,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 +646,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: TOKEQ, 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: TOKEQ, 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 6c865ec

Please sign in to comment.