LocalSolver logo
is now
Hexaly logo

We're excited to share that we are moving forward. We're leaving behind the LocalSolver brand and transitioning to our new identity: Hexaly. This represents a leap forward in our mission to enable every organization to make better decisions faster when faced with operational and strategic challenges.


Statements

Assignment statements

Assignments are used to assign values to variables. There are many kind of assignments in LSP depending on the type of the variable (local or global) and the operation you want to perform.

assignment_operator
    : '='
    | '<-'
    | '+='
    | '-='
    | '/='
    | '*='
    | '%='

local_assignment_operator
    : '='
    | '<-'

assignment_statement
    : identifier assignment_operator expression ';'
    | identifier assignment_compositor_list assignment_operator expression ';'
    ;

assignment_compositor_list
    : assignment_compositor
    | assignment_compositor_list assignment_compositor
    ;

assignment_compositor
    : '[' filter_iterator ']'
    | '[' arithm_expression ']'
    | '.' identifier
    ;

local_assignment_statement
    : 'local' identifier local_assignment_operator expression ';'
    | 'local' identifier assignment_compositor_list local_assignment_operator expression ';'
    ;

local_statement
    : 'local' identifier ';'
    ;

Simple statements

The simple statement is the classical variable = value; that assigns the given value to the specified variable, overriding the previous value. The variable can be local or global. If the variable was not previously declared as local, the variable is considered as global by default and is implicitly declared if necessary.

This statement cannot be used to add an LSExpression to the mathematical model. See Link statements for that.

Examples:

a = true;     // a = 1
b = 9;        // b = 9
c = a + b;    // c = 10
c = a * b;    // c = 9
c = (a == b); // c = 0
c = a < b;    // c = 1

Coumpound assignment statements

A coumpound assignment (also called “augmented assignment”) is the combination of an arithmetic operation and an assignment. The arithmetic operations allowed with coumpound assignment statements are:

  • + addition

  • - substraction

  • * multiplication

  • / division

  • % modulo

Thus, an assignment like a = a + b can be rewritten as a += b; with the coumpound assignment syntax.

Exemples:

a += 2;     // a = a + 2
a /= 2;     // a = a/2
a *= b + c; // a = a*(b + c)

Local assignment and local statements

Local statements local variable, local variable = value or local variable <- value introduce a new local variable with the given name and set its value to nil or to the specified value.

If a global variable with the same name was already defined, then the global variable will be masked while this local variable is visible. If a local variable with the same name was already defined an error is thrown. See Variables for the differences between local and global variables and for details on the visibility scopes of variables.

Examples:

local i;        // Declare i as local and set its value to nil.
local j = 2;    // Declare j as local and set its value to 2.

for[z in 0...10] {
    // This assignment will throw an exception since z was already implicitly
    // declared as local in the for loop.
    local z = "foo";
}

Iterated assignment statements

a[v in V] = f(v); is an iterated assignment which is strictly equivalent to for [v in V] a[v] = f(v); Similarly a[v in V] <- f(v); is an iterated assignment which is strictly equivalent to for [v in V] a[v] <- f(v); These iterated assignment features will help you will help you to make your models shorter and clearer. Similarly to the for statement introduced in the previous section, iterations can be nested and filtered:

for[i in 0...10][j in i+1...10][k in j+2...10]
    a[i][j][k] = i + j + k;
a[i in 0...10][j in i+1...10][k in j+2...10] = i + j + k; // compact

If statements

The if statement is used for conditional execution.

if_condition
    : 'if' '(' expression ')'
    ;

if_else_statement
    : if_condition statement
    | if_condition statement 'else' statement
    ;

if the if_condition is true (equals to 1), then the first statement is executed. Otherwise, the second statement after the else is executed. Note that the else branch is optional and a block of statements can be declared instead of a sole statement by using the braces {}. If the expression is not an integer equal to 0 or 1 an error will be thrown.

Note

The conditional ternary operator ? : can also be used as shortcut.

Examples:

if (0) c = "ok";
if (true) c = "ok";
if (2) c = "error"; // ERROR: invalid condition

For statements

The for statement is used to iterate over a range, the elements of a map or any other iterable value. The for statements can be nested with a compact syntax and can be filtered with an optional conditionnal statement.

for_statement
    : 'for' for_compositor statement
    ;

