Scripting Overview
Tinman 3D contains a scripting language that can be used to create and configure objects from classes found in the native API. Script code can be generated from existing native objects, which allows round-tripping between scripts and native objects. The scripting functionality is provided by the ConfigScript class.
For examples on how to use scripting, please refer to the Geodata Examples and the tutorial script of the Workshop Application. |
Script File
A script is a text file in UTF-8 encoding, having a .tms
name suffix by convention.
The formal Grammar of a script is included in the documentation of the ConfigScript class. |
Comments
There are three ways to add comments to a script.
// This is a single-line comment.
/* This is a * multi-line * comment. */
Single-line and multi-line comments are ignored when the script code is processed. |
`This is a documentation comment. value : number = 0;
Documentation comments are retained and can be accessed programmatically via ConfigMember.Comment, for script variables and function parameters. |
Header
The header of a script starts with an optional script
directive, followed by zero or more using
directives, which can be used to access members of another script.
script Script1; (1) value_1 : number = 1; ~ ~ ~ using Script1; (2) value_2 : number = Script1::value_1 + 1; (3)
1 | Associates the script file with an identifier.The filename is not of relevance. |
2 | Declares a dependency to an external script. |
3 | Uses the fully-qualified syntax to access a member in an external script. |
Cross-script references work for those ConfigScript objects that have been put into the same ConfigDomain.The Workshop Application is doing this automatically for all scripts in a project. |
Variables
Script variables are used to construct ConfigValue objects at runtime, which are then used to configure native API objects.
`Script variables may be documented. (1)
intern name : string { # != "bad" } = "Hello World!";
\____/ \__/ \______/ \____________/ \______________/ (2) (3) (4) (5) (6)
1 | Optional documentation comment |
2 | Use the intern keyword to make a variable private, then it cannot be accessed from other scripts using the fully-qualified syntax Script::name . |
3 | The variable name is mandatory. |
4 | The variable type is optional. If omitted, the type will be inferred from the default value. |
5 | The constraint expression is optional.
It will be evaluated and validated at runtime, before the variable value is used.Validation fails if the result is false . |
6 | The default value of the variable. If omitted, the value must be provided from code. If present, it may still be overridden from code. |
Variables without a type specification and without a default value will have an invalid value. |
Functions
Script functions are used as building blocks to construct complex objects.
`Script functions may be documented. (1)
intern name( (2) (3)
`Function parameters may be documented. (1)
a : number = 10, (4)
b : number = 20)
: string (5)
= a + b (6)
1 | Optional documentation comment |
2 | Use the intern keyword to make a function private, then it cannot be called from other scripts using the fully-qualified syntax Script::name(…) . |
3 | The function name is mandatory. |
4 | Function parameters have the same syntax and semantic as script variables, except the intern keyword. |
5 | The function result type is optional. If omitted, the type will be inferred from the function expression. |
6 | The function expression is evaluated when the function is called. The expression may use the function parameters as well as other script members. |
Script functions without an expression must be implemented by code before they may be called, see ConfigScript.ImplementFunction. |
Data Types
The following sections describe the basic data types that are defined for config scripts.
bool
A boolean value which is either true or false.
The following literal expression can be used for boolean values:
value_1 : bool = true ;
value_2 : bool = false ;
number
A number value.
Number literals can be in decimal notation:
value_3 : number = 123 ;
value_4 : number = 123.45 ;
value_5 : number = 123E+67 ;
value_6 : number = 123.45E+67 ;
Integer literals can also be in hexadecimal notation:
value_7 : number = 0x123456789ABCDEF ;
string
A sequence of Unicode characters.
String literals can be enclosed in single quotes 'abc'
or double quotes "abc"
.Their escape sequences are ''
and ""
, respectively.
value_8 : string = 'Hello World!' ;
value_9 : string = "Hello World!" ;
value_10 : string = 'You are here: 12°34''56.789"N 123°45''57.891"E' ;
value_11 : string = "You are here: 12°34'56.789""N 123°45'57.891""E" ;
path
A filesystem path to a file or directory.
Path literals can be enclosed in angle brackets <abc>
.Alternatively, backticks `abc`
can be used, for example on the command-line where angle brackets have a special meaning.
value_12 : path = <mydataset.hgt> ;
value_13 : path = <../parent-directory/> ;
value_14 : path = `my file.dat` ;
enum
An enumerated value with a fixed set of possible value items.
value_15 = Colors.Red ;
value_16 = Colors.Green ;
value_17 = Colors.Blue ;
The enum name (Colors
in above example) may be omitted if it can be inferred from the context of the usage site:
value_18 : Colors = Red ;
value_19 : Colors = Green ;
value_20 : Colors = Blue ;
array
A sequence of equally typed values.
value_21 : number[] = [1, 2, 3, 4] ;
value_22 : string[] = ["a", 'b', "c"] ;
value_23 : number[][] = [[1,2,3], [2,3,4], [5,6,7]] ;
object
A struct
is a compound value that that aggregates named values of arbitrary types (i.e. fields).
The names, types and the order of the fields of a struct value are defined by native ConfigType object.
A class
is a compound value similar to a struct
value, with the following additions:
-
A class can inherit fields from a super class.
-
Classes can be abstract, which means values cannot be created from them, subclasses must be used instead.
Expressions
The following sections describe the expressions that are available in config scripts.
The placeholder names a
, b
and c
refer to expressions, whereas the placeholder name id
refers to identifiers.
Operators
The following table shows the operator expressions that are available in configuration scripts.
Operator | Description | Pre. |
---|---|---|
|
Evaluates
The order of elements in |
0 |
|
Evaluates
The order of elements in |
|
|
Evaluates
The order of elements in |
|
|
Evaluates |
1 |
|
Evaluates to Uses short-circuit evaluation, i.e. |
2 |
|
Evaluates to Uses short-circuit evaluation, i.e. |
3 |
|
If If Otherwise, evaluates |
4 |
|
If If Otherwise, evaluates |
5 |
|
If If Otherwise, evaluates |
6 |
|
Checks if a and b have the same type and value. Returns |
7 |
|
Evaluates Returns |
8 |
|
Evaluates Right shifting will keep the most-significant bit, so |
9 |
|
If If If If Otherwise, evaluates |
10 |
|
If If Otherwise, evaluates |
|
|
Evaluates |
11 |
|
Evaluates |
|
|
Evaluates The remainder is defined as |
|
|
Evaluates |
12 |
|
Evaluates |
|
|
If Otherwise, negates the |
13 |
+a |
If If ⇒ it is absolute Otherwise, returns the |
|
|
If Otherwise, inverts the |
|
|
If If Otherwise, evaluates |
|
|
Evaluates |
14 |
|
Evaluates |
|
|
An Array Creation or Object Creation expression. |
|
|
A call to a script function. |
|
|
Refers to a script variable, a type field, a namespace or type name. |
15 |
|
A braced expression that overrides default precedence rules. |
Array Creation
Array values are created using the following syntax:
value_24 = [1, 2, 3, 4] ;
value_25 = [number: 1, 2, 3, 4] ;
value_26 : number[] = [1, 2, 3, 4] ;
In the first example, the array element type is inferred from the types of the initializer expressions.
The second example specifies the array element type within the array creation expression, to avoid type inferring.
In the last example, the array element type is inferred from the usage site.
Object Creation
An object can only be created from a type iff values for all mandatory field have been specified. The way these values are specified depends on the syntax of the object creation. There are three ways of doing this, see below for details.
Initializer
Fields are set explicitly using their names, in arbitrary order.
value_27 : Color.Rgb = { red = 1, green = 0.5, blue = 0 } ;
value_28 = Color.Rgb { red = 1, green = 0.5, blue = 0 } ;
The first example, the object type is inferred from the usage site.
In the second example, the object creation expression specifies the object type.
Type Fixing
To allow concise syntax constructs to be used, automatic type fixing is applied to expressions when the actual type does not match the expected type.
Conversion Rules
If possible, the following default rules are applied between the expected type and the actual type.
bool | number | string | path | enum | |
---|---|---|---|---|---|
bool |
- |
§1 |
§2 |
- |
- |
number |
§3 |
- |
§4 |
- |
§5 |
string |
§6 |
§7 |
- |
§8 |
§9 |
path |
- |
- |
§10 |
- |
- |
enum |
- |
§11 |
§12 |
- |
- |
Name | Description |
---|---|
§1 |
|
§2 |
|
§3 |
|
§4 |
Convert number to locale-agnostic and round-trip safe string representation. |
§5 |
Lookup enum item by using the |
§6 |
Map |
§7 |
Parse |
§8 |
Convert |
§9 |
Lookup enum item by using the |
§10 |
Convert |
§11 |
Return zero-based ordinal of enum item. |
§12 |
Return tag value of enum item. |
Array Wrapping
If the expected type is an array
, the expression is wrapped in an Array Creation expression.
value_34 : number[] = 123 ;
// Fixed with:
value_35 : number[] = [ 123 ] ;
Object Wrapping
If the expected type is a struct
or class
, the expression is wrapped in an Object Creation expression, if there is a unique choice for a matching type.
value_36 : Tinman.Color = Colors.Red ;
// Fixed with:
value_37 : Tinman.Color = {Color.Name: Colors.Red} ;
Tutorial Script
The Workshop Application contains a built-in tutorial script, for reference and as an example. Here is a listing of it.
// A config script can have an optional name prefix, which is used to access its public members from
// other scripts in the same domain via the '::' operator, for example 'Tutorial::variable'.
// The Workshop puts all scripts of a project into the same domain.
script Tutorial;
// -------------------------------------------------------------------------------------------------
// Script members
// -------------------------------------------------------------------------------------------------
` This is a public script variable, documented with the special 'backtick' comment, which is
` accessible via the API.
variable : string = "Hello World!";
// \____________/
// This is the default value of the script variable; it can be overwritten via
// the API.
// \____/
// This is the variable type. If omitted, the type is inferred from the default value.
// \_______/
// This is the variable name.
` This is a private script variable. It cannot be accessed from other scripts.
intern variable1 : string = "Hello World!";
` This is a variable without a default value. The API must be used to specify a value. It cannot be
` browsed or inspected in the Workshop.
intern variable2 : string;
` This is a variable with a value constraint. Constraint violations will cause validation failures,
` which are shown in the Messages windows.
intern variable3 : number { # < 0 } = -1;
` This is a public script function. The Workshop can browse resp. inspect a script function only
` if all parameters have default values.
function(` Function parameters can also be documented using the special 'backtick' comment.
a : number = 10, b : number = 20) : string = "a=" + a + ", b=" + b + ", a+b=" + (a + b);
// \________________________________________/
// This is the expression that is evaluated
// when the function is called.
// \____/
// This is the function return type. If omitted, the
// type is inferred from the right-side expression.
// \______________________________/
// These are the function parameters. Parameter declarations use the same syntax as script
// variables, except the 'intern' keyword. Each parameter can have a leading documentation
// comment.
// \______/
// This is the function name.
// The built-in script 'Tinman' provides some utility functions, which are implemented externally.
intern extern1 = Tinman::string_part('Hello World!', 1, 5);
intern extern2 = Tinman::string_part('Hello World!', 5, 1);
intern extern3 = Tinman::string_part('Hello World!', 0, 6);
intern extern4 = Tinman::string_part('Hello World!', 6, 0);
intern extern5 = Tinman::string_part('Hello World!', 0, -7);
intern extern6 = Tinman::string_part('Hello World!', -7, 0);
// -------------------------------------------------------------------------------------------------
// Data types
// -------------------------------------------------------------------------------------------------
// Data type 'bool', can be true or false.
typeBool : bool = true;
intern typeBool1 : bool = false;
// Data type 'number', represented as a 64-bit floating-point number.
typeNumber : number = 123456;
intern typeNumber1 : number = 0xFF;
intern typeNumber2 : number = -1.234e+56;
intern typeNumber3 : number = +inf;
intern typeNumber4 : number = -inf;
intern typeNumber5 : number = nan;
// Data type 'string', a nullable character sequence.
typeString : string = null;
intern typeString1 : string = "";
intern typeString2 : string = "Hello'""World!";
intern typeString3 : string = 'Hello"''World!';
// Data type 'path', a nullable filesystem path.
typePath : path = null;
intern typePath1 : path = <c:\windows\style>;
intern typePath2 : path = <\\localhost\UNC\style>;
intern typePath3 : path = </unix/style>;
intern typePath4 : path = <relative/to/current/directory.txt>;
intern typePath5 : path = <./relative/to/script/directory.txt>;
intern typePath6 : path = `alternative/syntax/for/command/line`;
// Data type 'array', a nullable, fixed-length list of equally typed values.
typeArray = [1,2,3,"4",5]; // Element type is inferred from first element,
intern typeArray1 = [number: "1",2,3,"4",5]; // ...is specified in array expression,
intern typeArray2 : number[] = ["1",2,3,4,5]; // ...or will be inferred from array type.
// Data type 'enum', a pre-defined set of possible values.
typeEnum = Colors.Red;
intern typeEnum1 : Colors = Red; // Can omit name of enum type if inferred from context.
// Data type 'object', an instance of a pre-defined class.
typeObject = {Vec3: 1,2,3}; // Constructor-style with explicit class name.
intern typeObject1 = Vec3 { z = 3, y = 2, x = 1 }; // Initializer-style with explicit class name.
intern typeObject2 : Vec3 = {1,2,3}; // Constructor-style with inferred class name.
intern typeObject3 : Vec3 = { z = 3, y = 2, x = 1 }; // Initializer-style with inferred class name.
intern typeObject4 : PixelRange = {Vec2: 1, 2}; // Mismatching types are fixed automatically,
// if the constructor-style syntax can be used,
// passing the value as single argument.
// -------------------------------------------------------------------------------------------------
// Expressions (lowest precedence first)
// -------------------------------------------------------------------------------------------------
// Set operators (e.g. union, intersection) can be used on arrays that have a simple element type,
// i.e. bool, number, string, path or enum.
// Functional operators
intern expr_1_1 = typeArray1 => # * @; // Evaluates the right-side expression for each element in
// left-side array and returns an array that holds the
// resulting values.
// '#' refers to the array element
// '@' refers to the array index
intern expr_1_2 = typeArray1 ?> # > 2; // Evaluates the right-side expression for each element in
// left-side array and returns an array that holds those
// elements for which the resulting value is true.
// '#' refers to the array element
// '@' refers to the array index
intern expr_1_3 = typeArray1 +> # + @; // Uses the right-side expression to aggregate the elements
// in the left-side array, starting with the first element as
// current value, consecutively for each element pair.
// '#' refers to the current value
// '@' refers to the array element
// Conditional operators
intern expr_2 = typeBool ? "A" : "B"; // Depending on the condition value, either the left-side
// (true) or right-side (false) expression is evaluated.
intern expr_3 = typeBool || typeBool1; // Conditional OR with short-circuit evaluation.
intern expr_4 = typeBool && typeBool1; // Conditional AND with short-circuit evaluation.
// Logical operators
intern expr_5_1 = typeBool | typeBool1; // bool : Evaluates both sides and computes OR.
intern expr_5_2 = typeNumber | 0xFFFF; // number : Interprets both sides as 64-bit integers
// and computes the bit-wise OR.
intern expr_5_3 = [1,2] | [2,3]; // set : Computes the union of the given sets.
intern expr_6_1 = typeBool ^ typeBool1; // bool : Evaluates both sides and computes XOR.
intern expr_6_2 = typeNumber ^ 0xFFFF; // number : Interprets both sides as 64-bit integers
// and computes the bit-wise XOR.
intern expr_6_3 = [1,2] ^ [2,3]; // set : Computes the symmetric difference of the
// given sets.
intern expr_7_1 = typeBool & typeBool1; // bool : Evaluates both sides and computes AND.
intern expr_7_2 = typeNumber & 0xFFFF; // number : Interprets both sides as 64-bit integers
// and computes the bit-wise AND.
intern expr_7_3 = [1,2] & [2,3]; // set : Computes the intersection of the given sets.
// Equality operators
intern expr_8_1 = typeBool == typeBool1; // Left and right values are equal?
intern expr_8_2 = typeBool != typeBool1; // Left and right values are not equal?
// Relational operators
intern expr_9_1 = typeNumber < 1; // Left number is smaller than right one?
intern expr_9_2 = typeNumber <= 1; // Left number is smaller than or equal to right one?
intern expr_9_3 = typeNumber > 1; // Left number is greater than right one?
intern expr_9_4 = typeNumber >= 1; // Left number is greater than or equal to right one?
// Shift operators
intern expr_10_1 = typeNumber << 1; // Interprets the left number as a 64-bit integer and
// performs a bit-wise left shift by the given amount.
intern expr_10_2 = typeNumber >> 1; // Interprets the left number as a 64-bit integer and
// performs a bit-wise right shift by the given amount.
// Additive operators
intern expr_11_1 = typeNumber + 1; // number : Adds the left and right numbers.
intern expr_11_2 = typeString2 + "1"; // string : Appends the right string to the left one.
intern expr_11_3 = typePath5 + "a"; // path : Appends a suffix to the path value.
intern expr_11_4 = typePath5 + <a>; // path : Concatenates the given path values.
intern expr_11_5 = [1,2] + [2,3]; // array : Concatenates the given arrays.
intern expr_11_6 = typeNumber - 1; // number : Subtracts the right number from the left one.
intern expr_11_7 = typePath5 - 'y'; // path : Removes the path suffix, starting at the
// rightmost occurrence of the given character.
intern expr_11_8 = [1,2,3] - [2,4]; // set : Subtracts the right set from the left one.
// Multiplicative operators
intern expr_12_1 = typeNumber * 2; // Multiplies the left and right numbers.
intern expr_12_2 = typeNumber / 2; // Divides the left number by the right number.
intern expr_12_3 = typeNumber % 2; // Computes the remainder of the division.
intern expr_13_1 = typeNumber ** 10; // Raises the number to the given power.
intern expr_13_2 = typeNumber \\ 10; // Extracts the nth root from the number.
// Unary operators
intern expr_14_1 = - typeNumber; // number : Negates the number value.
intern expr_14_2 = - typePath5; // path : Removes the path suffix.
intern expr_14_3 = + typeNumber; // number : Replicates the number value.
intern expr_14_4 = + typePath5; // path : Returns the canonical path value.
intern expr_14_5 = ! typeBool; // bool : Inverts the boolean value.
intern expr_14_6 = ! typePath5; // path : Returns the last path element.
intern expr_14_7 = ~ typeNumber; // number : Interprets the number value as a
// 64-bit integer value and inverts the bits.
intern expr_15_8 = ~ typePath5; // path : Removes the last path element.
intern expr_15_9 = ~ [3,2,1,2]; // set : Collapsed the array into a sorted set.
intern expr_15_10 = + [1,2,3,4]; // array : Shorthand for ([...] +> # + @)
// Compound expressions
intern expr_16_1 = typeObject.x; // Member access on plain object value.
intern expr_16_2 = typeObject->x; // Member access on round-tripped object value.
// First, the object value is used to configure an SDK
// runtime object. Then, the SDK runtime object is converted
// back to a new object value.
intern expr_16_3 = typeArray[2]; // Element access on array value.
intern expr_16_4 = typeObject // Chained object creation: left-side expression
@ Vector.Constant(); // becomes first constructor argument or field value.
// Primary expressions
intern expr_17_1 = "literal"; // Literal
intern expr_17_2 = ["array"]; // Array creation
intern expr_17_3 = {Vec2: 1,2}; // Object creation
intern expr_17_4 = function(1,2); // Function call
intern expr_17_5 = ("braces"); // Braced expression
intern expr_17_6 = $TINMAN_3D_LICENCEKEY; // Value of environment variable