Home / Index | www.icosaedro.it | ![]() |
M2 version: 1.5-20110205
M2 is a procedural, high-level programming language with a garbage collector. Data structures are automatically allocated, arrays are dynamically expanded. The syntax of M2 is similar to that of Modula-2: all the keywords are written in uppercase letters, and all the identifiers are case-sensitive: file, FILE and File are different names.
An M2 program is a text file with extension .mod (.def and .imp will be explained later). For example, open your preferred text editor and type-in this source (probably you will use some sort of copy-and-paste mechanism provided by your system):
MODULE hello IMPORT m2 BEGIN print("Hello, world!\n") END
then save this source into the file hello.mod
, compile and
execute with the command
$ m2 hello.mod $ ./hello Hello, world!
m2
actually is a script that first call the actual
m2c
M2-to-C cross compiler, so that the C source
hello.c
is generated, then it calls the C compiler
in order to generate the executable program hello
(hello.exe
on Windows). If all the compilation stages was
successful, the intermediate source hello.c
gets deleted.
Check m2 -h
for other useful options of the front-end script.
All the text following a # character up to the end of the line is ignored: this is a single line comment.
All the text enclosed between (* and *) is ignored: this is a multi-line comment. Multi-line comments can be nested.
BOOLEAN
has only the values FALSE
or TRUE. Boolean expressions can be evaluated through the logical
operators AND, OR and NOT. The following tables summarizes the computation
done by these operators:
a b | a OR b a AND b a | NOT a -------------+-------------------- ------+------- FALSE FALSE | FALSE FALSE FALSE | TRUE FALSE TRUE | TRUE FALSE TRUE | FALSE TRUE FALSE | TRUE FALSE TRUE TRUE | TRUE TRUEThe NOT operator has the highest priority, followed by AND and then OR. The round parenthesis can be used to alter the order of the evaluation:
NOT TRUE AND FALSE OR TRUE gives TRUE NOT TRUE AND (FALSE OR TRUE) gives FALSEThe boolean expression are evaluated from left to right, and the first partial value that determinate the result terminates the evaluation of the sub-expression. That means that the first TRUE term of the expression
a OR b OR c
makes all the expression TRUE and
the remaining terms aren't evaluated; the first FALSE factor of the
expression a AND b AND c
makes all the expression FALSE and
the remaining factors aren't evaluated.
INTEGER
number is a two-complement integer number, i.e.
the int
data type of the underlying C compiler, typically
32 bit long. There are only two levels of priority for the operators
involving integer numbers
first: * DIV MOD & << >> next: + - | ^
i DIV j gives the quotient, i MOD j gives the remainder of the division.
i << n is a bitwise left shift of n bits, i >> n is a bitwise right shift of n bits.
i|j gives the bitwise OR, i^j gives the XOR, & gives the bitwise AND.
Integer numbers can be compared through the operators < <=
= >= <>
, that give a BOOLEAN result.
REAL
number is a floating-point number, i.e. the
double
data type of the underlying C compiler, typically
64 bit long. A literal REAL number requires at least the decimal point
or the scale part:
3.141592 1.6E-27 -1.234The allowed operators are (in order of priority):
first: * / next: + -REAL numbers can be compared through the operators
< <=
= >= <>
, that give a BOOLEAN result.
STRING
is a sequence of zero or more bytes, dynamically
allocated. A STRING can have the special value NIL, that means the string
isn't allocated at all. Only the ASCII printable characters are allowed
in strings, with the exception of the "
(double quote)
and the \
(back-slash) that have special meaning.
Example of literal strings:
"abcdef" "A line.\nAnother line." "\x00"Two or more strings can be concatenated through the
+
operator.
If all the strings concatenated are NIL, the result will be NIL.
INTEGER and REAL numbers can be concatenated to a string, but they
cannot appear as the first term. The conversion from number to string
is automatically done by the functions m2runtime.itos()
and m2runtime.rtos()
. Examples:
"abc" + "def" + NIL + "" gives "abcdef" "Pi = " + 3.141592 gives "Pi = 3.14159" "Record no. " + (1+2) gives "Record no. 3"Strings can be compared through the operators
< <= = >=
<>
, that give a BOOLEAN result. The NIL string is equal to NIL
and less of any other string. The empty string ""
is greater
that NIL and less than any other string containing one or more characters:
NIL < "" < "abc"Two non-NIL, non-empty strings are compared byte-by-byte, from left to right. Examples:
NIL < "" gives TRUE NIL = NIL gives TRUE NIL = "" gives FALSE "a" < "b" gives TRUE "abc" < "abcd" gives TRUE
Only the printable ASCII characters are allowed in strings. Any other byte value can be entered using the escape sequence \xHH where HH is the hexadecimal value of the byte. Some commonly used special escape sequences are allowed:
\xHH | hexadecimal code HH |
\\ | \ |
\" | " |
\a | \x07 (ASCII BEL) |
\b | \x08 (ASCII BS) |
\n | \x0A (ASCII LF) |
\r | \x0D (ASCII CR) |
\t | \x09 (ASCII HT) |
The content of a string cannot be changed, but a variable of the type STRING can be re-assigned.
The substring operator [] can be used to explore a string and return substrings. s[i,j] gives a string (j-i) bytes long containing the characters ranging from s[i] up to s[j-1]. s[i] is short version of s[i,i+1] and it gives a string one-byte long containing the byte at offset i. For examples:
s = "hello" s[0,2] gives "he" s[0] gives "h" s[2,2] gives "" (empty string) s[0,length(s)-1] gives "hello"
The substring operator cannot be applied to a NIL string. Use the m2runtime.length(s) function to return the current length of a string. To summarize, the range [i,j] is applicable to a string only if the string is not NIL and 0<=i<=j<length(s) otherwise it is a run-time fatal error.
The standard module str provides a set of basic string handling functions.
An ARRAY is an ordered list of variables, all of the same type, that can be selected through an integer index ranging from 0 up to the last element assigned. The general syntax of this type is as follows:
ARRAY OF T
where T
is the type of the elements. A variable
of the type ARRAY has the initial value NIL, i.e. means it is not
allocated. To allocate an array, simply assign an element to it with any
non-negative integer index; if this index is greater that 0, the elements
not assigned takes the initial value of the variable of their type (FALSE
for BOOLEAN, zero for numbers, NIL for strings and for RECORD and ARRAY).
For example
VAR a: ARRAY OF STRING ... a[123] = "hello"
In this example the elements from a[0] up to a[122] are set to NIL.
Arrays are expanded as necessary to hold the new element.
The function m2runtime.count(a)
gives the number of elements
actually stored. For example, to print a list of strings:
... VAR names: ARRAY OF STRING i: INTEGER BEGIN FOR i=0 TO count(names)-1 DO print("element no. " + i + " = " + names[i] + "\n") END END
The ARRAY constructor can be used to assign the whole array; the elements specified will occupies the offsets 0, 1, 2, etc:
a = {123, 456, 789}
To assign a new element to an array just after the last one:
a[ count(a) ] = 456
Adding an element to an array is so frequent that M2 allows to simply omit the index, so bringing to the short form that follows:
a[] = 456
A matrix is an ARRAY OF ARRAY. This example will set a 3x3 identity matrix with 1.0 in all the diagonal elements; remember that the elements not assigned takes the value 0.0:
VAR m: ARRAY OF ARRAY OF REAL i: INTEGER BEGIN FOR i=0 TO 2 DO m[1][2] = 0.0 # ensures the row be 3 elems wide m[i][i] = 1.0 END (* or simply: m = { {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0} } *) END
ARRAYs can be compared through the operators =
and
<>
. What will be actually compared are the addresses where
these ARRAYs are allocated in memory, NOT their content. The special value
NIL can be compared with an ARRAY. This chunk of code prints the current
status of the array `a'. Note that an array can be allocated with zero
elements in it: this can be accomplished with the array constructor left
empty, for example a={}
.
IF a = NIL THEN print("not allocated") ELSIF count(a) = 0 THEN print("allocated, but empty") ELSE print("allocated with one or more elements") END
A RECORD is a list of variables (aka "fields"). Every field has a name that can be used as selector of the field. The general syntax of a RECORD type is as follows:
RECORD fieldname1: type1 fieldname2: type2 fieldname3, fieldname4, fieldname5: type3 ... END
where fieldname1,... are the names of the fields, and type1,... are their types. A variable of the type RECORD is initially unallocated and its value is NIL. To allocate a RECORD simply assign one of its fields; the fields not assigned takes the initial value of their type (FALSE for BOOLEAN, zero for numbers, NIL for strings and for RECORD and ARRAY). For example
... VAR point: RECORD x, y: REAL END BEGIN point[x] = 1.0 # here the RECORD is allocated point[y] = 2.0 END
Note that the selector [fieldname] is the name of the field. The number of the fields of a RECORD is fixed and cannot changed at run-time. The types of the fields can be any valid type, including ARRAY and RECORD. If you need a RECORD containing a variable number of data, use a field of the type ARRAY.
The RECORD constructor can be used to assign all the fields to a RECORD; note that all the fields must be specified, in the order:
point = {1.0, 0.0}
Array constructors and record constructors can be combined to build complex structures.
RECORD types typically have a name declared inside a TYPE section. For example, a typical single-linked list of strings can be declared in this way:
TYPE List: RECORD next: List key: STRING END
The following program first adds some strings to a list, then prints all the strings of this list:
MODULE lists IMPORT m2 TYPE List: RECORD next: List key: STRING END FUNCTION add_elem(VAR l: List, s: STRING) VAR m: List BEGIN m[key] = s m[next] = l l = m (* or simply: l = {l, s} *) END FUNCTION print_elems(l: List) BEGIN WHILE l <> NIL DO print(l[key] + "\n") l = l[next] END END VAR list: List BEGIN add_elem(list, "Sunday") add_elem(list, "Monday") (* ...and so on *) print_elems(list) END
RECORDs can be compared through the operators =
and
<>
. What will be actually compared are the addresses
where these RECORDs are allocated in memory. The special value NIL
can be compared with a RECORD. For example:
VAR p,q: Point ... IF p = NIL THEN print("not allocated") END IF p = q THEN print("same point") END
A variable of a given type is assignment-compatible only with a variable of the same type. For dynamically allocated data types (STRING, ARRAY, RECORD) the assignment simply copy the pointer to the allocate data. Two ARRAYs are compatible if and only if their elements are compatible. Two RECORDs are compatible if and only if they have the same number of fields and their fields are compatible in the order. Different data types can have different names although being assignment-compatible.
Every module can have several sections, listed in arbitrary order. Only the IMPORT sections must appear before any other section. The general layout of the module is as follows:
MODULE name IMPORT list of modules to be imported CONST constants TYPE types VAR variables FUNCTION name(arguments): return_type BEGIN ... END BEGIN here the main body of the program END
IMPORT m2, io, win, MyModule IMPORT AnotherModule
The required modules are searched first inside the same directory of the requiring module, then inside the list of directories given by the configuration of the compiler.
If an imported module depends on other modules (either in its DEFINITION or in its IMPLEMENTATION), these modules are imported automatically and included in the final source.
The items imported from a module (constants, types, variables and functions)
are immediately available to the program. If two or more modules export
the same item name, this item must be qualified by its module name. This
is the case, for example, of the io.Open()
function and the
win.Open()
function.
CONST DEBUG = FALSE MAX_FILES = 128 PI = 3.141592 ROOT_PATH = "/usr/local/bin/"
TYPE Point = RECORD x, y: INTEGER END Poly = ARRAY OF Point ProcessStatus = (ready, running, waiting)Two variables are of the same type if they have the same structure in terms of the basic simple types (BOOLEAN, INTEGER, REAL, STRING) and the same structure builders (ARRAY, RECORD). The names of fields of a RECORD may differ. For example, any RECORD containing two INTEGER fields of any name, are equivalent to the
Point
record declared above;
the type ARRAY OF RECORD a, b: INTEGER END
is equivalent
to the type Poly
declared above.
VAR i, j: INTEGER in_fn, out_fn: STRING in, out: io.FILE origin: Point drawing: ARRAY OF Poly proc1, proc2: ProcessStatusAll the variables have a default value assigned:
Type Default value ---------------------------- BOOLEAN FALSE INTEGER 0 REAL 0.0 STRING NIL ARRAY NIL RECORD NIL
Variables that are local to a function can have the STATIC attribute that make these variables statically allocated, i.e. their value is allocated once for all when the program starts, then it is never released. For example, the function seq() of the following example will print the sequence of numbers 0, 1, 2, etc:
MODULE test_static IMPORT m2 FUNCTION seq() VAR STATIC i: INTEGER BEGIN print("i=" + i + "\n") i = i + 1 END BEGIN seq() # prints "i=0" seq() # prints "i=1" seq() # prints "i=2" END
Note that the initial value of the static variable is zero: even the static variables are initialized to their default value.
FUNCTION min(a: INTEGER, b: INTEGER): INTEGER (* Returns the minimum value between a,b. Note: this function is already available as m2.min(). *) BEGIN IF a <= b THEN RETURN a ELSE RETURN b END END FUNCTION InArray(s: STRING, a: ARRAY OF STRING): BOOLEAN (* Returns TRUE if the string "s" is contained inside the array "a". *) VAR i: INTEGER BEGIN FOR i=0 TO count(a)-1 DO IF a[i] = s THEN RETURN TRUE END END RETURN FALSE END FUNCTION CharList(s: STRING): ARRAY OF CHAR (* Returns the list of chars of which the given string is composed of. *) VAR i: INTEGER a: ARRAY OF STRING BEGIN FOR i=0 TO length(s)-1 DO a[i] = s[i] END RETURN a ENDThe types BOOLEAN, INTEGER and REAL are passed as a copy of the actual argument. STRING, ARRAY and RECORD are passed by address pointing to the allocated area, or NIL if not allocated. A formal argument can be passed by "reference" specifying the VAR attribute: in this case the actual argument must be a variable whose address is passes to the function, and any change to the formal arguments modify the original actual argument. Example:
MODULE solve IMPORT m2, math FUNCTION solve(a: REAL, b: REAL, c: REAL, VAR r1: REAL, VAR r2: REAL) (* Returns the roots of the equation a*x*x + b*x + c = 0 *) VAR delta, r: REAL BEGIN IF a = 0.0 THEN HALT("a = 0") END delta = b*b - 4.0 * a * c IF delta <= 0.0 THEN HALT("no real solutions") END r = math.sqrt(delta) r1 = (-b - r) / (2.0 * a) r2 = (-b + r) / (2.0 * a) END VAR s1, s2: REAL BEGIN solve(1.0, 2.0, -3.0, s1, s2) print("s1=" + s1 + ", s2=" + s2 + "\n") END
A function can declare any number of local constants, types, variables and nested function. All the items declared inside a function are local to the function, apart the STATIC variables. Local items are visible only inside the function.
A function can access any item declared in global scope, including itself.
Functions can be nested inside another function. The inner functions can have their local items and have access to the the items of the parent function, including variables and formal arguments.
IF DEBUG THEN print("still alive!") END IF (s = NIL) OR (s = "") THEN print("missing value for the string") ELSE print("s=" + s) END print("the number is ") IF i > 0 THEN print("positive") ELSIF i = 0 THEN print("zero") ELSE print("negative") END
SWITCH i DO CASE 0: print("zero") CASE 1, 2, 3: print("one or two or three") ELSE print("invalid value: expected 0, 1, 2 or 3") ENDThe ELSE branch is optional. It is a fatal, run-time error if the result of the controlling expression is a value that does not match any of the CASE branches and the ELSE branch is not provided.
i = 0 LOOP IF i >= 10 THEN EXIT END print("i=" + i + "\n") i = i + 1 ENDThere may be several EXIT statements inside a LOOP statement. Another typical way to leave a LOOP cycle is through the RETURN statement.
i = 0 WHILE i < 10 DO print("i=" + i + "\n") i = i + 1 ENDThe block of controlled statements might not be executed if the BOOLEAN expression evaluates to FALSE at the first execution of the WHILE statement.
i = 0 REPEAT print("i=" + i + "\n") i = i + 1 UNTIL i >= 10
FOR i=0 TO 9 DO print("i=" + i + "\n") ENDThe expressions giving the initial and the final value of the range are evaluated only once when the FOR statement is executed, so they cannot contain values update inside the loop itself. If the final value is less than the initial value, the controlled statements are not executed. The range of values are scanned from the the initial value to the final value with an increment of 1. A different increment, possibly negative, can be provided:
FOR i=9 TO 0 BY -1 DO print("i=" + i + "\n") END
RETURN 0The exit code from a process must be an INTEGER number between 0 and 255 (the value resulting from the expression is masked with 0xFF).
Inside a FUNCTION the RETURN statement causes the termination of the FUNCTION. If the FUNCTION returns a value, the RETURN statement must specify the value returned through an expression of the proper type.
IF i < 0 THEN HALT("unexpected negative value") ENDwill terminate the process sending to standard error a message similar to this:
MyModule:342: HALT: unexpected negative value Abort
It allows to set the error condition:
RAISE ERROR code message
where code
is an integer expression (typically a
constant or a literal value) and message
is a string giving
the human readable description of the error occurred.
The error code and its description are available as
m2runtime.ERROR_CODE
and m2runtime.ERROR_MESSAGE
respectively. If the errors are not catch-ed inside a TRY...END section
(see below) the RAISE ERROR instruction HALTs immediately the program
sending to the standard error a message having the structure
module.function(), line n, code code: message
and the error status on the exit of the program will be 1.
Vice-versa, if the RAISE ERROR is protected inside a TRY...END instruction, this instruction has no other effects than setting the error status. It is responsibility of the program to return to the caller and take any other countermeasure to handle the error condition.
Functions that want to raise errors MUST have the "RAISE ERROR" qualifier in their declaration:
FUNCTION FuncName() RAISE ERROR BEGIN # ... END
It has the general structure
TRY assignment or function call CATCH code1: code that handle the error code1 code2: code that handle the error code1 (* ...more branches here *) ELSE code that handle any other error END
Typical usages of the TRY...END statement are along the handling of file access. For example, this function will return TRUE if the file exists and it is readable, and FALSE in any other case:
MODULE TryTest IMPORT m2, io VAR f: FILE BEGIN print("Trying to read myself: ") TRY io.Open(f, "TryTest.mod", "r") CATCH ENOENT: print("I do not exist, strange...\n") EACCESS: print("access denied, very strange...\n") ELSE print(ERROR_MESSAGE + "\n") END IF ERROR_CODE = 0 THEN print("success!\n") TRY io.Close(f) ELSE END # ignore any error END END
The ELSE branch is optional: if omitted and none of the catch branches catch-ed the error, the error is raised.
A library module consists in a DEFINITION MODULE and an IMPLEMENTATION MODULE. The DEFINITION MODULE gives the interface to the module, that is the list of constants, types, variables and functions accessible to any client module. A DEFINITION MODULE cannot contain executable code.
The IMPLEMENTATION MODULE gives the implementation of the exported functions. The IMPLEMENTATION MODULE can also contains private constants, private types, private variables, private functions and it MUST contain the implementation of the exported functions. Example:
DEFINITION MODULE example (* File: example.def *) IMPORT io CONST (* My PC: *) CPU = "i686" RAM = 256 (* MB *) HD = 80 (* GB *) TYPE Point2D = RECORD x, y: REAL END VAR afile: io.FILE # actually not used in this example FUNCTION Add2D(a: Point2D, b: Point2D): Point2D END
This file defines the interface to the example.imp
implementation module that follows. The DEFINITION example.def does not
require to be compiled, but it can be checked with M2.
IMPLEMENTATION MODULE example (* File: example.imp *) (* IMPORT, CONST, TYPE, VAR and FUNCTIONS private to the module goes here. *) FUNCTION Add2D(a: Point2D, b: Point2D): Point2D VAR p: Point2D BEGIN p[x] = a[x] + b[x] p[y] = a[y] + b[y] RETURN p END END
Compiling this file gives example.c
containing the C
source of the module, and example.lnk
containing possible
options for the C compiler or the linker (see "Mixing C code" below for
details). To summarize, these files are involved:
example.def
example.imp
example.c
example.lnk
A client program might looks like this:
MODULE tryExample IMPORT m2, example VAR one, img, sum: Point2D BEGIN one = {1.0, 0.0} img = {0.0, 1.0} sum = Add2D(one, Add2D(one, img)) print("it works!\n") END
Use a Makefile to define the dependencies of the various elements. For example, suppose the main module M.mod requires the modules A.def,A.imp that in turn required B.def,B.imp. The Makefile can be the following:
# Makefile for the M project B.c: B.def B.imp m2 B.imp A.c: A.def A.imp B.c m2 A.imp M: M.mod A.c B.c m2 M.mod test: M ./M
The program can be compiled simply giving the command "make", and it can be compiled and executed simply giving the command "make test".
It is really simple to mix C code along the M2 code. Lines beginning with a $ in the first column are passed verbatim to the resulting C source. All the M2 identifiers take the form MODULE_ITEM, where MODULE is the name of the module and ITEM is the name of a variable or function.
WARNING! This simple naming scheme MODULE_ITEM was just intended to simplify the integration between C code and M2 code, but it does not protect from possible collisions. For example, a module M exporting the item A_B and a module M_A exporting the item B both will produce the name M_A_B. To avoid these situations the names of the modules should not contain the underscore character.
For example, here is the implementation of a function that returns the current process identifier number (PID):
FUNCTION getpid(): INTEGER BEGIN $ return getpid(); END
The following function returns TRUE if the file exists, FALSE otherwise:
MODULE mymod $ #include <sys/types.h> $ #include <sys/stat.h> $ #include <unistd.h> FUNCTION file_exists(fn: STRING) VAR $ char *s; $ struct stat buf; BEGIN IF InvalidZString(fn) THEN # The file name contain a NUL byte --> not a valid file name RETURN FALSE END # Build a valid zero-terminated C string: $ MK_ZSTRING(s, mymod_fn); $ return stat(mymod_fn->s, &buf) == 0; END BEGIN IF file_exists("mymod.mod") THEN print("I exist!\n") END END
InvalZString(fn) returns TRUE is the passed string contains a zero byte, forbidden in zstrings usually handled by the C standard library. MK_ZSTRING(s, mymod_fn) dynamically allocates into the stack a zero-terminated copy of the string mymod_fn and sets s to be a pointer to this string; that string will be released on exit from the function.
Any line beginning with "$$ linker options:" can contain a list of options for the C compiler and linker. See for example the module math.def that requires the linker option -lm.
AND ARRAY BEGIN BOOLEAN BY CASE CATCH CONST DEFINITION DIV DO ELSE ELSIF END ERROR EXIT FALSE FOR FORWARD FUNCTION HALT IF IMPLEMENTATION IMPORT |
INTEGER LOOP MOD MODULE NIL NOT OF OR RAISE REAL RECORD REPEAT RETURN STATIC STRING SWITCH THEN TO TRUE TRY TYPE UNTIL VAR VOID WHILE |
A BOOLEAN is represented as a 32 bit word, whose value is 0 for FALSE, TRUE otherwise.
An INTEGER is a 32 bit word, 2-complement.
A REAL is a 64 bit word.
A STRING is a pointer to a dynamically allocated area containing the actual string and its length. Several pointers can share the same string: since strings, once created, can never be changed (only pointers can), copying a string to another or passing a string as an argument of a function simply involves the copy of a pointer. The NIL string is a pointer whose value is zero. The empty string "" is pre-allocated by the m2runtime module.
M2 do not has a "CHAR" data type; single characters are simply strings one byte long. For efficiency reasons, the strings whose length is exactly 1 byte are allocate only one time in a special internal table, so that programs that scans a file byte-per-byte do not really cause the dynamic memory to be used.
The ARRAYs are dynamically allocated. The picture below illustrates the structure used. Some elements of the array are preallocated, so adding elements to an array does not produce too many re-allocations of the memory block.
The RECORDs are dynamically allocated. The picture below illustrate the structure used.
For more informations about the internal representation of data, read the file lib/m2runtime.c.
Errors detected at run-time cause the termination of the program. The message displayed on standard error has the format below:
MODULE.FUNCTION(), line LINE: MESSAGE
The messages that can be produced by the run-time module m2runtime are as follow:
Substring of a NIL string
Can't apply the substring operator s[*] to a NIL string.Invalid substring index
Range s[i] invalid because i<0 or i>=length(s).Invalid substring range
Range s[i,j] invalid because i<0 or j>=length(s) or i>j.Cannot dereference NIL array
Can't access elements of an unallocated array.Array index is negative
The expression a[EXPR] gives an invalid negative index.Array index too large
Can't read the element a[EXPR] because EXPR >= count(a).Cannot dereference NIL record
Can't read the fields of an unallocated RECORD.Unexpected case in SWITCH
The expression SWITCH EXPR gives a value not found between the expected CASEs and the ELSE branch is not present.Missing RETURN <expr>
Missing RETURN EXPR in a function returning a value. Since this issue is difficult to detect from the analysis of the source, this control is made at run-time.
The following EBNF declarations describes in a concise but precise form the syntax of the M2 language. For learn more about this formalism, you can look at the page www.icosaedro.it/bnf_chk/index.html.
1. compilation_unit = definition_module2 | implementation_module3 | module4 ;"DEFINITION"
"MODULE"
module_name5 import6 { const_decl7 | var_decl11 | function_decl23 } "END"
;"IMPLEMENTATION"
"MODULE"
module_name5 import6 { const_decl7 | type_decl10 | var_decl11 | function_decl23 function_body25 } "END"
;"MODULE"
module_name5 import6 function_body25 ;"IMPORT"
[ module_name5 { ","
module_name5 } ] } ;"CONST"
{ name68 "="
const_value8 } ;"+"
| "-"
] number49 | [ "+"
| "-"
] const_name9 | string60 ;"TYPE"
{ name68 "="
( "FORWARD"
| type12 ) } ;"VAR"
[ "STATIC"
] { name68 { ","
name68 } ":"
type12 } ;"VOID"
| "BOOLEAN"
| "INTEGER"
| enum_type15 | "REAL"
| "STRING"
;"("
enum_elem16 { ","
enum_elem16 } ")"
;"="
const_integer17 ] ;"ARRAY"
"OF"
type12 ;"RECORD"
{ field_decl20 } "END"
;","
field_name21 } ":"
type12 ;"FUNCTION"
name68 "("
[ formal_arg24 { ","
formal_arg24 } ] ")"
[ ":"
type12 ] [ "RAISE"
"ERROR"
] ;"VAR"
] name68 ":"
type12 ;"BEGIN"
{ instruction26 } "END"
;"="
expr43 ;"("
{ actual_arg32 } ")"
;"("
{ actual_arg32 } ")"
) { "->"
qualified_name70 "("
{ actual_arg32 } ")"
} ;"IF"
expr43 "THEN"
{ instruction26 } { "ELSIF"
expr43 "THEN"
{ instruction26 } } [ "ELSE"
{ instruction26 } ] "END"
;"SWITCH"
expr43 "DO"
{ "CASE"
const_integer17 { ","
const_integer17 } ":"
{ instruction26 } } [ "ELSE"
{ instruction26 } ] "END"
;"WHILE"
expr43 "DO"
{ instruction26 } "END"
;"REPEAT"
{ instruction26 } "UNTIL"
expr43 ;"FOR"
qualified_name70 "="
expr43 "TO"
expr43 [ "BY"
const_integer17 ] "DO"
{ instruction26 } "END"
;"LOOP"
{ instruction26 } "END"
;"EXIT"
;"TRY"
( assignment27 | function_call28 ) { "CATCH"
const_integer17 { ","
const_integer17 } ":"
{ instruction26 } } [ "ELSE"
{ instruction26 } ] "END"
;"RAISE"
"ERROR"
expr43 expr43 ;"RETURN"
[ expr43 ] ;"+"
| "-"
] term45 { add_operator50 term45 } ;"NIL"
| boolean48 | number49 | string60 | const_name9 | var_name64 { selector65 } [ substr_selector63 ] | function_call28 | "("
expr43 ")"
| "NOT"
factor46 | "~"
factor46 ;"<"
| "<="
| "="
| ">="
| ">"
| "<>"
;"FALSE"
| "TRUE"
;"+"
| "-"
| "OR"
| "^"
| "|"
;"*"
| "/"
| "DIV"
| "MOD"
| "AND"
| "&"
;"0".."9"
;"0x"
hex_digit56 { hex_digit56 } ;"a".."f"
| "A".."F"
;"."
integer53 ;"e"
| "E"
) [ "+"
| "-"
] integer53 ;"\""
{ str_char61 | str_escape62 } "\""
;" "
| "!"
| "#".."["
| "]".."~"
;"\\"
( "\\"
| "\""
| "a"
| "b"
| "n"
| "r"
| "t"
| "x"
hex_digit56 hex_digit56 ) ;"["
expr43 [ ","
expr43 ] "]"
;"["
( index67 | field_name21 ) "]"
| next_elem_in_array66 ;"["
"]"
;"_"
) { letter69 | digit54 | "_"
} ;"a".."z"
| "A".."Z"
;"."
] name68 ;www.icosaedro.it/m2 is the official WEB site of the M2 language. Check her for new versions of the language and of the compiler.
www.icosaedro.it/m2/applications.html contains a list of applications developed with M2.
Umberto Salsi | Comments | Contact | Site map | Home / Index |
Still no comments to this page. Use the Comments link above to add your contribute.