A simple embedded expression calculator.
type Person struct {
dob time.Time
city string
married bool
}
func (p *Person) Age() int {
return int(math.Floor(time.Since(p.dob).Hours() / 24 / 365))
}
// Implements exprcalc.Gettable
func (p *Person) GetByName(name string) (interface{}, error) {
switch strings.ToLower(name) {
case "age":
return p.Age(), nil
case "city":
return p.city, nil
case "married":
return p.married, nil
}
return nil, fmt.Errorf("Getter '%v' not found", name)
}
expr := `(city == "Massachusetts" OR city == "Berkeley") AND age > 23 AND married == true`
unix := &Person{
dob: time.Unix(0, 0),
city: "Berkeley",
married: true,
}
value, err := exprcalc.Eval(expr, unix)
// or preparsed flavour
parsed, err := exprcalc.Parse(expr)
value, err = exprcalc.EvalParsed(parsed, unix)will return true as the value.
number (e.g. 1984, 3.14, –273.15, 6.62607004e-34)
string (e.g. "foo", 'bar')
bool (case-insensitive true and false)
identifier, see details below (case-sensitive, C-variable-like names, i.e. starting from an alphabetic symbol or underscore followed by alphabetic symbols, digits or underscore, e.g. foo, BAR, _myvar, __my_var, Moon44)
number == != < > <= >=
string == != < > <= >=
boolean == !=
AND OR (case-insensitive)
Parentheses, as in maths
( ), e.g. (true OR false) AND true
( ) (highest)
== != < > <= >=
AND
OR (lowest)
Implicit type casting is not supported, i.e. both operands must be of the same type. Both operands of logical operators must be boolean. Also, see caveats.
Short-circuit logical expression evaluation can result in correct evaluation of expressions with incompatible types. This behaviour trades performance against possible bug prone code. For instance, true OR "String" and false AND 123 will not fail because only the first operand is required to evaluate the result of the logical expression.
When you pass a context object implementing exprcalc.Gettable interface, it is possible to use identifiers in expressions, e.g. age > 23. The identifier is just a string that is passed into the GetByName() method of the object, that usually returns object field values or calls object's methods. Obviously, it must return one of the supported data types, i.e. any numeric type, string or boolean. Since using the interface instead of reflection, the overhead of the call is very small.