Skip to main content
Version: v1.15

Visitor and Patch

The ast package provides the ast.Visitor interface and the ast.Walk function. It can be used to customize the AST before compiling.

For example, to collect all variable names:

package main

import (
"fmt"

"github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/parser"
)

type visitor struct {
identifiers []string
}

func (v *visitor) Visit(node *ast.Node) {
if n, ok := (*node).(*ast.IdentifierNode); ok {
v.identifiers = append(v.identifiers, n.Value)
}
}

func main() {
tree, err := parser.Parse("foo + bar")
if err != nil {
panic(err)
}

visitor := &visitor{}
ast.Walk(&tree.Node, visitor)

fmt.Printf("%v", visitor.identifiers) // outputs [foo bar]
}

Patch

Specify a visitor to modify the AST with expr.Patch function.

program, err := expr.Compile(code, expr.Patch(&visitor{}))

For example, let's pass a context to every function call:

package main

import (
"context"
"fmt"
"reflect"

"github.com/expr-lang/expr"
"github.com/expr-lang/expr/ast"
)

func main() {
env := map[string]interface{}{
"foo": func(ctx context.Context, arg int) any {
// ...
},
"ctx": context.Background(),
}

code := `foo(42)` // will be converted to foo(ctx, 42)

program, err := expr.Compile(code, expr.Env(env), expr.Patch(patcher{}))
if err != nil {
panic(err)
}

output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Print(output)
}

type patcher struct{}

var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()

func (patcher) Visit(node *ast.Node) {
callNode, ok := (*node).(*ast.CallNode)
if !ok {
return
}
callNode.Arguments = append([]ast.Node{&ast.IdentifierNode{Value: "ctx"}}, callNode.Arguments...)
}