for_compositor_list
    : for_compositor
    | for_compositor_list for_compositor
    ;

for_compositor
    : '[' filter_iterator ']'
    ;

for_compositor
    : '[' filter_iterator ']'
    | for_compositor '[' filter_iterator ']'
    ;

filter_iterator
    : identifier 'in' expression ':' expression
    | identifier ',' identifier 'in' expression ':' expression
    | identifier 'in' expression
    | identifier ',' identifier 'in' expression
    ;

Simple for-loops

for [v in A] S; is a simple for-loop where the statement S is iteratively executed with v taking all values in A, where A can be a range or an iterable value like a map. Variable v is implicitly declared as a local variable visible within this for statement and in nested blocks only.

When a range is given, it is declared with the from...to syntax where to is excluded.

When an iterable object is given, the iteration is performed on the sub-values of the object. The iteration order is specific to the type of the value. For the maps, the order is defined as follows:

  1. Iterates on number keys (integer or floats) in ascending order.

  2. Iterates on strings in ascending order.

  3. Iterates on other types in an implementation specific way.

It is also possible to iterate on the pairs (key, value) using the syntax for [k,v in M]. In that case, k and v are both declared implicitly as local variables and contain respectively the current key and the current value of the loop.

Example:

s = { -44, 12, 14 };
for [i in 0...3] a[i] = i + 1; // a[0] = 1, a[1] = 2, a[2] = 3
for [v in s] println(v/2); // prints -22, 6 and 7.
for [k, v in s] println(k, ":", v); // prints 0:-44, 1:12 and 2:14

Filtered for-loops

for [v in V : C] is a filtered iteration loop: v takes only the values in V satisfying the condition C.

Compact for-loops

Filtered and non filtered for-loops can be nested in a compact way, as follows:

// Non compact
for[i in 0...10]
    for [j in i+1...10 : j % 2 == 0]
        for [k in j+2...10]
            a[i][j][k] = i + j + k;

// Compact
for[i in 0...10][j in i+1...10 : j % 2 == 0][k in j+2...10]
    a[i][j][k] = i + j + k;

The behavior of these 2 examples is similar except that the language considers the non-compact case as the association of 3 enclosing loops, while the compact example is solely a loop with 3 iterated indexes. This semantic difference is particularly important for break statement: a break statement stops the most inner loop, regardless of the number of iterated indexes.

For all kinds of loops, a block of statements can be declared instead of a sole statement by using the braces {}:

for[i in 0...10][j in i+1...10][k in j+2...10] {
    a[i][j][k] = i + j + k;
    b[i][j][k] = i * j * k;
}

for-loops can contain ‘break’ and ‘continue’ statement to respectively terminate the loop unconditionnaly or to go back to testing the condition of the loop whitout executing the rest of the statement suite.

While/do-while statements

while_statement
    : 'while' '(' expression ')' statement
    ;

dowhile_statement
    : 'do' statement 'while' '(' expression ')' ';'
    ;

While or do-while loops are used for repeating a section of code as long as an expression is true.

For the ‘while’ loop, the expression is tested first. If it is true, the statement is executed. The loop terminates otherwise.

For the ‘do-while’ loop, the statement is executed first, then the expression is tested. Contrary to the ‘while’ loop, the ‘do-while’ loop ensures that at least one statement suite is executed.

While or do-while loops can contain ‘break’ and ‘continue’ statement to respectively terminate the loop unconditionnaly or to go back to testing the condition of the loop whitout executing the rest of the statement.

Continue statement

continue_statement
    : 'continue' ';'
    ;

The continue statement, when encountered, goes back to testing the condition of the nearest enclosing loop, whitout executing the rest of the block code.

Continue statements can only take place in for/while/do-while loops. Use of this statement outside of these loops will throw an error.

Break statement

break_statement
    : 'break' ';'
    ;

Break statement terminates the nearest enclosing loop. Please note that for ‘for’ loops, the entire loop is stopped, regardless of the number of iterated indexes.

Break statements can only take place in for/while/do-while loops. Use of this statement outside of these loops will throw an error.

Try-catch statement

In the LSP Language, all errors, faults or exceptionnal situations are handled with an exception mechanism.

trycatch_statement
    : 'try' statement
       'catch' '(' identifier ')' statement
    ;

Throw statement

