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.

Single-line comment
// This is a single-line comment.
Multi-line comment
/* This is a
 * multi-line
 * comment. */
Single-line and multi-line comments are ignored when the script code is processed.
Documentation comment
`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.

Listing of Script1 and Script2
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.

A script variable
`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.

A script function
`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.

Table 1. Operators and precedences
Operator Description Pre.

a => b

Evaluates a as an array, then evaluates b for each element using the scope variables below and finally returns an array that holds the resulting values.

# : Current element value of a

@ : Current element index in a

The order of elements in a is preserved.

0

a ?> b

Evaluates a as an array, then evaluates b for each element into a bool value using the scope variables below and finally returns an array that holds those elements of a for which b evaluated to true.

# : Current element value of a

@ : Current element index in a

The order of elements in a is preserved.

a ? b : c

Evaluates a as a bool value, then evaluates either b if true or c if false.

1

a || b

Evaluates to true iff a or b is true (logical OR).

Uses short-circuit evaluation, i.e. b is only evaluated if a is false.

2

a && b

Evaluates to true iff a and b are true (logical AND).

Uses short-circuit evaluation, i.e. b is only evaluated if a is true.

3

a | b

If a and b are array values of a simple type, computes the union of the set a and the set b.

If a or b is a number value, evaluates a and b as a 64-bit signed integer and returns the bitwise OR of them.

Otherwise, evaluates a and b as bool values and returns the logical OR of them.

4

a ^ b

If a and b are array values of a simple type, computes the symmetric difference of the set a and the set b.

If a or b is a number value, evaluates a and b as a 64-bit signed integer and returns the bitwise XOR of them.

Otherwise, evaluates a and b as bool values and returns the logical XOR of them.

5

a & b

If a and b are array values of a simple type, computes the intersection of the set a and the set b.

If a or b is a number value, evaluates a and b as a 64-bit signed integer and returns the bitwise AND of them.

Otherwise, evaluates a and b as bool values and returns the logical AND of them.

6

a == b
a != b

Checks if a and b have the same type and value.

Returns true and false respectively.

7

a < b
a <= b
a > b
a >= b

Evaluates a and b as a number value, then performs the respective comparison.

Returns true and false respectively.

8

a << b
a >> b

Evaluates a and b as a 64-bit signed integer, then performs a bit-shift of the amount given by b.

Right shifting will keep the most-significant bit, so -1>>64 is still -1.

9

a + b

If a and b are path values, concatenates the path elements.

If a or b is a path value, appends b to a, taking into account leading and trailing directory separators.

If a or b is an array value, concatenates the array elements.

If a or b is a string value, concatenates the string values.

Otherwise, evaluates a and b as a number value, then adds a and b.

10

a - b

If a and b are array values of a simple type, subtracts the set b from the set a.

If a is a path value, removes the path suffix beginning at the last occurrence of the first character of the string value of b (or '.' if empty).

Otherwise, evaluates a and b as a number, then subtracts b from a.

a * b

Evaluates a and b as a number value, then computes the result of the multiplication of a and b.

11

a / b

Evaluates a and b as a number value, then computes the result of the division of a by b.

a % b

Evaluates a and b as a number value, then computes the remainder of the division of a by b.

The remainder is defined as a-b*Q, where Q is equal to a/b rounded towards zero.

a ** b

Evaluates a and b as a number value, then raises a by the power of b.

12

a \\ b

Evaluates a and b as a number value, then raises a by the power of 1/b.

-a

If a is a path value, removes the path suffix beginning at the last occurrence of '.'.

Otherwise, negates the number value of a.

13

+a

If a is a path value, evaluates to its canonical form: ⇒ it is absolute
⇒ it does not have a trailing directory separator.
⇒ it does not contain any '.' or '..' elements.
⇒ uses directory separators that match the operating system.

Otherwise, returns the number value of a.

!a

If a is a path value, evaluates to the last path element.

Otherwise, inverts the bool value of a.

~a

If a is an array value of a simple type, collapses a to a set.

If a is a path value, removes the last path element.

Otherwise, evaluates a as a 64-bit signed integer and computes the bitwise complement.

a.id

Evaluates a as a struct or class value and accesses the field named id.

14

a[b]

Evaluates a as an array value and accesses the element at zero-based index b.

{ ... }

An Array Creation or Object Creation expression.

id(a, b, c)

A call to a script function.

id

Refers to a script variable, a type field, a namespace or type name.

15

( a )

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.

Constructor

Fields are set implicitly according to their declared order.

value_29 : Color.Rgb = { 1, 0.5, 0 } ;
value_30             = {Color.Rgb: 1, 0.5, 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.

Chained

An object value is used as the implicit first parameter for a subsequent creation.

value_31 = {Color.Name: Red} @ Opaque(0.5) ;
value_32 = {Color.Name: Red} @ Opaque { alpha = 0.5 } ;

// Equivalent to:
value_33 = Opaque
           {
             value = Colors.Red,
             alpha = 0.5
           } ;

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.

Table 2. Allowed type conversion paths from actual (row) to expected (column)
bool number string path enum

bool

-

§1

§2

-

-

number

§3

-

§4

-

§5

string

§6

§7

-

§8

§9

path

-

-

§10

-

-

enum

-

§11

§12

-

-

Table 3. Type conversion rules
Name Description

§1

value ? 1 : 0

§2

value ? "true" : "false"

§3

value == 0 ? false : true

§4

Convert number to locale-agnostic and round-trip safe string representation.

§5

Lookup enum item by using the number value as its zero-based ordinal, which may generate an error.

§6

Map "true" to true and "false" to false, otherwise generate an error.

§7

Parse string value as locale-agnostic string representation, which may generate an error.

§8

Convert string value to path value, which may generate an error

§9

Lookup enum item by using the string value as its tag name, which may generate an error.

§10

Convert path value to string value.

§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.

Listing of tutorial script
// 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

// 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.

// 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