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