Expression Language
The expression language in JADE is used to specify computations right in configuration. Many configuration elements in JADE, especially in plugin configurations, support the use of expressions. In its simplest form, the expression language looks just like “written equations” such as:
2 * 3 + 5
which would of course evaluate to 11
. Standard arithmetic operators (ex. *
for multiplication) and boolean operators (ex. ==
for equality checking) may be used and will respect the standard order of precendence. Furthermore, many functions are provided for you (ex. SIN
); a complete List Of Functions is also provided. Parentheses can also be used to dictate the order in which expressions are evaluated such as:
3 - (5 - 4)
which would of course evaluate to 2
. One thing to keep in mind is that all functions expect valid JSON types to be passed. For example, when passing a string it must be quote-wrapped (ex. \"hello kitty\"
) where notice that because expressions are always composed within the context of a JSON string in configuration, we must escape literal quote ("
) characters within the JSON string, in order to pass a quoted string to a function defined as part of that JSON string. This will become clearer below.
Variable
Expressions become powerful when used to transform data received from a device or another plugin. For example, you may wish to transform data received from some publisher before updating a chart. In this case, we need some kind of “variable syntax” to use in our expressions. Variables in JADE often take the form
@VAR{variable_name}
Sometimes VAR
will be replaced with other “variable container names” (think of these a bit like namespaces) such as PUB
for data received from some publisher, but the general form of variables is always:
@CONTAINER{variable_name}
Let’s take a look at an example of a variable used in an expression. For the moment we won’t assign the result to anything - for now we’re just focused on the variable syntax used in expressions. Suppose a plugin receives a temperature value in Celcius which is made available to parts of the plugin configuration as
@VAR{temperature}
Well, let’s write an expression to covert it to Fahrenheit:
(9/5)*@VAR{temperature}+32
We could use such an expression, for example, to set the value of a new data point for a plot in a chart or graph. Notably, whitespace is generally optional. We could equivalently write that expression (perhaps for readability) as
(9/5) * @VAR{temperature} + 32
Below we’ll see that in order to tell JADE to interpret the result of our expression as a number
(vs a string
), we’ll need to add some return type
syntax.
Return Types
When we use expressions, they will inevitably be assigned to some configuration parameter. The expression language in JADE does not attempt to auto-detect return types. Rather, we must explicitly indicate the return type to use, otherwise it will return the value as type string
.
Let’s look at an example of the “return type” syntax in the context of a snippet of an XY Chart configuration. To start, let’s explain a bit about how the XY Chart plugin works. Generally, the XY Chart plots data vs time and allows us to subscribe to data from any publishing plugins. When published data arrives, it is put into a buffer and is used to update the chart at the specified update rate. But the XY Chart also allows us to use expressions to transform the incoming data before we store it.
Below is a snippet of an XY Chart configuration corresponding to a plot definition, where notice the data
element allows us to define the timestamp
, value
, and unit
of the data (where below we apparently receive data for timestamp
and temperature
from the Temperature Publisher
, and the XY Chart has put that data into a variable container named VAR
for us). More specifically, take note of the syntax which has the form: Float:( ... )
.
{ "name": "Temperature", "subscription": "Temperature Publisher", "data": { "timestamp": "Float:(@VAR{timestamp})", "value": "Float:( (9/5) * @VAR{temperature} + 32 )", // <-- the line of interest here "unit": "Fahrenheit" }, "min": 0, "max": 10, "historyLength": 100, "properties": { "visible": true, "antiAlias": false, "pointStyle": "circleFull", "lineStyle": "solid", "lineWidth": "1", "fillTo": "none", "interpolation": "linear" } }
The Float:( ... )
syntax instructs JADE to return the value as a floating point value (i.e. a double-precision floating point value). This is required by the XY Chart because it expects to receive the timestamp
and value
elements as numbers. Notice that we see a familiar expression in the value
element - it seems to be converting a received @VAR{temperature}
in Celcius to Fahrenheit. And the entire expression sits inside the Float:( ... )
return type wrapping.
Also notice that the whole expression sits in double quotes: expressions are always specified in the form of a string in the JSON configuration. JADE evalutates expressions in strings wherever expressions are supported and uses the return type syntax to determine which type to return (i.e. which type is used to update the corresponding configuration option). If no return type is specified, the expression will still be computed but the value will remain a string
instead of being stored as a number
.
The available return types are:
- String
- Boolean
- Float
- Integer
- Array
- Object
Functions
As noted above, several functions are provided in addition to the core arithmetic operations such as +
(addition), -
(subtraction), *
(multiplcation), /
(division), ^
(power), etc. and parameters passed to functions must be a valid JSON type. Many functions are provided and the syntax is natural. For exmaple, to compute the sine of 0 you can use SIN(0)
. See the List Of Functions for a list of functions with descriptions and examples.
Note on JSON Special Characters
Functions shake out cleaning in many cases. However because our expressions always live in a JSON string inside some configuration, anytime we have JSON special characters in our expresssions they’ll need to be “escaped” (i.e. “slashed”). If you aren’t already familiar with JSON and the notion of special characters requiring “escaping” or “slashing”, check out our JSON Overview. Either way, let’s take a look at an example where a function requires a string parameter, which will require us to specify a "
character inside our JSON string (which must then be “slashed”).
JADE provides a GetDateTime
function which expects a format argument of type string, such as: GetDateTime("SecondsSinceEpoch")
. Now, that looks reasonable because we’ve specified a string input to the function as "SecondsSinceEpoch"
with quotes as required. However, let’s see what happens when we want to use this in a configuration context, which is itself inherently JSON. We’ll just use some dummy configuration snippet below to unveil the nuance here:
// THIS IS INCORRECT :( { "timestamp": "Float:( GetDateTime("SecondsSinceEpoch") )" }
The above would be INCORRECT and you’d see an error in the configuration editor. This is becuase the "
around our SecondsSinceEpoch
will be interpreted as string delimiters instead of being used as characeters within the string which holds our full expression. Below is what we need to do to properly specify "
characters in such cases:
// THIS IS CORRECT :) { "timestamp": "Float:( GetDateTime(\"SecondsSinceEpoch\") )" }
Notice we must use \"
instead of simply "
around the original SecondsSinceEpoch
bit. That is, we have now properly “slashed” the special characeters so our configuration knows we meant to use a literal quote character as part of our expression. Again, if this doesn’t make sense immediately, spend a minute with our JSON Overview for more details.
Return Types (Revisited)
We also allow putting return types right in front of function names, though this only has meaning for a function used as the outermost operation in our expression. For example, our timestamp
element above could be rewritten as:
{ "timestamp": "Float:GetDateTime(\"SecondsSinceEpoch\")" }
What’s really happening here is that when we use the Float:( ... )
syntax, the outer most function is just the unnamed function
which instructs JADE to simply “evaluate the innards”. It’s like using parentheses anywhere else in your expressions; the innards are simply evaluated. If we provide a named function, JADE will simply use that function and take whatever is inside as it’s argument (or arguments if comma-delimited). This form is never required but can be slighly more performant since it removes one layer of function evaluation. It’s also arguably a bit easier on the eyes since it has less syntax (i.e. fewer characters).
Ignoring Variables
Sometimes it can be useful to ignore variables in a string. For exmaple, perhaps for some reason we want to actually send a message which literally contains a string like "... @VAR{temperature} ..."
even if @VAR{temperature}
is an available variable in that context. In that case, we would like a way to tell JADE to ignore replacing variables. One way to achieve this is to wrap our string in ``` characters such as:
{ "value": "`We can access the temperature using syntax like: @VAR{temperature}.`" }
In that exmaple above, the string assigned to value
would be: We can access the temperature using syntax like: @VAR{temperature}.
even if @VAR{temperature} existed as a variable in that context. The other way to ignore variables (on a per variable basis) is to use variable “flags” which we discuss in its own section below.
Ignoring Expressions
Sometimes it can be useful to ignore entire expressions. In our case, this will mean also ignore variables for nuanced reasons which we will not dive into here. For now, let’s suppose we want to actually send a message which literally contains a string like "... SIN(0) ..."
instead of evalutating SIN(0)
as the SIN
function with parameter 2
(as an aside, note that the SIN function takes an argument in units of radians
). In that case, we would like a way to tell JADE to ignore the expression. There are a couple of ways to do this, but let’s focus on the case where we want to ignore variables in the whole string we’ve specified. To achieve this, can wrap our string in ~
characters such as:
{ "value": "~We can use functions with syntax like: SIN(0).~" }
In that exmaple above, the string assinged to value
would be: We can use functions with syntax like: SIN(0).
where SIN(0)
would not be evaluated.
Variable Flags
Variable flags allow us to instruct JADE as to how to interpret and process variables. Let’s take a look at an example of a variable which uses the ignore
flag to see variable flags in action:
@(?i)VAR{temperature}
Notice the inclusion of the (?i)
after the @
symbol. Here we’ve specified the i
flag to tell JADE to ignore the variable (i.e. don’t replace it with it’s value).
The following are valid variable flags:
r
recursive (start from previous search location in string; useful when variabeles are used within variables)d
delete (replace the variable with empty string)i
ignore (don’t replace the variable)c
clean (removes the flags syntax from the variable syntax; only applies if the ‘i’ flag is set)d
disable (disables all flags so the variable behaves as if no flags were used)
In general, multiple flags can be used although some combinations will conflate. For example, if the delete flag d
is used, then it doesn’t really make sense to also include the ignore flag i
(after all, the variable will be removed from string when processed which sort of overrides the notion of ignoring it and leaving the syntax as-is). They are mostly self-explanatory however it’s perhaps taking a look at an example using the recursive flag r
:
@VAR{@(?r)VAR{var_name}}
Let’s dissect this to really understand it. First, there’s an outer variable specified, but it’s name seems to be a variable itself: @(?r)VAR{var_name}
. This is a sort of “dynamic” variable in the sense that the name of the outer variable is only determined at runtime. The inner variable must refer to some string - the name of some variable - and the outer variable will then resolve the value of the variable with that name. Importantly here, the inner variable uses the recursive flag r
which ensures that JADE will not only replace the inner variable, but also replace the outer variable after resolving the inner variable. So, if @VAR{var_name}
had the string value temperature
, then whole crazy variable @VAR{@(?r)VAR{var_name}}
would first resolve to @VAR{temperature}
, and then resolve to whatever the value of temperature
was in the VAR
variable container. If we hadn’t specified the recursive flag r
, JADE would have resolved the whole crazy variable to simply @VAR{temperature}
and it would not have done the final replacement for the actual value of the temperature
variable.
Variable Metadata Functions
It is often useful to extract information about a variable, such as when it’s defined, or the size of an array or object. The expression language also provides syntax for return such metadata for variables. For example, if we wanted to know if some variable named myVar
existed, we could use syntax like: @VAR{IsDefined(myVar)}
. Notice how the variable name myVar
is wrapped in functional notation where the function’s name is IsDefined
. It’s now clear why we call these variable metadata functions
- they are little functions which can be used inside variable syntax to extract information - or metadata - about a variable. In many languages this capability would be exposed by a so-called operator or strict function, but for various reasons the syntax shown above is favored in JADE’s expression language. What follows is a list of the variable metadata functions
with examples.
IsDefined
Use of the IsDefined
variable metadata function returns true if the variable is defined, false otherwise.
Example: @VAR{IsDefined(myVar)}
returns boolean true if myVar
is defined, false otherwise.
TypeOf
Use of the TypeOf
variable metadata function returns the type of the variable as one of the following strings: Number
, Boolean
, String
, Object
, Array
, null
, Not Found
Note that variables of type type null return the string null
with a lowercase n
while the others all begin with upper case letters.
Example: @VAR{TypeOf(myVar)}
returns the string Number
if myVar
is of type number
SizeOf
Use of the SizeOf
variable metadata function returns the size of (i.e. number of elements in) either an array or object variable.
Note that the size of types other than arrays and objects are always returned as 0.
Example: @VAR{SizeOf(myArray)}
returns 3 if myArray
is an array with 3 elements
Example: @VAR{SizeOf(myObject)}
returns 3 if myObject
is an object with 3 key-value pairs
IsNumber
Use of the IsNumber
variable metadata function returns true if the variable is of type number, false otherwise.
Example: @VAR{IsNumber(myVar)}
returns true if myVar
is a number, false otherwise
IsString
Use of the IsString
variable metadata function returns true if the variable is of type string, false otherwise.
Example: @VAR{IsString(myVar)}
returns true if myVar
is a string, false otherwise
IsBoolean
Use of the IsBoolean
variable metadata function returns true if the variable is of type boolean, false otherwise.
Example: @VAR{IsBoolean(myVar)}
returns true if myVar
is a boolean, false otherwise
IsArray
Use of the IsArray
variable metadata function returns true if the variable is of type array, false otherwise.
Example: @VAR{IsArray(myVar)}
returns true if myVar
is an array, false otherwise
IsObject
Use of the IsObject
variable metadata function returns true if the variable is of type object, false otherwise.
Example: @VAR{IsObject(myVar)}
returns true if myVar
is an object, false otherwise