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. Here is an example, there all fmt.Stringer
interface 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",
},
})
}
}
- Next: Internals