M++ v.4 предоставляет готовые шаблоны для управления компиляцией проектов на языке программирования C++ и для проектов, которые являются совокупностью отдельных подпроектов. Использование этих шаблонов требует минимального знания особенностей языка M++ и способов запуска утилиты mxxc.
M++ является интерпритатором собственного языка программирования, а проект рассматривается как частный случай программы на языке M++. Программой является последовательность инструкций языка M++, изложенная в одном файле.
Интерпритатором языка M++ является утилита mxxc. Ей в командной строке передается имя исходного файла с программой, а mxxc последовательно выполняет инструкции, хранящиеся в этом файле. При достижении конца файла выполнение программы завершается.
Утилита mxxc принимает в качестве аргумента имя только одного исходного файла. Если программа находится в нескольких исходных файлах, то их нужно подключить в один исходный файл при помощи инструкций препроцессора #include. Инструкции #include указывают mxxc сначала перенести весь код из подключаемых файлов в один включающий файл (имя которого передается аргументом для mxxc) и только после этого выполнить получившуюся программу.
Эта особенность использована в шаблонах управления проектами. Шаблоны - это набор файлов, которые должны быть подключены в один проектный файл. Имя проектного файла передается утилите mxxc, после чего mxxc начинает исполнять программу, которой является проект.
Шаблоны M++ состоят из двух групп подключаемых файлов. Первая группа предназначена для подключения в самом начале файла проекта. Файлы первой группы объявляют набор переменных, присваивание значений которым определяет содержимое проекта. Вторая группа файлов предназначена для подключения в самом конце файла проекта. В них проверяются значения переменных, объявленных в файлах первой группы, осуществляется построение make-правил и выполняются действия по управлению проектом. Разделение шаблона на две части необходимо из-за того, что язык M++ является типизированным и каждая переменная перед использованием должна быть объявлена. Использование шаблона было продемонстрировано на проектном файле для примера "Hello, world":
// Подключение первой части шаблона для проектов C++ #include <start.4xx> // Описание проекта, выполненное в виде программы на языке M++ appMode = "exe"; screenMode = "console"; target = "hello"; cppSource += "hello.cpp"; // Подключение второй части шаблона для проектов C++ #include <finish.4xx>
Аналогичным образом оформляются и более сложные проекты.
Сам проект, в общем случае, должен просто использовать объявленные в первой части шаблона переменные для указания типа модуля, его состава и т.д. Например, для проектов на языке C++ необходимо указать тип модуля (EXE-файл, статическая или динамическая библиотека), режим работы модуля (оконное или консольное приложение), имя получаемого модуля, исходные C++ файлы, подключаемые библиотеки и т.д. Некоторым переменным нужно обязательно присвоить значения (например, имя модуля, тип модуля, режим работы модуля). Некоторым переменным можно присваивать только определенные значения (например, переменная screenMode может получать значение "window" или "console").
После того, как в проекте будут присвоены необходимые значения шаблонным переменным необходимо подключить вторую часть шаблона. Она проверяет допустимость значений и наличие значений у переменных, которые должны быть определены обязательно. Если все нормально, то происходит генерация make-правил.
Для создания проектов с использованием M++ достаточно поверхносных знаний языка M++, а именно - особенностей типов переменных и способов присваивания значений переменным.
В M++ v.4 существует всего один тип атомарных значений - строки. Строковые значения могут иметь произвольную длину и оформляются практически так же, как в C/C++:
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";
Переменные в M++ v.4 могут иметь два типа - string (скалярная переменная типа строка) и string[] (вектор значений типа строка):
string scalar; string[] vector;
Переменные объявляются только по одной и без инициализации.
Скалярные переменные могут иметь только одно значение. Вектора могут иметь не ограниченный набор значений, упорядоченный в порядке занесения значений в вектор. Для занесения значений в переменную используется либо оператор присваивания "=", который полностью заменяет значение переменной, либо оператор дополнения "+=", который дописывает новое значение к значению переменной. Причем, для скаляра оператор "+=" является операцией конкатенации (склейки) строк. Для вектора оператор "+=" добавляет значения в конец вектора, не изменяя предыдущих значений элементов вектора.
string s; string[] v; s = "1234"; // s == "1234" s += "5678"; // s == "12345678 v = [ "12345" ]; // v = [ "12345" ] v += "67890"; // v = [ "12345", "67890" ] v += s; // v = [ "12345", "67890", "12345678" ] v += v; // v = [ "12345", "67890", "12345678", "12345", "67890", "12345678" ]
Для операции присваивания значения вектору посредством оператора "=", в качестве правого операнда нужно указать либо имя переменной-вектора, либо специальное значение-вектор. Значение-вектор - это указаный в квадратных скобках набор разделенных запятыми строковых значений, имен переменных (как скалярных, так и векторных) или других значений-векторов:
string s; string[] v; s = "a"; v = [ "a" ]; v += [ "b", "c", [ "d", s ], s, [ v ] ]; // v = [ "a", "b", "c", "d", "a", "a", "a" ]
В языке M++ есть специальное значение, аналогичное значению NULL в C/C++ - empty. Значение empty указывает отсутствие какого-либо значения. В момент своего создания переменная (скалярная или вектор) имеет значение empty. empty не может быть элементом вектора, хотя может указываться как элемент в значении-векторе. Если это происходит, то пустое значение в вектор не попадает:
string a; string b; string[] v; a = "a"; v += [ "b", v, "c", [ empty, a, b ], b, [ v ], "d" ]; // v = [ "b", "c", "a", "d" ]
Операторы "=" и "+=" являются частными случаями выражений. Так же в языке M++ выражением является оператор "+", который выполняет конкатенацию значений, но без изменения значений своих операндов. При этом, если оператор "+" применяется для скалярных значений, то результатом так же является скалярное значение. Если оператор "+" применяется для векторов, то результатом является значение-вектор.
Каждое выражение имеет значение. Это позволяет использовать операторы "=" и "+=" в качестве операндов для других операторов:
string a; string b; string[] v; a = "а"; // a = "a" b = a += "b"; // a = "ab", b = "ab" b = b + a + " " + a + empty + b; // b = "аbаb аbаb" v = [ b ] + [ " ", empty ] + [ a ]; // v = [ "аbаb аbаb", " ", "аb" ]
Учитывать особенности языка M++ необходимо, в основном, при присваивании значений векторным переменным. При этом необходимо помнить, что:
#include <start.4xx> appMode = "exe"; screenMode = "console"; target = "my_target"; cppSource = [ "main.cpp", "file1.cpp", "file2.cpp" ]; #include <finish.4xx>
Он является полностью работоспособным, т.к. в стандарном подключаемом файле start.4xx переменной cppSource ничего не присваивается. Но, допустим, что с течением времени понадобилось добавить еще несколько файлов в проект:
#include <start.4xx> appMode = "exe"; screenMode = "console"; target = "my_target"; cppSource = [ "pre_main.cpp", "new_file2.cpp", new_file3.cpp" ]; cppSource = [ "main.cpp", "file1.cpp", "file2.cpp" ]; #include <finish.4xx>
В результате получится не работоспособный проект, т.к. второе присваивание значений переменной cppSource уничтожит результат первого присваивания. А если бы изначально применялся оператор "+=", то этого бы не произошло;
#include <start.4xx> appMode = "exe"; screenMode = "console"; target = "my_app"; cppSource += [ "main.cpp", "cmd_line_args.cpp", "cfg_file_reader.cpp", "compute.cpp", "show_results.cpp", "utils.cpp", #if !defined( RELEASE ) // отладочные средства "dbg_new.cpp", "dbg_out.cpp", "dbg_cfg_file.cpp" #endif ]; #include <finish.4xx>
Если при старте mxxc определить символ RELEASE, то возникнет синтаксическая ошибка, связанная с запятой после значения "utils.cpp". А если бы проект был записан, например, так:
#include <start.4xx> appMode = "exe"; screenMode = "console"; target = "my_app"; cppSource += "main.cpp"; cppSource += "cmd_line_args.cpp"; cppSource += "cfg_file_reader.cpp"; cppSource += "compute.cpp"; cppSource += "show_results.cpp"; cppSource += "utils.cpp"; #if !defined( RELEASE ) // отладочные средства cppSource += "dbg_new.cpp"; cppSource += "dbg_out.cpp"; cppSource += "dbg_cfg_file.cpp"; #endif #include <finish.4xx>
то ошибки не возникнет.