Rule Syntax
The syntax for creating rules is based off of logical expressions evaluating to either True (matching) or False (non- matching). Rules support a small set of data types which can be defined as literals or resolved using the Python object on which the rule is being applied. See the Data Types table for a comprehensive list of the supported types.
Not all supported operations work with all data types as noted in the table below. Rules follow a standard order of operations.
Grammar
The expression grammar supports a number of operations including basic arithmetic for numerical data and regular expressions for strings. Operations are type aware and will raise an exception when an incompatible type is used.
Supported Operations
The following table outlines all operators that can be used in Rule Engine expressions.
Operation |
Description |
Compatible Data Types |
Arithmetic Operators |
||
|
Addition |
|
|
Subtraction |
|
|
Multiplication |
|
|
Exponent |
|
|
True division |
|
|
Floor division |
|
|
Modulo |
|
Bitwise-Arithmetic Operators |
||
|
Bitwise-and 1 |
|
|
Bitwise-or 1 |
|
|
Bitwise-xor 1 |
|
|
Bitwise right shift 1 |
|
|
Bitwise left shift 1 |
|
Comparison Operators |
||
|
Equal to |
ANY |
|
Not equal to |
ANY |
Arithmetic-Comparison Operators |
||
|
Greater than |
|
|
Greater than or equal to |
|
|
Less than |
|
|
Less than or equal to |
|
Fuzzy-Comparison Operators |
||
|
Regex match 3 |
|
|
Regex search 3 |
|
|
Regex match fails 3 |
|
|
Regex search fails 3 |
|
Logical Operators |
||
|
Logical and |
ANY |
|
Logical not |
ANY |
|
Logical or |
ANY |
|
Ternary Operator |
ANY |
Accessor Operators |
||
|
Attribute access |
|
|
Safe attribute access |
|
|
Item lookup |
|
|
Safe item lookup |
1 Bitwise operations support floating point values, but if the value is not a natural number, an
EvaluationError
will be raised.
2 The arithmetic comparison operators support multiple data types however the data type of the left value must be
the same as the data type of the right. For example, a STRING
can be compared to another
STRING
but not a FLOAT
. The technique is the same lexicographical ordering
based sequence comparison technique used by Python.
3 When using regular expression operations, the expression on the left is the string to compare and the expression on the right is the regular expression to use for either the match or search operation.
Accessor Operators
Some data types support accessor operators to obtain sub-values and attributes. One example is the
STRING
which supports both attribute and item lookup operations. For example, “length” is a valid
attribute and can be accessed by appending .length
to either a string literal or symbol. Alternatively, a specific
character in a string of characters can be accessed by index. For example, the first character in a string can be
referenced by appending [0]
to either the string literal or symbol. Attempts to lookup either an invalid attribute
or item will raise a LookupError
.
Both attribute and item lookups have “safe” variants which utilize the &
operator prefix (not to be confused with
the bit-wise and operator which leverages the same symbol). The safe operator version will evaluate to
NULL
instead of raising an exception when the container value on which the operation is applied is
NULL
. Additionally, the safe version of item lookup operations will evaluate to
NULL
instead of raising a LookupError
exception when the item is
not held within the container. This is analogous the Python’s dict.get()
method.
The item lookup operation can also evaluate to an array when a stop boundary is provided. For example to reference the
first four elements of a string by appending [0:4]
to the end of the value. Alternatively, only the ending index
may be specified using [:4]
. Finally, just as in Python, negative values can be used to reference the last elements.
Array Comprehension
An operation may be able to be applied to each member of an iterable value to generate a new ARRAY
composed of the resulting expressions. This could for example be used to determine how many values within an array
match an arbitrary condition. The syntax is very similar to the list comprehension within Python and is composed of
three mandatory components with an optional condition expression. The three required components in order from left to
right are the result expression, the variable assignment and the iterable (followed by the optional condition). Each
component uses a reserved keyword as a delimiter and the entire expression is wrapped within brackets just like an array
literal.
For example, to square an array of numbers: [ v ** 2 for v in [1, 2, 3] ]
. In this case, the resulting expression is
the square operation (v ** 2
) which uses the variable v
defined in the assignment. Finally, the operation is
applied to the array literal [1, 2, 3]
, which could have been any iterable value.
An optional condition may be applied to the value before the resulting expression is evaluated using the if
keyword.
Building on the previous example, if only the squares of each odd number was needed, the expression could be updated to:
[ v ** 2 for v in [1, 2, 3] if v % 2]
. This example uses the modulo operator to filter out even values.
One limitation to the array comprehension syntax when compared to Python’s list comprehension is that the variable
assignment may not contain more than one value. There is currently no support for unpacking multiple values like Python
does, (e.g. [ v for k,v in my_dict.items() if test(k) ]
.
Ternary Operators
The ternary operator can be used in place of a traditional “if-then-else” statement. Like other languages the question mark and colon are used as the expression delimiters. A ternary expression is a combination of a condition followed by an expression used when the condition is true and ending with an expression used when the condition is false.
For example: condition ? true_case : false_case
Reserved Keywords
The following keywords are reserved and can not be used as the names of symbols.
Keyword |
Description |
|
The |
Array Comprehension |
|
|
Array comprehension result and assignment delimiter |
|
Array comprehension iterable and (optional) condition delimiter |
Booleans ( |
|
|
The “True” boolean value |
|
The “False” boolean value |
Floats ( |
|
|
Floating point value for infinity |
|
Floating point value for not-a-number |
Logical Operators |
|
|
Logical “and” operator |
|
Logical “not” operator |
|
Logical “or” operator |
Membership Operators |
|
|
Checks member is in the container |
Reserved For Future Use |
|
|
Reserved for future use |
|
Reserved for future use |
|
Reserved for future use |
Literal Values
DATETIME
and STRING
literal values are specified in a very similar manner by
defining the value as a string of characters enclosed in either single or double quotes. The difference comes in an
optional leading character before the opening quote. Either no leading character or a single s
will specify a
standard STRING
value, while a single d
will specify a DATETIME
value.
DATETIME
literals must be specified in ISO-8601 format. The underlying parsing logic is provided
by dateutil.parser.isoparse()
. DATETIME
values with no time specified (e.g.
d"2019-09-23"
) will evaluate to a DATETIME
of the specified day at exactly midnight.
TIMEDELTA
literals must be specified in a subset of the ISO-8601 format for durations. Everything
except years and months are supported in ~.DataType.TIMEDELTA values, to match the underlying representation provided
by the Python standard library.
Example rules showing equivalent literal expressions:
"foobar" == s"foobar"
d"2019-09-23" == d"2019-09-23 00:00:00"
t"P1D" == t"PT24H"
FLOAT
literals may be expressed in either binary, octal, decimal, or hexadecimal formats. The
binary, octal and hexadecimal formats use the 0b
, 0o
, and 0x
prefixes respectively. Values in the decimal
format require no prefix and is the default base in which values are represented. Only base-10, decimal values may
include a decimal place component.
Example rules showing equivalent literal expressions:
0b10 == 2
0o10 == 8
10.0 == 10
0x10 == 16
FLOAT
literals may also be expressed in scientific notation using the letter e
.
Example rules show equivalent literal expressions:
1E0 == 1
1e0 == 1
1.0e0 == 1
Builtin Symbols
The following symbols are provided by default using the from_defaults()
method. These symbols
can be accessed through the $
prefix, e.g. $pi
. The default values can be overridden by defining a custom
subclass of Context
and setting the builtins
attribute.