Elvin Subscription Language
Elvin operates in a client-server architecture. Clients register subscriptions with the server and/or send notifications to it. When the server receives a notification, it forwards it to all clients whose subscriptions match that notification. This document describes the Elvin Subscription Language and how expressions in this language are used to select notifications for delivery to clients.
The Elvin server routes notifications to clients which have expressed interest in that notification. The client registers its interest with a subscription expression, written in the Elvin Subscription Language. The elvin server evaluates the subscription for each notification it receives and, if the result is true, delivers it to the client which registered that subscription. Here is an example of a subscription expression:
(Group == "elvin" || Group == "Chat") && ! regex (User, "[Ss]egall?")
This subscription expression will match any notification whose
field has the string value "elvin" or "Chat" except those whose User
field also matches the regular expression "[Ss]egall?". This
subscription would match the following notification example:
Group: "Chat" User: "alice" Message: "hello sailor" Timeout: 10 Message-Id: "07cf0b15003409-5i3N7XDKbPVaQ-28cf-22"
but would not match:
Group: "elvin" User: "email@example.com" Message: "release early" Timeout: 10 Message-Id: "7c0b1f00540039-i357XNKbDPaQV-8c2f-22"
The names are on the left and are immediately followed by a colon (eg: Group) and the values are on the right (eg: "Chat").
The first notification would also match the following expression:
wildcard (Message, "*[Hh]ello*") && Timeout > 5
The evaluation of a subscription uses Lukasiewicz's tri-state logic that adds the value bottom (which represents "undecidable" or "indefinite") to the familiar true and false.
|A||B||!A||A && B||A || B||A ^^ B|
Any subscription expression that refers to a name that is not present in the notification being evaluated results in bottom.
In addition, many of the functions in Elvin have constraints on their
parameters (ie. data type) and have an undefined result should these
constraints not be met. For example, if
begins-with (), which
expects a string parameter is provided with a 32-bit integer then the
result of the function is bottom.
Notifications are delivered only if the result of subscription evaluation is true.
A name evaluates to its corresponding value in the notification. Where a name occurs in an expression, during evaluation of a notification, the value of the name is taken to be the value of the attribute with the matching name in the notification. If the name is not in the notification, its value becomes bottom (see the previous section on logic).
Names may contain only printing ASCII characters (32-126) and are
case-sensitive. Certain characters are treated as special and must be
escaped using a backslash (
\) if they are used in a name. If a name
does not start with an alphabetic character or an underscore (_) then
the name must start with the escape character followed by the desired
The following characters must be escaped when used in a name:
The following are all valid names:
fnord _underscore \ we\ can\ have\ spaces \1.0\ Contents this:is:a:test:
Note that certain operator characters are legal within a name. So
pipe= > 20" is a valid expression, with the equal-sign as the last
character of the name. This allows most strings to be valid names. It
does, however, have two important impacts. First, names, operators and
literals must be separated by whitespace (as above) and "
is an error. Second, while names can contain whitespace, it must be
escaped with a backslash, as in "
the\ pipe > 20".
Within a subscription expression there are five kinds of literals.
32-bit integer literals may be written in octal, decimal or hexadecimal. Octal literals must begin with 0 and may only contain digits from 0 through 7. Hexadecimal literals begin with 0x and may contain any hexadecimal digit. All integers are signed. The following are legitimate 32-bit integers:
42 052 -0x2a
64-bit integers are written in the same way as 32-bit ones except that they have a trailing uppercase or lowercase "L". The following are valid 64-bit integers:
Floating point numbers must include a decimal point and at least one digit thereafter. An exponent is permitted after the decimal digits. Below are some examples of floating point numbers:
-3.14 6.023e23 6.67e-11
Strings are any characters enclosed in single or double quotes. Here are some examples of strings:
"hello sailor" "this isn't a \"number\"" 'this isn\'t a "number"'
Because no comparison operations which return meaningful results for opaques are defined in the subscription language, support for specifying opaque constants is neither necessary nor provided.
As alluded to in the examples, expressions may be combined using logical operators. The following logical operators (in order of precedence) are defined:
- An expression may be negated by a preceding
!. The negation of bottom is bottom.
- A conjunction of two or more expressions is constructed by placing
&&between those expressions.
- The exclusive-disjunction (injunction?) of two or more expressions
is constructed by placing
^^between those expressions.
- A disjunction of two or more expressions is constructed by placing
||between those expressions.
- equality (== and !=)
- Two values may be compared for equality by placing
==between them. Strings may be compared with other strings for equality. Numbers may be compared with other numbers for equality. No equality test is supported on opaque data. If a comparison is attempted between incomparable types or if either of the values being compared refers to a value not present in the notification, then the result is bottom. The != operator is the negation of the equality operator.
x != yis functionally equivalent to
! (x == y).
- inequality (<, > and <=, >=)
- Two numeric values may be compared with other numbers by placing one
of the inequality operators between the values. If either of the
values is not a number (or not defined) then the result of the
comparison is bottom.
Simple arithmetic and logical operators are supported too, making
subscriptions like the following possible:
temp > prev-temp + 5
Arithmetic and logical operators include:
- *, / and %
- The multiplication, division and modulus operators have higher precedence than addition and subtraction but lower precedence than the bitwise operators. Values are promoted to from int32 to int64 to real64 as necessary to represent the result.
- + and -
- The addition and subtraction operators have higher precedence than the logical operators. The bitwise operators are only defined on the two integer types. If they are applied to other types then the result is undefined which will result in the smallest enclosing logical evaluating to bottom.
- <<, >> and >>>
- Shift operators the value on the left in the direction of the
arrows by the value on the right number of bits. The
>>operator does a signed right whereas
>>>does a logical right (it always shifts in zeros).
- &, ^ and |
- In order of precedence, the bitwise &, ^ and | operators operate on the values on either side of the operator.
- Performs bitwise inversion.
The following functions take one argument and return a logic value. If the value of name is of the given type, they evaluate to true, if name is not present in the notification, they evaluate to bottom, otherwise they evaluate to false.
- int32 (name)
- True if name is an int32 value.
- int64 (name)
- True if name is an int64 value.
- real64 (name)
- True if name is an real64 value.
- string (name)
- True if name is a string value.
- opaque (name)
- True if name is an opaque value.
- nan (name)
- True if name equals the special real64 NaN (not a number) value.
The string comparison functions test strings in various ways. They
test the first argument, which must either name a value in the
notification or be a string-type function such as
to see if it matches any of the subsequent arguments.
To perform case-insensitive string matching, use the
function, for example:
begins-with (fold-case (name), "foo")
would match when the
name field is "Foobar", "FOOBAR", "fOoBaR",
- begins-with (name, string, ...)
- Returns true if the value of name is a string and begins with any of the string arguments, bottom if the value name is not a string or not present in the notification and false otherwise.
- contains (name, string, ...)
- Returns true if the value of name is a string and it contains any of the string arguments, bottom if the value name is not a string or not present in the notification and false otherwise.
- ends-with (name, string, ...)
- Returns true if the value of name is a string and ends with any of the string arguments, bottom if the value name is not a string or not present in the notification and false otherwise.
- wildcard (name, string, ...)
- The strings are interpreted as wildcard (a.k.a. glob, filename, or shell expansions) expressions as defined by the wildcard specification.
- regex (name, string, ...)
- The strings are interpreted as regular expressions. That's POSIX 1003.2 regular expressions with UTR #18 Level 1 compatible extensions for Unicode.
The following functions may be used perform a limited set of transformations in order to simplify string comparisons.
- fold-case (string)
- Transform string to folded case (typically the same as lower case).
- decompose (string)
- Performs canonical decomposition (NFD) of the string and returns the resulting string value.
- decompose-compat (value)
- Performs compatible (and canonical) decomposition (NFKD) of the string and returns the resulting string value.
Notification and Name Functions
- require (name)
- Returns true if name is present in the notification, bottom otherwise.
- equals (name, value, ...)
- Returns true if the name is equal to any of the values.
- size (name)
- If name is a string then
size ()returns the number of characters in the string. If name is an opaque then it returns the number of bytes in the opaque. Returns bottom for all other data types.