Skip to main content
Version: v1.13

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/antonmedv/expr/ast"
"github.com/antonmedv/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, we are going to replace the expression list[-1] with the list[len(list)-1].

func main() {
env := map[string]interface{}{
"list": []int{1, 2, 3},
}

code := `list[-1]` // will output 3

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{}

func (p *patcher) Visit(node *ast.Node) {
n, ok := (*node).(*ast.IndexNode)
if !ok {
return
}
unary, ok := n.Index.(*ast.UnaryNode)
if !ok {
return
}
if unary.Operator == "-" {
ast.Patch(&n.Index, &ast.BinaryNode{
Operator: "-",
Left: &ast.BuiltinNode{Name: "len", Arguments: []ast.Node{n.Node}},
Right: unary.Node,
})
}

}

Type information is also available. In the following example, any struct implementing the fmt.Stringer interface is automatically converted to string type.

func main() {
code := `Price == "$100"`

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

env := Env{100_00}

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

type Env struct {
Price Price
}

type Price int

func (p Price) String() string {
return fmt.Sprintf("$%v", int(p)/100)
}

var stringer = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()

type stringerPatcher struct{}

func (p *stringerPatcher) Visit(node *ast.Node) {
t := (*node).Type()
if t == nil {
return
}
if t.Implements(stringer) {
ast.Patch(node, &ast.CallNode{
Callee: &ast.MemberNode{
Node: *node,
Field: "String",
Property: &ast.StringNode{Value: "String"},
},
})
}

}