mirror of
https://github.com/miguelmota/cointop
synced 2024-11-18 15:25:31 +00:00
188 lines
4.6 KiB
Go
188 lines
4.6 KiB
Go
package expr
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/antonmedv/expr/ast"
|
|
"github.com/antonmedv/expr/file"
|
|
"reflect"
|
|
|
|
"github.com/antonmedv/expr/checker"
|
|
"github.com/antonmedv/expr/compiler"
|
|
"github.com/antonmedv/expr/conf"
|
|
"github.com/antonmedv/expr/optimizer"
|
|
"github.com/antonmedv/expr/parser"
|
|
"github.com/antonmedv/expr/vm"
|
|
)
|
|
|
|
// Option for configuring config.
|
|
type Option func(c *conf.Config)
|
|
|
|
// Eval parses, compiles and runs given input.
|
|
func Eval(input string, env interface{}) (interface{}, error) {
|
|
if _, ok := env.(Option); ok {
|
|
return nil, fmt.Errorf("misused expr.Eval: second argument (env) should be passed without expr.Env")
|
|
}
|
|
|
|
tree, err := parser.Parse(input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
program, err := compiler.Compile(tree, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
output, err := vm.Run(program, env)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return output, nil
|
|
}
|
|
|
|
// Env specifies expected input of env for type checks.
|
|
// If struct is passed, all fields will be treated as variables,
|
|
// as well as all fields of embedded structs and struct itself.
|
|
// If map is passed, all items will be treated as variables.
|
|
// Methods defined on this type will be available as functions.
|
|
func Env(env interface{}) Option {
|
|
return func(c *conf.Config) {
|
|
if _, ok := env.(map[string]interface{}); ok {
|
|
c.MapEnv = true
|
|
} else {
|
|
if reflect.ValueOf(env).Kind() == reflect.Map {
|
|
c.DefaultType = reflect.TypeOf(env).Elem()
|
|
}
|
|
}
|
|
c.Strict = true
|
|
c.Types = conf.CreateTypesTable(env)
|
|
c.Env = env
|
|
}
|
|
}
|
|
|
|
// AllowUndefinedVariables allows to use undefined variables inside expressions.
|
|
// This can be used with expr.Env option to partially define a few variables.
|
|
// Note what this option is only works in map environment are used, otherwise
|
|
// runtime.fetch will panic as there is no way to get missing field zero value.
|
|
func AllowUndefinedVariables() Option {
|
|
return func(c *conf.Config) {
|
|
c.Strict = false
|
|
}
|
|
}
|
|
|
|
// Operator allows to override binary operator with function.
|
|
func Operator(operator string, fn ...string) Option {
|
|
return func(c *conf.Config) {
|
|
c.Operators[operator] = append(c.Operators[operator], fn...)
|
|
}
|
|
}
|
|
|
|
// ConstExpr defines func expression as constant. If all argument to this function is constants,
|
|
// then it can be replaced by result of this func call on compile step.
|
|
func ConstExpr(fn string) Option {
|
|
return func(c *conf.Config) {
|
|
c.ConstExpr(fn)
|
|
}
|
|
}
|
|
|
|
// AsBool tells the compiler to expect boolean result.
|
|
func AsBool() Option {
|
|
return func(c *conf.Config) {
|
|
c.Expect = reflect.Bool
|
|
}
|
|
}
|
|
|
|
// AsInt64 tells the compiler to expect int64 result.
|
|
func AsInt64() Option {
|
|
return func(c *conf.Config) {
|
|
c.Expect = reflect.Int64
|
|
}
|
|
}
|
|
|
|
// AsFloat64 tells the compiler to expect float64 result.
|
|
func AsFloat64() Option {
|
|
return func(c *conf.Config) {
|
|
c.Expect = reflect.Float64
|
|
}
|
|
}
|
|
|
|
// Optimize turns optimizations on or off.
|
|
func Optimize(b bool) Option {
|
|
return func(c *conf.Config) {
|
|
c.Optimize = b
|
|
}
|
|
}
|
|
|
|
// Patch adds visitor to list of visitors what will be applied before compiling AST to bytecode.
|
|
func Patch(visitor ast.Visitor) Option {
|
|
return func(c *conf.Config) {
|
|
c.Visitors = append(c.Visitors, visitor)
|
|
}
|
|
}
|
|
|
|
// Compile parses and compiles given input expression to bytecode program.
|
|
func Compile(input string, ops ...Option) (*vm.Program, error) {
|
|
config := &conf.Config{
|
|
Operators: make(map[string][]string),
|
|
ConstExprFns: make(map[string]reflect.Value),
|
|
Optimize: true,
|
|
}
|
|
|
|
for _, op := range ops {
|
|
op(config)
|
|
}
|
|
|
|
if err := config.Check(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tree, err := parser.Parse(input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = checker.Check(tree, config)
|
|
|
|
// If we have a patch to apply, it may fix out error and
|
|
// second type check is needed. Otherwise it is an error.
|
|
if err != nil && len(config.Visitors) == 0 {
|
|
return nil, err
|
|
}
|
|
|
|
// Patch operators before Optimize, as we may also mark it as ConstExpr.
|
|
compiler.PatchOperators(&tree.Node, config)
|
|
|
|
if len(config.Visitors) >= 0 {
|
|
for _, v := range config.Visitors {
|
|
ast.Walk(&tree.Node, v)
|
|
}
|
|
_, err = checker.Check(tree, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if config.Optimize {
|
|
err = optimizer.Optimize(&tree.Node, config)
|
|
if err != nil {
|
|
if fileError, ok := err.(*file.Error); ok {
|
|
return nil, fileError.Bind(tree.Source)
|
|
}
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
program, err := compiler.Compile(tree, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return program, nil
|
|
}
|
|
|
|
// Run evaluates given bytecode program.
|
|
func Run(program *vm.Program, env interface{}) (interface{}, error) {
|
|
return vm.Run(program, env)
|
|
}
|