Files
expr/docs/Usage.md
T
Anton Medvedev 706eff9d60 Remove info about marshaling of program
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.
2019-10-21 11:35:56 +03:00

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