Skip to content

Commit 564b637

Browse files
committed
Fix pipeline and call parsing
1 parent 44dfabc commit 564b637

File tree

2 files changed

+118
-135
lines changed

2 files changed

+118
-135
lines changed

builtin/builtin_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,24 @@ func TestBuiltin_allow_builtins_override(t *testing.T) {
327327
program, err := expr.Compile(fmt.Sprintf("%s()", name), fn)
328328
require.NoError(t, err)
329329

330+
out, err := expr.Run(program, nil)
331+
require.NoError(t, err)
332+
assert.Equal(t, 42, out)
333+
})
334+
}
335+
})
336+
t.Run("via expr.Function as pipe", func(t *testing.T) {
337+
for _, name := range builtin.Names {
338+
t.Run(name, func(t *testing.T) {
339+
fn := expr.Function(name,
340+
func(params ...any) (any, error) {
341+
return 42, nil
342+
},
343+
new(func(s string) int),
344+
)
345+
program, err := expr.Compile(fmt.Sprintf("'str' | %s()", name), fn)
346+
require.NoError(t, err)
347+
330348
out, err := expr.Run(program, nil)
331349
require.NoError(t, err)
332350
assert.Equal(t, 42, out)

parser/parser.go

+100-135
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,31 @@ import (
1515
"github.com/expr-lang/expr/parser/utils"
1616
)
1717

18+
type arg byte
19+
20+
const (
21+
expr arg = 1 << iota
22+
closure
23+
)
24+
25+
const optional arg = 1 << 7
26+
1827
var predicates = map[string]struct {
19-
arity int
28+
args []arg
2029
}{
21-
"all": {2},
22-
"none": {2},
23-
"any": {2},
24-
"one": {2},
25-
"filter": {2},
26-
"map": {2},
27-
"count": {2},
28-
"find": {2},
29-
"findIndex": {2},
30-
"findLast": {2},
31-
"findLastIndex": {2},
32-
"groupBy": {2},
33-
"reduce": {3},
30+
"all": {[]arg{expr, closure}},
31+
"none": {[]arg{expr, closure}},
32+
"any": {[]arg{expr, closure}},
33+
"one": {[]arg{expr, closure}},
34+
"filter": {[]arg{expr, closure}},
35+
"map": {[]arg{expr, closure}},
36+
"count": {[]arg{expr, closure}},
37+
"find": {[]arg{expr, closure}},
38+
"findIndex": {[]arg{expr, closure}},
39+
"findLast": {[]arg{expr, closure}},
40+
"findLastIndex": {[]arg{expr, closure}},
41+
"groupBy": {[]arg{expr, closure}},
42+
"reduce": {[]arg{expr, closure, expr | optional}},
3443
}
3544

3645
type parser struct {
@@ -143,7 +152,9 @@ func (p *parser) parseExpression(precedence int) Node {
143152
p.next()
144153

145154
if opToken.Value == "|" {
146-
nodeLeft = p.parsePipe(nodeLeft)
155+
identToken := p.current
156+
p.expect(Identifier)
157+
nodeLeft = p.parseCall(identToken, []Node{nodeLeft}, true)
147158
goto next
148159
}
149160

@@ -279,7 +290,7 @@ func (p *parser) parsePrimary() Node {
279290
p.next()
280291
token = p.current
281292
p.expect(Identifier)
282-
return p.parsePostfixExpression(p.parseCall(token, false))
293+
return p.parsePostfixExpression(p.parseCall(token, []Node{}, false))
283294
}
284295

285296
return p.parseSecondary()
@@ -307,7 +318,12 @@ func (p *parser) parseSecondary() Node {
307318
node.SetLocation(token.Location)
308319
return node
309320
default:
310-
node = p.parseCall(token, true)
321+
if p.current.Is(Bracket, "(") {
322+
node = p.parseCall(token, []Node{}, true)
323+
} else {
324+
node = &IdentifierNode{Value: token.Value}
325+
node.SetLocation(token.Location)
326+
}
311327
}
312328

313329
case Number:
@@ -386,68 +402,86 @@ func (p *parser) toFloatNode(number float64) Node {
386402
return &FloatNode{Value: number}
387403
}
388404

389-
func (p *parser) parseCall(token Token, checkOverrides bool) Node {
405+
func (p *parser) parseCall(token Token, arguments []Node, checkOverrides bool) Node {
390406
var node Node
391-
if p.current.Is(Bracket, "(") {
392-
var arguments []Node
393-
394-
isOverridden := p.config.IsOverridden(token.Value)
395-
isOverridden = isOverridden && checkOverrides
396-
397-
// TODO: Refactor parser to use builtin.Builtins instead of predicates map.
398-
if b, ok := predicates[token.Value]; ok && !isOverridden {
399-
p.expect(Bracket, "(")
400-
401-
if b.arity == 1 {
402-
arguments = make([]Node, 1)
403-
arguments[0] = p.parseExpression(0)
404-
} else if b.arity == 2 {
405-
arguments = make([]Node, 2)
406-
arguments[0] = p.parseExpression(0)
407-
p.expect(Operator, ",")
408-
arguments[1] = p.parseClosure()
409-
}
410407

411-
if token.Value == "reduce" {
412-
arguments = make([]Node, 2)
413-
arguments[0] = p.parseExpression(0)
414-
p.expect(Operator, ",")
415-
arguments[1] = p.parseClosure()
416-
if p.current.Is(Operator, ",") {
417-
p.next()
418-
arguments = append(arguments, p.parseExpression(0))
419-
}
420-
}
408+
isOverridden := p.config.IsOverridden(token.Value)
409+
isOverridden = isOverridden && checkOverrides
410+
411+
if b, ok := predicates[token.Value]; ok && !isOverridden {
412+
p.expect(Bracket, "(")
421413

422-
p.expect(Bracket, ")")
414+
// In case of the pipe operator, the first argument is the left-hand side
415+
// of the operator, so we do not parse it as an argument inside brackets.
416+
args := b.args[len(arguments):]
423417

424-
node = &BuiltinNode{
425-
Name: token.Value,
426-
Arguments: arguments,
418+
for i, arg := range args {
419+
if arg&optional == optional {
420+
if p.current.Is(Bracket, ")") {
421+
break
422+
}
423+
} else {
424+
if p.current.Is(Bracket, ")") {
425+
p.error("expected at least %d arguments", len(args))
426+
}
427427
}
428-
node.SetLocation(token.Location)
429-
} else if _, ok := builtin.Index[token.Value]; ok && !p.config.Disabled[token.Value] && !isOverridden {
430-
node = &BuiltinNode{
431-
Name: token.Value,
432-
Arguments: p.parseArguments(),
428+
429+
if i > 0 {
430+
p.expect(Operator, ",")
433431
}
434-
node.SetLocation(token.Location)
435-
} else {
436-
callee := &IdentifierNode{Value: token.Value}
437-
callee.SetLocation(token.Location)
438-
node = &CallNode{
439-
Callee: callee,
440-
Arguments: p.parseArguments(),
432+
var node Node
433+
switch {
434+
case arg&expr == expr:
435+
node = p.parseExpression(0)
436+
case arg&closure == closure:
437+
node = p.parseClosure()
441438
}
442-
node.SetLocation(token.Location)
439+
arguments = append(arguments, node)
440+
}
441+
442+
p.expect(Bracket, ")")
443+
444+
node = &BuiltinNode{
445+
Name: token.Value,
446+
Arguments: arguments,
447+
}
448+
node.SetLocation(token.Location)
449+
} else if _, ok := builtin.Index[token.Value]; ok && !p.config.Disabled[token.Value] && !isOverridden {
450+
node = &BuiltinNode{
451+
Name: token.Value,
452+
Arguments: p.parseArguments(arguments),
443453
}
454+
node.SetLocation(token.Location)
444455
} else {
445-
node = &IdentifierNode{Value: token.Value}
456+
callee := &IdentifierNode{Value: token.Value}
457+
callee.SetLocation(token.Location)
458+
node = &CallNode{
459+
Callee: callee,
460+
Arguments: p.parseArguments(arguments),
461+
}
446462
node.SetLocation(token.Location)
447463
}
448464
return node
449465
}
450466

467+
func (p *parser) parseArguments(arguments []Node) []Node {
468+
// If pipe operator is used, the first argument is the left-hand side
469+
// of the operator, so we do not parse it as an argument inside brackets.
470+
offset := len(arguments)
471+
472+
p.expect(Bracket, "(")
473+
for !p.current.Is(Bracket, ")") && p.err == nil {
474+
if len(arguments) > offset {
475+
p.expect(Operator, ",")
476+
}
477+
node := p.parseExpression(0)
478+
arguments = append(arguments, node)
479+
}
480+
p.expect(Bracket, ")")
481+
482+
return arguments
483+
}
484+
451485
func (p *parser) parseClosure() Node {
452486
startToken := p.current
453487
expectClosingBracket := false
@@ -575,7 +609,7 @@ func (p *parser) parsePostfixExpression(node Node) Node {
575609
memberNode.Method = true
576610
node = &CallNode{
577611
Callee: memberNode,
578-
Arguments: p.parseArguments(),
612+
Arguments: p.parseArguments([]Node{}),
579613
}
580614
node.SetLocation(propertyToken.Location)
581615
} else {
@@ -641,72 +675,3 @@ func (p *parser) parsePostfixExpression(node Node) Node {
641675
}
642676
return node
643677
}
644-
645-
func (p *parser) parsePipe(node Node) Node {
646-
identifier := p.current
647-
p.expect(Identifier)
648-
649-
arguments := []Node{node}
650-
651-
if b, ok := predicates[identifier.Value]; ok {
652-
p.expect(Bracket, "(")
653-
654-
// TODO: Refactor parser to use builtin.Builtins instead of predicates map.
655-
656-
if b.arity == 2 {
657-
arguments = append(arguments, p.parseClosure())
658-
}
659-
660-
if identifier.Value == "reduce" {
661-
arguments = append(arguments, p.parseClosure())
662-
if p.current.Is(Operator, ",") {
663-
p.next()
664-
arguments = append(arguments, p.parseExpression(0))
665-
}
666-
}
667-
668-
p.expect(Bracket, ")")
669-
670-
node = &BuiltinNode{
671-
Name: identifier.Value,
672-
Arguments: arguments,
673-
}
674-
node.SetLocation(identifier.Location)
675-
} else if _, ok := builtin.Index[identifier.Value]; ok {
676-
arguments = append(arguments, p.parseArguments()...)
677-
678-
node = &BuiltinNode{
679-
Name: identifier.Value,
680-
Arguments: arguments,
681-
}
682-
node.SetLocation(identifier.Location)
683-
} else {
684-
callee := &IdentifierNode{Value: identifier.Value}
685-
callee.SetLocation(identifier.Location)
686-
687-
arguments = append(arguments, p.parseArguments()...)
688-
689-
node = &CallNode{
690-
Callee: callee,
691-
Arguments: arguments,
692-
}
693-
node.SetLocation(identifier.Location)
694-
}
695-
696-
return node
697-
}
698-
699-
func (p *parser) parseArguments() []Node {
700-
p.expect(Bracket, "(")
701-
nodes := make([]Node, 0)
702-
for !p.current.Is(Bracket, ")") && p.err == nil {
703-
if len(nodes) > 0 {
704-
p.expect(Operator, ",")
705-
}
706-
node := p.parseExpression(0)
707-
nodes = append(nodes, node)
708-
}
709-
p.expect(Bracket, ")")
710-
711-
return nodes
712-
}

0 commit comments

Comments
 (0)