Программа на M++ размещается в одном или нескольких текстовых файлах (обычно с раширением .4xx). Один из этих файлов должен быть главным файлом программы - именно его имя передается утилите mxxc и именно с него начинается выполненние программы. Главный файл программы может подключать другие файлы программы через директиву препроцессора #include.
Исходный текст программы сначала обрабатывается препроцесором. В результате получается линейная последовательность выражений языка M++ (деклараций и операторов), как если бы она изначально была записана только в одном исходном файле. После этого полученная последовательность компилируется и выполняется.
В общем виде, программа на M++ имеет следующую структуру:
программа ::= <декларация> | <оператор> | <программа> <декларация> | <программа> <оператор> декларация ::= <декларация_строковой_переменной> | <деларация_переменной_вектора> оператор ::= <оператор_if> | <оператор_выражения> | <оператор_for_each> | <оператор_halt> | <пустой_оператор> | <составной_оператор>
Программа может состоять только из одних операторов или только из одних деклараций. Операторы и декларации могут следовать в любом порядке.
Программа на языке M++ формируются из символов, входящих в следующий алфавит:
'A' - 'Z' 'a' - 'z' '_' '0' - '9' '{' '}' '[' ']' '(' ')' '"' ',' ';' '=' '+' '&' '|' '!'
Лексическими единицами в M++ являются:
В языке M++ допускается применение двух типов комментариев: однострочных и многострочных. Однострочные комментарии начинаются символами // и заканчиваются в конце строки. Многострочные комментарии начинаются символами /* и заканчиваются символами */. Многострочные и однострочные комментарии могут быть вложенными друг в друга. Многострочные комментарии не могут быть вложенными.
Следующие ключевые слова в языке M++ зарезервированы и не могут использоваться в качестве идентификаторов:
else empty foreach halt if in string
В языке M++ существуют два способа записи строк: традиционный и способ записи verbatim-строк. Традиционный способ записи строк аналогичен способу записи строк в языках C/C++ (за исключением т.н. триграфов).
В традиционном способе записи строкой считается последовательность символов, заключенных в кавычки. Строки могут содержать следующие escape-последовательности: \n, \r, \t, \b, \", \\. Так же символы могут кодироваться шестнадцатиричными (escape-последовательность, начинающаяся с префикса \x, за которым следуют две шестнадцатиричные цифры) и восьмиричными (escape-последовательность, начинающаяся с префикса \ за которым следуют три восьмиричные цифры) значениями. Если строка не помещается на одной строке, ее можно продолжить на следующей строке поставив в конце строки символ обратной косой \ (за этим символом может находиться только переход на новую строку). Например:
string t; t = "String value"; t = "String value '2'"; t = "String value \"3\""; t = "c:\\home\\eao\\mxx-4"; t = "~/mxx-4/default.4xx"; t = "Multi\tline\nstring"; t = "Spec char\x02"; t = "String on \ multiple lines";
Примечание. Несколько расположенных друг за другом строк (например, "abc" "def" "ghi") считаются различными экземплярами лексического элемента строка. Они объединяются в одну строку только на этапе синтаксического анализа программы.
Verbatim-строка начинается символами #" и заканчивается символами "#. Все, что находится между этими символами (включая переходы на новую строку) обрабатывается препроцессором и преобразуется в традиционный способ записи строк. Verbatim-строки предназначены для представления в виде строки произвольных фрагментов текста. Например, следующая verbatim-строка:
string t; t = #" @echo off set MXX4=-i c:\home\eao\mxx-4\lib -d MSC set PATH=%path%;c:\home\eao\mxx-4 "c:\program files\far\far" "#;
Будет неявно преобразована препроцессором в следующую традиционную запись строки:
string t; t = "\n\ @echo off\n\ set MXX4=-i c:\\home\\eao\\mxx-4\\lib -d MSC\n\ set PATH=%path%;c:\\home\\eao\\mxx-4\n\ \"c:\\program files\\far\\far\"\n\ ";
В M++ переменные могут иметь два типа: "строка" и "вектор строк". Каждым элементом вектора строк является строка.
В M++ различаются самостоятельные переменные и переменные-итераторы. Самостоятельные переменные (далее просто переменные) создаются посредством деклараций, могут иметь тип "строка" или "вектор строк". Переменные-итераторы объявляются и создаются в рамках оператора for_each (7.8), имеют тип "строка" и служат для доступа к значениям переменной типа "вектор строк".
Областью видимости переменной является блок в котором она объявлена. Блоком является составной оператор. Для переменных-итераторов блоком является оператор for_each. Если переменная объявляется вне какого-либо блока, то она является глобальной переменной (для нее блоком является вся программа).
Время жизни переменной ограничено временем жизни блока. Переменные создаются при объявлении и разрушаются при выходе из блока, в котором объявлены.
Имя переменной должно быть уникально в рамках блока. Локальная переменная скрывает видимость глобальной переменной с таким же именем. Т.е., если внутри блока объявляется переменная с именем, которое уже использовано для переменной в объемлющем блоке, то обращение к этому имени в блоке будет означать обращение к переменной данного блока, а не объемлющего.
Формат:
декларация_строковой_переменной ::= string <идентификатор>;
Имя переменной должно быть уникально в рамках блока.
Примечание. В текущей версии языка M++ переменные должны объявляться по одной. При объявлении нельзя сразу присвоить значение переменной.
Пример:
string a; string b;
Формат:
деларация_переменной_вектора ::= string[] <идентификатор>;
Имя переменной должно быть уникально в рамках блока.
Примечание. В текущей версии языка M++ переменные должны объявляться по одной. При объявлении нельзя сразу присвоить значение переменной.
Пример:
string[] c; string[] d;
Выражение - это сочетание одной или более констант, переменных, вызовов функций выполненное посредством нуля или более операций.
Результат вычисления каждого выражения имеет тип строка или вектор строк, в зависимости от применяемых в нем констант и/или переменных.
Результатом выражения является значение-строка, значение вектор или специальное значение empty. Значение empty означает отсутствие значения (т.е. пустая строка, пустой вектор). Значение empty совместимо как с типом строка, так и с типом вектор строк. Реальный тип значения empty автоматически определяется в зависимости от типа выражения.
Формат
<выражение> ::= <выражение_присваивания> | <условное_выражение> <выражение_присваивания> ::= <идентификатор> = <выражение> | <идентификатор> += <выражение> <условное_выражение> ::= <условное_выражение_И> | <условное_выражение> || <условное_выражение_И> <условное_выражение_И> ::= <выражение_равенства> | <условное_выражение_И> && <выражение_равенства> <выражение_равенства> ::= <выражение_сложения> | <выражение_равенства> == <выражение_сложения> | <выражение_равенства> != <выражение_сложения> <выражение_сложения> ::= <унарное_выражение> | <выражение_сложения> + <унарное_выражение> <унарное_выражение> ::= <первичное_выражение> | ! <унарное_выражение> <первичное_выражение> ::= <идентификатор> | <строка> | <вызов_функции> | <выражение_вектор> | empty | ( <выражение> ) <вызов_функции> ::= <идентификатор> () | <идентификатор> ( <параметры_функции> ) <параметры_функции> ::= <выражение> | <параметры_функции> , <выражение> <вражение_вектор> ::= [] | [ <значения_выражения_вектора> ] ; <значения_выражения_вектора> ::= <выражение> | <значения_выражения_вектора> , <выражение>
Примечание. Особенностью данной грамматики является то, что правым операндом любой бинарной операции, а так же операндом унарной операции, должно быть выражение с операцией более высокого приоритета (7.5.5). Так, нельзя, чтобы правым операндом операции сложения была операция присваивания, например, d = a + b = "3". В языке M++ это вызовет синтаксическую ошибку (в языке C++ это так же ошибка, но диагностируемая как необходимость иметь l-value в левой части операции присваивания). Для преодоления этого необходимо заключать менее приоритетные операции в скобки: d = a + ( b = "3" ).
Приоритеты операций в языке M++ распределены следующим образом (в порядке убывания приоритета):
() вызов функции
Операции присваивания (= и +=) вычисляются справа налево. Остальные операции вычисляются слева направо. Для изменения порядка вычисления элементов выражения, необходимо использовать скобки (). Например, в результате вычисления:
string a; string b; string c; b = ( c = ( a = "begin" ) + " - " ) + ( a += ", end" );
Будет получено:
b: "begin - begin, end" c: "begin -" a: "begin, end"
Формат:
<оператор_if> ::= if( <выражение> ) <оператор1> | if( <выражение> ) <оператор1> else <оператор2>
Вычисляет значение <выражения> и, если оно отлично от empty, выполняет <оператор1>. Если значение <выражения> равно empty и используется необязательная часть else, то выполняется <оператор2>.
Формат:
<оператор_выражения> ::= <выражение> ;
Вычисляет значение <выражения>.
В менее формальном виде можно сказать, что частными случаями выражений являются операции присваивания, конкатенации и присваивания, вызова функций. Приведенная выше формальная запись означает, что эти операции можно оформлять в программе в виде отдельных операторов, например:
a = "a" + "b"; c += [ a, "b", "c" ]; io_print( a + "\n" );
Формат:
<оператор_foreach> ::= foreach( <идентификатор> in <выражение> ) <оператор>
Создает переменную с именем <идентификатор> и выполняет цикл по всем значениям <выражение>. На каждом проходе цикла переменная <идентификатор> получает очередное значение и выполняется <оператор>. <выражение> должно иметь тип вектор строк. Если значение <выражение> равно empty, то цикл не выполняется ни разу. После завершения цикла переменная <идентификатор> уничтожается (т.е. блоком для переменной является оператор foreach).
Оператор foreach является единственным способом работы с одиночными значениями векторов строк в языке M++.
Пример.
string[] v; v = [ "1", "2", [ "3", "4" ], "5" ]; // Распечатываем все элемены вектора foreach( i in v ) io_print( i + "\n" ); // Распечатываем все значения выражения-вектора foreach( i in [ "1", "2" ] + [ "2", "3" ] + [ "3", "4" ] ) io_print( i + "\n" );
Примечание. Оператор foreach выполняет цикл по значениям выражения, которое вычисляется перед началом цикла. После этого выражение заново не вычисляется. Поэтому, изменение в цикле значений переменных, входивших в выражение, не влияет на продолжительность цикла. Например, в следующем случае цикл будет выполнен пять раз, не взирая на изменение переменной vect:
string[] vect; vect = [ "1", "2", "3", "4", "5" ]; foreach( v in vect ) { if( "3" == v ) vect = [ "1", "2", "3", "4" ]; io_print( v + "\n" ); }
Формат:
<оператор_halt> ::= halt;
Завершает выполнение программы.
Формат:
<пустой_оператор> ::= ;
Ничего не выполняет. Предназначен для случаев, когда синтаксис языка требует наличие оператора, но никаких действий выполнять не требуется.
Формат:
<составной_оператор> ::= {} | { <тело_составного_оператора> } <тело_составного_оператора> ::= <оператор> | <декларация> | <тело_составного_оператора> <оператор> | <тело_составного_оператора> <декларация>
Определяет новый блок программы, в котором может определяться собственный список переменных. Предназначен для случаев, когда синтаксис языка требует оператор, а нужно указать несколько операторов.
Примечание. Препроцессор языка M++ является сильно упрощенным аналогом препроцессора языка C++. Поэтому здесь дается только краткая сводка грамматики препроцессора M++.
#include <имя_файла> #include expression #define ID expressionopt #undef ID #error expression #warrning expression if: if_part elif_partopt else_partopt endif_part if_part: #if cond_expr elif_part: #elif cond_expr else_part: #else endif_part: #endif expression: ID | STRING | expression ID | expression STRING cond_expr: primary_cond_expr | cond_expr || cond_expr | cond_expr && cond_expr primary_cond_expr: is_defined | ! is_defined | ( cond_expr ) | !( cond_expr ) is_defined: defined( ID )