Programs

In a Script Step you can use the full range of FlowScript statements.

Variable declarations

Variables are declared using the let keyword and re-assigned using the set keyword. Because FlowScript is an implicitly typed language, you cannot declare a variable without also assigning it.

let x = 12;  
set x = x + 1;  
// x is now 13 let

Return statement

To return a value from a FlowScript, use the return keyword. The Flow Designer checks to make sure that each branch of a FlowScript returns a value.

return x * 16;

Conditional statement

Conditional statements are written using the if/elseif/else keywords.

if quantity > 100 {
    let discountPercent = 10;
    let amount = quantity * price;
    return amount - (amount * (discountPercent/100));
}
else {
    return quantity * price;
}

When the body of a conditional clause consists of only one statement, it is possible to omit the curly braces and use the colon (:) sign instead.

if x > 0:
    return "greater than zero";
elseif x < 0:
    return "less than zero";
else:
    return "zero";

Loops

Loops are written using the for and in keywords. You can loop over table variables, strings (one iteration per character in the string) and numerical ranges.

Table example

let items = [name: 'soda', price: 4.1] & [name: 'pizza', price: 8.9];
let totalPrice = 0;
for item in items {
    set totalPrice = totalPrice + item.price;
}

String example

let barcode = 'A123522BA12332';
let number_of_a = 0;
for c in barcode {
    if c = 'A':
        set number_of_a = number_of_a + 1;
}

In order to exit a loop prematurely, you can use the break; statement. In order to exit the current iteration and jump right into the next one, use the continue; statement.

If the body of a loop consists of only one statement, you can omit the curly braces and use the colon (:) sign instead.

for item in items:
    set totalPrice = totalPrice + item.price;

NOTE: To support backward compatibility with previous versions of Novacura Flow, the do and done keywords are also supported in lieu of the opening and closing curly braces.

Index loops

To loop over a range of indexes, use the range(start, count) function.

for i in range(0, 10) {
    // statements
}

Type declarations

It is possible to define types in FlowScript programs. A type defines the structure of a record (or table) variable. Types are defined like this:

type Person = [name, age];
// This creates a type named 'Person' with simple fields 'name' and 'age'.

When creating a new record based on a type, use the following syntax:

type Person = [name, age];
let donaldDuck = Person[name: 'Donald Duck', age: 35];

Types can have nested structures:

type Car = [model, year, engine: [power, torque]];
// This creates a type named "Car" with two simple fields (model and year) and a complex (record) field containing sub-fields "power" and "torque".
let myCar = Car[model: 'Volkswagen Golf', year: 2016, engine: [power: 170, torque: 184]];

Types can also refer to other types:

type Car = [model, year, engine: [power, torque]];
type personWithCar = [name, age, car: Car];

Types always define the structure of record variables. However, by adding the multiplier (*) unary operator, you can convert a type into the corresponding table type:

type Car = [model, year, engine: [power, torque]];
type PersonWithManyCars = [name, age, cars: *Car];

The multiplier (*) unary operator can also be used when initializing a record:

type Point = [x, y];
type DotGraph = [points: *Point];
let mySingleDotGraph = DotGraph[points: *[1, 1]];

It is possible to define default values for type fields. If a default value is given, the field does not have to be initialized.

type Customer = [name, email, phone = 'Not specified'];
let myCustomer = Customer[name: 'Acme Industries', email: 'info@acme.com'];
// myCustomer.phone will now be automatically initialized as 'Not specified'

It is possible to use nil values for type fields which themselves are records. nil values are only permitted for such cases; simple or table values cannot be nil.

type Employee = [name, salary, manager: Employee];
let anEmployee = Employee[
                    name: 'Bob',
                    salary: 10000,
                    manager: Employee[name: 'Alice', salary: 20000, manager: nil]
                ];


if not anEmployee.manager.manager = nil:
    error 'Didn't work';

Automatic types

Each record and table variable in a workflow can also be used as a type. This is useful when you need to programmatically create a record that matches the output from a machine step or some other workflow step. Automatic types are prefixed with the dollar sign ($). For record a variable myRecord, the automatically created type will be named $myRecord_type For a table variable myTable, the automatically created type will be named $myTable_rowtype.

let newRow1 = $myTable_rowtype[a: 1, b: 2];
let newRow2 = default($myTable_rowtype) with [b: 2];

Function declarations

Functions are declared using the let and function keywords.

let least = function(a, b) => {
    if a < b:
        return a;
    else:
        return b;
};

For a single-parameter function, no parentheses are needed around the argument list. For a single-statement bodied function, the curly braces, as well as the return keyword, can be omitted.

let square = function x => x * x;

If no parameter type is given for a function parameter, it is assumed to be a simple value. For complex values (records and tables), the syntax is similar to that of type definitions:

type Employee = [name, salary, position];
let employeesWorkingForFree = function(employees: *Employee) => {
    return employees where salary = 0;
};


let distance = function(point1: [x, y], point2: [x, y]) => {
    return sqrt(pow(point2.x - point1.x, 2) + pow(point2.x - point1.y, 2));
};

return distance([x: 3, y: 28], [x: 99, y: 0]);

If no return type is declared for a function, Flow tries to infer the return type based on the function body. It is also possible to specify an explicit return type.

type Employee = [name, salary, position];
let employeesWorkingForFree = function(employees: *Employee) : *Employee => {
    return employees where salary = 0;
};
// This function returns a table of Employee records

If recursion is required (i.e. a function that calls itself), return types MUST be explicitly specified. If such a function returns a simple value, the return type should be specified using the simple keyword.

let recursiveFunction = function(x) : simple => case when x > 10 then 999 else recursiveFunction(x + 1);

NOTE: To support backward compatibility with previous versions of Novacura Flow, the do and done keywords are also supported in lieu of the opening and closing curly braces.

Errors

If you need to raise an error from a FlowScript, use the error keyword.

error "This is the error message";

Parsing Example

The following FlowScript program example will take a table with one string column and parse it into a table with several columns. It will split columns based on a column delimiter and take the right-side value by using a key-value delimiter.

let newTable = 
    [rowName:'id=1,price=10,name=Soda'] & 
    [rowName:'id=2,price=100,name=Burger'] & 
    [rowName:'id=3,price=50,name=Fries'];

let firstDelimiter = ',';
let secondDelimiter = '=';

let parseFunction = function(rowName, columnIndex, subColumnIndex) =>
    rowName.Split(firstDelimiter).Skip(columnIndex).First().value
        .Split(secondDelimiter).Skip(subColumnIndex).First().value;

return map newTable as 
    [id: parseFunction(rowName, 0, 1),
     price: parseFunction(rowName, 1, 1), 
     name: parseFunction(rowName, 2, 1)];

Last updated