273 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			273 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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
 | 
						|
}
 |