throw_statement
    : 'throw' expression ';'
    | 'throw' ';'
    ;

With statement

The with statement is used for resource management. It ensures that a system or a native object like files or solver instances are correctly freed at the end of the statement, whatever flow the code follows (return, exception).

The with statement in LSP is similar to the with statement in Python, the try-with-resource in Java or the using statement in C#.

with_statement
    : 'with' '(' with_resource ')' statement
    ;

with_resource
    : identifier
    | identifier '=' expression

Examples:

// f is a local variable whose scope is limited to the "with" statement
with(f = io.openRead("/path/to/file")) {
    local a = f.readInt();
    ...
}
// The file is automatically closed at the end of the "with" statement,
// even if an exception is thrown.

Constraint/minimize/maximize statements

modifier
    : 'minimize'
    | 'maximize'
    | 'constraint'
    ;

modifier_statement
    : modifier expression ';'
    ;

These statements modify expressions of the mathematical model (LSExpression) to add them to the constraints list or the objectives list. These statements can also be applied on integers or doubles.

  • constraint c; adds the LSExpression c as a constraint to the mathematical model. An error will be thrown is c is not a boolean LSExpression (introduced with a logical or relational operator) or a constant equals to 0 or 1.

  • minimize c; adds the LSExpression c as an objective to be minimized by the mathematical model.

  • maximize c; adds the LS expression c as an objective to be maximized by the mathematical model.

Note

At least one objective must be defined in the mathematical model.

Pragma statements

pragma_list
    : pragma_statement
    | pragma_list pragma_statement
    ;

pragma_statement
    : 'pragma' identifier ';'
    | 'pragma' identifier identifier ';'
    | 'pragma' identifier integer ';'
    | 'pragma' identifier double ';'
    ;

Pragma statements are special instructions to put at the beginning of an LSP file (before the use section) to alter the behavior of the compiler or the LSP virtual machine. The pragmas only affect the module in which they are declared, without affecting the other modules.

For the moment, there is only one pragma: modelingset whose behavior is detailed below.

modelingset pragma

As LocalSolver evolves, new modeling functions are added (like sum, bool or list). Any new addition can cause compatibility issues with existing LSP models when there is a conflict between the name of the new modeling operator and the name of a user variable or a user function.

The use of this pragma allows to freeze the list of modeling functions imported in the module to a specific version of LocalSolver.

For instance:

pragma modelingset 10.0;

function model() {
    cover("Tiramisu");
    maximize 1;
}

function cover(cake) {
    println(cake + " is now covered with cocoa powder");
}

In this example, we have frozen the modeling functions to the ones present in LocalSolver 10.0. The COVER operator was introduced in LocalSolver 10.5. Without the pragma instruction, the above model would not compile since the user cannot redefine modeling functions.

You can specify any valid version of LocalSolver. You can also specify 0.0. In the latter case, no modeling functions will be imported.

usedeprecated pragma

This pragma restores a number of global LSP functions that were removed in version 12.5 of Hexaly Modeler. The functions in question were present in the very first versions of the language, but were deprecated several years ago in version 6.0 when the modules were added. Their use is now strongly discouraged. It is preferable to use modules and class functions instead.

The list of restored functions is shown in the table below. The equivalent using the modules is given in the second column.

Deprecated function

Replacement

split(str, delimiter)

str.split(delimiter)

toString(value)

“” + value

toInt(str)

str.toInt()

toDouble(str)

str.toDouble()

trim(str)

str.trim()

length(str)

str.length()

substring(str, start [, length])

str.substring(start [, length])

startsWith(str, prefix)

str.startsWith(prefix)

endsWith(str, prefix)

str.endsWith(prefix)

lowerCase(str)

str.toLowerCase()

upperCase(str)

str.toUpperCase()

replace(str, search, replace)

str.replace(search, replace)

openRead(filename)

io.openRead(filename)

openWrite(filename)

io.openWrite(filename)

openAppend(filename)

io.openAppend(filename)

close(file)

file.close()

eof(file)

file.eof()

readInt(file)

file.readInt()

readDouble(file)

file.readDouble()

readString(file)

file.readString()

readln(file)

file.readln()

add(m, value)

m.add(value)

keys(m)

m.keys()

values(m)

m.values()

error(message)

throw message

getSolutionStatus()

lsSolution.status