Expression Language
A small, fast, easy-to-use scripting language and evaluation engine.
Introduction
An embedded scripting language and evaluation engine for Trento Checks Expressions that gives a safe and easy way to script specific steps during Checks Execution.
Types
Type | Example |
---|---|
Nothing/void/nil/null/Unit |
|
Integer |
|
Float |
|
Boolean |
|
String |
|
Array |
|
Map |
|
Logic Operators and Boolean
Operator | Description(x operator y ) |
x , y same type
or are numeric |
x , y different types |
---|---|---|---|
|
|
error if not defined |
|
|
|
error if not defined |
|
|
|
error if not defined |
|
|
|
error if not defined |
|
|
|
error if not defined |
|
|
|
error if not defined |
|
Comparing different types defaults to false
Comparing two values of different data types defaults to false
.
The exception is !=
(not equals) which defaults to true
. This is
in line with intuition.
42 > "42"; // false: i64 cannot be compared with string
42 <= "42"; // false: i64 cannot be compared with string
ts == 42; // false: different types cannot be compared
ts != 42; // true: different types cannot be compared
Boolean Operators
Operator | Description | Arity | Short-circuits? |
---|---|---|---|
|
NOT |
unary |
no |
|
AND |
binary |
yes |
|
AND |
binary |
no |
|| |
OR |
binary |
yes |
| |
OR |
binary |
no |
Double boolean operators &&
and ||
short-circuit – meaning
that the second operand will not be evaluated if the first one already
proves the condition wrong.
Single boolean operators &
and |
always evaluate both operands.
a() || b(); // b() is not evaluated if a() is true
a() && b(); // b() is not evaluated if a() is false
a() | b(); // both a() and b() are evaluated
a() & b(); // both a() and b() are evaluated
If Statement
if
statements follow C syntax.
if foo(x) {
print("It's true!");
} else if bar == baz {
print("It's true again!");
} else if baz.is_foo() {
print("Yet again true.");
} else if foo(bar - baz) {
print("True again... this is getting boring.");
} else {
print("It's finally false!");
}
Unlike C, the condition expression does not need to be enclosed in
parentheses (
…)
, but all branches of the if
statement must
be enclosed within braces {
…}
, even when there is only one
statement inside the branch. Like Rust, there is no ambiguity regarding
which if
clause a branch belongs to.
// not C!
if (decision) print(42);
// ^ syntax error, expecting '{'
If Expression
if
statements can also be used as expressions, replacing the
? :
conditional operators in other C-like languages.
// The following is equivalent to C: int x = 1 + (decision ? 42 : 123) / 2;
let x = 1 + if decision { 42 } else { 123 } / 2;
x == 22;
let x = if decision { 42 }; // no else branch defaults to '()'
x == ();
Arrays
All elements stored in an array are dynamic, and the array can freely grow or shrink with elements added or removed.
Array literals are built within square brackets [
… ]
and
separated by commas ,
:
[
value`,` value`,` … ,
value ]
[
value`,` value`,` … ,
value ,
]
// trailing comma is OK
let some_list = [1, 2, 3];
let another_list = ["foo", "bar", 42];
Access Element From beginning
Like C, arrays are accessed with zero-based, non-negative integer indices:
array [
index position from 0 to length−1 ]
let some_list = ["foo", "bar", 42];
let second_element = some_list[1];
// second_element is "bar"
Access Element From end
A negative position accesses an element in the array counting from the end, with −1 being the last element.
array [
index position from −1 to −length ]
let some_list = ["foo", "bar", 42];
let second_element = some_list[-2];
let last_element = some_list[-1];
// second_element is "bar"
// last_element is 42
Function | Parameter(s) | Description |
---|---|---|
|
position, counting from end if < 0 |
gets a copy of the element
at a certain position ( |
|
none |
returns the number of elements |
|
predicate (usually a closure) |
constructs a new array with
all items that return |
|
predicate (usually a closure) |
returns |
Examples
let some_list = [1, 2, 3, 4, "foo", "bar"];
let foo = some_list.get(4); // "foo"
let items_count = some_list.len(); // 6
let only_foo_and_bar = some_list.filter(|item| item == "foo" || item == "bar"); // ["foo", "bar"]
// let only_foo_and_bar = some_list.filter(|item, idex_in_array| item == "foo" || item == "bar");
let another_list = [3, 5, 7, 9, 10, 20, 30];
let all_greater_than_2 = another_list.all(|item| item > 2); // true
let all_greater_than_10 = another_list.all(|item| item > 10); // false
// let all_greater_than_10 = another_list.all(|item, idex_in_array| item > 10);
Maps
Maps are hash dictionaries. Properties are all dynamic values and can be freely added and retrieved.
Map literals are built within braces #{
… }
with
name`:value pairs separated by commas `,
:
#{
property :
value`,` … ,
property :
value
}
#{
property :
value`,` … ,
property :
value
,
}
// trailing comma is OK
let some_map = #{ // map literal with 2 properties
foo: 42,
bar: "hello",
};
Dot notation
The dot notation allows to access properties by name.
object .
property
let some_map = #{ // map literal with 2 properties
foo: 42,
bar: "hello",
};
some_map.foo // 42
some_map.bar // "hello"
Non-existing property
Trying to read a non-existing property returns an error.
let some_map = #{ // map literal with 2 properties
foo: 42,
bar: "hello",
};
some_map.another_property // returns "Property not found: another_property (line X, position Y)"
A more complex example
let some_map = #{ // map literal with 2 properties
foo: 42,
bar: "hello",
rabbits: [
#{
name: "wanda",
power: 9001
},
#{
name: "tonio",
power: 9002
},
#{
name: "weak_rabbit",
power: 8999
}
]
};
// Tell me how many strong rabbits are there
let strong_rabbits = some_map.rabbits.filter(|rabbit| rabbit.power > 9000).len() // 2
let rabbits = some_map.rabbits
let all_rabbits_are_strong = rabbits.all(|rabbit| rabbit.power > 9000) // false, unfortunately
Rhai
For extra information about the underlying scripting language see Rhai.