The Mosel Language
The Mosel language can be thought of as both a modeling language and a programming language. Like other modeling languages it offers the required facilities to declare and manipulate problems, decision variables, constraints and various data types and structures like sets and arrays. On the other hand, it also provides a complete set of functionalities proper to programming languages: it is compiled and optimized, all usual control flow constructs are supported (selection, loops) and can be extended by means of modules. Among these extensions, optimizers can be loaded just like any other type of modules and the functionality they offer may be used in the same way as any Mosel procedures or functions. These properties make of Mosel a powerful modeling, programming and solving language with which it is possible to write complex solution algorithms.
The syntax has been designed to be easy to learn and maintain. As a consequence, the set of reserved words and syntax constructs has deliberately been kept small avoiding shortcuts and `tricks' often provided by modeling languages. These facilities are sometimes useful to reduce the size of a model source (not its readability) but also are likely to introduce inconsistencies and ambiguities in the language itself, making it harder to understand and maintain.
Introduction
Comments
A comment is a part of the source file that is ignored by the compiler. It is usually used to explain what the program is supposed to do. Either single line comments or multi lines comments can be used in a source file. For the first case, the comment starts with the '!' character and terminates with the end of the line. A multi-line commentary must be inclosed in '(!' and '!)'. Note that it is possible to nest several multi-line commentaries.
! In a comment This text will be analyzed (! Start of a multi line (! Another comment blabla end of the second level comment !) end of the first level !) Analysis continues here
Comments may appear anywhere in the source file.
Identifiers
Identifiers are used to name objects (variables, for instance). An identifier is an alphanumeric (plus '_') character string starting with an alphabetic character or '_'. All characters of an identifier are significant and the case is important (the identifier 'word' is not equivalent to 'Word').
Reserved words
The reserved words are identifiers with a particular meaning that determine a specific behaviour within the language. Because of their special role, these keywords cannot be used to name user defined objects (i.e. they cannot be redefined). The list of reserved words is:
and, array, as, boolean, break, case, count, counter, declarations, div, do, dynamic, elif, else, end, evaluation, false, forall, forward, from, function, if, imports, in, include, initialisations, initializations, integer, inter, is_binary, is_continuous,
is_free, is_integer, is_partint, is_semcont, is_semint, is_sos1, is_sos2, linctr, list, max, min, mod, model, mpvar, next, not, of, options, or, package, parameters, procedure, public, prod, range, real, record, repeat, requirements, return, set, string, sum, then, to, true, union, until, uses, version, while, with.
Note that, although the lexical analyzer of Mosel is case-sensitive, the reserved words are defined both as lower and upper case (i.e. AND and and are keywords but not And).
Separation of instructions, line breaking
In order to improve the readability of the source code, each statement may be split across several lines and indented using as many spaces or tabulations as required. However, as the line breaking is the expression terminator, if an expression is to be split, it must be cut after a symbol that implies a continuation like an operator ('+', '-', ...) or a comma (',') in order to warn the analyzer that the expression continues in the following line(s).
A+B ! Expression 1 -C+D ! Expression 2 A+B- ! Expression 3... C+D ! ...end of expression 3
Moreover, the character ';' can be used as an expression terminator.
A+B ; -C+D ! 2 expressions on the same line
Some users prefer to explicitly mark the end of each expression with a particular symbol. This is possible using the option explterm (see Section The compiler directives) which disables the default behaviour of the compiler. In that case, the line breaking is not considered any more as an expression separator and each statement finishing with an expression must be terminated by the symbol ';'.
A+B; ! Expression 1 -C+D; ! Expression 2 A+B ! Expression 3... -C+D; ! ...end of expression 3
Conventions in this document
In the following sections, the language syntax is explained. In all code templates, the following conventions are employed:
- word: 'word' is a keyword and should be typed as is;
- todo: 'todo' is to be replaced by something else that is explained later;
- [ something ]: 'something' is optional and the entire block of instructions may be omitted;
- [ something ...]: 'something' is optional but if used, it can be repeated several times.
Structure of the source file
The Mosel compiler may compile both models and packages source files. Once compiled, a model is ready for execution but a package is intended to be used by a model or another package (see Section The compiler directives).
The general structure of a model source file is as follows:
model model_name [ Directives ] [ Parameters ] [ Body ] end-model |
The model statement marks the beginning the program and the statement end-model its end. Any text following this instruction is ignored (this can be used for adding plain text comments after the end of the program). The model name may be any quoted string or identifier, this name will be used as the model name in the Mosel model manager. An optional set of directives and a parameters block may follow. The actual program/model is described in the body of the source file which consists of a succession of declaration blocks, subroutine definitions and statements.
The structure of a package (see Section Packages) source file is similar to the one of a model:
package package_name [ Directives ] [ Parameters ] [ Body ] end-package |
The package statement marks the beginning the library and the statement end-package its end. The package name must be a valid identifier.
It is important to understand that the language is procedural and not declarative: the declarations and statements are compiled and executed in the order of their appearance. As a consequence, it is not possible to refer to an identifier that is declared later in the source file or consider that a statement located later in the source file has already been executed. Moreover, the language is compiled and not interpreted: the entire source file is first translated — as a whole — into a binary form (the BIM file), then this binary form of the program is read again to be executed. During the compilation, except for some simple constant expressions, no action is actually performed. This is why only some errors can be detected during the compilation time, any others being detected when running the program.
The compiler directives
The compiler accepts four different types of directives: the uses statement, the imports statement, the options statement and the version statement.
Directive uses
The general form of a uses statement is:
uses libname1 [, libname2 ...][;] |
This clause asks the compiler to load the listed modules or packages and import the symbols they define. Modules must still be available for running the model but packages are incorporated into the generated bim file when compiling a model. If the source file being processed is a package, the bim files associated to the listed packages must be available for compiling another file using this package. It is also possible to merge bim files of several packages by using imports instead of uses when building packages.
By default the compiler tries first to find a package (the corresponding file is libname.bim) then, if this fails, it searches for a module (which file name is libname.dso). It is possible to indicate the type of library to look for by appending either ".bim" or ".dso" to the name (then the compiler does not try the alternative in case of failure). A package may also be specified by an extended file name (see Section File names and input/output drivers) including the IO driver in order to disable the automatic search (i.e. "a.bim" searches the file a.bim in the library path but ":a.bim" takes the file a.bim from the current directory).
For example,
uses 'mmsystem','mmxprs.dso','mypkg.bim' uses ':/tmp/otherpkg.bim'
Both packages and modules are searched in a list of possible locations. Upon startup, Mosel uses as the default for this list the value of the environment variable MOSEL_DSO completed by a path deduced from the location (rtdir) of the Mosel runtime library (in the following # can be "32" on a 32bit system, "64" on a 64bit system or an empty string):
- "rtdir\..\dso#"
- Under Windows if rtdir terminates by "\bin#" and "rtdir\..\dso#" exists or
- "rtdir/../dso#"
- On Posix systems if rtdir terminates by "/lib#" and "rtdir/../dso#" exists or
- "rtdir/dso#"
- if this directory exists or
- "rtdir"
- if none of the above rules apply
The variable MOSEL_DSO is expected to be a list of paths conforming to the operating system conventions: for a Posix system the path separator is ':' (e.g. "/opt/Mosel/dso:/tmp") and it is ';' under Win32 (e.g. "E:\Mosel\Dso;C:\Temp"). The search path for modules and packages may also be set from the mosel command (using the -dp option, see Section Running Mosel) as well as inspected and modified from the Mosel Libraries (see functions XPRMgetdsopath and XPRMsetdsopath in the Mosel Libraries Reference Manual). Note however that Mosel will ignore modules not located in read-only locations when the restriction NoExec is active (see Section mosel command: restricted mode).
If the compiler option -bx is used (Section Running Mosel) a package with the specified prefix will be tried before proceeding to the search as decribed above. For instance if the option -bx "bimdir/" is used with the directive uses 'mypkg', the compiler will try to load the package "bimdir/mypkg.bim" before looking for "mypkg.bim" and "mypkg.dso" in the usual locations.
Directive imports
The general form of an imports statement is:
imports pkgname1 [, pkgname2 ...][;] |
This clause is a special version of the uses directive that can only be used in packages: it asks the compiler to load the listed packages, import the symbols they define and incorporate the corresponding bim file. As a consequence, the generated package provides the functionality of the packages it imports.
For example,
imports 'mypkg'
Directive options
The compiler options may be used to modify the default behaviour of the compiler. The general form of an options statement is:
options optname1 [, optname2 ...] |
The supported options are:
- explterm: asks the compiler to expect explicit expression termination (see Section Separation of instructions, line breaking).
- noimplicit: disables the implicit declarations (see Section About implicit declarations). This option can also be activated by using the '-ni' compiler flag (see Section Running Mosel)
- noautofinal: by default initialization blocks finalize sets they populate (section About automatic finalization). This option disables this behaviour that may be activated afterwards using the autofinal control parameter (cf. setparam).
- keepassert: assertions (cf. assert) are compiled only in debug mode. With this option assertions are preserved regardless of the compilation mode.
- xbim: store additional symbol information in the generated bim file (in particular array index names). This option can also be enabled by using the '-I' compiler flag (see Section Running Mosel).
- fctasproc: by default return values of functions must be used such that a function call is not a valid statement. With this option functions can be used as procedures: when a statement consists in a function call its return value is silently ignored (see also asproc).
For example,
options noimplicit,explterm
Directive version
In addition to the model/package name, a file version number may be specified using this directive: a version number consists in 1, 2 or 3 integers between 0 and 999 separated by the character '.'.
version major [. minor [. release ]] |
For example,
version 1.2
The file version is stored in the BIM file and can be displayed from the Mosel console (command list) or retrieved using the Mosel Libraries (see function XPRMgetmodprop in the Mosel Libraries Reference Manual). From the model itself, the version number is recorded as a string in the control parameter model_version (see function getparam).
The parameters block
A model parameter is a symbol, the value of which can be set just before running the model (optional parameter of the 'run' command of the command line interpreter). The general form of the parameters block is:
parameters ident1 = Expression1 [ ident2 = Expression2 ...] end-parameters |
where each identifier identi is the name of a parameter and the corresponding expression Expressioni its default value. This value is assigned to the parameter if no explicit value is provided at the start of the execution of the program (e.g. as a parameter of the 'run' command). Note that the type (integer, real, text string or Boolean) of a parameter is implied by its default value. Model parameters are manipulated as constants in the rest of the source file (it is not possible to alter their original value).
parameters size=12 ! Integer parameter R=12.67 ! Real parameter F="myfile" ! Text string parameter B=true ! Boolean parameter end-parameters
In addition to model parameters, Mosel and some modules provide control parameters : they can be used to give information on the system (e.g. success of an I/O operation) or control its behaviour (e.g. select output format of real numbers). These parameters can be accessed and modified using the routines getparam and setparam. Refer to the documentation of these functions for a complete listing of available Mosel parameters. The documentation of the modules include the description of the parameters they publish.
Source file preprocessing
Source file character encoding
The Mosel compiler expects source files to be encoded in UTF-8 and will handle properly UTF-16 and UTF-32 encodings when the file begins with a BOM (Byte Order Mark). It is also possible to select an alternative encoding using the encoding annotation (see section Annotations).
For instance to notify the compiler that the the source file is encoded using ISO-8859-1, the following comment has to be copied at the beginning of the fie:
!@encoding:iso-8859-1
Source file inclusion
A Mosel program may be split into several source files by means of file inclusion. The 'include' instruction performs this task:
include filename |
where filename is the name of the file to be included. This file name may contain environment variable references using the notation ${varname} (e.g. '${MOSEL}/examples/mymodel') that are expanded to generate the actual name. The 'include' instruction is replaced at compile time by the contents of the file filename.
Assuming the file a.mos contains:
model "Example for file inclusion" writeln('From the main file') include "b.mos" end-model
And the file b.mos:
writeln('From an included file')
Due to the inclusion of b.mos, the file a.mos is equivalent to:
model "Example for file inclusion" writeln('From the main file') writeln('From an included file') end-model
If the compiler option -ix is used (Section Running Mosel) all file names used in the 'include' instruction will be prefixed as requested. For instance, if the option -ix "incdir/" is used with the compiler, the statement include "myfile.mos" will be replaced by the content of "incdir/myfile.mos".
Note that file inclusion cannot be used inside of blocks of instructions or before the body of the program (as a consequence, a file included cannot contain any of the following statements: uses, options or parameters).
Line control directives
In some cases it may be useful to process a Mosel source through an external preprocessor before compilation. For instance this may enable the use of facilities not supported by the Mosel compiler like macros, unrestricted file inclusion or conditional compilation. In order to generate meaningful error messages, the Mosel compiler supports line control directives: these directives are inserted by preprocessors (e.g. cpp or m4) to indicate the original location (file name and line number) of generated text.
#[line] linenum [filename] |
To be properly interpreted, a line control directive must be the only statement of the line. Malformed directives and text following valid directives are silently ignored.
The declaration block
The role of the declaration block is to give a name, a type, and a structure to the entities that the processing part of the program/model will use. The type of a value defines its domain (for instance integer or real) and its structure, how it is organized, stored (for instance a reference to a single value or an ordered collection in the form of an array). The declaration block is composed of a list of declaration statements enclosed between the instructions declarations and end-declarations.
declarations Declare_stat [ Declare_stat ...] end-declarations |
Several declaration blocks may appear in a single source file but a symbol introduced in a given block cannot be used before that block. Once a name has been assigned to an entity, it cannot be reused for anything else.
Elementary types
Elementary objects are used to build up more complex data structures like sets or arrays. It is, of course, possible to declare an entity as a reference to a value of one of these elementary types. Such a declaration looks as follows:
ident1 [, ident2 ...]: type_name |
where type_name is the type of the objects to create. Each of the identifiers identi is then declared as a reference to a value of the given type. The type name may be either a basic type (integer, real, string, boolean), an MP type (mpvar, linctr), an external type or a user defined type (see section User defined types). MP types are related to Mathematical Programming and allow declaration of decision variables and linear constraints. Note that the linear constraint objects can also be used to store linear expressions. External types are defined by modules (the documentation of each module describes how to use the type(s) it implements).
declarations i,j: integer str: string x,y,z: mpvar end-declarations
Basic types
- integer: an integer value between -2147483648 and 2147483647
- real: a real value between -1.7e+308 and 1.7e+308.
- string: some text.
- boolean: the result of a Boolean (logical) expression. The value of a Boolean entity is either the symbol true or the symbol false.
After its declaration, each entity receives an initial value of 0, an empty string, or false depending on its type.
MP types
Two special types are provided for mathematical programming.
Sets
Sets are used to group an unordered collection of elements of a given type. Set elements are unique: if an element is added several times it is only contained once in the set. Declaring a set consists of defining the type of elements to be collected.
The general form of a set declaration is:
ident1 [, ident2 ...] : set of type_name |
where type_name is one of the elementary types. Each of the identifiers identi is then declared as a set of the given type.
A particular set type is also available that should be preferred to the general form wherever possible because of its better efficiency: the range set is an ordered collection of consecutive integers in a given interval. The declaration of a range set is achieved by:
ident1 [, ident2 ...] : range [set of integer] |
Each of the identifiers identi is then declared as a range set of integers. Every newly created set is empty.
declarations s1: set of string r1: range end-declarations
Lists
Lists are used to group a collection of elements of a given type. An element can be stored several times in a list and order of the elements is specified by construction. Declaring a list consists of defining the type of elements to be collected.
The general form of a list declaration is:
ident1 [, ident2 ...] : list of type_name |
where type_name is one of the elementary types. Each of the identifiers identi is then declared as a list of the given type.
Every newly created list is empty.
declarations l1: list of string l2: list of real end-declarations
Arrays
An array is a collection of labelled objects of a given type. A label is defined by a list of indices taking their values in domains characterized by sets: the indexing sets. An array may be either of fixed size or dynamic. For fixed size arrays, the size (i.e. the total number of objects it contains, or cells) is known when it is declared. All the required cells (one for each object) are created and initialized immediately. Dynamic arrays are created empty. The cells are created explicitly (cf. procedure create) or when they are assigned a value (cf. Section Assignment) and the array may then grow `on demand'. It is also possible to delete some or all cells of a dynamic array using the procedure delcell. A cell that has not been created can be identified using the exists function and its value is the default initial value of the type of the array. The general form of an array declaration is:
ident1 [, ident2 ...] : [dynamic] array(list_of_sets) of type_name |
where list_of_sets is a list of set declarations/expressions separated by commas and type_name is one of the elementary types. Each of the identifiers identi is then declared as an array of the given type and indexed by the given sets. In the list of indexing sets, a set declaration can be anonymous (i.e. rs:set of real can be replaced by set of real if no reference to rs is required) or shortened to the type of the set (i.e. set of real can be replaced by real in that context).
declarations e: set of string t1:array ( e, rs:set of real, range, integer ) of real t2:array ( {"i1","i2"}, 1..3 ) of integer end-declarations
An array is of fixed size if all of its indexing sets are of fixed size (i.e. they are either constant or finalized (cf. procedure finalize)). If the qualifier dynamic is used, the array is dynamic and created empty. Otherwise (at least one indexing set is not constant), the array is created with as many cells as possible (i.e., the array is empty if one of the indexing sets is not initialized) and may grow if necessary. Such an array is not the same as a dynamic array even if it is created empty: Mosel may use a dedicated internal representation through which the creation of a single cell (via an assignment for instance) may induce the creation of a row of adjacent cells. Also, if no cell can be created at declaration time, the array is effectively allocated when it is first accessed. As a consequence, if all its indexing sets are finalized at that time, the array is created as a fixed size array. The following example shows the different behaviour of an array that is simply declared with unknown index set (a and c) and an explicit dynamic array (b).
declarations r,u: range a: array(r) of integer ! a is created empty b: dynamic array(r) of integer ! b is created empty c: array(u,r) of integer ! c is created empty end-declarations r:=1..3 finalize(r) ! now the index set is known and constant a(2):=1 ! 'a' becomes a fixed size array b(2):=1 ! b(2) is the only entry of b c(1,2):=1 ! here entries c(1,1) and c(1,3) are also created
Note that once a set is employed as an indexing set, Mosel makes sure that its size is never reduced in order to guarantee that no entry of any array becomes inaccessible. Such a set is called fixed.
Special case of dynamic arrays of a type not supporting assignment
Certain types do not have assignment operators: for instance, writing x:=1 is a syntax error if x is of type mpvar. If an array of such a type is defined as dynamic or the size of at least one of its indexing sets is unknown at declaration time (i.e. empty set), the corresponding cells are not created. In that case, it is required to create each of the relevant entries of the array by using the procedure create since entries cannot be defined by assignment.
Records
A record is a finite collection of objects of any type. Each component of a record is called a field and is characterized by its name (an identifier) and its type. The general form of a record declaration is:
ident1 [, ident2 ...] : record field1 [, field2 ...]: type_name [...] end-record |
where fieldi are the identifiers of the fields of the record and type_name one of the elementary types. Each of the identifiers identi is then declared as a record including the listed fields.
Example:
declarations r1: record i,j:integer r:real end-record end-declarations
Each record declaration is considered unique by the compiler. In the following example, although r1 and r2 have the same definitions, they are not of the same type (but r3 is of course of the type of r2):
declarations r1: record i,j:integer end-record r2,r3: record i,j:integer end-record end-declarations
Constants
A constant is an identifier for which the value is known at declaration time and that will never be modified. The general form of a constant declaration is:
identifier = Expression |
where identifier is the name of the constant and Expression its initial and only value. The expression must be of one of the basic types, a set or a list of one of these types.
Example:
declarations STR='my const string' I1=12 R=1..10 ! constant range S={2.3,5.6,7.01} ! constant set L=[2,4,6] ! constant list end-declarations
The compiler supports two kinds of constants: a compile time constant is a constant which value can be computed by the compiler. A run time constant will be known only when the model is run.
Example:
parameters P=0 end-parameters declarations I=1/3 ! compile time constant J=P*2 ! run time constant end-declarations
User defined types
Naming new types
A new type may be defined by associating an identifier to a type declaration. The general form of a type definition is:
identifier = Type_def |
where Type_def is a type (elementary, set, list, array or record) to be associated to the symbol identifier. After such a definition, the new type may be used wherever a type name is required.
Example:
declarations entier=integer setint=set of entier i:entier ! <=> i:integer s:setint ! <=> s:set of integer end-declarations
Note that only compile time constant or globally defined sets are allowed as indices to array types:
declarations ar1=array(1..10) of integer ! OK ar2=array(range) of integer ! incorrect R:range ar3=array(R) of integer ! OK end-declarations
Combining types
Thanks to user defined types one can create complex data structures by combining structures offered by the language. For instance an array of sets may be defined as follows:
declarations typset=set of integer a1:array(1..10) of typset end-declarations
In order to simplify the description of complex data structures, the Mosel compiler can generate automatically the intermediate user types. Using this property, the example above can be written as follows (both arrays a1 and a2 are of the same type):
declarations a2:array(1..10) of set of integer end-declarations
Expressions
Expressions are, together with the keywords, the major building blocks of a language. This section summarizes the different basic operators and connectors used to build expressions.
Introduction
Expressions are constructed using constants, operators and identifiers (of objects or functions). If an identifier appears in an expression its value is the value referenced by this identifier. In the case of a set, a list, an array or a record, it is the whole structure. To access a single cell of an array, it is required to 'dereference' this array. The dereferencing of an array is denoted as follows:
array_ident (Exp1 [, Exp2 ...]) |
where array_ident is the name of the array and Expi an expression of the type of the ith indexing set of the array. The type of such an expression is the type of the array and its value the value stored in the array with the label 'Exp1 [, Exp2 ...]'. In order to access the cell of an array of arrays, the list of indices for the second array has to be appended to the list of indices of the first array. For instance, the array a:array(1..10) of array(1..10) of integer can be dereferenced with a(1,2).
Similarly, to access the field of a record, it is required to 'dereference' this record. The dereferencing of a record is denoted as follows:
record_ident.field_ident |
where record_ident is the name of the record and field_ident the name of the required field.
Dereferencing arrays of records is achieved by combining the syntax for the two structures. For instance a(1).b
A function call is denoted as follows:
function_ident or function_ident (Exp1 [, Exp2 ...]) |
where function_ident is the name of the function and Expi the ith parameter required by this function. The first form is for a function requiring no parameter.
The special function if allows one to make a selection among expressions. Its syntax is the following:
if (Bool_expr, Exp1, Exp2) |
which evaluates to Exp1 if Bool_expr is true or Exp2 otherwise. The type of this expression is the type of Exp1 and Exp2 which must be of the same type.
The Mosel compiler operates automatic conversions to the type required by a given operator in the following cases:
- in the dereference list of an array:
integer → real; - in a function or procedure parameter list:
integer → real, linctr;
real → linctr;
mpvar → linctr; - anywhere else:
integer → real, string, linctr;
real → string, linctr;
mpvar → linctr;
boolean → string.
It is possible to force a basic type conversion using the type name as a function (i.e. integer, real, string, boolean). In the case of string, the result is the textual representation of the converted expression. In the case of boolean, for numerical values, the result is true if the value is nonzero and for strings the result is true if the string is the word `true'. Note that explicit conversions are not defined for MP types, and structured types .(e.g. linctr(x) is a syntax error).
! Assuming A=3.5, B=2 integer(A+B) ! = 5 string(A-B) ! = "1.5" real(integer(A+B)) ! = 5.5 (because the compiler simplifies the expression)
Parentheses may be used to modify the predefined evaluation order of the operators or simply to group subexpressions.
Aggregate operators
An operator is said to be aggregate when it is associated to a list of indices for each of which a set or list of values is defined. This operator is then applied to its operands for each possible tuple of values (e.g. the summation operator sum is an aggregate operator). The general form of an aggregate operator is:
Aggregate_ident (Iterator1 [, Iterator2 ...]) Expression or count (Iterator1 [, Iterator2 ...]) |
where the Aggregate_ident is the name of the operator and Expression an expression compatible with this operator (see below for the different available operators). The type of the result of such an aggregate expression is the type of Expression. The count operator does not require an additional expression: its value, an integer, corresponds to the number of times the expression of another aggregate operator used with the same iterator list would be evaluated (i.e. it is equivalent to sum(iteratorlist) 1).
An iterator is one of the following constructs:
SetList_expr or ident1 [, ident2 ...] in SetList_expr [| Bool_expr] or ident = Expression [| Bool_expr] or ident as counter |
The first form gives the list of the values to be taken without specifying an index name. With the second form, the indices named identi take successively all values of the set or list defined by SetList_expr. With the third form, the index ident is assigned a single value (which must be a scalar). For the second and third cases, the scope of the created identifier is limited to the scope of the operator (i.e. it exists only for the following iterators and for the operand of the aggregate operator). Moreover, an optional condition can be stated by means of Bool_expr which can be used as a filter to select the relevant elements of the domain of the index. It is important to note that this condition is evaluated as early as possible. As a consequence, a Boolean expression that does not depend on any of the defined indices in the considered iterator list is evaluated only once, namely before the aggregate operator itself and not for each possible tuple of indices. The last form of an iterator declares a counter for the operator: the value of the corresponding symbol is incremented each time the operator's expression is evaluated. For this case, if ident has been declared before, it must be integer or real and its value is not reset. Otherwise, as for indices, the scope of the created integer identifier is limited to the scope of the operator and its initial value is 0. There can be only one counter for a given aggregate operator.
The Mosel compiler performs loop optimization when function exists is used as the first factors of the condition in order to enumerate only those tuples of indices that correspond to actual cells in the array instead of all possible tuples. To be effective, this optimization requires that sets used to declare the array on which the exist condition applies must be named and the same sets must be used to define the index domains. Moreover, the maximum speedup is obtained when order of indices is respected and all indices are defined in the same aggregate operator.
An index is considered to be a constant: it is not possible to change explicitly the value of a named index (using an assignment for instance).
Arithmetic expressions
Numerical constants can be written using the common scientific notation. Arithmetic expressions are naturally expressed by means of the usual operators (+, -, *, / division, unary -, unary +, ^ raise to the power). For integer values, the operators mod (remainder of division) and div (integral division) are also defined. Note that mpvar objects are handled like real values in expression.
The sum (summation) aggregate operators is defined on integers, real and mpvar. The aggregate operators prod (product), min (minimum) and max (maximum) can be used on integer and real values.
x*5.5+(2+z)^4+cos(12.4) sum(i in 1..10) (min(j in s) t(i)*(a(j) mod 2))
String expressions
Constant strings of characters must be quoted with single (') or double quote (") and may extend over several lines. Strings enclosed in double quotes may contain C-like escape sequences introduced by the 'backslash' character (\a \b \f \n \r \t \v \xxx \uhhhh with xxx being the character code as an octal number and hhhh a Unicode code as a four hexadecimal digits number).
Each sequence is replaced by the corresponding control character (e.g. \n is the `new line' command) or, if no control character exists, by the second character of the sequence itself (e.g. \\ is replaced by '\').
The escape sequences are not interpreted if they are contained in strings that are enclosed in single quotes.
Example:
'c:\ddd1\ddd2\ddd3' is understood as c:\ddd1\ddd2\ddd3 "c:\ddd1\ddd2\ddd3" is understood as c:ddd1ddd2ddd3 |
There are two basic operators for strings: the concatenation, written '+' and the difference, written '-'.
"a1b2c3d5"+"e6" ! = "a1b2c3d5e6" 'a1b2c3d5'-"3d5" ! = "a1b2c"
A constant string may also take 2 additional forms: initialised from the content of an external file or as a portion of the current input file. For the first case, a text string enclosed in backquotes will be replaced by the content of the file identified by this enclosed text. For the second case, a line ending by the backquote character optionally followed by some label (consisting in any sequence of characters not including backquote) will be interpreted as the beginning of a text block. The end of this text block is marked by a line starting with the previously used label (if any) followed by the backquote character.
Example:
`afile.txt` ! This string is the content of "afile.txt" `MyMarker line1 line2 MyMarker` ! This string is equivalent to "line1\nline2\n" |
Set expressions
Constant sets are described using one of the following constructs:
{[ Exp1 [, Exp2 ...]]} or Integer_exp1 .. Integer_exp2 |
The first form enumerates all the values contained in the set and the second form, restricted to sets of integers, gives an interval of integer values. This form implicitly defines a range set.
The basic operators on sets are the union written +, the difference written - and the intersection written *.
The aggregate operators union and inter can also be used to build up set expressions.
{1,2,3}+{4,5,6}-(5..8)*{6,10} ! = {1,2,3,4,5} {'a','b','c'}*{'b','c','d'} ! = {'b','c'} union(i in 1..4|i<>2) {i*3} ! = {3,9,12}
If several range sets are combined in the same expression, the result is either a range or a set of integers depending on the continuity of the produced domain. If range sets and sets of integers of more than one element are combined in an expression, the result is a set of integers. It is however possible to convert a set of integers to a range by using the notation range(setexpr) where setexpr is a set expression which result is either a set of integers or a range.
List expressions
A constant list consist in a list of expressions enclosed in square brackets:
[[ Exp1 [, Exp2 ...]]] |
There are two basic operators for lists: the concatenation, written '+' and the difference, written '-'. The aggregate operator sum can also be used to build up list expressions.
[1,2,3]+[1,2,3] ! = [1,2,3,1,2,3] [1,2,3,4]-[3,4] ! = [1,2] sum(i in 1..3) [i*3] ! = [3,6,9]
Boolean expressions
A Boolean expression is an expression whose result is either true or false. The traditional comparators are defined on integer and real values: <, <=, =, <> (not equal), >=, >.
These operators are also defined for string expressions. In that case, the order is defined by the ISO-8859-1 character set (i.e. roughly: punctuation < digits < capitals < lower case letters < accented letters).
With sets, the comparators <= (`is subset of'), >= (`is superset of'), = (`equality of contents') and <> (`difference of contents') are defined. These comparators must be used with two sets of the same type. Moreover, the operator `expr in Set_expr' is true if the expression expr is contained in the set Set_expr. The opposite, the operator not in is also defined.
With lists, the comparators = (`equality of contents') and <> (`difference of contents') are defined. These comparators must be used with two lists of the same type.
With arrays, the comparators = (`equality of contents') and <> (`difference of contents') are defined. These comparators must be used with two arrays of the same type and this type must support the requested operator (for instance arrays of mpvar cannot be compared).
With records, the comparators = (`equality of contents') and <> (`difference of contents') are defined. These comparators must be used with two records of the same type and all fields of this record type must support the requested operator (for instance records including mpvar entries cannot be compared).
To combine Boolean expressions, the operators and (logical and) and or (logical or) as well as the unary operator not (logical negation) can be used. The evaluation of an arithmetic expression stops as soon as its value is known.
The aggregate operators and and or are the natural extension of their binary counterparts.
3<=x and y>=45 or t<>r and not r in {1..10} and(i in 1..10) 3<=x(i)
Linear constraint expressions
Linear constraints are built up using linear expressions on the decision variables (type mpvar).
The different forms of constraints are:
Linear_expr or Linear_expr1 Ctr_cmp Linear_expr2 or Linear_expr SOS_type or mpvar_ref mpvar_type1 or mpvar_ref mpvar_type2 Arith_expr |
In the case of the first form, the constraint is unconstrained and is just a linear expression. For the second form, the valid comparators are <=, >=, =. The third form is used to declare special ordered sets. The types are then is_sos1 and is_sos2. The coefficients of the variables in the linear expression are used as weights for the SOS (as a consequence, a 0-weighted variable cannot be represented this way, procedure makesos1 or makesos2 has to be used instead).
The last two types are used to set up special types for decision variables. The first series does not require any extra information: is_continuous (default), is_integer, is_binary, is_free. Continuous and integer variables have the default lower bound 0, binary variables only take the values 0 or 1, and 'free' means that the variable is unbounded (i.e. ranging from -∞ to +∞). The second series of types is associated with a threshold value stated by an arithmetic expressions: is_partint for partial integer, the value indicates the limit up to which the variable must be integer, above which it is continuous. For is_semcont (semi-continuous) and is_semint (semi-continuous integer) the value gives the semi-continuous limit of the variable (that is, the lower bound on the part of its domain that is continuous or consecutive integers respectively). Note that these constraints on single variables are also considered as common linear constraints.
3*y+sum(i in 1..10) x(i)*i >= z-t x is_free ! Define an unbounded variable x <= -2 ! Upper bound on x t is_integer ! Define an integer variable t=0,1,2,... t >= -7 ! Change lower bound on t: t=-7,-6,-5,... sum(i in 1..10) i*x(i) is_sos1 ! SOS1 {x(1),x(2),...} with ! weights 1,2,... y is_partint 5 ! y=0 or y=5,6,... y <= 20 ! Upper bound on y: y=0 or y=5,6,...,20
Internally all linear constraints are stored in the same form: a linear expression (including a constant term) and a constraint type (the right hand side is always 0). This means, the constraint expression 3*x>=5*y-10 is internally represented by: 3*x-5*y+10 and the type `greater than or equal to'. When a reference to a linear constraint appears in an expression, its value is the linear expression it contains. For example, if the identifier ctl refers to the linear constraint 3*x>=5*y-10, the expression z-x+ctl is equal to: z-2*x-5*y+10.
Note that the value of a unary constraint of the type x is_type threshold is x-threshold.
Automatic arrays
The array keyword can be used as an aggregate operator in order to create an array that will exist only for the duration of the expression.
array (Iterator1 [, Iterator2 ...]) Expression |
here, the iterators define the indices of the array and the expression, the associated values.
This automatic array may be used wherever a reference to an array is expected: for instance to save the solution values of an array of decision variables in an initialization block (see Section Initialization block).
initializations to "mydata.txt" evaluation of array(i in 1..10) x(i).sol as "mylabel" end-initializations
Statements
Four types of statements are supported by the Mosel language. The simple statements can be seen as elementary operations. The initialization block is used to load data from a file or save data to a file. Selection statements allow one to choose between different sets of statements depending on conditions. Finally, the loop statements are used to repeat operations.
Each of these constructs is considered as a single statement. A list of statements is a succession of statements. No particular statement separator is required between statements except if a statement terminates by an expression. In that case, the expression must be finished by either a line break or the symbol ';'.
Simple statements
Assignment
An assignment consists in changing the value associated to an identifier. The general form of an assignment is:
ident_ref := Expression or ident_ref += Expression or ident_ref -= Expression |
where ident_ref is a reference to a value (i.e. an identifier or an array/record dereference) and Expression is an expression of a compatible type with ident_ref. The direct assignment, denoted := replaces the value associated with ident_ref by the value of the expression. The additive assignment, denoted +=, and the subtractive assignment, denoted -=, are basically combinations of a direct assignment with an addition or a subtraction. They require an expression of a type that supports these operators (for instance it is not possible to use additive assignment with Boolean objects).
The additive and subtractive assignments have a special meaning with linear constraints in the sense that they preserve the constraint type of the assigned identifier: normally a constraint used in an expression has the value of the linear expression it contains, the constraint type is ignored.
c:= 3*x+y >= 5 c+= y ! Implies c is 3*x+2*y-5 >= 0 c:= 3*x+y >= 5 c:= c + y ! Implies c is 3*x+2*y-5 (c becomes unconstrained)
Assignment of structured types
The direct assignment := can also be used with sets, lists, arrays and records under certain conditions. For sets and lists, reference and value must be of the same type, the system performing no conversion on structures. For instance it is not possible to assign a set of integers to a set of reals although assigning an integer value to a real object is valid.
When assigning records, reference and value must be of the same type and this type must be assignment compatible: two records having identical definitions are not considered to be the same type by the compiler. In most cases it will be necessary to employ a user type to declare the objects. A record is assignment compatible if all the fields it includes can be assigned a value. For instance a record including a decision variable (type mpvar) cannot be used in an assignment: copying a value of such a type has to be performed one field at a time skiping those fields that cannot be assigned.
Two arrays can be used in an assignment if they have strictly the same definition and are assignment compatible (i.e. their type supports assignment). Note that in a few cases arrays sharing the same definition cannot be assigned because their internal representations differ like in the following example:
declarations a:array(R:range) of integer ! 'a' is dynamic end-declarations R:=1..10 finalise(R) declarations b:array(R) of integer ! 'b' is static end-declarations a:=b ! fails at run time
About implicit declarations
Each symbol should be declared before being used. However, an implicit declaration is issued when a new symbol is assigned a value the type of which is unambiguous.
! Assuming A,S,SE are unknown symbols A:= 1 ! A is automatically defined ! as an integer reference S:={1,2,3} ! S is automatically defined ! as a set of integers SE:={} ! This produces a parser error as ! the type of SE is unknown
In the case of arrays, the implicit declaration should be avoided or used with particular care as Mosel tries to deduce the indexing sets from the context and decides automatically whether the created array must be dynamic. The result is not necessarily what is expected.
A(1):=1 ! Implies: A:array(1..1) of integer A(t):=2.5 ! Assuming "t in 1..10|f(t) > 0" ! implies: A:dynamic array(range) of real
The option noimplicit disables implicit declarations (see Section Directive options).
Inline initialization
Using inline initialization it is possible to assign several cells of an array in a single statement. The general form of an inline initialization is:
ident_ref ::[ Exp1 [, Exp2 ...] ] or ident_ref ::(Ind1 [, Ind2 ...] )[ Exp1 [, Exp2 ...] ] |
where ident_ref is the object to initialize (array, set or list) and Expi are expressions of a compatible type with ident_ref. The first form of this statement may be used with lists, sets and arrays indiced by ranges: the list of expressions is used to initialize the object. In the case of lists and sets this operation is similar to a direct assignment, with an array, the first index of each dimension is the lower bound of the indexing range or 1 if the range is empty.
The second form is used to initialize regions of arrays or arrays indiced by general sets: each Indi expression indicates the index or list of indices for the corresponding dimension. An index list can be a constant, a list of constants (e.g. ['a','b','c']) or a constant range (e.g. 1..10) but all values must be known at compile time.
declarations T:array(1..10) of integer U:array(1..9,{'a','b','c'}) of integer end-declarations T::[2,4,6,8] ! <=> T(1):=2; T(2):=4;... T::(2..5)[7,8,9,19] ! <=> T(2):=7; T(3):=8;... U::([1,3,6],'b')[1,2,3]! <=> U(1,'b'):=1; U(3,'b'):=2;...
Linear constraint expression
A linear constraint expression can be assigned to an identifier but can also be stated on its own. In that case, the constraint is said to be anonymous and is added to the set of already defined constraints. The difference from a named constraint is that it is not possible to refer to an anonymous constraint again, for instance to modify it.
10<=x; x<=20 x is_integer
Procedure call
Not all required actions are coded in a given source file. The language comes with a set of predefined procedures that perform specific actions (like displaying a message). It is also possible to import procedures from external locations by using modules or packages (cf. Section The compiler directives).
The general form of a procedure call is:
procedure_ident procedure_ident (Exp1 [, Exp2 ...]) |
where procedure_ident is the name of the procedure and, if required, Expi is the ith parameter for the call. Refer to Chapter Predefined functions and procedures of this manual for a comprehensive listing of the predefined procedures.The modules documentation should also be consulted for explanations about the procedures provided by each module.
writeln("hello!") ! Displays the message: hello! |
Initialization block
The initialization block may be used to initialize objects (scalars, arrays, lists or sets) of basic type from files or to save the values of such objects to files. Scalars and arrays of external/user types supporting this feature may also be initialized using this facility.
The first form of an initialization block is used to initialize data from a file:
initializations from Filename item1 [ as Label1] or [itemT11, itemT12 [ ,IdentT13 ...]] as LabelT1 [ item2 [ as Label2] or [itemT21, itemT22 [ ,IdentT23 ...]] as LabelT2 ...] end-initializations |
where Filename, a string expression, is the name of the file to read, itemi any object identifier and itemTij an array identifier. Each identifier is automatically associated to a label: by default this label is the identifier itself but a different name may be specified explicitly using a string expression Labeli. If a given item is of a record type, the operation is permitted only if all fields it contains can be initialized. For instance, if one of the fields is a decision variable (type mpvar), the compilation will fail. Alternatively, the fields to be initialized can be listed using the following syntax as an item:
Identifier(field1 [ ,filedi ...]) |
When an initialization block is executed, the given file is opened and the requested labels are searched for in this file to initialize the corresponding objects. Several arrays may be initialized with a single record. In this case they must be all indexed by the same sets and the label is obligatory. After the execution of an initializations from block, the control parameter nbread reports the number of items actually read in. Moreover, if control parameter readcnt is set to true before the execution of the block, counting is also achieved at the label level: the number of items actually read in for each label may be obtained using function getreadcnt.
An initialization file must contain one or several records of the following form:
Label: value |
where Label is a text string and value either a constant of a basic type (integer, real, string or boolean) or a collection of values separated by spaces and enclosed in square brackets. Collections of values are used to initialize lists, sets records or arrays — if such a record is requested for a scalar, then the first value of the collection is selected. When used for arrays, indices enclosed in round brackets may be inserted in the list of values to specify a location in the corresponding array.
Note also that:
- no particular formatting is required: spaces, tabulations, and line breaks are just normal separators
- the special value '*' implies a no-operation (i.e. the corresponding entity is not initialized)
- single line comments are supported (i.e. starting with '!' and terminated by the end of the line)
- Boolean constants are either the identifiers false (FALSE) and true (TRUE) or the numerical constants 0 and 1
- all text strings (including the labels) may be quoted using either single or double quotes. In the latter case, escape sequences are interpreted (i.e. use of '\').
By default Mosel expects that initialization files are encoded in UTF-8 and it can handle UTF-16 and UTF-32 when a BOM (Byte Order Mark) is used. To process files in another encoding, a special encoding comment line must be put at the beginning of the file (see section Source file character encoding). For instance a data file encoded with CP1252 should start with the following comment line:
!@encoding:CP1252
The second form of an initialization block is used to save data to a file:
initializations to Filename item1 [as Label1] or [itemT11, itemT12 [ ,IdentT13 ...]] as LabelT1 [ item2 [ as Label2] or [itemT21, itemT22 [ ,IdentT23 ...]] as LabelT2 ...] end-initializations |
In this form, any itemi can be replaced by the value of an expression using the following construct (Labeli is mandatory in this case):
evaluation of expression |
When this second form is executed, the value of all provided labels is updated with the current value of the corresponding identifier—A copy of the original file is saved prior to the update (i.e. the original version of fname can be found in fname ∼).— in the given file. If a label cannot be found, a new record is appended to the end of the file and the file is created if it does not yet exist.
For example, assuming the file a.dat contains:
! Example of the use of initialization blocks t:[ (1 un) [10 11] (2 deux) [* 22] (3 trois) [30 33]] t2:[ 10 (4) 30 40 ] 'nb used': 0
consider the following program:
model "Example initblk" declarations nb_used:integer s: set of string ta,tb: dynamic array(1..3,s) of real t2: array(1..5) of integer end-declarations initializations from 'a.dat' [ta,tb] as 't' ! ta=[(1,'un',10),(3,'trois',30)] ! tb=[(1,'un',11),(2,'deux',22),(3,'trois',33)] t2 ! t2=[10,0,0,30,40] nb_used as "nb used" ! nb_used=0 end-initializations nb_used+=1 ta(2,"quatre"):=1000 initializations to 'a.dat' [ta,tb] as 't' nb_used as "nb used" s end-initializations end-model
After the execution of this model, the data file contains:
! Example of the use of initialization blocks t:[(1 'un') [10 11] (2 'deux') [* 22] (2 'quatre') [1000 *] (3 'trois') [30 33]] t2:[ 10 (4) 30 40 ] 'nb used': 1 's': ['un' 'deux' 'trois' 'quatre']
In case of error (e.g. file not found, corrupted data format) during the processing of an initialization block, the execution of the model is interrupted. However if the value of control parameter ioctrl is true, executions continues. It is up to the user to verify whether data has been properly transfered by checking the value of control parameter iostatus.
About automatic finalization
During the execution of an initializations from block all sets are automatically finalized just after having been initialized. This also applies to sets indirectly initialized through the non-dynamic arrays for which they are index sets. In addition, such an array is created as a static array if it has not been used before the initialization block.
This behaviour is controled by the autofinal control parameter which value may be changed using the setparam procedure (i.e. it is therefore possible to have automatic finalization active for only some initializations blocks). The compiler option noautofinal (see section Directive options) allows to disable this feature from the beginning of the model (although it can be re-enabled as required using the control parameter).
Selections
If statement
The general form of the if statement is:
if Bool_exp_1 then Statement_list_1 [ elif Bool_exp_2 then Statement_list_2 ...] [ else Statement_list_E ] end-if |
The selection is executed as follows: if Bool_exp_1 is true then Statement_list_1 is executed and the process continues after the end-if instruction. Otherwise, if there are elif statements, they are executed in the same manner as the if instruction itself. If, all boolean expressions evaluated are false and there is an else instruction, then Statement_list_E are executed; otherwise no statement is executed and the process continues after the end-if keyword.
if c=1 then writeln('c=1') elif c=2 then writeln('c=2') else writeln('c<>1 and c<>2') end-if
Case statement
The general form of the case statement is:
case Expression_0 of Expression_1: Statement_1 or Expression_1: do Statement_list_1 end-do [ Expression_2: Statement_2 or Expression_2: do Statement_list_2 end-do ...] [ else Statement_list_E ] end-case |
The selection is executed as follows: Expression_0 is evaluated and compared sequentially with each expression of the list Expression_i until a match is found. Then the statement Statement_i (resp. list of statements Statement_list_i) corresponding to the matching expression is executed and the execution continues after the end-case instruction. If no matching is found and an else statement is present, the list of statements Statement_list_E is executed, otherwise the execution continues after the end-case instruction. Note that, each of the expression lists Expression_i can be either a scalar, a set or a list of expressions separated by commas. In the last two cases, the matching succeeds if the expression Expression_0 corresponds to an element of the set or an entry of the list.
case c of 1 : writeln('c=1') 2..5 : writeln('c in 2..5') 6,8,10: writeln('c in {6,8,10}') else writeln('c in {7,9} or c >10 or c <1') end-case
Loops
Forall loop
The general form of the forall statement is:
forall (Iterator_list) Statement or forall (Iterator_list) do Statement_list end-do |
The statement Statement (resp. list of statements Statement_list) is repeated for each possible index tuple generated by the iterator list (cf. Section Aggregate operators).
forall (i in 1..10, j in 1..10 | i<>j) do write(' (' , i, ',' , j, ')') if isodd(i*j) then s+={i*j} end-if end-do
While loop
The general form of the while statement is:
while (Bool_expr) Statement or while (Bool_expr) do Statement_list end-do |
The statement Statement (resp. list of statements Statement_list) is repeated as long as the condition Bool_expr is true. If the condition is false at the first evaluation, the while statement is entirely skipped.
i:=1 while(i<=10) do write(' ',i) if isodd(i) then s+={i} end-if i+=1 end-do
Repeat loop
The general form of the repeat statement is:
repeat Statement1 [ Statement2 ...] until Bool_expr |
The list of statements enclosed in the instructions repeat and until is repeated until the condition Bool_expr is true. As opposed to the while loop, the statement(s) is (are) executed at least once.
i:=1 repeat write(' ',i) if isodd(i) then s+={i} end-if i+=1 until i>10
break and next statements
The statements break and next are respectively used to interrupt and jump to the next iteration of a loop. The general form of the break and next statements is:
break [n|label] or next [n|label] |
where n is an optional integer constant: n-1 nested loops are stopped before applying the operation. This optional argument may also be a label (in the form an identifier or a string constant): in this case the loop to consider is identified by a label that must be defined just before the corresponding loop using the following syntax:
label : |
The label can be either an identifier (that is not associated to any entity) or a constant string. The scope of each label is limited to the loop it identifies.
! in this example only the loop controls are shown L1: ! 1: Define label "L1" repeat ! 2: Loop L1 forall (i in S) do ! 3: Loop L2 while (C3) do ! 4: Loop L3 break 3 ! 5: Stop the 3 loops and continue after line 12 next ! 6: Go to next iteration of L3 (line 4) next 2 ! 7: Stop L3 and go to next 'i' (line 3) end-do ! 8: End of L3 next "L1" ! 9: Stop L2, go to next iteration of L1 (line 12) break !10: Stop L2 and continue after line 11 end-do !11: End of L2 until C1 !12: End of L1
with statement
The general syntax of this statement is:
with ident_1=exp_1 [, ident_2=exp_2...] do Statement [ Statement ...] end-do |
Although the with statement is not a loop it is handled like a single iteration forall loop such that it is possible to use the break statement within the block of instructions. The identifiers ident_i are defined as local symbols to the block.
! in this example LR is an array of records with r=LR(10) do r.x:=10 ! update LR(10).x r.y:=20 ! update LR(10).y end-do
Procedures and functions
It is possible to group sets of statements and declarations in the form of subroutines that, once defined, can be called several times during the execution of the model. There are two kinds of subroutines in Mosel, procedures and functions. Procedures are used in the place of statements (e.g. writeln("Hi!")) and functions as part of expressions (because a value is returned, e.g. round(12.3)). Procedures and functions may both receive arguments, define local data and call themselves recursively.
Definition
Defining a subroutine consists of describing its external properties (i.e. its name and arguments) and the actions to be performed when it is executed (i.e. the statements to perform). The general form of a procedure definition is:
procedure name_proc [(list_of_parms)] Proc_body end-procedure |
where name_proc is the name of the procedure and list_of_parms its list of formal parameters (if any). This list is composed of symbol declarations (cf. Section The declaration block) separated by commas. The only difference from usual declarations is that no constants or expressions are allowed, including in the indexing list of an array (for instance A=12 or t1:array(1..4) of real are not valid parameter declarations). The body of the procedure is the usual list of statements and declaration blocks except that no procedure or function definition can be included.
procedure myproc writeln("In myproc") end-procedure procedure withparams(a:array(r:range) of real, i,j:integer) writeln("I received: i=",i," j=",j) forall(n in r) writeln("a(",n,")=",a(n)) end-procedure declarations mytab:array(1..10) of real end-declarations myproc ! Call myproc withparams(mytab,23,67) ! Call withparams
The definition of a function is very similar to the one of a procedure:
function name_func [(List_of_params)]: Type Func_body end-function |
The only difference with a procedure is that the function type must be specified: it can be any type name except mpvar. Inside the body of a function, a special variable of the type of the function is automatically defined: returned. This variable is used as the return value of the function, it must therefore be assigned a value during the execution of the function.
function multiply_by_3(i:integer):integer returned:=i*3 end-function writeln("3*12=", multiply_by_3(12)) ! Call the function
Normally all statements of a subroutine are executed in sequence. It is however possible to interrupt the execution and return to the caller by using the special statement return.
Formal parameters: passing convention
Formal Parameters of basic types are passed by value and all other types are passed by reference. In practice, when a parameter is passed by value, the subroutine receives a copy of the information so, if the subroutine modifies this parameter, the effective parameter remains unchanged. But if a parameter is passed by reference, the subroutine receives the parameter itself. As a consequence, if the parameter is modified during the process of the subroutine, the effective parameter is also affected.
procedure alter(s:set of integer,i:integer) i+=1 s+={i} end-procedure gs:={1} gi:=5 alter(gs,gi) writeln(gs," ",gi) ! Displays: {1,6} 5
Local declarations
Several declaration blocks may be used in a subroutine and all identifiers declared are local to this subroutine. This means that all of these symbols exist only in the scope of the subroutine (i.e. between the declaration and the end-procedure or end-function statement) and all of the resource they use is released once the subroutine terminates its execution unless they are referenced outside of the routine (e.g. member of a set defined globally). As a consequence, active constraints (linctr that are not just linear expressions) declared inside a subroutine and the variables they employ are still effective after the termination of the subroutine (because they are part of the current problem) even if the symbols used to name the related objects are not defined any more. Note also that a local declaration may hide a global symbol.
declarations ! Global definition i,j:integer end-declarations procedure myproc declarations i:string ! This declaration hides the global symbol end-declarations i:="a string" ! Local 'i' j:=4 writeln("Inside of myproc, i=",i," j=",j) end-procedure i:=45 ! Global 'i' j:=10 myproc writeln("Outside of myproc, i=",i," j=",j)
This code extract displays:
Inside of myproc, i=a string j=4 Outside of myproc, i=45 j=4
Overloading
Mosel supports overloading of procedures and functions. One can define the same function several times with different sets of parameters and the compiler decides which subroutine to use depending on the parameter list. This also applies to predefined procedures and functions.
! Returns a random number between 1 and a given upper limit function random(limit:integer):integer returned:=round(.5+random*limit) ! Use the predefined ! 'random' function end-function
It is important to note that:
- a procedure cannot overload a function and vice versa;
- it is not possible to redefine any identifier; this rule also applies to procedures and functions. A subroutine definition can be used to overload another subroutine only if it differs for at least one parameter. This means, a difference in the type of the return value of a function is not sufficient.
Forward declaration
During the compilation phase of a source file, only symbols that have been previously declared can be used at any given point. If two procedures call themselves recursively (cross recursion), it is therefore necessary to be able to declare one of the two procedures in advance. Moreover, for the sake of clarity it is sometimes useful to group all procedure and function definitions at the end of the source file. A forward declaration is provided for these uses: it consists of stating only the header of a subroutine that will be defined later. The general form of a forward declaration is:
forward procedure Proc_name [(List_of_params)] or forward function Func_name [(List_of_params)]: Basic_type |
where the procedure or function Func_name will be defined later in the source file. Note that a forward definition for which no actual definition can be found is considered as an error by Mosel.
forward function f2(x:integer):integer function f1(x:integer):integer returned:=x+if(x>0,f2(x-1),0) ! f1 needs to know f2 end-function function f2(x:integer):integer returned:=x+if(x>0,f1(x-1),0) ! f2 needs to know f1 end-function
Suffix notation
Functions which name begins with get and taking a single argument may be called using a suffix notation. This alternative syntax is constructed by appending to the variable name (the intended function parameter) a dot followed by the function name without its prefix get. For instance the call getsol(x) is the same as x.sol. The compiler performing internally the translation from the suffix notation to the usual function call notation, the two syntaxes are equivalent.
Similarly, calls to procedures which name begins with set and taking two arguments may be written as an assignment combined with a suffix notation. In this case the statement can be replaced by the variable name (the intended first procedure parameter) followed by a dot and the procedure name without its prefix set then the assignment sign := and the value corresponding to the second parameter. For instance the statement sethidden(ctl,true) can also be written ctl.hidden:=true. As for the other alternative notation, the compiler performs the rewriting internally and the two syntaxes are equivalent.
Problems
In Mosel terms, a problem is a container holding various attributes and entities. The nature of the information stored is characterised by a problem type. The core system of Mosel provides the mpproblem problem type for the representation of mathematical programming problems with linear constraints. Other types may be published by modules either as entirely new problem types or as problem type extensions. An extension adds extra functionality or properties to an existing type; for instance, mpproblem.xprs provided by the module mmxprs adds support for solving mpproblem problems while the type mpproblem.nl of mmnl makes it possible to include non-linear constraints in an mpproblem.
When the execution of the model starts, an instance of each of the available problem types is created: this main problem constitutes the default problem context. As a consequence, all problem related operations (e.g., add constraints, solve...) refer to this context. Further problem instances may be declared just like any other symbol using a declarations section. The specification of a problem type (that is used as an elementary type in a declaration) has two forms:
problem_type or problem_type1 and problem_type2 [and problem_typen ...] |
where problem_type* are problem type names. The second syntax allows to define a problem instance that refers to several problem types: this can be useful if a particular problem consists in the combination of several problem types. Note also that the main problem can be seen as an instance of the combination of all available problem types.
The with construct can be used to switch to a different problem context for the duration of a block of instructions. The general form of this construct is:
with prob do Statement [ Statement ...] end-do |
where prob is a problem reference or a problem type specification. In the first case the referenced problem is selected, in the second case, a new problem instance is created for the duration of the block (i.e., it is released after the block has been processed). Both statements and declaration blocks as well as other with constructs may be included in this section: they are all executed in the context of the selected problem.
declarations p1,p2:mpproblem p3:mpproblem and mypb ! assuming 'mypb' is a problem type PT=mpproblem and mypb ! user defined problem type a:array(1..10) of PT x,y:mpvar end-declarations with p1 do x+y>=0 end-do with p2 do x-y=1 end-do
Some problem types support assignment (operator :=) and additive assignment (operator +=). These operators can be used between objects of same type but also when the right parameter of the operator is a component of the assigned object. For instance, assuming the declarations of the previous example we could state p3:=p2 meaning that the mpproblem part of p3 must be replaced by a copy of p2, the mypb part of p3 remaining unchanged. From the same context, the assignment p2:=p3 produces a compilation error.
The mpproblem type
An mpproblem instance basically consists in a set of linear constraints (the decision variables defined anywhere in a model are shared by all problems). A constraint is incorporated into a problem when it is expressed, so having the declaration of a linctr identifier in the context of a problem is not sufficient to attach it to this problem. The association will occur when the symbol is assigned its first value. Afterwards, the constraint will remain part of the same problem even if it is altered from within the context of another problem (a constraint cannot belong to several problems at the same time).
with p1 do C1:=x+y+z>=0 x is_integer end-do with p2 do 2*x-3*z=0 ! here we state constraints of p2 ... minimize(z) C1+= x.sol*z.sol end-do
In the example above, the constraint C1 is part of problem p1. From the context of a second problem p2 the constraint C1 is modified using solution information of p2: this change affects only the first problem since the constraint does not belong to the current context. Note that since is_integer is a (unary) constraint, the decision variable x is integer for problem p1 but it is a continuous variable in p2.
When a problem is released or reset (see reset), all its constraints are detached. Constraints which are not referenced (anonymous constraints) are released at the same time, named constraints however are not freed, they become available to be associated to some other problem.
with mpproblem do C1:=x+y+z>=0 ! (1) x-2*y=10 ! (2) x is_integer ! (3) end-do with p1 do C1 end-do
In this example, at the end of the first with block, the local problem is released. As a consequence the constraint C1 is detached from this problem (but remains unchanged) and the 2 other constraints are freed. The following statements add C1 to the problem p1.
The type mpproblem supports both assignment (operator :=) and additive assignment (operator +=).
The public qualifier
Once a source file has been compiled, the identifiers used to designate the objects of the model become useless for Mosel. In order to access information after a model has been executed (for instance using the print command of the command line interpreter), a table of symbols is saved in the BIM file. If the source is compiled with the strip option (-s), all private symbols are removed from the symbol table — by default all symbols (except parameters) are considered to be private.
The qualifier public can be used in declaration and definition of objects to mark those identifiers (including subroutines) that must be published in the table of symbols even when the strip option is in use.
public declarations e:integer ! e is published f:integer ! f is published end-declarations declarations public a,b,c:integer ! a,b and c are published d:real ! d is private end-declarations forward public procedure myproc(i:integer) ! 'myproc' is published
This qualifier can also be used when declaring record types in order to select the fields of the record that can be accessed from outside of the file making the definitions: this allows to make available only a few fields of a record, hidding what is considered to be internal data.
declarations public t1=record i:integer ! t1.i is private public j:real ! t1.j is public end-record public t2=public record i:integer ! t2.i is public j:real ! t2.j is public end-record end-declarations
Packages
Declarations may be stored in a package: once compiled, the package can be used by any model by means of the uses statement. Except for its beginning and termination (keyword model is replaced by package) a package source is similar to a normal model source. The following points should be noticed:
- all statements and declarations outside procedure or function definitions are used as an initialization routine: they are automatically executed before statements of the model using the package;
- symbols that should be published by the package must be made explicitly public using the public qualifier (see Section The public qualifier);
- parameters of a package are automatically added to the list of parameters of the model using the package;
- as opposed to modules that are dynamically linked, bim files of packages are used only at compilation time — they are not required for execution;
- a package cannot be imported several times by a model and packages publish symbols of packages they use. For instance, assuming package P1 imports package P2, a model using P1 cannot import explicitly P2 (with a uses statement) but has access to the functionality of P2 via P1.
The requirements block
Requirements are symbols a package requires for its processing but does not define. These required symbols are declared in requirement blocks which are a special kind of declaration blocks in which constants are not allowed but procedure/functions can be declared. The symbols of such a block have to be defined when the model using the package is compiled: the definitions may appear either in the model or in another package but cannot come from a module. Several packages used by a given model may have the same requirements (i.e. same identifier and same declaration). It is also worth noting that a package inherits the requirements of the packages it uses.
requirements an_int:integer s0: set of string bigar: array(S0) of real procedure doit(i:integer) end-requirements
Annotations
Annotations are meta data expressed in the Mosel source file that are stored in the resulting bim file after compilation. Thanks to a dedicated API it is possible to retrieve the information both from the model itself during its execution (see getannotations) or before/after execution from a host application (see function XPRMgetannotations in the Mosel Libraries Reference Manual).
Syntax
Annotations are organised in categories. A category groups a set of annotations and other categories (or sub-categories). When expressing a full annotation name, categories are separated by the '.' symbol. For instance:
doc.name
will be used to select the annotation name that is a member of the doc category. Similarly:
mycat1.cat2.info
will reference the annotation info recorded in the category cat2 that is itself part of category mycat1. Annotations and annotation categories must be valid Mosel identifiers: their names can only use alpha-numeric symbols plus '_'.
Some predefined categories are available at the beginning of the compilation:
- the default category (its name is empty) collects annotations that are not explicitly member of any particular category. For instance the annotation myannot will be recorded in the default category. This annotation may also be referenced by its full name .myannot
- mc (for Mosel Compiler) is used to pass information to the compiler during the compilation. For example, the mc.def annotation makes it possible to declare an annotation type (see section Declaration)
- doc can be used to document a model or package file (see section Documenting models using annotations)
In the Mosel source file annotations are included in special comments. A single-line annotation is of the form:
!@ name value
Here name is the name of the annotation (spaces between '@' and the name are ignored) and the following text (up to the end of line) its corresponding value. The separation character between the name and the value can be a space, ':' or '=' (there must be no space between the name and the symbol). There is no restriction on the content of the value: it can be any kind of text (unless the annotation is typed—see section Declaration).
A multi-line annotation is of the form:
(!@name value ... @name2 value2 ... !)
where name is an annotation name while the text following this name is its associated value. With this syntax the value may spread over several lines, its termination is marked either by the end of the comment block or by a new annotation specification. In this context, a new annotation must start with the '@' symbol at the beginning of a new line (leading spaces are ignored). As for a one-line annotation, symbols ':' and '=' can be used instead of a space to separate the name and its value.
If several annotations of the same category have to be defined in the same block, a current category may be defined such that following annotation names can be shortened. This mechanism is activated by specifying the category name terminated by a dot (the remainder of this line is ignored) before the first annotation statement. The category selection is effective for the current comment block only and remains active until the next selection. Using a dot in place of a category name restores the default behaviour (i.e. the full path must be used for annotation reference). For instance:
(!@doc. Switch to 'doc' category (this text is ignored) @name:my_function @type:integer @mycat.cat1. Switch to 'mycat.cat1' @memb1 10 @memb2 20 @. Unselect current category @glb=useless !)
Is equivalent to:
(!@doc.name:my_function @doc.type:integer @mycat.cat1.memb1 10 @mycat.cat1.memb2 20 @glb=useless !)
By default any new annotation name is added to the internal dictionary and no checking is applied to the provided value. If a given annotation is defined several times only the last assignment is preserved. The compiler will however emit a warning if an attempt is made to assign a value to a category or to use an annotation as a category. For instance:
!@mycat.memb1 10 !@mycat.memb1.memb2 20
The second definition will fail to use mycat.memb1 as a category because the first one has already implicitly declared it as an annotation.
Symbol association
An annotation is either global or associated with a specific public symbol (see section The public qualifier). The association depends on the location of the definition in the source code:
- annotations preceding a subroutine declaration (forward statement) or definition are associated with the subroutine name
- annotations preceding a declarations block are distributed to all the symbols declared in the block
- inside of a declarations block: annotations preceding or terminating the line of a declaration are associated with the corresponding symbols
In all other cases the annotations are global (i.e. not associated with any particular symbol) — in particular trying to associate annotations to private symbols will result in global annotations.
Annotations that precede a subroutine declaration, a declarations block or an entity in a declarations block can be turned into global annotations by inserting the compiler annotation mc.flush between the annotation and the following code.
Declaration
Declaration of annotations is achieved via the mc.def compiler annotation. Once an annotation is declared, the compiler checks the validity of definitions and rejects those that are not compliant, issuing a warning message (invalid annotations will not make the compilation fail unless the flag strict is used).
The general syntax of the annotation declaration statement is:
!@mc.def aname [prop1[,prop2...]]
Where aname is an annotation name and prop? a property keyword. The possible keywords are:
- alias name1 name2...
- aname Defining an alias to name1, name2...
- text|integer|real|boolean
- Type of the annotation value (default: text).
- last|first|merge|multi
-
Handling of multiple definitions of an annotation (default:
last)
- last: the last definition is kept
- first: keep the first definition (the following ones are ignored)
- merge: definitions are concatenated (separated by new lines)
- multi: all definitions are kept
- global|specific
- By default, the association of annotations depends on the location of the definition. If global is stated, the annotation is always global; with option specific, the annotation will be kept only if it can be associated with a symbol (otherwise it is ignored instead of being stored as a global one).
- values=v1 v2 v3...
- If used, this option must be the last one of the definition and it cannot be combined with range. It defines a list of possible values for the annotation.
- range=lb ub
- If used, this option must be the last one of the definition, it requires the type to be specified ( integer or real) and it cannot be combined with values. It defines a range of possible values.
- strict
- When this option has been stated any error detected on this annotation (or path when applied to a category) will make the compilation fail
Example:
!@mc.def person.name text,first,specific !@mc.def person.age integer,first,specific,range=0 150 !@mc.def person.gender values=male female
Categories are implicitly declared by the annotations they include (for instance declaring @mycat.myann implies the creation of mycat as a category). It is also possible to explicitly declare an empty category (i.e. containing no annotation) using the mc.def construct by appending a dot to the category name (the only supported property is strict). For instance:
!@mc.def mycat.
For a given annotation the declaration may be stated several times but the properties of an annotation cannot be changed. For instance, the following declarations can be used in the same source:
!@mc.def myann !@mc.def myann text,last
But the following declaration cannot be combined with any of the two preceding ones as they both result in the annotation type text:
!@mc.def myann integer
Declarations included in models are not exported to the bim file (i.e. they are only used during the compilation procedure) but declarations stated in packages are published if they are relative to a user defined category: any model using the package inherits the annotation declarations of the package.
Additional properties can be set using the mc.set compiler annotation. The general syntax of this special statement is:
!@mc.set name flag
Where name is an annotation or category name and flag one of the following keywords:
- complete
- Applied to a category this flag indicates that no other annotation can be added to this category (ignored for an annotation). It is however still possible to declare aliases. Note that sub-categories are not concerned by this flag: if required each sub-category has also to be tagged.
- disable
- Disable the named category or annotation. From the point where this flag has been set onwards, all definitions deriving from the provided name are silently ignored.
- enable
- Revert the effect of disable.
- unpublish
- Disable the automatic publication of the specified declaration.
- publish
- Publish the specified declaration.
Note that mc.set expects a full explicit name: for this command ann refers to category ann and not to annotation .ann as in other places.
File names and input/output drivers
Mosel handles data streams using IO drivers: a driver is an interface between Mosel and a physical data source. Its role is to expose the data source in a standard way such that from the user perspective, all data sources can be accessed using the same methods (i.e. initializations blocks, file handling functions). Drivers are specified in file names: all Mosel functions supporting IO operations though drivers can be given an extended file name. This type of name is composed of the pair driver_name:file_name. When Mosel needs to access a file, it looks for the specified driver in the table of available drivers. This table contains all predefined drivers as well as drivers published by modules currently loaded in memory. If the driver is provided by a module, the module name may also be indicated in the extended file name: module_name.driver_name:file_name. Using this notation, Mosel loads the required module if necessary (otherwise the file operation fails if the module is not already loaded). For instance it is better to use mmodbc.odbc:database than odbc:database.
The file_name part of the extended file name is specific to the driver and its structure and meaning depends on the driver. For instance, the sysfd driver expects a numerical file descriptor so file sysfd:1 is a valid name but sysfd:myfile cannot work. A driver may act as a filter and expects as file_name another extended file name (e.g. zlib.deflate:mem:myblk).
When no driver name is specified, Mosel uses the default driver which name is an empty string (myfile is equivalent to :myfile). This driver relies on OS functions to access files from the file system.
The tmp driver is an extension to the default driver: it locates the specified file in the temporary directory used by Mosel (i.e. tmp:toto is equivalent to getparam("tmpdir")/toto).
The null driver can be used to disable a stream: whatever written to file "null:" is ignored and reading from it is like reading from an empty file.
The mem driver uses a memory block instead of a file handled by the operating system. A file name for this driver is of the form mem:label[/minsize[/incstep]] where label is an identifier whose first character is a letter and minsize an optional initial amount of memory to be reserved (size is expressed in bytes, in kilobytes with suffix "k" or in megabytes with suffix "m"). The label being recorded in the dictionary of the model symbols it cannot be identical to any of the identifiers of the model (the function newmuid might be used to generate a unique identifier). The memory block is allocated dynamically and resized as necessary. By default the size of the memory block is increased by pages of 4 kilobytes: the optional parameter incstep may be used to change this page size (i.e. the default setting is "label/0/4k"). The special value 0 modifies the allocation policy: instead of being increased of a fixed amount, the block size is doubled. In all cases unused memory is released when the file is closed.
The mem driver may also be used to exchange data with an application using the Mosel libraries (refer to the Mosel Libraries Reference Manual for further explanation).
The tee driver can only be open for writing and expects as file name a list of up to 6 extended file names separated with `&': it opens all the specified files and duplicates what it receives to each of them. If only one file is given or if the string terminates with `&', output is also sent to the default output stream (or error stream if the file is used for errors). For instance, writing to the file "tee:log1&log2&" has the effect of writing at the same time to files "log1" and "log2" as well as sending a copy to the console.
The bin driver can only be used for initializations blocks as a replacement of the default driver: it allows to write (and read) data files in a platform independent binary format. This file format is generally smaller than its ASCII equivalent and preserves accuracy of floating point numbers. This driver can be used in 2 different ways: a single file including all records of the initialisations block is produced if a file name is provided. For instance, in the following example the file "mydata" will contain both A and B:
initialisations to "bin:mydata" A B end-initialisations
With the second form (without file name) one file is generated for each record of the block. The following example produces 2 files: "mydata_A" to contain the values of record A and "mydata_B" for values of B:
initialisations to "bin:" A as "mydata_A" B as "mydata_B" end-initialisations
When using this form in an initialisations to block, the option append may be specified such that files are open in append mode.
The other predefined drivers (sysfd, cb and raw) are useful when interfacing Mosel with a host application. They are described in detail in the Mosel Libraries Reference Manual.
Character encoding of text files
Mosel uses UTF-8 for its internal representation of text strings and this is also the default character encoding for text files. It is however possible to read and write text files in different encodings: for model source and initialization block files the selection can be achieved by means of a special comment (see sections Source file character encoding and Initialization block) but the encoding may also be specified at the time of opening a file by prefixing its name with the "enc:" prefix:
enc:encoding [+unix|+dos|+sys] [+bom|+nobom],filename |
Mosel supports natively the encodings UTF-8, UTF-16, UTF-32, ISO-8859-1, ISO-8859-15, CP1252 and US-ASCII. For UTF-16 and UTF-32 the byte ordering depends on the architecture of the running system (e.g. this is Little Endian on an x86 processor) but it can also be specified by appending LE (Little Endian) or BE (Big Endian) to the encoding name (e.g. UTF-16LE). The availability and names of other encodings depends on the operating system.
The following aliases may also be used in place of an encoding name: RAW (no encoding), SYS (default system encoding), WCHAR (wide character for the C library), FNAME (encoding used for file names), TTY (encoding of the output stream of the console), TTYIN (encoding of the input stream of the console), STDIN, STDOUT, STDERR (encoding of the default input/output/error stream).
In addition to the encoding name a couple of options might be applied: +unix and +dos select the line termination (note that +dos is automatically used when writing to a physical file on Windows). Options +bom and +nobom decides whether a Byte Order Mark is to be inserted at the beginning of the file (this option only applies to UTF encodings when the file is not open in appending mode). By default a BOM is inserted when the encoding is UTF-16 or UTF-32, the option +nobom disables this insertion. The option +bom implies the insertion of a BOM on UTF-8 encoded files (this is usually not required for this encoding but often used on Windows systems). The option +sys selects the line termination and BOM convention of the running system (i.e. it is equivalent to +unix on a Posix system and +dos+bom on a Windows machine).
Working directory and temporary directory
Except for absolute path names, file or path name expansion are relative to the current working directory. By default this reference location corresponds to the operating system current working directory which usually is the directory from which Mosel has been started. Since the working directory is an execution parameter, a model may be running with a current working directory which might be different from the one used by the operating system. It is therefore recommended to use absolute file names when a Mosel model communicates with an external component (for instance when a file name is part of the DSN to be used for an ODBC connection).
In addition to the current working directory, Mosel creates a temporary directory that is shared by all models for storing temporary data handled as physical files. This directory is located in the system temporary directory as specified by one of the environment variables TMP, TEMP or USERPROFILE under Windows and TMPDIR on Posix systems. If none of these environment variables is defined, the default base directory will be "C:\" on Windows and "/tmp" on Posix systems. The Mosel temporary directory is automatically created when needed and deleted at program termination.
The path names of the working directory and the temporary directory are identified respectively by the "workdir" and "tmpdir" control parameters and can be retrieved using the getparam function. It is possible to change the current working directory of a running model by updating the "workdir" parameter using setparam.
Handling of input/output
At the start of the execution of a program/model, three text streams are created automatically: the standard input, output and error streams. The standard output stream is used by the procedures writing text (write, writeln, fflush). The standard input stream is used by the procedures reading text (read, readln, fskipline). The standard error stream is the destination of error messages reported by Mosel during its execution. These streams are inherited from the environment in which Mosel is being run: usually using an output procedure implies printing something to the console and using an input procedure implies expecting something to be typed by the user.
The procedures fopen and fclose make it possible to associate text files to the input, output and error streams: in this case the IO functions can be used to read from or write to files. Note that when a file is opened, it is automatically made the active input, output or error stream (according to its opening status) but the file that was previously assigned to the corresponding stream remains open. It is however possible to switch between different open files using the procedure fselect in combination with the function getfid.
model "test IO" def_out:=getfid(F_OUTPUT) ! Save file ID of default output fopen("mylog.txt",F_OUTPUT) ! Switch output to 'mylog.txt' my_out:=getfid(F_OUTPUT) ! Save ID of current output stream repeat fselect(def_out) ! Select default ouput... write("Text? ") ! ...to print a message text:='' readln(text) ! Read a string from the default input fselect(my_out) ! Select the file 'mylog.txt' writeln(text) ! Write the string into the file until text='' fclose(F_OUTPUT) ! Close current output (='mylog.txt') writeln("Finished!") ! Display message to default output end-model
Deploying models
Once a model has been compiled to a BIM file it may be deployed in a variety of ways. It may be
- run from some remote code using the remote invocation library XPRD (see the XPRD reference manual),
- be integrated in an application through the Mosel libraries (see Mosel libraries reference manual),
- form part of an Xpress Insight application (see the Xpress Insight Developer Guide), or
- simply be invoked from a command window or shell.
For the last option the usual approach consists in using the mosel command line tool (see section Running Mosel) with the run command. For instance, the following command may be used to run the model mycmd.bim:
> mosel run mycmd.bim
The aim of the deploy module is to ease the use of a model published this way. This module makes it possible to generate an executable program from the BIM file. Moreover, it gives the model access to the command line arguments and exposes a method for embedding configuration files into the resulting program. The deploy module is usually used through one of its two IO drivers: the first driver, csrc, generates a C program (based on the Mosel libraries) from a BIM file and the second one, exe, produces directly the executable by running a C compiler on the generated C source (this requires the availability of a C compiler on the system). For example the following command will create the program runmycmd (or runmycmd.exe on Windows) from the model mycmd.mos:
> mosel comp mycmd.mos -o deploy.exe:runmycmd
In addition to its IO drivers, the deploy module publishes two functions for accessing the program arguments: argc returns the number of parameters passed to the command (counting the command itself as the first) and argv(i) returns the ith argument (as a string). As an example, the following model displays the arguments it receives:
model mycmd uses 'deploy' writeln("My arguments:") forall(i in 1..argc) writeln(argv(i)) end-model
After compiling this example into an executable with the command shown above, an execution of the command runmycmd a b c will display:
My arguments: runmycmd a b c
In addition to giving access to command line arguments, deploy makes it possible to embed files into the resulting executable. File locations are passed via model parameters. The following example outputs its source when the program is called with the argument 'src' — otherwise it reports an error message:
model mycmd2 uses 'deploy','mmsystem' parameters SRC="null:" end-parameters if argc<>2 or argv(2)<>"src" then writeln("Usage: ", argv(1), " src") exit(1) else writeln("Source:") fcopy(SRC,"") end-if end-model
In this example, the source file is identified by the model parameter SRC. To generate the program, the following command has to be issued:
> mosel comp mycmd2.mos -o deploy.exe:runmycmd2,SRC=mycmd2.mos
With the command above, the file mycmd2.mos is included in the executable and the SRC parameter is redefined such that the model can access the file through memory. Note that the model file can also be included in the executable in compressed form. To enable this feature, the parameter name has to be suffixed with -z in the compilation command:
> mosel comp mycmd2.mos -o deploy.exe:runmycmd2,SRC-z=mycmd2.mos
Documenting models using annotations
The predefined doc annotation category can be used to document a Mosel file. Using a dedicated set of annotations the model author can add descriptions to the various entities defined in the source, the user-defined descriptions are completed by definitions automatically generated by the Mosel compiler.
From a bim file that includes such definitions a documentation processor may produce a complete document: as an example, the Xpress distribution comes with the moseldoc processor that generates an HTML documentation from an annotated bim file.
doc annotation category
Unlike other annotation categories, the doc annotation category is disabled by default such that the corresponding annotations are silently ignored. To generate a documentation-enabled bim file the compiler has to be run with the option -D. In addition to enabling the doc category, this flag also activates the automatic generation of certain documentation annotations by the compiler. Alternatively to using this flag, a model may define the following annotations:
!@mc.set doc enable !@doc.autogen=true
Note that these special annotations can also be used in the source file as a means to exclude some definitions from the documentation, setting doc.autogen to false right before the definitions to be excluded and back to true immediately after.
Global definitions
The following global annotations are automatically generated by the compiler:
- @doc.name
- Name of the package or model
- @doc.version
- Version number as stated by the 'version' statement
- @doc.date
- Current date
- @doc.ispkg
- Set to 'true' if the file is a package
All automatic annotations can also be defined explicitly in the Mosel source to overwrite their default values.
The following annotations may be added to complete the general appearance of the document to be produced (they are used by the moseldoc documentation processor):
- @doc.title
- Title of the document
- @doc.subtitle
- Subtitle of the document
- @doc.xmlheader
- Header of the XML document
- @doc.xmlroot
- Name of the XML element containing the documentation
- @doc.id
- Prefix used to generate IDs of chapters, sections, and subsections. If the documentation for several packages is generated from a single master model then a unique ID must be explicitly defined in each of the packages in order to avoid ID collision
The @doc category is complete (i.e. it is not possible to create new doc.X annotations), however, the category @doc.ext can be used to define further information assuming a particular documentation processor can exploit it.
Document structure
Optionally, the resulting document may be organised in chapters, sections and subsections. Each of these constructs can contain both text paragraphs and entity descriptions (declarations and subroutines). To enter a new documentation component, one of the following annotations has to be defined:
- @doc.chapter
- Start a chapter
- @doc.section
- Start a section inside of a chapter
- @doc.subsection
- Start a subsection inside of a section
In addition to the provided title a short title might also be defined (using @doc.shorttitle) that will be used in place of the (long) title in the table of contents. Whenever a new division starts, a unique ID is automatically generated based on the section number and any defined prefix specified in the header of the document with @doc.id. It is also possible to explicitly define an ID using @doc.id just after entering the section (this is required when the section has to be referenced using a <ref> tag).
From inside of any of these divisions a new paragraph is added with the @doc.p annotation. By default any new addition (paragraph or entity description) is appended to the current component but it is possible to select an alternative location. A target location has first to be defined using the annotation @doc.location: this creates a label associated with the current section. Defining the annotation @doc.relocate with this target elsewhere in the source file will move all subsequent additions to the target location; this relocation will continue up to the next division marker or relocation definition. Note that defining an empty relocation reverts to the effective current location. Example:
(!@doc. @chapter My first chapter @p some text related to the first chapter @location first_chap @section first section of first chapter @p something about the section @relocate first_chap @p this paragraph will be inserted directly under first chapter @relocate @p but this one will remain in the section !)
Symbol definitions
The following sections list the various documentation annotations that can be defined depending on the kind of the entity (parameter, variable, type or subroutine) to be documented. Some of these annotations are automatically defined by the compiler: in the case of values (like the value of a constant) the automatic definition may not be performed if the value is the result of a calculation that cannot be evaluated at compile time ("runtime constant"). In this case it is required to explicitly specify the text that should be retained in the documentation.
Parameters
- @doc.descr
- Description (1-2 text lines)
- @doc.default
- Default value (automatically generated)
- @doc.type
- Type (automatically generated)
- @doc.value
- Possible value and explanation of its meaning (may be defined several times)
- @doc.info
- Some more detailed explanations (may be defined several times)
- @doc.ignore
- The symbol will be ignored by the documentation processor
Types, constants and variables
This set of annotations apply to symbols declared in declarations blocks. Record fields (both for a type declaration and for a variable) can be described using @doc.recflddescr: the value of this annotation consists in the name of the field followed by its description (a space should separate these two components)
- @doc.descr
- Description (1-2 text lines)
- @doc.const
- For a constant: value (automatically generated)
- @doc.type
- Type (automatically generated)
- @doc.typedef
- For a type definition: type (automatically generated)
- @doc.value
- Possible value and explanation of its meaning (may be defined several times)
- @doc.info
- Some more detailed explanations (may be defined several times)
- @doc.setby
- Name of subroutines modifying this entity
- @doc.recfldtype
- Type of a record field (automatically generated)
- @doc.recflddescr
- Description of a record field
- @doc.ignore
- The symbol will be ignored by the documentation processor
Procedures and functions
Information from different overloaded versions of a given subroutine is merged automatically. The @doc.group annotation may be used to merge information of routines with different names but used for a similar task (up to 3 different subroutine names can be grouped). The @doc.param annotation is used to describe the parameters of the routine: the value of this annotation consists in the name of the parameter followed by its description (a space should separate these two components)
- @doc.group
- Name of another subroutine that this one should be grouped with
- @doc.descr
- Description (1-2 text lines)
- @doc.shortdescr
- Shortened description for table of contents and list display
- @doc.syntax
- Routine signature (automatically generated)
- @doc.param
- Name and meaning of a subroutine argument (may be defined several times)
- @doc.paramval
- Possible value and meaning of a subroutine argument (may be defined several times). The value of this annotation is the name of the parameter (as specified with a preceding @doc.param) followed by the value and the explanation
- @doc.return
- For functions only: what is returned
- @doc.err
- Possible error code (may be defined several times)
- @doc.example
- Example of use (may be defined several times)
- @doc.info
- Some more detailed explanations (may be defined several times)
- @doc.related
- List of related symbols
- @doc.ignore
- The subroutine will be ignored by the documentation processor
Annotation definitions
A special set of annotations (category @doc.annot) is available for documenting annotation definitions in Mosel packages (not supported for Mosel models). The annotations for documenting annotation definitions are global annotations, their value must start with an annotation name in order to associate them with the corresponding annotation definition.
- @doc.annot.descr
- Annotation name followed by a short description (1-2 text lines)
- @doc.annot.default
- Annotation name and default value
- @doc.annot.value
- Annotation name, possible value and explanation of its meaning (may be defined several times)
- @doc.annot.type
- Annotation type
- @doc.annot.info
- Annotation name and some more detailed explanations (may be defined several times)
- @doc.annotcat
- Annotation category to document (may be defined several times), if undefined all categories are documented
- @doc.annotloc
- Insertion point (specified via @doc.location) for annotations documentation
moseldoc documentation processor
Running moseldoc
The moseldoc program takes as input either a bim file produced from a Mosel model compiled with the -D compiler option or directly a Mosel source file (in which case a compilation step is automatically executed). Typically the generation of the documentation from a source file will be obtained with the following command:
>moseldoc mymodel.mos
The result of this process is an XML file ("mymodel_doc.xml") and a directory containing an HTML version of the documentation ("mymodel_html"). The program will produce only the XML file (from a bim or source file) if option -xml is used and only the HTML output (from an XML file) if -html is selected. The option -f is required to force the replacement of existing files.
As a Mosel program available in source form, moseldoc can be adapted to fit specific requirements. To re-generate the executable use this compilation command:
>mosel comp -s moseldoc.mos -o deploy.exe:moseldoc,css-z=moseldoc.css
Structure of the generated document
The resulting document respects the structure defined by the dedicated annotations (chapter, section, subsection). In each of these divisions, the paragraphs are exposed first, then the parameters and variables and finally the list of subroutines. If no structural elements have been defined, a chapter per entity type is automatically created to group similar objects (Parameters, Constants, Types, Variables and Subroutines).
Processing of annotation values
Values associated with descriptive text annotations (like section titles or descriptions) are interpreted as XML. Paragraphs (@doc.p) and examples (@doc.example) are handled in a specific way: by default the value is inserted as XML but, if the value starts with [TXT], the content is treated as plain text; if it starts with [SRC], the value is considered to be some example code and it is reproduced preserving spacing. If it starts with [NOD], it is interpreted as a self-contained XML node (i.e. it is not inserted in a paragraph block). In an XML block of text, the markers ref (chapter/section/subsection reference), fctRef (subroutine reference) and entRef (entity reference) are processed such that in the HTML document they are turned into hyperlinks to the corresponding objects. Similarly, the tt element type is replaced by an appropriate style for displaying code samples.
Message translation
Mosel supports a message translation mechanism that makes it possible to display messages in the current language of the operating environment. This system requires that all messages are originally written in English and identified as messages to be translated (it is usually not desirable to translate all text strings of a model). The Mosel compiler can then collect all messages to be translated for building message catalogs. Each message catalog file contains the translations of the messages for a given language: Mosel will select the appropriate file for the current language during its execution to use the right set of translations. The system is designed such that it will not fail if a translation or an entire language is missing: in such a case the original English text is used.
Preparing the model source
Most often, not all text strings occurring in a program are to be translated to native language. This is why it is necessary to tag each message to be translated such that the automatic message translation system can process only the relevant texts. The tagging is achieved by using the operators _c(), _() or the modified procedures write_(), writeln_(), fwrite_() and fwriteln_().
The operator _c() is used to identify constant strings that should be collected for translation but the string will not be translated at the place where it is used. This operator can be applied to a list of string constants. A similar effect can be obtained with the annotation mc.msgid.
The function _() applies to both constant strings and variables: it replaces its argument by the translated string. As with the operator _c() constant strings are collected for the message catalogs, but they will also be replaced by their translation at the place where the operator is applied to the string.
The write_ and writeln_ procedures are equivalent to their normal versions except that they process the constant strings they have to display for translation.
All translations of a model (or package) are grouped under a domain: this identifier is used to name the message catalog files. The default domain name is the model (or package) name after having replaced spaces and non-ascii characters by underscores (for instance the domain name of the model "my mod" is "my_mod"). The domain name can also be specified using the mc.msgdom annotation.
The following model example shows the use of the various markers:
model translate ! The message domain is 'trs' (default name would be the model name 'translate') !@mc.msgdom trs declarations ! The elements of 'nums' are kept in English, but collected for translation nums=[_c("one","two","three")] ! Add 'four' to the message catalogs (although it is not used here) !@mc.msgid:four end-declarations ! Translate the message text, without translating 'nums' writeln_("all numbers (in English): ", nums) n:=getfirst(nums) ! Translate the message text and the first occurrence of 'n', but not ! its second occurrence writeln_("the first number is: ", _(n), " (in English:", n, ")") end-model
Building the message catalogs
Once the model source has been prepared, the list of messages to be translated can be extracted. This operation is performed by the Mosel compiler when executed with the option -x:
>mosel comp -x mymod.mos -o trs.pot
The output of this command is a Portable Object Template (POT): this is a text file consisting of a list of pairs msgid (message to translate), msgstr (translation) for which only the first entry is populated.
With the example model from the previous section the generated POT file results in the following:
# Created by Mosel v4.0.0 from 'translate.mos' # Domain name: trs msgid "all numbers (in English): %L\n" msgstr "" msgid "four" msgstr "" msgid "one" msgstr "" msgid "the first number is: %s (in English:%s)\n" msgstr "" msgid "three" msgstr "" msgid "two" msgstr ""
For each of the supported languages a separate PO (Portable Object) file that will contain the corresponding translations has to be created from this template. The command xprnls is used for this task (for further details please refer to the XPRNLS Reference Manual). For instance the following command will create the file for the Italian translations of the messages:
>xprnls init -o trs.it.po trs.pot
Here we name the file domain.language.po in order to ease the management of these translation files (where language stands for the ISO639 language code).
The generated file is a copy of the template with an additional header that should be completed by the translator (it is pre-populated with information obtained from the system), in particular the language (property "Language") and the encoding (property "Content-Type"). For each of the msgid records the translation in the language associated to the file has to be provided in the msgstr record. Note that some messages include escape sequences (like "\n") and format markers (e.g. "%s"): the corresponding translation must include the same format markers as the original text and they must appear in the same order (otherwise the translation will be ignored).
The beginning of the translation file of our example for French (named "trs.fr.po") should be similar to the following (the extract below shows only the header and the translation of the first message):
msgid "" msgstr "" "Project-Id-Version: My translation example\n" "POT-Creation-Date: 2015-12-04 18:16+0100\n" "PO-Revision-Date: 2015-12-04 18:16+0100\n" "Last-Translator: Jules Verne\n" "Language: fr\n" "Content-Type: text/plain; charset=ISO8859-15\n" msgid "all numbers (in English): %L\n" msgstr "tous les nombres (en anglais): %L\n"
The message catalogs for the PO files are obtained by running once more the xprnls command, this time using the option mogen:
>xprnls mogen -d locale trs.*.po
This command will compile each of the PO files into a Machine Object (MO) file named trs.mo that will be saved under the directory locale/lang/LC_MESSAGES. This directory tree must be distributed along with the model file for the automatic translation to work.
Model execution
During the execution of the model the message catalogs for the current language (as indicated by the operating system) are loaded automatically from the 'locale' directory. This location is defined by the "localedir" control parameter (by default this is "./locale"). If no message catalog can be found for the requested language then the original English text is used. This will also be the case if a translation is missing (e.g. if the message catalog has not been updated after some model source change).
When run on a computer configured for French our example displays:
tous les nombres (en anglais): [`one',`two',`three'] le premier nombre est: un (en anglais:one)