123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- package govaluate
- import (
- "errors"
- "fmt"
- )
- const isoDateFormat string = "2006-01-02T15:04:05.999999999Z0700"
- const shortCircuitHolder int = -1
- var DUMMY_PARAMETERS = MapParameters(map[string]interface{}{})
- /*
- EvaluableExpression represents a set of ExpressionTokens which, taken together,
- are an expression that can be evaluated down into a single value.
- */
- type EvaluableExpression struct {
- /*
- Represents the query format used to output dates. Typically only used when creating SQL or Mongo queries from an expression.
- Defaults to the complete ISO8601 format, including nanoseconds.
- */
- QueryDateFormat string
- /*
- Whether or not to safely check types when evaluating.
- If true, this library will return error messages when invalid types are used.
- If false, the library will panic when operators encounter types they can't use.
- This is exclusively for users who need to squeeze every ounce of speed out of the library as they can,
- and you should only set this to false if you know exactly what you're doing.
- */
- ChecksTypes bool
- tokens []ExpressionToken
- evaluationStages *evaluationStage
- inputExpression string
- }
- /*
- Parses a new EvaluableExpression from the given [expression] string.
- Returns an error if the given expression has invalid syntax.
- */
- func NewEvaluableExpression(expression string) (*EvaluableExpression, error) {
- functions := make(map[string]ExpressionFunction)
- return NewEvaluableExpressionWithFunctions(expression, functions)
- }
- /*
- Similar to [NewEvaluableExpression], except that instead of a string, an already-tokenized expression is given.
- This is useful in cases where you may be generating an expression automatically, or using some other parser (e.g., to parse from a query language)
- */
- func NewEvaluableExpressionFromTokens(tokens []ExpressionToken) (*EvaluableExpression, error) {
- var ret *EvaluableExpression
- var err error
- ret = new(EvaluableExpression)
- ret.QueryDateFormat = isoDateFormat
- err = checkBalance(tokens)
- if err != nil {
- return nil, err
- }
- err = checkExpressionSyntax(tokens)
- if err != nil {
- return nil, err
- }
- ret.tokens, err = optimizeTokens(tokens)
- if err != nil {
- return nil, err
- }
- ret.evaluationStages, err = planStages(ret.tokens)
- if err != nil {
- return nil, err
- }
- ret.ChecksTypes = true
- return ret, nil
- }
- /*
- Similar to [NewEvaluableExpression], except enables the use of user-defined functions.
- Functions passed into this will be available to the expression.
- */
- func NewEvaluableExpressionWithFunctions(expression string, functions map[string]ExpressionFunction) (*EvaluableExpression, error) {
- var ret *EvaluableExpression
- var err error
- ret = new(EvaluableExpression)
- ret.QueryDateFormat = isoDateFormat
- ret.inputExpression = expression
- ret.tokens, err = parseTokens(expression, functions)
- if err != nil {
- return nil, err
- }
- err = checkBalance(ret.tokens)
- if err != nil {
- return nil, err
- }
- err = checkExpressionSyntax(ret.tokens)
- if err != nil {
- return nil, err
- }
- ret.tokens, err = optimizeTokens(ret.tokens)
- if err != nil {
- return nil, err
- }
- ret.evaluationStages, err = planStages(ret.tokens)
- if err != nil {
- return nil, err
- }
- ret.ChecksTypes = true
- return ret, nil
- }
- /*
- Same as `Eval`, but automatically wraps a map of parameters into a `govalute.Parameters` structure.
- */
- func (this EvaluableExpression) Evaluate(parameters map[string]interface{}) (interface{}, error) {
- if parameters == nil {
- return this.Eval(nil)
- }
- return this.Eval(MapParameters(parameters))
- }
- /*
- Runs the entire expression using the given [parameters].
- e.g., If the expression contains a reference to the variable "foo", it will be taken from `parameters.Get("foo")`.
- This function returns errors if the combination of expression and parameters cannot be run,
- such as if a variable in the expression is not present in [parameters].
- In all non-error circumstances, this returns the single value result of the expression and parameters given.
- e.g., if the expression is "1 + 1", this will return 2.0.
- e.g., if the expression is "foo + 1" and parameters contains "foo" = 2, this will return 3.0
- */
- func (this EvaluableExpression) Eval(parameters Parameters) (interface{}, error) {
- if this.evaluationStages == nil {
- return nil, nil
- }
- if parameters != nil {
- parameters = &sanitizedParameters{parameters}
- }
- return this.evaluateStage(this.evaluationStages, parameters)
- }
- func (this EvaluableExpression) evaluateStage(stage *evaluationStage, parameters Parameters) (interface{}, error) {
- var left, right interface{}
- var err error
- if stage.leftStage != nil {
- left, err = this.evaluateStage(stage.leftStage, parameters)
- if err != nil {
- return nil, err
- }
- }
- if stage.isShortCircuitable() {
- switch stage.symbol {
- case AND:
- if left == false {
- return false, nil
- }
- case OR:
- if left == true {
- return true, nil
- }
- case COALESCE:
- if left != nil {
- return left, nil
- }
-
- case TERNARY_TRUE:
- if left == false {
- right = shortCircuitHolder
- }
- case TERNARY_FALSE:
- if left != nil {
- right = shortCircuitHolder
- }
- }
- }
- if right != shortCircuitHolder && stage.rightStage != nil {
- right, err = this.evaluateStage(stage.rightStage, parameters)
- if err != nil {
- return nil, err
- }
- }
- if this.ChecksTypes {
- if stage.typeCheck == nil {
- err = typeCheck(stage.leftTypeCheck, left, stage.symbol, stage.typeErrorFormat)
- if err != nil {
- return nil, err
- }
- err = typeCheck(stage.rightTypeCheck, right, stage.symbol, stage.typeErrorFormat)
- if err != nil {
- return nil, err
- }
- } else {
- // special case where the type check needs to know both sides to determine if the operator can handle it
- if !stage.typeCheck(left, right) {
- errorMsg := fmt.Sprintf(stage.typeErrorFormat, left, stage.symbol.String())
- return nil, errors.New(errorMsg)
- }
- }
- }
- return stage.operator(left, right, parameters)
- }
- func typeCheck(check stageTypeCheck, value interface{}, symbol OperatorSymbol, format string) error {
- if check == nil {
- return nil
- }
- if check(value) {
- return nil
- }
- errorMsg := fmt.Sprintf(format, value, symbol.String())
- return errors.New(errorMsg)
- }
- /*
- Returns an array representing the ExpressionTokens that make up this expression.
- */
- func (this EvaluableExpression) Tokens() []ExpressionToken {
- return this.tokens
- }
- /*
- Returns the original expression used to create this EvaluableExpression.
- */
- func (this EvaluableExpression) String() string {
- return this.inputExpression
- }
- /*
- Returns an array representing the variables contained in this EvaluableExpression.
- */
- func (this EvaluableExpression) Vars() []string {
- var varlist []string
- for _, val := range this.Tokens() {
- if val.Kind == VARIABLE {
- varlist = append(varlist, val.Value.(string))
- }
- }
- return varlist
- }
|