github.com/expr-lang/expr@v1.16.9/expr.go (about)

     1  package expr
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"reflect"
     7  	"time"
     8  
     9  	"github.com/expr-lang/expr/ast"
    10  	"github.com/expr-lang/expr/builtin"
    11  	"github.com/expr-lang/expr/checker"
    12  	"github.com/expr-lang/expr/compiler"
    13  	"github.com/expr-lang/expr/conf"
    14  	"github.com/expr-lang/expr/file"
    15  	"github.com/expr-lang/expr/optimizer"
    16  	"github.com/expr-lang/expr/patcher"
    17  	"github.com/expr-lang/expr/vm"
    18  )
    19  
    20  // Option for configuring config.
    21  type Option func(c *conf.Config)
    22  
    23  // Env specifies expected input of env for type checks.
    24  // If struct is passed, all fields will be treated as variables,
    25  // as well as all fields of embedded structs and struct itself.
    26  // If map is passed, all items will be treated as variables.
    27  // Methods defined on this type will be available as functions.
    28  func Env(env any) Option {
    29  	return func(c *conf.Config) {
    30  		c.WithEnv(env)
    31  	}
    32  }
    33  
    34  // AllowUndefinedVariables allows to use undefined variables inside expressions.
    35  // This can be used with expr.Env option to partially define a few variables.
    36  func AllowUndefinedVariables() Option {
    37  	return func(c *conf.Config) {
    38  		c.Strict = false
    39  	}
    40  }
    41  
    42  // Operator allows to replace a binary operator with a function.
    43  func Operator(operator string, fn ...string) Option {
    44  	return func(c *conf.Config) {
    45  		p := &patcher.OperatorOverloading{
    46  			Operator:  operator,
    47  			Overloads: fn,
    48  			Types:     c.Types,
    49  			Functions: c.Functions,
    50  		}
    51  		c.Visitors = append(c.Visitors, p)
    52  	}
    53  }
    54  
    55  // ConstExpr defines func expression as constant. If all argument to this function is constants,
    56  // then it can be replaced by result of this func call on compile step.
    57  func ConstExpr(fn string) Option {
    58  	return func(c *conf.Config) {
    59  		c.ConstExpr(fn)
    60  	}
    61  }
    62  
    63  // AsAny tells the compiler to expect any result.
    64  func AsAny() Option {
    65  	return func(c *conf.Config) {
    66  		c.ExpectAny = true
    67  	}
    68  }
    69  
    70  // AsKind tells the compiler to expect kind of the result.
    71  func AsKind(kind reflect.Kind) Option {
    72  	return func(c *conf.Config) {
    73  		c.Expect = kind
    74  		c.ExpectAny = true
    75  	}
    76  }
    77  
    78  // AsBool tells the compiler to expect a boolean result.
    79  func AsBool() Option {
    80  	return func(c *conf.Config) {
    81  		c.Expect = reflect.Bool
    82  		c.ExpectAny = true
    83  	}
    84  }
    85  
    86  // AsInt tells the compiler to expect an int result.
    87  func AsInt() Option {
    88  	return func(c *conf.Config) {
    89  		c.Expect = reflect.Int
    90  		c.ExpectAny = true
    91  	}
    92  }
    93  
    94  // AsInt64 tells the compiler to expect an int64 result.
    95  func AsInt64() Option {
    96  	return func(c *conf.Config) {
    97  		c.Expect = reflect.Int64
    98  		c.ExpectAny = true
    99  	}
   100  }
   101  
   102  // AsFloat64 tells the compiler to expect a float64 result.
   103  func AsFloat64() Option {
   104  	return func(c *conf.Config) {
   105  		c.Expect = reflect.Float64
   106  		c.ExpectAny = true
   107  	}
   108  }
   109  
   110  // WarnOnAny tells the compiler to warn if expression return any type.
   111  func WarnOnAny() Option {
   112  	return func(c *conf.Config) {
   113  		if c.Expect == reflect.Invalid {
   114  			panic("WarnOnAny() works only with combination with AsInt(), AsBool(), etc. options")
   115  		}
   116  		c.ExpectAny = false
   117  	}
   118  }
   119  
   120  // Optimize turns optimizations on or off.
   121  func Optimize(b bool) Option {
   122  	return func(c *conf.Config) {
   123  		c.Optimize = b
   124  	}
   125  }
   126  
   127  // Patch adds visitor to list of visitors what will be applied before compiling AST to bytecode.
   128  func Patch(visitor ast.Visitor) Option {
   129  	return func(c *conf.Config) {
   130  		c.Visitors = append(c.Visitors, visitor)
   131  	}
   132  }
   133  
   134  // Function adds function to list of functions what will be available in expressions.
   135  func Function(name string, fn func(params ...any) (any, error), types ...any) Option {
   136  	return func(c *conf.Config) {
   137  		ts := make([]reflect.Type, len(types))
   138  		for i, t := range types {
   139  			t := reflect.TypeOf(t)
   140  			if t.Kind() == reflect.Ptr {
   141  				t = t.Elem()
   142  			}
   143  			if t.Kind() != reflect.Func {
   144  				panic(fmt.Sprintf("expr: type of %s is not a function", name))
   145  			}
   146  			ts[i] = t
   147  		}
   148  		c.Functions[name] = &builtin.Function{
   149  			Name:  name,
   150  			Func:  fn,
   151  			Types: ts,
   152  		}
   153  	}
   154  }
   155  
   156  // DisableAllBuiltins disables all builtins.
   157  func DisableAllBuiltins() Option {
   158  	return func(c *conf.Config) {
   159  		for name := range c.Builtins {
   160  			c.Disabled[name] = true
   161  		}
   162  	}
   163  }
   164  
   165  // DisableBuiltin disables builtin function.
   166  func DisableBuiltin(name string) Option {
   167  	return func(c *conf.Config) {
   168  		c.Disabled[name] = true
   169  	}
   170  }
   171  
   172  // EnableBuiltin enables builtin function.
   173  func EnableBuiltin(name string) Option {
   174  	return func(c *conf.Config) {
   175  		delete(c.Disabled, name)
   176  	}
   177  }
   178  
   179  // WithContext passes context to all functions calls with a context.Context argument.
   180  func WithContext(name string) Option {
   181  	return Patch(patcher.WithContext{
   182  		Name: name,
   183  	})
   184  }
   185  
   186  // Timezone sets default timezone for date() and now() builtin functions.
   187  func Timezone(name string) Option {
   188  	tz, err := time.LoadLocation(name)
   189  	if err != nil {
   190  		panic(err)
   191  	}
   192  	return Patch(patcher.WithTimezone{
   193  		Location: tz,
   194  	})
   195  }
   196  
   197  // Compile parses and compiles given input expression to bytecode program.
   198  func Compile(input string, ops ...Option) (*vm.Program, error) {
   199  	config := conf.CreateNew()
   200  	for _, op := range ops {
   201  		op(config)
   202  	}
   203  	for name := range config.Disabled {
   204  		delete(config.Builtins, name)
   205  	}
   206  	config.Check()
   207  
   208  	tree, err := checker.ParseCheck(input, config)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	if config.Optimize {
   214  		err = optimizer.Optimize(&tree.Node, config)
   215  		if err != nil {
   216  			var fileError *file.Error
   217  			if errors.As(err, &fileError) {
   218  				return nil, fileError.Bind(tree.Source)
   219  			}
   220  			return nil, err
   221  		}
   222  	}
   223  
   224  	program, err := compiler.Compile(tree, config)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  
   229  	return program, nil
   230  }
   231  
   232  // Run evaluates given bytecode program.
   233  func Run(program *vm.Program, env any) (any, error) {
   234  	return vm.Run(program, env)
   235  }
   236  
   237  // Eval parses, compiles and runs given input.
   238  func Eval(input string, env any) (any, error) {
   239  	if _, ok := env.(Option); ok {
   240  		return nil, fmt.Errorf("misused expr.Eval: second argument (env) should be passed without expr.Env")
   241  	}
   242  
   243  	program, err := Compile(input)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	output, err := Run(program, env)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  
   253  	return output, nil
   254  }