This library contains quite a lot of functionality, this document is meant to be formal documentation on the operators and features of it. Some of this documentation may duplicate what's in README.md, but should never conflict.
This library only officially deals with four types; float64
, bool
, string
, and arrays.
All numeric literals, with or without a radix, will be converted to float64
for evaluation. For instance; in practice, there is no difference between the literals "1.0" and "1", they both end up as float64
. This matters to users because if you intend to return numeric values from your expressions, then the returned value will be float64
, not any other numeric type.
Any string literal (not parameter) which is interpretable as a date will be converted to a float64
representation of that date's unix time. Any time.Time
parameters will not be operable with these date literals; such parameters will need to use the time.Time.Unix()
method to get a numeric representation.
Arrays are untyped, and can be mixed-type. Internally they're all just interface{}
. Only two operators can interact with arrays, IN
and ,
. All other operators will refuse to operate on arrays.
If either left or right sides of the +
operator are a string
, then this operator will perform string concatenation and return that result. If neither are string, then both must be numeric, and this will return a numeric result.
Any other case is invalid.
**
refers to "take to the power of". For instance, 3 ** 4
== 81.
- Left side: numeric
- Right side: numeric
- Returns: numeric
All of these operators convert their float64
left and right sides to int64
, perform their operation, and then convert back.
Given how this library assumes numeric are represented (as float64
), it is unlikely that this behavior will change, even though it may cause havoc with extremely large or small numbers.
- Left side: numeric
- Right side: numeric
- Returns: numeric
Prefix only. This can never have a left-hand value.
- Right side: numeric
- Returns: numeric
Prefix only. This can never have a left-hand value.
- Right side: bool
- Returns: bool
Prefix only. This can never have a left-hand value.
- Right side: numeric
- Returns: numeric
For all logical operators, this library will short-circuit the operation if the left-hand side is sufficient to determine what to do. For instance, true || expensiveOperation()
will not actually call expensiveOperation()
, since it knows the left-hand side is true
.
- Left side: bool
- Right side: bool
- Returns: bool
Checks if the left side is true
. If so, returns the right side. If the left side is false
, returns nil
.
In practice, this is commonly used with the other ternary operator.
- Left side: bool
- Right side: Any type.
- Returns: Right side or
nil
Checks if the left side is nil
. If so, returns the right side. If the left side is non-nil, returns the left side.
In practice, this is commonly used with the other ternary operator.
- Left side: Any type.
- Right side: Any type.
- Returns: Right side or
nil
Similar to the C# operator. If the left value is non-nil, it returns that. If not, then the right-value is returned.
- Left side: Any type.
- Right side: Any type.
- Returns: No specific type - whichever is passed to it.
If both sides are numeric, this returns the usual greater/lesser behavior that would be expected. If both sides are string, this returns the lexicographic comparison of the strings. This uses Go's standard lexicographic compare.
- Accepts: Left and right side must either be both string, or both numeric.
- Returns: bool
These use go's standard regexp
flavor of regex. The left side is expected to be the candidate string, the right side is the pattern. =~
returns whether or not the candidate string matches the regex pattern given on the right. !~
is the inverted version of the same logic.
- Left side: string
- Right side: string
- Returns: bool
The separator, always paired with parenthesis, creates arrays. It must always have both a left and right-hand value, so for instance (, 0)
and (0,)
are invalid uses of it.
Again, this should always be used with parenthesis; like (1, 2, 3, 4)
.
The only operator with a text name, this operator checks the right-hand side array to see if it contains a value that is equal to the left-side value.
Equality is determined by the use of the ==
operator, and this library doesn't check types between the values. Any two values, when cast to interface{}
, and can still be checked for equality with ==
will act as expected.
Note that you can use a parameter for the array, but it must be an []interface{}
.
- Left side: Any type.
- Right side: array
- Returns: bool
Parameters must be passed in every time the expression is evaluated. Parameters can be of any type, but will not cause errors unless actually used in an erroneous way. There is no difference in behavior for any of the above operators for parameters - they are type checked when used.
All int
and float
values of any width will be converted to float64
before use.
At no point is the parameter structure, or any value thereof, modified by this library.
The default form of parameters as a map may not serve your use case. You may have parameters in some other structure, you may want to change the no-parameter-found behavior, or maybe even just have some debugging print statements invoked when a parameter is accessed.
To do this, define a type that implements the govaluate.Parameters
interface. When you want to evaluate, instead call EvaluableExpression.Eval
and pass your parameter structure.
During expression parsing (not evaluation), a map of functions can be given to govaluate.NewEvaluableExpressionWithFunctions
(the lengthiest and finest of function names). The resultant expression will be able to invoke those functions during evaluation. Once parsed, an expression cannot have functions added or removed - a new expression will need to be created if you want to change the functions, or behavior of said functions.
Functions always take the form <name>(<parameters>)
, including parens. Functions can have an empty list of parameters, like <name>()
, but still must have parens.
If the expression contains something that looks like it ought to be a function (such as foo()
), but no such function was given to it, it will error on parsing.
Functions must be of type map[string]govaluate.ExpressionFunction
. ExpressionFunction
, for brevity, has the following signature:
func(args ...interface{}) (interface{}, error)
Where args
is whatever is passed to the function when called. If a non-nil error is returned from a function during evaluation, the evaluation stops and ultimately returns that error to the caller of Evaluate()
or Eval()
.
There aren't any builtin functions. The author is opposed to maintaining a standard library of functions to be used.
Every use case of this library is different, and even in simple use cases (such as parameters, see above) different users need different behavior, naming, or even functionality. The author prefers that users make their own decisions about what functions they need, and how they operate.
The ==
and !=
operators involve a moderately complex workflow. They use reflect.DeepEqual
. This is for complicated reasons, but there are some types in Go that cannot be compared with the native ==
operator. Arrays, in particular, cannot be compared - Go will panic if you try. One might assume this could be handled with the type checking system in govaluate
, but unfortunately without reflection there is no way to know if a variable is a slice/array. Worse, structs can be incomparable if they contain incomparable types.
It's all very complicated. Fortunately, Go includes the reflect.DeepEqual
function to handle all the edge cases. Currently, govaluate
uses that for all equality/inequality.