Currently this is not possible. Maybe in future such functionality will be available. For now you should save original source and compile in before running.
4.0 KiB
Usage
Expr package can compile and evaluate expressions.
A simple example of an expression is 1 + 2. You can also use more complicated expressions, such as foo[3].Method('bar').
See Language Definition to learn the syntax of the expr package.
The package provides 2 ways to work with expressions:
- compile: the expression is compiled to bytecode, so it can be cached and evaluated.
- evaluation: the expression is evaluated using our own virtual machine;
import "github.com/antonmedv/expr"
program, err := expr.Compile(`1 + 2`)
output, err := expr.Run(program, nil)
fmt.Println(out) // outputs 3
Passing in Variables
You can also pass variables into the expression, which can be map or struct:
env := map[string]interface{}{
"Foo": ...
"Bar": ...
}
// or
env := Env{
Foo: ...
Bar: ...
}
// Pass env option to compile for static type checking.
program, err := expr.Compile(`Foo == Bar`, expr.Env(env))
output, err := expr.Run(program, env)
Expr uses reflection for accessing and iterating passed data. For example you can pass nested structures without any modification or preparation:
type Cookie struct {
Key string
Value string
}
type User struct {
UserAgent string
Cookies []Cookie
}
type Request struct {
User *user
}
req := Request{
User: &User{
Cookies: []Cookie{{"origin", "www"}},
UserAgent: "Firefox",
},
}
program, err := expr.Compile(`User.UserAgent matches "^Fire.+$" and User.Cookies[0].Value == "www"`, expr.Env(env))
output, err := expr.Run(program, env)
Passing in Functions
You can also pass functions into the expression:
env := map[string]interface{}{
"Request": req,
"Values": func(xs []Cookie) []string {
vs := make([]string, 0)
for _, x := range xs {
vs = append(vs, x.Value)
}
return vs
},
}
program, err := expr.Compile(`"www" in Values(Request.User.Cookies)`, expr.Env(env))
output, err := expr.Run(program, env)
Struct's methods
All methods of passed struct also available as functions inside expr:
type Env struct {
value int
}
func (e *Env) Value() int {
return e.value
}
program, err := expr.Compile(`Value()`, expr.Env(&Env{}))
output, err := expr.Run(program, &Env{1})
Map types
As well as methods defined of map types.
type Env map[string]interface{}
func (Env) Swipe(in string) string {
return strings.Replace(in, "world", "user", 1)
}
env := Env{
"greeting": "hello world",
}
program, err := expr.Compile(`Swipe(greeting)`, expr.Env(env))
output, err := expr.Run(program, env)
fmt.Println(out) // outputs "hello user"
Embedded structs
If you have lots of different contexts for expressions, but want to separate functionality you can use embedded structs.
type EnvContextOne struct {
*Helpers
Price int
}
type EnvContextTwo struct {
*Helpers
City *City
}
type Helpers struct {
Value int
}
func (h *Helper) IsMore(i int) bool {
return i > h.Value
}
program, err := expr.Compile(`IsMore(Price)`, expr.Env(&EnvContextOne{}))
output, err := expr.Run(program, &EnvContextOne{...})
// ...
program, err := expr.Compile(`IsMore(City.Population)`, expr.Env(&EnvContextTwo{}))
output, err := expr.Run(program, &EnvContextTwo{...})
Visitor
ast package provides Visitor interface and BaseVisitor implementation.
You can use it for traveling ast tree of compiled program.
For example if you want 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) Enter(node *ast.Node) {}
func (v *visitor) Exit(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")
visitor := &visitor{}
ast.Walk(&tree.Node, visitor)
fmt.Printf("%v", visitor.identifiers) // outputs [foo bar]
}