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

FLOAT

-

Subtraction

FLOAT

*

Multiplication

FLOAT

**

Exponent

FLOAT

/

True division

FLOAT

//

Floor division

FLOAT

%

Modulo

FLOAT

Bitwise-Arithmetic Operators

&

Bitwise-and 1

FLOAT, SET

|

Bitwise-or 1

FLOAT, SET

^

Bitwise-xor 1

FLOAT, SET

>>

Bitwise right shift 1

FLOAT

<<

Bitwise left shift 1

FLOAT

Comparison Operators

==

Equal to

ANY

!=

Not equal to

ANY

Arithmetic-Comparison Operators

>

Greater than

ARRAY, BOOLEAN, DATETIME, FLOAT, NULL, STRING 2

>=

Greater than or equal to

ARRAY, BOOLEAN, DATETIME, FLOAT, NULL, STRING 2

<

Less than

ARRAY, BOOLEAN, DATETIME, FLOAT, NULL, STRING 2

<=

Less than or equal to

ARRAY, BOOLEAN, DATETIME, FLOAT, NULL, STRING 2

Fuzzy-Comparison Operators

=~

Regex match 3

NULL, STRING

=~~

Regex search 3

NULL, STRING

!~

Regex match fails 3

NULL, STRING

!~~

Regex search fails 3

NULL, STRING

Logical Operators

and

Logical and

ANY

not

Logical not

ANY

or

Logical or

ANY

?, :

Ternary Operator

ANY

Accessor Operators

.

Attribute access

ARRAY, DATETIME, MAPPING, STRING

&.

Safe attribute access

ARRAY, DATETIME, MAPPING, NULL, STRING

[

Item lookup

ARRAY, MAPPING, STRING

&[

Safe item lookup

ARRAY, MAPPING, NULL, STRING

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

null

The NullExpression literal value

Array Comprehension

for

Array comprehension result and assignment delimiter

if

Array comprehension iterable and (optional) condition delimiter

Booleans (BooleanExpression Literals)

true

The “True” boolean value

false

The “False” boolean value

Floats (FloatExpression Literals)

inf

Floating point value for infinity

nan

Floating point value for not-a-number

Logical Operators

and

Logical “and” operator

not

Logical “not” operator

or

Logical “or” operator

Membership Operators

in

Checks member is in the container

Reserved For Future Use

elif

Reserved for future use

else

Reserved for future use

while

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.

Example rules showing equivalent literal expressions:

  • "foobar" == s"foobar"

  • d"2019-09-23" == d"2019-09-23 00:00:00"

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.