Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions conf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ type Config struct {
Builtins FunctionsTable
Disabled map[string]bool // disabled builtins
NtCache nature.Cache
// DisableIfOperator disables the built-in `if ... { } else { }` operator syntax
// so that users can use a custom function named `if(...)` without conflicts.
// When enabled, the lexer treats `if`/`else` as identifiers and the parser
// will not parse `if` statements.
DisableIfOperator bool
}

// CreateNew creates new config with default values.
Expand Down
8 changes: 8 additions & 0 deletions expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ func AsFloat64() Option {
}
}

// DisableIfOperator disables the `if ... else ...` operator syntax so a custom
// function named `if(...)` can be used without conflicts.
func DisableIfOperator() Option {
return func(c *conf.Config) {
c.DisableIfOperator = true
}
}

// WarnOnAny tells the compiler to warn if expression return any type.
func WarnOnAny() Option {
return func(c *conf.Config) {
Expand Down
11 changes: 11 additions & 0 deletions expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ func ExampleCompile() {
// Output: true
}

func TestDisableIfOperator_AllowsIfFunction(t *testing.T) {
env := map[string]any{
"if": func(x int) int { return x + 1 },
}
program, err := expr.Compile("if(41)", expr.Env(env), expr.DisableIfOperator())
require.NoError(t, err)
out, err := expr.Run(program, env)
require.NoError(t, err)
assert.Equal(t, 42, out)
}

func ExampleEnv() {
type Segment struct {
Origin string
Expand Down
3 changes: 3 additions & 0 deletions parser/lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ type Lexer struct {
byte, rune int
}
eof bool
// When true, keywords `if`/`else` are not treated as operators and
// will be emitted as identifiers instead (for compatibility with custom if()).
DisableIfOperator bool
}

func (l *Lexer) Reset(source file.Source) {
Expand Down
8 changes: 7 additions & 1 deletion parser/lexer/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,14 @@ loop:
switch l.word() {
case "not":
return not
case "in", "or", "and", "matches", "contains", "startsWith", "endsWith", "let", "if", "else":
case "in", "or", "and", "matches", "contains", "startsWith", "endsWith", "let":
l.emit(Operator)
case "if", "else":
if !l.DisableIfOperator {
l.emit(Operator)
} else {
l.emit(Identifier)
}
default:
l.emit(Identifier)
}
Expand Down
10 changes: 9 additions & 1 deletion parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ func (p *Parser) Parse(input string, config *conf.Config) (*Tree, error) {
p.lexer = New()
}
p.config = config
// propagate config flags to lexer
if p.lexer != nil {
if config != nil {
p.lexer.DisableIfOperator = config.DisableIfOperator
} else {
p.lexer.DisableIfOperator = false
}
}
source := file.NewSource(input)
p.lexer.Reset(source)
p.next()
Expand Down Expand Up @@ -218,7 +226,7 @@ func (p *Parser) parseExpression(precedence int) Node {
return p.parseVariableDeclaration()
}

if precedence == 0 && p.current.Is(Operator, "if") {
if precedence == 0 && (p.config == nil || !p.config.DisableIfOperator) && p.current.Is(Operator, "if") {
return p.parseConditionalIf()
}

Expand Down
Loading