Skip to content

Commit a9c94aa

Browse files
committed
Fix pipe operator parsing
1 parent 677dfb5 commit a9c94aa

File tree

2 files changed

+45
-20
lines changed

2 files changed

+45
-20
lines changed

parser/parser.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ var unaryOperators = map[string]operator{
3232
}
3333

3434
var binaryOperators = map[string]operator{
35+
"|": {0, left},
3536
"or": {10, left},
3637
"||": {10, left},
3738
"and": {15, left},
@@ -147,12 +148,13 @@ func (p *parser) expect(kind Kind, values ...string) {
147148
func (p *parser) parseExpression(precedence int) Node {
148149
nodeLeft := p.parsePrimary()
149150

150-
lastOperator := ""
151+
prevOperator := ""
151152
opToken := p.current
152153
for opToken.Is(Operator) && p.err == nil {
153154
negate := false
154155
var notToken Token
155156

157+
// Handle "not *" operator, like "not in" or "not contains".
156158
if opToken.Is(Operator, "not") {
157159
p.next()
158160
notToken = p.current
@@ -164,7 +166,12 @@ func (p *parser) parseExpression(precedence int) Node {
164166
if op.precedence >= precedence {
165167
p.next()
166168

167-
if lastOperator == "??" && opToken.Value != "??" && !opToken.Is(Bracket, "(") {
169+
if opToken.Value == "|" {
170+
nodeLeft = p.parsePipe(nodeLeft)
171+
goto next
172+
}
173+
174+
if prevOperator == "??" && opToken.Value != "??" && !opToken.Is(Bracket, "(") {
168175
p.errorAt(opToken, "Operator (%v) and coalesce expressions (??) cannot be mixed. Wrap either by parentheses.", opToken.Value)
169176
break
170177
}
@@ -191,21 +198,18 @@ func (p *parser) parseExpression(precedence int) Node {
191198
nodeLeft.SetLocation(notToken.Location)
192199
}
193200

194-
lastOperator = opToken.Value
195-
opToken = p.current
196-
continue
201+
goto next
197202
}
198203
}
199204
break
205+
206+
next:
207+
prevOperator = opToken.Value
208+
opToken = p.current
200209
}
201210

202211
if precedence == 0 {
203212
nodeLeft = p.parseConditional(nodeLeft)
204-
205-
if p.current.Is(Operator, "|") {
206-
p.next()
207-
return p.parsePipe(nodeLeft)
208-
}
209213
}
210214

211215
return nodeLeft
@@ -626,11 +630,6 @@ func (p *parser) parsePipe(node Node) Node {
626630
node.SetLocation(identifier.Location)
627631
}
628632

629-
if p.current.Is(Operator, "|") {
630-
p.next()
631-
return p.parsePipe(node)
632-
}
633-
634633
return node
635634
}
636635

test/pipes/pipes_test.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,38 @@ func TestPipes(t *testing.T) {
1313
"sprintf": fmt.Sprintf,
1414
}
1515

16-
program, err := expr.Compile(`"%s bar %d" | sprintf("foo", -42 | abs())`, expr.Env(env))
17-
require.NoError(t, err)
16+
tests := []struct {
17+
input string
18+
want interface{}
19+
}{
20+
{
21+
`-1 | abs()`,
22+
1,
23+
},
24+
{
25+
`"%s bar %d" | sprintf("foo", -42 | abs())`,
26+
"foo bar 42",
27+
},
28+
{
29+
`[] | first() ?? "foo"`,
30+
"foo",
31+
},
32+
{
33+
`['a'] | first() + "b" | upper()`,
34+
"AB",
35+
},
36+
}
1837

19-
out, err := expr.Run(program, env)
20-
require.NoError(t, err)
21-
require.Equal(t, "foo bar 42", out)
38+
for _, test := range tests {
39+
t.Run(test.input, func(t *testing.T) {
40+
program, err := expr.Compile(test.input, expr.Env(env))
41+
require.NoError(t, err)
42+
43+
out, err := expr.Run(program, env)
44+
require.NoError(t, err)
45+
require.Equal(t, test.want, out)
46+
})
47+
}
2248
}
2349

2450
func TestPipes_map_filter(t *testing.T) {

0 commit comments

Comments
 (0)