Expressions
This document describes the revised AOP expression language. The implementation of AOP expression in current versions of WinGuard and the SDKs is not yet updated to this specification. See Legacy expressions for an overview of the differences. For most common use cases, the syntax and semantics are unchanged.
This document specifies the syntax and semantics of the AOP Expression Language.
Expressions are built on top of the AOP Data Model.
They are available in various contexts such as filter conditions in data APIs, or in conditions (condition, available) in device adapter XMLs.
Grammar
The syntax of AOP expressions can be defined by the following grammar in ABNF form (RFC 5234). As usual, all string sequences in the grammar are case-insensitive.
; character classes
WSP = " " / %x09 / %x0D / %x0A ; space, \t, \r, \n
DIGIT = %x30-39 ; 0-9
HEXDIGIT = DIGIT / %x41-46 / %x61-66 ; 0-9, A-F, a-f
ALPHA = %x41-5A / %x61-7A ; A-Z, a-z
; number literals
decimal-int = [ "-" ] 1*DIGIT
hex-int = [ "-" ] "0x" 1*HEXDIGIT
fraction = "." 1*DIGIT
exponent = "e" [ "+" / "-" ] 1*DIGIT
float = [ "-" ] 1*DIGIT ( fraction [ exponent ] / exponent )
number-literal = float / decimal-int / hex-int
; strings
sqs-char = %x20-26 / %x28-7E / %x80-10FFFF ; Unicode code points except ASCII control chars and ' (0x27)
dqs-char = %x20-21 / %x23-7E / %x80-10FFFF ; Unicode code points except ASCII control chars and " (0x22)
escape-sequence = "\" ( "\" / "'" / %x22 / %x09 / %x0D / %x0A ) ; \' \" \\ \t \r \n
/ "\" %x75 "{" 1*6HEXDIGIT "}" ; \u{X...X} for Unicode code points
single-quoted-string = "'" *( sqs-char / escape-sequence ) "'"
double-quoted-string = %x22 *( dqs-char / escape-sequence ) %x22
string-literal = single-quoted-string / double-quoted-string
; variable
symbol = [ "@" ] ALPHA *( ALPHA / DIGIT / "_" )
identifier = symbol *( "." symbol )
; guid
guid-literal = "{" 8HEXDIGIT "-" 4HEXDIGIT "-" 4HEXDIGIT "-" 4HEXDIGIT "-" 12HEXDIGIT "}"
; null, bool, special float
keyword-literal = "null" / "true" / "false" / "NaN" / "Infinity"
; expressions
expression = *WSP primary *WSP
primary = "(" expression ")"
/ literal ; -> ConstExpression
/ identifier ; -> VariableExpression
/ identifier 1*WSP "exists" ; -> VariableExistsExpression
/ expression *WSP binary-operator *WSP expression ; -> BinaryExpression
/ unary-operator *WSP expression ; -> UnaryExpression
/ expression postfix-operator ; -> UnaryExpression
/ identifier *WSP "(" [ argument-list ] ")" ; -> FunctionCallExpression
/ "[" [ argument-list ] "]" ; -> ArrayExpression
argument-list = expression *( "," expression )
binary-operator = symbolic-binary-operator | (1*WSP textual-binary-operator 1*WSP)
symbolic-binary-operator = "+" / "-" / "*" / "/" / "%"
/ "&&" / "||"
/ "&" / "|" / "^"
/ "=" / "==" / "<" / "<=" / ">" / ">="
textual-binary-operator = "and" / "or" / "in" / "not" 1*WSP "in"
/ "lt" / "lte" / "gt" / "gte"
/ "bitand" / "bitor" / "bitxor"
unary-operator = symbolic-unary-operator | (textual-unary-operator 1*WSP)
symbolic-unary-operator = "-" / "!" / "~"
textual-unary-operator = "not"
postfix-operator = 1*WSP ( "is" 1*WSP "empty" / "is" 1*WSP "not" 1*WSP "empty" )
literal = keyword-literal / string-literal / number-literal / guid-literal
Keywords
The following is a list of reserved keywords: null, true, false, NaN, Infinity, not, if, match, let.
Keywords can not be used as identifiers.
For example, if the parser encounters the token true in a context where an identifier is acceptable, it must be parsed as a keyword literal instead of an identifier.
However, if the keyword is merely a prefix of an identifier (e.g. trueValue, null.var), it must be parsed as an identifier instead.
Keywords are case-insensitive, so TRUE, True, and true are all valid keyword literals that produce the boolean value true.
The keyword not is reserved and only used as a unary operator, since a function call to a function with that name would be ambiguous with the operator and a parenthesized operand (i.e., not (x)).
For this reason, further textual prefix unary operators should not be added in the future.
The keywords if, match, and let are reserved for future use.
Using them as identifiers will result in a parsing error.
Literals
A literal is a constant AOP value that is directly represented in the expression syntax.
Not all types have literal syntax; for example, there is no literal syntax for time or data values, but they can be constructed using built-in type constructor functions as described in the Type functions section.
The array syntax ([a, b, c]) is technically not a literal in this sense, since each element can contain arbitrary sub-expressions (including variable references or function calls) and its value is not fixed until evaluated.
AOP expressions support the following literal types:
Null literal
null produces the null value.
Bool literals
true and false produce the corresponding boolean values.
Integer literals
Integer literals are either decimal (e.g., 42, -100) or hexadecimal (e.g., 0xFF, -0x1A).
The leading - is considered part of the literal if it is immediately followed by a digit; otherwise, it is parsed as a unary negation operator.
Values larger than 263-1 or smaller than −263 must result in a parsing error.
Hexadecimal literals are parsed as a magnitude with optional leading -, not as a raw bit pattern, so -1 is expressed as -0x1, not 0xFFFFFFFFFFFFFFFF.
A negative literal therefore always has a leading -, and there is no exception for hex literals.
The interpretation of hex literals also does not depend on the integer storage size.
Float literals
Decimal numbers with fractional part and/or exponent (e.g., 3.14, -1e-5).
If a float literal has neither a fractional part nor an exponent, it is parsed as an integer literal instead (e.g., 1 is an integer, not a float).
The leading - is considered part of the literal if it is immediately followed by a digit; otherwise, it is parsed as a unary negation operator.
Values exceeding the range of representable finite 64-bit float values (approximately ±1.7976931348623157e+308) must result in a parsing error. Literals with a magnitude too small to represent may round to zero and are accepted.
Keyword literals NaN and Infinity represent the special float values NaN and positive infinity, respectively.
Negative infinity can be represented as -Infinity (which can be parsed as unary - operator plus Infinity constant expression, or as an combined -Infinity constant expression, both of which are equivalent).
String literals
String literals are text enclosed in single or double quotes, with support for escape sequences. Control characters (U+0000 to U+001F, U+007F) are not allowed unescaped in string literals, other Unicode characters are allowed verbatim.
The following escape sequences are supported:
\'→'\"→"\\→\\t→ tab (U+0009)\r→ carriage return (U+000D)\n→ line feed (U+000A)\u{X...X}→ Unicode code point with 1 to 6 hex digits (e.g.,\u{1F600}for 😀)
An invalid escape sequence results in a parsing error. Unicode code points above U+10FFFF are not allowed and result in a parsing error.
Guid literals
Guid literals use the standard format enclosed in curly braces (e.g., {550e8400-e29b-41d4-a716-446655440000}).
Operators
The following operators are supported in AOP expressions.
Operators are listed from highest to lowest binding strength.
The parser must respect the precedence rules when parsing expressions without parentheses; for example, 1 + 2 * 3 must be parsed as 1 + (2 * 3), not (1 + 2) * 3.
Operators with the same precedence are parsed according to their associativity rules as described below; for example, 1 - 2 - 3 must be parsed as (1 - 2) - 3, not 1 - (2 - 3).
As usual, parentheses can be used to override precedence and associativity rules when needed.
| Level | Operator | Alias | Category | Description |
|---|---|---|---|---|
| 1 | - |
- | Prefix unary | Unary negation |
~ |
- | Prefix unary | Bitwise NOT | |
! |
not |
Prefix unary | Logical NOT | |
| 2 | is empty |
- | Postfix unary | Checks if empty |
is not empty |
- | Postfix unary | Checks if not empty | |
| 3 | * |
- | Binary | Multiplication |
/ |
- | Binary | Division | |
% |
- | Binary | Modulus | |
| 4 | + |
- | Binary | Addition |
- |
- | Binary | Subtraction | |
| 5 | & |
bitand |
Binary | Bitwise AND |
| 6 | ^ |
bitxor |
Binary | Bitwise XOR |
| 7 | | |
bitor |
Binary | Bitwise OR |
| 8 | in |
- | Binary | Membership test |
not in |
- | Binary | Negated membership test | |
| 9 | < |
lt |
Binary | Less than |
<= |
lte |
Binary | Less than or equal | |
> |
gt |
Binary | Greater than | |
>= |
gte |
Binary | Greater than or equal | |
| 10 | == |
= |
Binary | Equality |
!= |
- | Binary | Inequality | |
| 11 | && |
and |
Binary | Logical AND |
| 12 | || |
or |
Binary | Logical OR |
All binary and unary postfix operators are left-associative. All unary prefix operators are right-associative.
Note that exists is not an operator but rather a reserved keyword that can only appear immediately after an identifier to form a VariableExistsExpression.
It binds tighter than any operator, so !x exists is parsed as !(x exists), not (!x) exists.
Lexical categories
Operators are divided into two lexical categories:
- Symbolic operators (e.g.,
+,&&,!=)- Symbolic operators are self-delimiting and do not require surrounding whitespace.
- Textual operators (e.g.,
and,or,is not empty)- Textual operators must be surrounded by at least one whitespace character (on both sides for binary operators, on the operand side for unary operators).
- Textual operators are case-insensitive, so
AND,And, andandare all valid and equivalent. - Textual operators that consist of multiple words (e.g.,
is not empty) must have at least one whitespace character between each word.
Operator aliases
Some operators have multiple equivalent forms.
All forms are semantically equivalent and have the same precedence and associativity.
They are provided as aliases for convenience and readability; for example, in XML contexts where & and < characters would require escaping, and can be used instead of &&, and lt can be used instead of <.
The alias for = for == is provided to prevent errors, but the use of == is generally recommended.
Abstract syntax
The abstract syntax defines the logical structure of AOP expressions by decomposing them into distinct expression types. This representation is independent of concrete syntax and defines the semantic categories that the parser produces. Each expression type corresponds to a specific grammatical construct and has well-defined evaluation semantics as described in the Semantics section. The abstract syntax of AOP expressions is defined as follows:
AopExpression =
| ConstExpression(AopValue Value)
| VariableExpression(SymbolPath Name)
| VariableExistsExpression(SymbolPath Name)
| BinaryExpression(AopExpression Left, BinaryOperation Operation, AopExpression Right)
| UnaryExpression(UnaryOperation Operation, AopExpression Operand)
| FunctionCallExpression(SymbolPath FunctionName, AopExpression[] Operands)
| ArrayExpression(AopExpression[] Elements)
An implementation must disallow creating AST nodes which do not round-trip. In other words, it must be possible to unambiguously convert between concrete syntax and the above abstract syntax. In particular:
- Creating
VariableExpression,VariableExistsExpression, orFunctionCallExpressionwith an empty name or with a name that is a reserved keyword is not allowed.
Semantics
The following section defines the semantics of AOP expressions, i.e., how expressions are evaluated to produce values.
ConstExpression
A ConstExpression evaluates to its declared AOP value.
VariableExpression
A VariableExpression evaluates to the value of the variable with the given name in the current environment.
Which variables are available in the environment depends on the context in which the expression is evaluated. For device adapter XMLs, refer to Device adapter XML contexts for details.
If a variable with the given name does not exist in the provided environment, the expression evaluates to null.
The same variable should always evaluate to the same value within a single evaluation of an expression. The evaluator may therefore cache variable values to avoid repeated lookups, but it must not cache values across multiple evaluations of the same expression.
VariableExistsExpression
A VariableExistsExpression evaluates to true if a variable with the given name exists in the current environment, and false otherwise.
BinaryExpression
The semantics of a BinaryExpression depend on the specific BinaryOperation.
See the Operators section below for details.
With the exception of logical AND (&& / and) and OR (|| / or), all other binary operators evaluate both operands first, then apply the operator to the resulting values (eager evaluation).
Logical AND and OR use short-circuiting evaluation as described in the Operators section.
UnaryExpression
The semantics of a UnaryExpression depend on the specific UnaryOperation.
See the Operators section below for details.
All unary operators evaluate their operand first, then apply the operator to the resulting value (eager evaluation).
FunctionCallExpression
All arguments are evaluated first, then the function is called (applicative order evaluation).
The expression is evaluated to the return value of the function.
The semantics of a FunctionCallExpression depend on the specific function being called.
Function names are case-insensitive, i.e., time("2024-01-01T00:00:00Z") and Time("2024-01-01T00:00:00Z") are equivalent.
See the Built-in functions section below for details on built-in functions; custom functions may have their own semantics as defined by their implementation.
ArrayExpression
An ArrayExpression is evaluated by evaluating each element expression in order, and returning an array value containing the resulting values.
Operator semantics
Binary operators receive exactly two operands and return a value. Unary operators receive exactly one operand and return a value.
Operators are defined for specific operand types as described below.
If an operator is applied to unsupported operand types, the result is null.
Operators do not automatically convert operand types unless explicitly stated; for example, if an operator requires a string operand, passing an int will not be implicitly converted.
Comparison operators
All comparison operators return bool.
All comparison operators are defined for all operand types; there is no combination of operand types that does not return a boolean.
When comparing two AOP values, the result is one of the following:
Equal: Both operands are considered equal.Less: The left operand is considered less than the right operand.Greater: The left operand is considered greater than the right operand.Unordered: The operands are not equal but also cannot be meaningfully ordered.
Depending on the operator, the comparison result is then interpreted as follows to produce the final boolean result:
| Operator | Equal | Less | Greater | Unordered |
|---|---|---|---|---|
==, = |
true |
false |
false |
false |
!= |
false |
true |
true |
true |
<, lt |
false |
true |
false |
false |
<=, lte |
true |
true |
false |
false |
>, gt |
false |
false |
true |
false |
>=, gte |
true |
false |
true |
false |
Value promotion
When comparing values of different types, comparison operators use the rules defined in Data model: conversions and comparison of values to promote values to a common type first.
If the values cannot be converted to a common type, the result is Unordered.
Comparing null with any other type always results in Unordered.
Legacy time formats
In addition to the standard type conversion rules, expressions also allow additional legacy formats when converting string to time values:
- “yyyy-M-d H:m:s” (example: “2026-6-19 8:30:45” or “2026-06-19 08:30:45”)
- “yyyy-M-d H:m” (example: “2026-6-19 8:30” or “2026-06-19 08:30”)
- “yyyy-M-d” (example: “2026-6-19” or “2026-06-19”)
These formats do not specify a time zone; they must be interpreted as the respective local time of the system. Because this is indeterministic, it is recommended to use the standard ISO 8601 format when possible, which is unambiguous and universally supported.
Value comparison
Values of the same type are compared according to the following rules:
| Type | Ordering |
|---|---|
(null) |
Equal only to null |
bool |
Equal if both are true or both are false, otherwise Unordered |
int |
Equal, Less or Greater based on integer values |
float |
Equal, Less or Greater based on IEEE 754 rules; NaN is Unordered with everything including itself |
string |
Equal, Less or Greater based on Unicode code-point ordinal; no locale, no normalization |
guid |
Equal, Less or Greater based on guid comparison rules |
time |
Equal, Less or Greater based on chronological order |
data |
Equal, Less or Greater based on lexicographic byte order |
object |
Equal iff same property set (case-insensitive) with pairwise-equal values; otherwise Unordered |
map |
Equal iff same key set (case-sensitive) with pairwise-equal values; otherwise Unordered |
array |
Equal, Less, Greater or Unordered; lexicographic by element; first non-equal element pair determines order, result is Unordered if that element pair is Unordered; if one array is a prefix of the other, the shorter array is Less |
indexed array |
Equal iff same index set with pairwise-equal values; otherwise Unordered |
Logical operators
Logical AND (&& / and) and OR (|| / or)
&& / and (short-circuit AND):
- Evaluate left operand.
- Convert the operand value to
boolaccording to standard conversion rules, defaulting tofalseif conversion fails. - If the result is
false, returnfalsewithout evaluating right.
- Convert the operand value to
- Evaluate right operand.
- Convert the operand value to
boolaccording to standard conversion rules, defaulting tofalseif conversion fails. - Return the result of the right operand evaluation.
- Convert the operand value to
|| / or (short-circuit OR):
- Evaluate left operand.
- Convert the operand value to
boolaccording to standard conversion rules, defaulting tofalseif conversion fails. - If the result is
true, returntruewithout evaluating right.
- Convert the operand value to
- Evaluate right operand.
- Convert the operand value to
boolaccording to standard conversion rules, defaulting tofalseif conversion fails. - Return the result of the right operand evaluation.
- Convert the operand value to
Logical NOT (! / not)
If the operand is null, the result is null.
For all other values, applies the same truthiness rule as is empty.
Arithmetic operators
Arithmetic operators are defined for int and float as described below.
All arithmetic operators involving float follow IEEE 754 double-precision semantics, including overflow, underflow, and special values like NaN and Infinity.
For example, 1.0 / 0.0 → Infinity, -1.0 / 0.0 → -Infinity, 0.0 / 0.0 → NaN, and any operation involving NaN results in NaN.
Addition (+)
Performs addition for numeric types and concatenation for strings.
| A | B | Description |
|---|---|---|
int |
int |
Integer addition, with overflow wrapping |
float |
float |
IEEE 754 double precision addition |
int |
float |
Convert int to float, then add as floats |
float |
int |
Convert int to float, then add as floats |
string |
string |
String concatenation |
Subtraction (-), multiplication (*), division (/), modulus (%)
Performs the corresponding arithmetic operation for numeric types.
| A | B | Description |
|---|---|---|
int |
int |
Integer operation, with overflow wrapping |
float |
float |
IEEE 754 double precision operation |
int |
float |
Convert int to float, then operate as floats |
float |
int |
Convert int to float, then operate as floats |
Special cases:
- Integer division by zero evaluates to
null. - Integer modulus by zero evaluates to
null. -9223372036854775808 / −1and-9223372036854775808 % −1evaluates tonull.int / int: Truncates toward zero.int % int: Sign follows the dividend (e.g.,-1 % 2→-1,1 % -2→1).
Unary negation (-)
Unary negation is defined for int and float as the arithmetic negation of the value.
−(-9223372036854775808) overflows back to -9223372036854775808.
Bitwise (&, |, ^, ~)
Both operands must be int.
All operations are 64-bit.
| Operator | Type | Description |
|---|---|---|
&, bitand |
binary | Bitwise AND: x & y has a 1 bit wherever both x and y have a 1 bit |
|, bitor |
binary | Bitwise OR: x | y has a 1 bit wherever either x or y has a 1 bit |
^, bitxor |
binary | Bitwise XOR: x ^ y has a 1 bit wherever x and y differ |
~ |
unary | Bitwise NOT: ~x has a 1 bit wherever x has a 0 bit |
Membership (in / not in)
Returns bool.
The semantics of in depend on the types of the left and right operands as described in the table below.
not in is the logical negation of in, i.e., x not in y is equivalent to !(x in y).
| Left operand | Right operand | Semantics |
|---|---|---|
not array |
array |
true iff the left operand equals at least one element of the array according to == semantics |
array |
array |
true iff every element of the left array equals at least one element in the right array according to == semantics |
string |
string |
true iff the left string is a contiguous substring of the right string |
The following edge cases illustrate the semantics of in:
Not-array-in-array (x in arr):
- Duplicate elements in the right operand do not affect the result; for example,
x in [x, x]istrue. null in arristrueif any element ofarrisnull.x in []is alwaysfalse.- To check if an array value
xis in an array, use[x] in arr(i.e., wrapxin a single-element array).
Array-in-array (arr1 in arr2):
- Duplicate left elements do not require duplicates on the right, e.g.,
[x, x] in [x]istrue. [] in arr2is alwaystrue.
String-in-string (s in t, both string):
"" in tis alwaystrue.
is empty / is not empty
Returns bool. is not empty is the logical negation of is empty.
A value is empty according to the following per-type rules:
| Type | Empty when |
|---|---|
null |
always |
bool |
value is false |
int |
value is 0 |
float |
value is 0.0 (note: NaN is not empty) |
string |
value is "" |
guid |
value is all-zeros |
time |
value is the epoch / zero time |
data |
zero bytes |
object |
no properties or object schema is known, object type is sparse and all properties are empty |
map |
no entries |
array |
zero elements |
indexed array |
no entries |
Since a missing variable evaluates to null, missing_var is empty is true.
Built-in functions
AOP expressions provide a set of built-in functions which are available in every implementation and in any context. Additional custom functions beyond these built-ins may be provided by specific implementations.
For each function, the syntax, semantics, and error behavior are defined in this section.
If a built-in function is called with an incorrect number of arguments, or with unsupported argument types, or if a evaluation error occurs, the result is null.
Note that functions do not automatically convert argument types; for example, if a built-in function requires a string argument, passing an int will not be converted automatically unless a function explicitly documents and supports it.
Type functions
AOP expressions provide type functions for all AOP value types (except for null).
These functions serve as both type constructors and type conversion functions, depending on the way they are called.
When used as a type constructor, they can create values of types for which there is no literal syntax in the AOP expression grammar.
For example, to create a time value, you can use the Time function with an appropriate argument.
Type constructors can also be used to coerce values that have decayed to string during transit back to their original types (e.g., guid, time, data).
All type functions follow a common pattern for their behavior based on the number and types of arguments:
- When called with no arguments, they return the empty/zero value of the corresponding type.
For example,
Guid()returns an empty GUID,Object()returns an empty object. - When called with one argument, they attempt to convert that argument to the corresponding type using the standard conversion rules defined in the Data Model conversion tables.
If the conversion fails, the result is
null. - Calling with more than one argument is only supported for
Object,MapandIndexedArray, where it serves as a variadic constructor for creating values from key-value pairs.
For all type functions, unsupported arity, unsupported argument types, or violated constructor constraints result in null.
Bool([value]) → bool
Bool()returnsfalse.Bool(value)applies standard conversion tobool.
Int([value]) → int
Int()returns0.Int(value)applies standard conversion toint.
Float([value]) → float
Float()returns0.0.Float(value)applies standard conversion tofloat.
String([value]) → string
String()returns"".String(value)applies standard conversion tostring.
Guid([value]) → guid
Guid()returns the empty GUID.Guid(value)applies standard conversion toguid.
Time([value]) → time
Time()returns the zero or emptytimevalue.Time(value)applies standard conversion totime. It also supports the legacy time formats described in the legacy time formats section for string inputs, in addition to the standard ISO 8601 UTC format.
Data([value]) → data
Data()returns emptydata.Data(value)applies standard conversion todata.
Object(...) → object
Object()returns an empty object.Object(value)applies standard conversion toobject.Object(prop1, value1, prop2, value2, ...)constructs from key-value pairs.- Pair constructor constraints:
- Argument count must be even.
- The first argument of each pair is a property name, which must be a
stringwhich complies to Symbol restrictions. - Duplicate property names are not allowed.
Note that property names (symbols) are case-insensitive, so
propandPROPwould be considered duplicates.
- Example:
Object("a", 1, "b", 2)produces an object with propertiesa=1andb=2.
- Pair constructor constraints:
Map(...) → map
Map()returns an empty map.Map(value)applies standard conversion tomap.Map(key1, value1, key2, value2, ...)constructs from key-value pairs.- Pair constructor constraints:
- Argument count must be even.
- The first argument of each pair is a key, which must be a
string. - Duplicate keys are not allowed.
- Example:
Map("a", 1, "b", 2)produces a map with entries"a"→1and"b"→2.
- Pair constructor constraints:
Array([value]) → array
Array()returns an empty array.Array(value)applies standard conversion toarray.Arraydoes not provide a variadic constructor. Use[ ... ]syntax instead to produce array values.
IndexedArray(...) → indexed array
IndexedArray()returns an empty indexed array.IndexedArray(value)applies standard conversion to indexed array.IndexedArray(index1, value1, index2, value2, ...)constructs from index-value pairs.- Pair constructor constraints:
- Argument count must be even.
- The first argument of each pair is an index, which must be a
int. - Indices must be non-negative.
- Duplicate indices are not allowed.
- Example:
IndexedArray(1, "a", 5, "b")produces an indexed array with entries1→"a"and5→"b".
- Pair constructor constraints:
String functions
Case-insensitive functions perform a 1:1 Unicode simple case-fold:
- Correct behavior is mandatory for code points U+0000–U+00FE (Basic Latin through Latin-1 Supplement). Case-insensitivity for code points above U+00FE should be implemented correctly if possible, but minor implementation-specific differences are acceptable.
- No multi-character expansions: e.g.,
ß(U+00DF) folds only to itself, not toSS. - No Unicode normalization: precomposed and decomposed forms are not equated.
EqualsNoCase(a: string, b: string) → bool
Returns true iff a and b are equal case-insensitively.
A case-sensitive equality check can be performed with the a = b or a == b operators, hence there is no separate Equals function.
ContainsNoCase(haystack: string, needle: string) → bool
Returns true iff needle is a contiguous substring of haystack, comparing case-insensitively.
A case-sensitive substring check can be performed with the needle in haystack operator, hence there is no separate Contains function.
StartsWith(str: string, prefix: string) → bool
Returns true iff str starts with prefix (case-sensitive).
StartsWithNoCase(str: string, prefix: string) → bool
Returns true iff str starts with prefix case-insensitively.
EndsWith(str: string, suffix: string) → bool
Returns true iff str ends with suffix (case-sensitive).
EndsWithNoCase(str: string, suffix: string) → bool
Returns true iff str ends with suffix case-insensitively.
Trim(str: string) → string
Removes the following leading and trailing ASCII whitespace: U+0020 SPACE, U+0009 TAB, U+000A LF, U+000D CR, U+000B VT, U+000C FF. Non-ASCII whitespace (e.g., U+00A0 NO-BREAK SPACE) is not removed.
Substring(str: string, start: int [, maxLength: int]) → string
Returns a substring of str starting at index start with length at most maxLength.
- Indices are zero-based.
- Indices are counted in Unicode code points for code points in the Unicode BMP (U+0000 to U+FFFF). For code points outside the BMP (e.g., emoji), the behavior is implementation-defined: either count as 1 or 2 code points.
startmust be ≥ 0; otherwisenull.maxLengthmust be ≥ 0 if provided; otherwisenull.- If
startis greater than or equal to the length of the string, returns an empty string. - If
maxLengthis omitted or exceeds the remaining length, returns the rest of the string.
Miscellaneous functions
Size(value) → int
Returns the size of an AOP value. The function is defined for specific types as described in the table below.
Type of value |
Returns | Example |
|---|---|---|
string |
Number of characters1 | Size("hello") → 5 |
data |
Number of bytes | Size(Data("3q2+7w==")) → 4 |
object |
Number of properties | Size(Object("a", 1, "b", 2)) → 2 |
map |
Number of entries | Size(Map("a", 1, "b", 2)) → 2 |
array |
Number of elements | Size(Array(1, 2, 3)) → 3 |
indexed array |
Number of entries | Size(IndexedArray(1, "a", 5, "b")) → 2 |
1 For string inputs, Size returns the number of Unicode code points for code points in the Unicode BMP (U+0000 to U+FFFF).
For code points outside the BMP (e.g., emoji), the behavior is implementation-defined. They may either count as 1 or 2 code points.
Get(collection, key [, default]) → value
Looks up a single element from a collection.
If the element does not exist, returns default if provided; otherwise null.
Type of collection |
Type of key |
Lookup |
|---|---|---|
array |
int |
Key is zero-based index; negative index → null |
object |
string |
Property name (case-insensitive); non-property-name → null |
map |
string |
Exact key (case-sensitive) |
indexed array |
int |
Key is index; negative index → null |
Custom functions
Custom functions allow users to extend the evaluator with additional functionality beyond the built-in functions. Refer to the documentation of the specific API or implementation for details on which additional custom functions are available and how to use them.
Custom functions should generally follow these guidelines:
- In case of an error during evaluation (e.g., invalid argument type, invalid argument value, or any other error), the function should return
null. - If the function is called with an unsupported number of arguments (which includes too many arguments), the function should return
null.
A custom function should have a unique name which must not conflict with any built-in function name. Shadowing a built-in function is not allowed; the evaluator will generally treat this as an error. It may be advisable to prefix custom function names with a unique namespace to avoid conflicts.
Legacy expressions
This document defines the syntax and semantics of the revised AOP Expression Language.
The original implementation is currently still in use in WinGuard and Advancis.SST v1.x (for Config XML evaluation).
Note that the existing implementations of the original expression language already had subtle differences (for example, + and in with string operands was not universally implemented), which were unified in the revised language.
In practice, the differences between the original and revised expression languages are minor and only affect rare edge cases which most users are unlikely to encounter.
The following is a non-exhaustive list of differences between the original and revised expression languages:
- Syntax: The alternative JSON syntax is no longer supported.
- Whitespace: Degenerate forms such as
1in[1]ornot-1or[]is emptyare no longer accepted; textual binary operators require surrounding whitespace, textual unary prefix operators require trailing whitespace, and postfix operators require leading whitespace. No changes were made to symbolic operators, which can be used without whitespace (e.g.,1+1is still valid). - String literals: Rarely used escape sequences
\v,\b,\f,\XXX(octal),\xXX(2 hex digits),\uXXXX(4 hex digits) were removed. - Float literals: The float literal
1.(no decimal digits after the dot) is no longer accepted; a digit is always required after the dot (e.g.,1.0). - Array expression: The original language supported array literals with arbitrary sub-expression, but statically evaluated them with an empty environment, which meant that variables and custom functions were not accessible within array literals. To fix this inconsistency, a dedicated AST node for array expressions was introduced in the revised language, which is evaluated dynamically.
- Operator aliases: The operator aliases
lt,lte,gt,gte,bitand,bitor,bitxorwere added in the revised language. - Operator precedence: The binding strength of bitwise operators was changed to be between additive and membership operators, instead of below all other operators. This allows expressions like
a & b == 0to be parsed intuitively without extra parentheses. - Comparison semantics: Some comparison semantics were changed to be more intuitive.
In general, comparison semantics were aligned with standard conversion rules and comparison of values for consistency.
This includes the following notable changes:
nullvsnot-nullis always Unordered, instead ofnull < not-null.boolvsboolis Unordered if the values differ, instead offalse < true.boolvsint/float/string: Values are now promoted to bool to do a bool comparison, instead of promoting the bool to a number to do a numeric comparison. This means expressions liketrue < 2,true < '2'andfalse < '0.5'are no longer true.boolvsother type: These comparisons are now Unordered, rather than comparing against the empty-ness of the value. To test for empty-ness, use theis emptyoperator instead.guid/timevsstring: If the conversion of string to guid/time fails, the comparison is now always Unordered instead of comparing against an empty value.int/floatvsstring: The allowed convertible formats for string to int/float conversion were aligned such that exactly those formats are allowed that are also legal number literals in the expression language.arrayvsarray: Arrays are now compared fully lexicographically, instead of comparing their sizes first.
- Operators semantics:
+andinwith string operands are now consistently defined.!now applies the same truthiness rule asis emptyinstead of only applying tobool/int/floatvalues, with the exception of!nullwhich is nownullinstead oftrue.
- Built-in functions: The original language did not have a consistent set of built-in functions.