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 3You 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) 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) 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}) 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"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{...})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]
}