Use the function DSL (Domain Specific Language) to define a function fieldfunction field.
This DSL is a procedural and strongly statically typed language.
To create and modify a function field, use the Data Model Assistant (DMA).
The Data Model Assistant (DMA) includes a script editor that provides contextual code completion.
A script has following structure:
<unit usage statement 1> <unit usage statement 2> ... <unit usage statement N> <function or procedure definition 1> <function or procedure definition 2> ... <function or procedure definition M>
For more information, see Unit and function and procedure.
Example:
// This field returns the full address for current record. export function getValue(): string begin var address := record.FirstName | ' ' | record.LastName; for street in record.OfficeAddress.Street do begin address |= '\n' | street; end; address |= '\n' | record.OfficeAddress.ZipCode | ' ' | record.OfficeAddress.City; address |= '\n' | record.OfficeAddress.Country; return address; end
The Unicode character set is supported.
The DSL is case-sensitive.
A single line comment extends from // to the end of the current line:
// This is a comment if record.LastName = 'Doe' then // This is another comment. return true;
A multi-line comment extends from /* and ends with */:
/* This is an example of a multi-line comment */ if record.isActive then return false;
The reserved keywords are: and, or, not, uses, as, export, typeof, mutable, immutable, unmodifiable, function, procedure, const, var, if, then, else, for, while, in, do, begin, end, return, true, false, null.
Reserved keywords cannot be used as plain (unquoted) identifiers.
An unquoted identifier is an unlimited-length sequence of letters, digits, or underscore (_). The first character must be a letter or an underscore.
Valid letters are a to z and A to Z. Valid digits are 0 to 9.
An unquoted identifier may not be equal to a reserved keyword.
A quoted identifier is an unlimited length of any Unicode character except double quote (").
Quoted identifiers must be used surrounded by double quotes.
An unquoted identifier can be used surrounded by double quotes.This means that identifier "a_name" is equal to a_name.
Quoted identifiers can be reserved keywords.
The following simple types are supported:
Type | Keyword | Properties | EBX® corresponding types |
---|---|---|---|
Boolean | boolean | xs:boolean | |
Decimal (unlimited precision) | decimal | xs:decimal | |
Integer (32 bits) | int | xs:int xs:integer | |
String | string | xs:string xs:Name osd:text osd:html osd:email osd:password 1 osd:color osd:dataspaceKey osd:datasetName | |
Timestamp (millisecond precision and without time-zone) | timestamp | year month (1 to 12) day (1 to 31) hour (0 to 23) minute (0 to 59) second (0.000 to 59.999) | xs:dateTime |
Date (without time-zone) | date | year month (1 to 12) day (1 to 31) | xs:date |
Time (millisecond precision) | time | hour (0 to 23) minute (0 to 59) second (0.000 to 59.999) | xs:time |
Locale | locale | osd:locale | |
URI (Uniform Resource Identifier ) | uri | xs:anyURI | |
Resource | resource | osd:resource |
1 It is not possible to define a function field for type osd:password.
A complex type is the type of a group (complex) node defined in an EBX® schema.
For more information, see the chapter complex variables
Reference to a schema node’s type (for example, when you declare a function parameter or a variable) implies using keyword typeof.
The DSL supports lists. Declare a list by using the following syntax:
list<item_type>
Declare an unmodifiable list by using following syntax:
unmodifiable list<item_type>
An item cannot be added, removed, or replaced if the list is unmodifiable. An item of the list might or might not be modifiable, depending on its type.
Use indexed notation to set or get a value. (The first index is 0):
// Set first value of cities. cities[0] := 'Paris'; // Get the second item of cities. return cities[1];
An index can be any expression of decimal type. Convert the index value to an integer by dropping the fractional part.
A get expression returns null if the index is null, negative, or out of range.
A set expression reports an error at runtime if the index is null, negative, or out of range.
The property size returns the size of the list:
var size := cities.size;
You can iterate a list. For more information, see for loops.
Multi-value schema fields
Schema fields that are multi-valued are considered of list type.
A multi-valued schema field is a field that has its maximum number of values (maxOccurs) greater than one or set to "unbounded".
Indexed notation is used to access or set a value. The first index is 0. In the following example, record field 'OfficeAddress/street' is multi-valued:
// Return the second line of the street part of the address. return OfficeAddress.street[1].
The typeof keyword can be used with multi-valued fields (see chapter keyword typeof).
You cannot modify an immutable complex or list object.
You can modify a mutable complex or list object, but other constraints may apply that can prevent modifying some attributes of an object.
A simple type is always immutable.
You can assign a mutable value to a variable or a return value of a compatible immutable type, but assigning an immutable value to a mutable variable or a return value generates an error at compile time.
Use the keyword typeof to specify a type depending on a field or variable. You can use this keyword preceded by the keyword mutable or immutable.
For example, to reference the type of record field OfficeAddress, use the following syntax:
typeof record.OfficeAddress
By default, the type of a record field is always immutable. You can specify a mutable type using following syntax:
mutable typeof record.OfficeAddress
If a field is multi-valued, it is considered a list. You can reference the type of an item of the list using following syntax:
typeof record.Addresses[*]
You can use the keyword 'mutable' on types that have a mutable form:
mutable typeof record.Addresses[*]
If the type does not have a mutable form, then the keyword is simply ignored. This is the case for simple types.
===
String literals can be any sequence of Unicode characters surrounded by single quotes. The following table displays characters that need to be replaced by an escape sequence:
Character | Escape sequence |
---|---|
Tab | \t |
Backspace | \b |
New line | \n |
Carriage return | \r |
Form feed | \f |
Single quote | \' |
Backlash | \\ |
Specify a character by using a Unicode escape sequence that has format \uXXXX, where XXXX is the hexadecimal code of the Unicode character.
Examples
Value | Syntax |
---|---|
O’Harra | 'O\'Harra' |
Noël | 'No\u00EBl' |
été | '\u00e9\u00E9' |
Note
An invalid escape or Unicode sequence generates an error at compile time.
The following decimal formats are supported:
Format | Examples |
---|---|
Integer | 546 -67 |
Floating point | 54.987 -433.876 0.00054 -0.0032 |
Exponent notation | 34.654e-5 -45E+65 1.543e23 |
Timestamp literals have the format dt(yyyy-MM-dd hh:mm:ss.sss).
Seconds are optional. When they are not specified, 0 is assumed. Seconds can have fractions up to millisecond precision.
Any dates that are not valid in the Gregorian calendar generate errors at compile time.
Examples:
dt(2010-01-02 00:00:00.000) dt(2019-2-3 12:56:7) dt(2019-2-3 12:56:7.5) dt(2019-5-7 1:6)
Date literals have the format d(yyyy-MM-dd).
Any dates that are not valid in the Gregorian calendar generate errors at compile time.
Examples:
d(2010-01-02) d(2019-2-3) dt(2019-5-7)
Time literals have the format t(hh:mm:ss.sss).
Seconds are optional. When they are not specified, 0 is assumed. Seconds can have fractions up to millisecond precision.
Invalid times generate an error at compile time.
Examples:
t(00:00:00) t(12:56:7) t(12:56:7.5) t(1:6)
A Boolean literal is either the keyword true or the keyword false.
A null literal is the keyword null.
Use this literal only in the following situations:
To set a variable or field,
As a return value.
Note
It is not possible to use this keyword to test if a variable or a field is null. Instead, use the function isNull().
By default, operation evaluation order is based on precedence and associativity. Use parentheses to explicitly indicate the order of evaluation.
The following table shows all operators, from highest to lowest precedence, and their associativity:
Precedence Level | Operator | Operand type | Result type | Associativity |
---|---|---|---|---|
9 | [](access to an element of a list) . (access to fields) () (parenthesis) | List index must be a decimal. | Can be any type. | Left to right. |
8 | not | Boolean | Boolean | |
7 | * / | Decimal | Decimal | Left to right. |
6 | + - | Decimal | Decimal | Left to right. |
5 | | (string concatenation) | String | String | Left to right. |
4 | < <= > >= | String, Decimal, timestamp, date, time (3). | Boolean | Not associative. |
3 | = <> | String, decimal, timestamp, date, time, boolean (3). | Boolean | |
2 | and | Boolean | Boolean | Left to right. |
1 | or | Boolean | Boolean | Left to right. |
All arithmetic operators (*, /, + and -) are evaluated in decimal with 34 digits precision (IEEE 754R Decimal128 format). Result is null if any operand is null.
A decimal value is automatically converted when assigned to an int type variable. An error occurs if the value cannot be converted because it is not an integer, is greater than 2147483647, or is less than -2147483648.
The string concatenation operator (|) operands can be of any type and are automatically converted to a string before concatenation. The null value is replaced by the empty string.
The string concatenation operator (|) replaces all null operands by the empty string before executing the concatenation.
Boolean operators use thread-value logic.
The truth table for the and operator is as follows:
And | true | false | null |
true | true | false | null |
false | false | false | false |
null | null | false | null |
The truth table for the or operator is as follows:
Or | true | false | null |
true | true | true | true |
false | true | false | null |
null | true | null | null |
A comparison operator (<, <=, >, =>, = and <>) compares two operands of same type. Variables of ìnt' type are always converted to decimal before comparison.
The returned value of a comparator is of boolean type. This value is true or false only if all operands are not null. It is null if any of its operand is null.
Built-in and unit functions usually return null if a parameter is null.
There can be exceptions, so be sure to read the API documentation.
The following table shows all assignment operators:
Operator | Operand type | Description |
---|---|---|
:= | Can be used with any type. | Standard assignment. |
|= | string | a |= b is equivalent to a := a | b |
+= | decimal | a += b is equivalent to a := a + b |
-= | decimal | a -= b is equivalent to a := a - b |
*= | decimal | a *= b is equivalent to a := a * b |
/= | decimal | a /= b is equivalent to a := a / b |
A statement block is used to group multiple statements. It starts by the keyword begin and ends with the keyword end. Most statements need to be terminated by a ;:
begin statement_1; statement_2; end
A statement block can contain one or more statement blocks that can also contain statement blocks:
begin statement_1; begin statement_2_1; statement_2_3; begin statement_3_1; statement_3_2; end end statement_3; end
Any statement in a block can declare a variable or a constant.
A variable or a constant always has a type that is fixed when the variable is declared.
A value can be assigned to a variable using the assignment operator :=.
Its is an error to assign a value of the wrong type to a variable, or to change the value of a constant after its declaration.
A variable or constant type can be detected automatically if initialized when declared. This feature is called type inference.
Syntax for a variable is:
var variable_name := a_value;
The syntax for a constant is:
const const_name := a_value;
Examples:
begin var firstName := 'John'; // Variable of type string. var age := 30; // Variable of type decimal. var address := record.address; // Variable of an immutable complex. const MAX_AGE := 100; // Decimal constant. const DEFAULT_COUNTRY := 'France'; // String constant. end
Note
A constant is not necessarily immutable.
It is possible to explicitly specify the variable’s type.
The syntax for a variable is:
// Following variable initial value is null. var variable_1_name : variable_1_type; // Value a_value must be compatible with type variable_1_type. var variable_2_name : variable_1_type := a_value;
The syntax for a constant is:
const const_name : variable_1_type := a_value;
Examples:
begin var firstName : string; firstName := 'John'; var age : decimal; age := 30; var address : typeof record.address; address := record.address; const MAX_AGE : decimal := 100; const DEFAULT_COUNTRY : string := 'France'; end
Variables that are not initialized have a null value:
begin var firstName : string; if isNull(fistName) then do_something(); // Statement will be executed. end
The following statements have errors:
begin // Following will not compile because variable is not initialized and no type // is defined. var firstName; // Following will not compile because variable type is decimal and value is // a string. var age : decimal := 'test'; end
A variable or constant scope spans from its declaration to the end of the statement block where it was declared:
begin var firstName := 'John'; var lastName := 'Doe'; begin // Following is OK because firstName and lastName are visible. var fullName := firstName | ' ' | lastName; .... end // Following will NOT compile because fullName is out of the scope. var message := 'Please contact ' | fullName; end
A variable with same name cannot exist in same block or sub blocks:
begin var firstName := 'John'; begin var firstName := 'Bob'; // Error! Variable already declared. .... end end
The following is correct:
begin begin var firstName := 'Bob'; .... end // The following is not an error, because block where previous variable was // declared is ended. var firstName := 'John'; end
Variable or constants of complex type can be declared using type detection or typeof keyword.
Dot notation is used to access fields of a variable of complex type.
Examples:
// Declaration using type detection. var address1 := record.OfficeAddress; // Declaration using typeof notation. var address2 : typeof record.OfficeAddress; address2 := address1;
Each step (parts separated by a dot) is an identifier. This means that following quoted notation can be used for any step:
var city := record."OfficeAddress".City;
This is useful for steps equal to a reserved keyword or using characters, such as the minus character (-) or dot (.), that are not compatible with unquoted identifiers.
At runtime, any step can evaluate to null. In this case the full field expression evaluates to null.
Multi-valued fields are treated as ordered lists. Index notation [index] is used to access an item of the list. Indexes are 0 based (first index is 0).
In the following example, the field "Address" is multivalued:
var city := record.Address[1].City;
If an index is out of bound, the indexed expression will return null. In this case the full field expression evaluates to null.
An index may be a decimal expression. Only the integer part of the expression will be used.
Predefined variables allow access to the context. These contextual variables are constant and of an immutable complex type.
The predefined variable record is available only if the current script is for a function field of a table. It allows read-only access to the current record.
Its fields are defined by the current EBX® schema.
Example:
// Returns the full name. export function getValue(): string begin return record.FirstName | ' ' | record.LastName; end
The predefined variable root is available only if the current script is for a function instance field of a dataset. It provides read-only access to the instance fields of current datasets.
Its fields are defined by the current EBX® schema.
Example:
// Returns information on current dataset data. export function getValue(): string begin return root.City | ' ' | root.Region; end
The predefined variable dataspace provides access to information on the current dataspace.
This variable has following fields:
Name | Type | Description |
---|---|---|
name | string | The name of the dataspace. Since the dataspace namespace and the snapshot namespace are independent, the returned string is only an identifier in the context of one of the namespaces. For a global dataspace or snapshot identifier, use field id. |
id | string | The persistent identifier of a dataspace or snapshot. Compared to name, this identifier additionally specifies whether this id is for a dataspace or a snapshot. |
isSnapshot | boolean | Is true if dataspace is a snapshot and false if dataspace is a branch. |
Example:
// Returns details on the current dataspace. export function getValue(): string begin if dataspace.isSnapshot then return 'Snapshot: ' | dataspace.name; else return 'Branch: ' | dataspace.name; end
The predefined variable dataset provides access to information on the current dataset.
This variable has the following fields:
Name | Type | Description |
---|---|---|
name | string | The name of the dataset |
Example:
// Returns the current dataset name. export function getValue(): string begin return dataset.name; end
Functions are methods that return a value.
A function with no parameters has the following syntax:
function function_name(): return_value_type begin .... return a_value; end
A function with parameters has the following syntax:
function function_name( parameter_1 : parameter_1_type, parameter_2 : parameter_2_type...): return_value_type begin .... return a_value; end
The last statement of a function must always be a return statement. The function can include as many return statement as necessary.
Example:
function getFullName(firstName: string, lastName: string): string begin if isNull(firstName) then return lastName; return firstName | ' ' | lastName; end
It is illegal to return a value different from the one declared:
function getValue(): string begin return 0; // Error! Return type is decimal, not string. end
An exported function can be called directly by EBX®.
Only one exported function is allowed per script. Its signature (name, and return type) depends on the type of the function field.
For the field of a record, definition must be similar to:
export function getValue(): typeof record.<path> begin ... end
For the field of a dataset, definition must be similar to:
export function getValue(): typeof root.<path> begin ... end
When a function field is first edited, EBX® provides an initial script with the correct definition of the exported function.
Procedures are methods that do not return a value.
A procedure with no parameters has the following syntax:
procedure procedure_name() begin .... end
A procedure with parameters has the following syntax:
procedure procedure_name( parameter_1 : parameter_1_type, parameter_2 : parameter_2_type...) begin .... end
A procedure cannot have a return statement, but the function can include as many return statements as necessary. A procedure statement cannot return a value.
Simple type parameters are passed by value. This means that a function or a procedure receives a copy of the original value.
Complex and list types are passed by reference. This means that a function or a procedure receives the original object. If the function or procedure modifies the object, the original one is modified.
An "if then" statement has the following syntax:
if condition-expression then then-statement
The condition expression must evaluate to a boolean type.
The 'then' statement can be a statement block.
An "if then else" statement has following syntax:
if condition-expression then then-statement else else-statement
The condition expression must be of boolean type.
A 'then' or 'else' statement can be a statement block.
Note
Expression:
if condition-expression then statements-a; else statements-b;
Cannot be equivalent to:
if not condition-expression then statements-b; else statements-a;
Indeed, if the expression is null, the else statement is executed in both cases.
A "for in do" loop statement is used to select each item of a list and execute a block statement. It has following syntax:
for item_variable_name in list do begin statement_a; statement_b; ... end
The block statement is executed once for each value of the list. At each iteration, the read-only item variable takes a value of the list in the order of the list.
The name of the item variable must be unique in the current scope or an error will be generated at compile time.
If a single statement must be executed for each item, the following simpler syntax can be used:
for item_variable_name in list do statement;
The following example iterates a list of complex:
// Concatenate all city addresses in a single string. var cities := ''; for address in record.Addresses do begin if cities <> '' then cities |= ', '; cities |= address.city; end
A "while do" loop statement is used to execute a statement block until a condition is true. It has following syntax:
while condition do begin statement_a; statement_b; ... end
If a single statement must be executed, the following simpler syntax can be used:
while condition do statement_a;
The following example calculates factorial of a value:
function factorial(value : decimal): decimal begin var factorial := value; while(value > 1) do begin value -= 1; factorial *= value; end return factorial; end
EBX® provides an API that is packaged in "units".
A unit can define multiple function or procedures.
Except for the default one, a unit must be declared before usage. The declaration must be at the top of the script and must follow these types of syntaxes:
uses package_name.unit_name_a; uses package_name.unit_name_b as alias_b;
Currently, package_name is always core. A unit alias must be unique in a script.
Methods can be referenced using following syntaxes:
value1 := package_name.unit_name_a.function_a(); value2 := package_name.unit_name_a.function_b(parameter1, parameter2); value3 := alias_a.function_c(); value4 := alias_a.function_d(parameter1, parameter2); package_name.unit_name_a.procedure_a(); package_name.unit_name_a.procedure_b(parameter1, parameter2); alias_a.procedure_c(); alias_a.procedure_d(parameter1, parameter2);
The following example uses the core.list unit to create a list:
uses core.list as list; export function getValue(): typeof record.Cities begin return list.of('Paris', 'Bruxelles', 'Berlin'); end
For details on the provided units, see the API Documentation.
The unit unit.log provides a function that can be used to log a message.
A message logged by scripts can be viewed using Administration>Repository management>Scripting EBX® menu.
Another useful feature is that if a runtime encounters an error while executing a script, it is usually logged with the line in the script where the error occurred.
When a new function field is created using the DMA, an initial script is created.
The following example is a script created for a field of type string:
export function getValue(): string begin return 'A string value'; end
The exported function signature (name, and return type) depends on the field’s type and should not be changed.