Skip to content

Commit 6ed72a2

Browse files
authored
Add DisableIfOperator option (#881)
1 parent 4d38449 commit 6ed72a2

File tree

6 files changed

+43
-2
lines changed

6 files changed

+43
-2
lines changed

conf/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ type Config struct {
3636
Builtins FunctionsTable
3737
Disabled map[string]bool // disabled builtins
3838
NtCache nature.Cache
39+
// DisableIfOperator disables the built-in `if ... { } else { }` operator syntax
40+
// so that users can use a custom function named `if(...)` without conflicts.
41+
// When enabled, the lexer treats `if`/`else` as identifiers and the parser
42+
// will not parse `if` statements.
43+
DisableIfOperator bool
3944
}
4045

4146
// CreateNew creates new config with default values.

expr.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ func AsFloat64() Option {
109109
}
110110
}
111111

112+
// DisableIfOperator disables the `if ... else ...` operator syntax so a custom
113+
// function named `if(...)` can be used without conflicts.
114+
func DisableIfOperator() Option {
115+
return func(c *conf.Config) {
116+
c.DisableIfOperator = true
117+
}
118+
}
119+
112120
// WarnOnAny tells the compiler to warn if expression return any type.
113121
func WarnOnAny() Option {
114122
return func(c *conf.Config) {

expr_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,17 @@ func ExampleCompile() {
7070
// Output: true
7171
}
7272

73+
func TestDisableIfOperator_AllowsIfFunction(t *testing.T) {
74+
env := map[string]any{
75+
"if": func(x int) int { return x + 1 },
76+
}
77+
program, err := expr.Compile("if(41)", expr.Env(env), expr.DisableIfOperator())
78+
require.NoError(t, err)
79+
out, err := expr.Run(program, env)
80+
require.NoError(t, err)
81+
assert.Equal(t, 42, out)
82+
}
83+
7384
func ExampleEnv() {
7485
type Segment struct {
7586
Origin string

parser/lexer/lexer.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ type Lexer struct {
4646
byte, rune int
4747
}
4848
eof bool
49+
// When true, keywords `if`/`else` are not treated as operators and
50+
// will be emitted as identifiers instead (for compatibility with custom if()).
51+
DisableIfOperator bool
4952
}
5053

5154
func (l *Lexer) Reset(source file.Source) {

parser/lexer/state.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,14 @@ loop:
129129
switch l.word() {
130130
case "not":
131131
return not
132-
case "in", "or", "and", "matches", "contains", "startsWith", "endsWith", "let", "if", "else":
132+
case "in", "or", "and", "matches", "contains", "startsWith", "endsWith", "let":
133133
l.emit(Operator)
134+
case "if", "else":
135+
if !l.DisableIfOperator {
136+
l.emit(Operator)
137+
} else {
138+
l.emit(Identifier)
139+
}
134140
default:
135141
l.emit(Identifier)
136142
}

parser/parser.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ func (p *Parser) Parse(input string, config *conf.Config) (*Tree, error) {
6262
p.lexer = New()
6363
}
6464
p.config = config
65+
// propagate config flags to lexer
66+
if p.lexer != nil {
67+
if config != nil {
68+
p.lexer.DisableIfOperator = config.DisableIfOperator
69+
} else {
70+
p.lexer.DisableIfOperator = false
71+
}
72+
}
6573
source := file.NewSource(input)
6674
p.lexer.Reset(source)
6775
p.next()
@@ -218,7 +226,7 @@ func (p *Parser) parseExpression(precedence int) Node {
218226
return p.parseVariableDeclaration()
219227
}
220228

221-
if precedence == 0 && p.current.Is(Operator, "if") {
229+
if precedence == 0 && (p.config == nil || !p.config.DisableIfOperator) && p.current.Is(Operator, "if") {
222230
return p.parseConditionalIf()
223231
}
224232

0 commit comments

Comments
 (0)