# mcpu-cpp `mcpu-cpp` — препроцессор языков программирования MCPU. Он является самостоятельным компонентом экосистемы LibMPU/LibMPUIO/LibMCPU и не привязан к названию одного конкретного языка: активный язык выбирается директивой `#lang`. Этот документ задаёт нормативное поведение `mcpu-cpp`: текстовую модель, директивы, macro engine, include pipeline, конфигурацию, диагностику и генерацию зависимостей для инструментов MCPU. ## 1. Текстовая модель Внешние исходные файлы и конфигурационные файлы имеют кодировку UTF-8. UTF-8 должен быть корректным. Для исходных программ проверка принадлежности символов диапазону UCS-2 выполняется после удаления комментариев: поэтому корректный Unicode scalar value выше `U+FFFF` допустим внутри комментария, но остаётся ошибкой в программном тексте. После этой стадии исходный текст обрабатывается как последовательность `__mpu_char16_t`. Входной UTF-8 BOM допускается и удаляется. Встроенный NUL в исходном файле запрещён. Переводы строк `CRLF` и `CR` нормализуются в `LF`. ## 2. Действия, выполняемые независимо от директив `mcpu-cpp` выполняет несколько преобразований до разбора директив. ### 2.1. Backslash-newline Последовательность `\\` непосредственно перед переводом строки удаляется до распознавания комментариев, директив и макросов. Поэтому, например, ```text #defi\ ne FOO 10\ 20 ``` эквивалентно логической строке ```text #define FOO 1020 ``` При этом физические номера строк продолжают учитываться при формировании текущей позиции; если пользователь не менял её директивой `#line`, они и будут видны в генерируемых line marker-ах. ### 2.2. Комментарии Комментарии `/* ... */` и `// ...` удаляются до последующей обработки. Там, где это необходимо для разделения соседних токенов, сохраняется пробельный разделитель. Если комментарий завершает непустую строку, после его удаления не сохраняются ни синтетический разделитель, ни пробелы, предшествовавшие комментарию: строка заканчивается последним значащим символом. Это относится и к многострочному комментарию, начавшемуся после программного текста. Если после удаления комментария строка вообще не содержит ничего кроме пробелов, она становится действительно пустой строкой. При этом комментарий между двумя токенами по-прежнему оставляет необходимый разделитель и не склеивает их. Переводы строк сохраняются, чтобы не разрушать координаты исходного текста. Комментарий не распознаётся внутри строковой или символьной константы. Для языка `diff` апостроф не считается началом символьной константы, поскольку используется в обозначениях производных. В буквальном аргументе `#include <...>` последовательности `/*` и `//` рассматриваются как часть имени файла. ## 3. Директивы и выходной поток Директива начинается символом `#`, если до него в логической строке находятся только пробельные символы или комментарии. Между `#` и именем директивы допускаются пробелы. Служебная информация о позиции в выходном потоке представлена в форме GNU **line marker**: ```text # номер "имя-файла" [флаги] ``` Это не входная директива `#line`. При входе во включаемый файл к line marker-у добавляется флаг `1`, а при возврате в файл, содержащий `#include`, — флаг `2`. Эти значения имеют тот же смысл, что и в GNU CPP: `1` означает вход в новый файл, `2` — возврат в предыдущий файл. Флаг `2` не является числом или уровнем вложенности. Например: ```text # 1 "main.c" # 1 "defs.h" 1 ... # 2 "main.c" 2 ``` Входная директива ```text #line 62 "main.y" ``` сама в выходной поток не копируется. Она изменяет логические значения `__LINE__` и `__FILE__` для последующего текста, а в выходе представляется line marker-ом: ```text # 62 "main.y" ``` Аргументы `#line` предварительно подвергаются macro expansion, как в принятой модели line control. Если после такого `#line` происходит `#include`, то после возврата marker получает флаг `2`, например `# 65 "main.y" 2`. Имя, заданное через `#line`, становится логическим именем для `__FILE__` и line marker-ов; оно не меняет каталог, относительно которого ищется quoted `#include`. Директивы препроцессора имеют только канонические английские имена. Unicode остаётся полностью допустимым в идентификаторах, строках, комментариях и другом пользовательском тексте. ## 4. Заголовочные файлы Поддерживаются: ```text #include "file" #include #include_next "file" #include_next #pragma once ``` Для обычного `#include "file"` первым всегда проверяется каталог **физического** текущего исходного файла. Логическое имя, установленное через `#line`, на этот шаг не влияет. Для `#include ` каталог текущего файла не проверяется. ### 4.1. Перемещаемый корень MCPU как общий принцип экосистемы Начиная с выпуска 0.0.37 каталог установки MCPU **не содержит версию конкретного инструмента** и не является абсолютной runtime-константой, зашитой в бинарный файл. Версия относится к самому `mcpu-cpp`, `mcpu-as`, `mcpu-ld`, `mcpu-run` или библиотеке, но не определяет корень единой среды MCPU. При типичной конфигурации: ```text ./configure --prefix=/usr --libdir=/usr/lib64 ``` `make install` создаёт дерево: ```text /usr/lib64/mcpu/ ├── bin/ │ └── mcpu-cpp ├── etc/ │ └── mcpu-cpp.conf ├── include/ │ ├── diff/ │ ├── dift/ │ ├── alg/ │ ├── as/ │ ├── avm/ │ └── acs/ └── lib/ # общий каталог будущих библиотек MCPU ``` Публичное имя программы находится в `$bindir`: ```text /usr/bin/mcpu-cpp -> ../lib64/mcpu/bin/mcpu-cpp ``` Абсолютный `/usr/lib64/mcpu` при этом **не является частью runtime ABI MCPU-CPP**. Он используется только `make install` как выбранное configure-time место размещения файлов. При каждом обычном запуске MCPU-CPP определяет фактический путь собственного исполняемого файла через Linux `/proc/self/exe`. Символическая ссылка публичной команды не мешает этому: `/proc/self/exe` указывает на реально выполняемый бинарный файл. Если `/proc/self/exe` недоступен, используется резервное разрешение `argv[0]` через `PATH` и `realpath(3)`; возврата к зашитому configure-time installation root нет. Для бинарного файла: ```text /bin/mcpu-cpp ``` runtime-корень определяется как: ```text executable = /bin/mcpu-cpp executable dir = /bin MCPU runtime root = ``` Из него автоматически выводятся: ```text /etc/mcpu-cpp.conf /include ``` Следовательно всё дерево можно физически перенести, например из: ```text /usr/lib64/mcpu/ ``` в: ```text /opt/mcpu-test/ ``` или: ```text $HOME/devel/mcpu-next/ ``` и `/bin/mcpu-cpp` без переконфигурирования начнёт использовать `/etc/mcpu-cpp.conf` и `/include`. Старый абсолютный путь не сохраняется ни в runtime default, ни в штатном `mcpu-cpp.conf`. Это не частная особенность препроцессора, а **общий принцип экосистемы MCPU**. Будущие `mcpu-as`, `mcpu-ld`, `mcpu-run`, библиотеки, CRT и другие компоненты должны разделять один перемещаемый корень: ```text /bin /etc /include /lib ``` Их собственные версии могут отличаться, но согласованность конкретной среды MCPU определяется тем, что все компоненты находятся в одном runtime tree, а не совпадением version suffix в именах каталогов. ### 4.2. Runtime defaults, уровни конфигурации и системный include root До чтения любого конфигурационного файла MCPU-CPP создаёт runtime-derived значение: ```text MCPU_CPP_SYSTEM_INCLUDE_PATH = /include ``` После этого конфигурационные слои применяются в порядке возрастающего приоритета: ```text runtime-derived defaults ↓ /etc/mcpu-cpp.conf ↓ /etc/mcpu/mcpu-cpp.conf ↓ $HOME/.mcpu/etc/mcpu-cpp.conf ``` `/etc/mcpu-cpp.conf` устанавливается вместе с MCPU-CPP, но сам файл намеренно не содержит абсолютного штатного `MCPU_CPP_SYSTEM_INCLUDE_PATH`: иначе перенос всего дерева восстановил бы старый путь. `/etc/mcpu/mcpu-cpp.conf` является необязательным machine-wide override: `make install` каталог `/etc/mcpu` не создаёт. Домашний `$HOME/.mcpu/etc/mcpu-cpp.conf` также необязателен, не версионируется и имеет максимальный config-приоритет. Если одна переменная определена несколько раз, побеждает последнее определение, включая пустое. Поэтому `MCPU_CPP_SYSTEM_INCLUDE_PATH` остаётся полностью заменяемым system root. Например: ```text MCPU_CPP_SYSTEM_INCLUDE_PATH = $HOME/mcpu-next/include; ``` полностью заменяет runtime-derived `/include`. Для активного `#lang "as"` тогда проверяются: ```text $HOME/mcpu-next/include/as $HOME/mcpu-next/include ``` Штатные language-подкаталоги всегда выводятся самим препроцессором из одного root; переменных вида `MCPU_CPP_SYSTEM__INCLUDE_PATH` нет. Пустое effective значение: ```text MCPU_CPP_SYSTEM_INCLUDE_PATH = ; ``` удаляет configured system stage полностью. Более приоритетный config может после этого снова включить его непустым значением. `--config-file FILE` применяет явно выбранный файл поверх runtime-derived default. `--no-config` отключает **только чтение файлов конфигурации**: `/etc/mcpu-cpp.conf`, `/etc/mcpu/mcpu-cpp.conf` и `$HOME/.mcpu/etc/mcpu-cpp.conf` не читаются, но `/include` остаётся штатным system root. `--sys-root=PATH` является более сильным command-line override. Он считает `PATH` системным корнем MCPU, формирует runtime-derived `MCPU_CPP_SYSTEM_INCLUDE_PATH` как `PATH/include` и неявно отключает чтение всех configuration files, включая явно заданный `--config-file`. `PATH` может быть абсолютным или относительным. Относительное значение разрешается относительно рабочего каталога, из которого была запущена команда, до формирования effective include root. Это позволяет для одного запуска выбрать другое полное дерево MCPU без изменения установленного дерева и persistent configuration. Только `-nostdinc` удаляет effective standard-system tree из include search для конкретного запуска; явно переданный `-isystem` при этом остаётся command-line каталогом. ### 4.3. Нормативный порядок поиска include-файлов Порядок поиска является частью контракта MCPU-CPP. Явно заданные параметры командной строки имеют приоритет над persistent configuration. После необязательного каталога текущего физического файла эффективная цепочка имеет строго следующий вид: ```text explicit -I ↓ explicit -isystem ↓ MCPU_CPP__INCLUDE_PATH ↓ MCPU_CPP_INCLUDE_PATH ↓ MCPU_CPP_SYSTEM_INCLUDE_PATH/ ↓ MCPU_CPP_SYSTEM_INCLUDE_PATH ↓ explicit -idirafter ↓ MCPU_CPP_AFTER_INCLUDE_PATH ``` Элементы, которых нет или которые не содержат требуемого файла, пропускаются. `MCPU_CPP__INCLUDE_PATH` — свободно настраиваемые пользователем language-specific path-list'ы: ```text MCPU_CPP_DIFF_INCLUDE_PATH MCPU_CPP_DIFT_INCLUDE_PATH MCPU_CPP_ALG_INCLUDE_PATH MCPU_CPP_AS_INCLUDE_PATH MCPU_CPP_AVM_INCLUDE_PATH MCPU_CPP_ACS_INCLUDE_PATH ``` Пользователь полностью распоряжается именами и расположением этих каталогов. `MCPU_CPP_INCLUDE_PATH` — общий пользовательский path-list, видимый во всех языковых состояниях. `-idirafter` и `MCPU_CPP_AFTER_INCLUDE_PATH` являются общим fallback-карманом. MCPU-CPP не строит для них автоматических ``-подкаталогов. Пользователь сам организует их внутреннюю структуру и при необходимости пишет, например: ```text #include ``` Именно semantic class, а не порядок появления разных классов в argv/config, определяет приоритет. Внутри одного класса сохраняется порядок добавления. ### 4.4. `#include_next` и wrapper headers `#include_next` предназначен прежде всего для заголовков-обёрток (wrapper headers). Он позволяет поставить локальный header раньше системного, изменить локальную политику и затем продолжить поиск одноимённого header по нормативной цепочке без копирования системного файла и без абсолютного имени. Например: ```text mcpu-cpp -isystem $HOME/mcpu-wrapper ... ``` и `$HOME/mcpu-wrapper/math.h`: ```text #ifndef SOME_SYSTEM_MACRO #define SOME_SYSTEM_MACRO temporary_value #define REMOVE_SOME_SYSTEM_MACRO 1 #endif #include_next #ifdef REMOVE_SOME_SYSTEM_MACRO #undef SOME_SYSTEM_MACRO #undef REMOVE_SOME_SYSTEM_MACRO #endif ``` Если домашний config одновременно задаёт: ```text MCPU_CPP_SYSTEM_INCLUDE_PATH = $HOME/mcpu-next/include; ``` wrapper найденный через `-isystem` продолжит `#include_next` уже через configured user paths, затем через `$HOME/mcpu-next/include/` и `$HOME/mcpu-next/include`; старое system tree исходного места установки при этом не участвует. Именно такой сценарий позволяет системному разработчику или тестеру жить в собственной sandbox. MCPU-CPP хранит конкретный **физический элемент effective search chain**, из которого найден текущий header. `#include_next` начинает со следующего элемента. Формы `"file"` и `` для `#include_next` эквивалентны; каталог текущего файла повторно не проверяется. Если текущий файл найден обычным quoted-поиском относительно содержащего файла и не имеет search-chain provenance, `#include_next` начинает с первого элемента configured chain. Операнд может быть получен macro expansion. Логическое имя после `#line` не влияет на физический provenance. Если после текущего entry подходящего файла нет, preprocessing завершается ошибкой. ### 4.5. `#pragma once` Активная директива ```text #pragma once ``` помечает **физический файл** как уже обработанный в текущем запуске MCPU-CPP. При последующей попытке включить тот же физический файл его содержимое повторно не обрабатывается. Сама директива потребляется препроцессором и в выходной поток не копируется, в том числе при `-dD`. Идентичность определяется по паре `st_dev`/`st_ino`, полученной файловой системой, а не по строковому имени пути. Поэтому один и тот же файл не может обойти `#pragma once`, если он достигнут как `./file.h`, через символическую ссылку или через другое жёсткое имя (hard link). Логическое имя после `#line` также не влияет на эту физическую идентичность. Пометка действует сразу в момент обработки активной директивы. Поэтому заголовок может после `#pragma once` включить самого себя: повторное включение будет пропущено и рекурсия не возникнет. Директива внутри неактивной ветви условной компиляции никакого действия не имеет. MCPU-CPP распознаёт только точную форму `#pragma once` с необязательными пробелами. Остальные `#pragma` не интерпретируются препроцессором и сохраняются для последующих стадий компиляции; например, `#pragma pack(...)` продолжает передаваться в выходной поток. `#pragma once` дополняет, но не изменяет нормативную search-chain `#include`/`#include_next`: сначала обычный механизм поиска находит физический файл, затем registry `once` решает, надо ли обрабатывать его содержимое. ### 4.6. Принудительные файлы: `-imacros FILE` и `-include FILE` Опции командной строки ```text -imacros FILE -include FILE ``` обрабатывают файл до главного input. Они используют обычный preprocessing engine, а не отдельный облегчённый parser. Нормативный порядок начала translation unit: ```text predefined macros -> -D/-U в порядке командной строки -> все -imacros в порядке командной строки -> все -include в порядке командной строки -> главный input ``` Таким образом, взаимное расположение `-imacros` и `-include` в `argv` не перемешивает эти две группы: **все** `-imacros` всегда выполняются раньше **всех** `-include`. `-imacros FILE` полностью обрабатывает файл: его `#define`/`#undef`, условные директивы, `#lang`/`#endlang`, `#include`, `#include_next`, `#pragma once` и диагностика имеют обычную семантику. Однако весь normal preprocessing output этого forced-файла, включая line markers и текст вложенных headers, отбрасывается. Полученное состояние macro table и других preprocessing-механизмов сохраняется для последующих forced-файлов и главного input. `-include FILE` использует тот же механизм, но normal output сохраняется, как если бы найденный header был включён непосредственно перед главным source. Forced include является настоящей include-границей: внутри него `__INCLUDE_LEVEL__ == 1`, внутри включённого им header уровень равен `2`, а главный input остаётся на уровне `0`. `__BASE_FILE__` внутри forced-файлов остаётся именем главного input. Абсолютный operand forced-файла используется непосредственно. Относительный operand ищется сначала в **current working directory**, затем по обычной include-chain: ```text explicit -I explicit -isystem MCPU_CPP__INCLUDE_PATH MCPU_CPP_INCLUDE_PATH MCPU_CPP_SYSTEM_INCLUDE_PATH/ MCPU_CPP_SYSTEM_INCLUDE_PATH explicit -idirafter MCPU_CPP_AFTER_INCLUDE_PATH ``` Каталог главного input не получает специального приоритета при поиске operand `-imacros`/`-include`. После нахождения forced-файла обычный quoted `#include "file"` внутри него снова разрешается относительно физического каталога этого файла. Если forced-файл найден через элемент include-chain, его provenance сохраняется и `#include_next` продолжает поиск со следующего элемента цепочки. Forced-файлы и реально достигнутые из них headers входят в обычный physical dependency registry. Их user/system classification определяется тем же search provenance, поэтому `-MM`/`-MMD` фильтруют system forced headers так же, как обычные system headers. Отсутствующий forced-файл является ошибкой. ### 4.7. Генерация зависимостей: `-M`, `-MM`, `-MG`, `-MD`, `-MMD`, `-MF`, `-MT`, `-MQ` Опции `-M` и `-MM` используют **тот же самый проход include pipeline**, что и обычная preprocessing. Отдельного повторного поиска заголовков не выполняется. Поэтому dependency graph автоматически наследует нормативный порядок путей, `#include_next`, macro-expanded include operands, conditional compilation и `#pragma once`. `-M` подавляет обычный preprocessing output и выводит одно правило Make: ```make file.o: file.c header1.h header2.h ``` В список входят главный source-файл и все реально достигнутые физические headers, включая system headers. Один физический файл записывается один раз; идентичность определяется как `st_dev + st_ino`, поэтому другое относительное имя, symbolic link или hard link не создают дополнительную dependency. Имя, назначенное директивой `#line`, является только logical source name и в dependency list не попадает. `-MM` строит тот же граф, но исключает system dependencies. System-контекстом считаются headers, найденные через explicit `-isystem`, configured system tree `MCPU_CPP_SYSTEM_INCLUDE_PATH/` / `MCPU_CPP_SYSTEM_INCLUDE_PATH`, explicit `-idirafter` и `MCPU_CPP_AFTER_INCLUDE_PATH`, а также вся ветвь headers, включённая непосредственно или косвенно из такого system header. Форма `#include "file"` или `#include ` сама по себе не определяет system-ness. Если один и тот же физический файл был достигнут из system-ветви, но затем также включён непосредственно из user-контекста, он остаётся пользовательской dependency и присутствует в `-MM`. Default target образуется из basename главного source-файла: его suffix заменяется object suffix (`.o` по умолчанию). Пути и target экранируются для Make. Для stdin используется GNU-подобная форма `-: -`. `-MD` и `-MMD` используют тот же dependency graph, но, в отличие от `-M` и `-MM`, **не подавляют обычный preprocessing output**. `-MD` включает system headers, как `-M`; `-MMD` применяет user-only фильтр `-MM`. Это позволяет одним проходом получить и препроцессированный текст, и side-effect dependency file. Если `-MF` не задан, side-effect режим выбирает имя `.d` автоматически: * без `-o` из basename входного файла удаляется suffix и добавляется `.d`; каталоги входного pathname в имя dependency-файла не переносятся; * при обычном `-o FILE` suffix output-файла заменяется на `.d`; * для stdin используется имя `-.d`. `-MF FILE` переопределяет автоматическое имя dependency-файла. Значение `-MF -` означает stdout. `-MF` работает также с dependency-only `-M`/`-MM`; в этом случае оно имеет приоритет над обычным destination для make-rule. Само по себе `-MF` без одного из `-M`, `-MM`, `-MD`, `-MMD` является ошибкой. Семантика намеренно следует GNU CPP: `-MD`/`-MMD` не принимают собственный аргумент, а `-MF` является отдельной опцией назначения dependency output. `-MT TARGET` заменяет автоматический target правила строкой `TARGET` **точно как она передана**. Make quoting при этом не выполняется. Поэтому один argument `-MT` может сам содержать несколько targets, разделённых пробелами: ```text -MT 'obj/a.o obj/a.pic.o' ``` и повторные `-MT` также добавляют targets одного и того же правила: ```text -MT obj/a.o -MT obj/a.pic.o ``` `-MQ TARGET` имеет ту же семантику выбора target, но экранирует специальные для Make символы. Например, ```text -MQ '$(OBJDIR)/foo.o' ``` даёт левую часть правила: ```make $$(OBJDIR)/foo.o: ``` Поддерживаются как отдельные arguments (`-MT TARGET`, `-MQ TARGET`), так и attached forms (`-MTTARGET`, `-MQTARGET`). Если задан хотя бы один `-MT` или `-MQ`, автоматический default target не выводится. В частности, `--object-suffix` влияет только на автоматический target и не переписывает явно заданные targets. Если явных targets нет, default target экранируется для Make так же, как при `-MQ`. Разрешены повторные и смешанные `-MT`/`-MQ`. В соответствии с GNU CPP сначала выводятся все `-MT` targets в их command-line order, затем все `-MQ` targets в их command-line order. Все они образуют левую часть **одного** dependency rule. `-MT` и `-MQ` имеют смысл только вместе с одним из dependency-generation режимов `-M`, `-MM`, `-MD` или `-MMD`. Без такого режима это ошибка командной строки. `-MG` изменяет только обработку **отсутствующих** include-файлов при dependency-only режимах `-M` и `-MM`. Без `-MG` неразрешённый `#include` остаётся ошибкой. С `-M -MG` или `-MM -MG` отсутствующий header считается будущим generated file: preprocessing не завершается ошибкой, а operand директивы добавляется в dependency rule **ровно в том виде, который получен после macro expansion**, без приписывания предполагаемого include-directory. Например: ```text #include "generated.h" ``` при `-M -MG` добавляет dependency `generated.h`, даже если такого файла ещё нет. Macro-expanded include ведёт себя аналогично: dependency получает уже развёрнутое имя. `-MG` разрешён только вместе с `-M` или `-MM`; комбинации с `-MD`/`-MMD` и использование без dependency-only режима являются ошибкой командной строки. Unresolved dependencies интегрированы в **тот же упорядоченный dependency registry**, что и физически найденные файлы, но образуют отдельный identity domain. Для найденного файла registry по-прежнему использует `st_dev/st_ino` и physical provenance. Для отсутствующего файла этих данных нет, поэтому `-MG` entry не выполняет `stat()` и дедуплицируется по точному тексту include operand. Это принципиально: наличие одноимённого файла в CWD не должно превращать неразрешённый `` в ложное physical совпадение, если angle-search этот файл не находил. Разные unresolved spellings (`generated.h` и `./generated.h`) считаются разными dependencies. Для `-MM` unresolved dependency получает user/system class из контекста поиска: отсутствующий `` является system-class, отсутствующий `"file"` — user-class, если сама включающая единица не является system header; любой missing include, достигнутый из system header, остаётся system-class. При повторении одного и того же unresolved operand сохраняется классификация его первого появления, что соответствует GNU CPP. Физически найденные зависимости сохраняют прежнее правило: если один и тот же inode позднее достигается из user-контекста, он перестаёт быть system-only. `-MG` распространяется также на отсутствующие command-line forced files `-include FILE` и `-imacros FILE`: их operand заносится в unresolved registry как user dependency без синтетического search prefix. При наличии реального файла `-include`/`-imacros` продолжают использовать обычный physical dependency registry и search provenance. ## 5. Переключение языков Препроцессор запускается в состоянии `0`. Это безымянный основной C-подобный язык и он не является допустимым аргументом `#lang`. Допустимые языки: | Имя | Назначение | |---|---| | `diff` | дифференциальные уравнения | | `dift` | разностные уравнения | | `alg` | алгебраические уравнения | | `as` | MCPU assembler (`mcpu-as`) | | `avm` | схемы аналоговых вычислительных машин | | `ACS` | структурные схемы систем автоматического управления | После `#lang` обязательна строковая константа с одним непустым словом: ```text #lang "diff" ``` Имя проверяется только по внутреннему списку языков выше и сравнивается без учёта ASCII-регистра. Поэтому `"diff"`, `"Diff"`, `"DIFF"` и `"dIfF"` эквивалентны при выборе языка. Исходное написание внутри кавычек при этом сохраняется в выходном потоке. Пробелы внутри строковой константы запрещены: `" diff"`, `"diff "` и `"di ff"` являются ошибками. Escape-последовательности внутри неё не разбираются. Закрывающая кавычка обязана находиться на той же физической строке исходного файла. После неё до конца строки допустимы только пробельные символы. Внешние пробелы директивы нормализуются. Например: ```text # lang "DiFf" ``` превращается в: ```text #lang "DiFf" ``` `#lang` помещает новый язык в стек, `#endlang` восстанавливает предыдущий. Стек не сбрасывается при `#include`, поэтому начало и конец языкового блока могут находиться в разных файлах. Директивы `#lang` и `#endlang` сохраняются в выходном потоке для последующего frontend dispatcher; `#lang` сохраняется в нормализованной форме. ## 6. Простые макроопределения Начиная с 0.0.4 поддерживаются object-like macros: ```text #define BUFFER_SIZE 1024 #define NAME value #define EMPTY ``` Директива `#define` сама в выходной поток не попадает. В обычном тексте идентификатор-макро заменяется его replacement list. Replacement затем снова просматривается на макроимена, поэтому допускается каскадное раскрытие: ```text #define A B #define B 10 A ``` даёт `10`. Во время раскрытия конкретное макро временно блокируется. Поэтому самоссылочные и взаимно-рекурсивные определения не вызывают бесконечной рекурсии. Макроимена не раскрываются внутри строковых и символьных констант. Для `diff` апостроф сохраняет специальную языковую семантику и не защищает последующий текст как C character constant. Многострочное определение через backslash-newline поддерживается, поскольку splice выполняется раньше `#define`. ### 6.1. `#undef` ```text #undef NAME ``` удаляет object-like macro. Отмена несуществующего определения не является ошибкой. ### 6.2. Вычисляемый `#include` Аргумент `#include`, который не начинается непосредственно с `"` или `<`, сначала проходит macro expansion. Поэтому допустимо: ```text #define HEADER #include HEADER ``` или ```text #define HEADER "local.h" #include HEADER ``` Результат раскрытия обязан иметь форму `"file"` или ``. ## 7. Макро с аргументами Macro engine поддерживает function-like macros: ```text #define идентификатор( список аргументов ) текст ``` Открывающая скобка в определении должна идти **непосредственно** после имени макро. Поэтому ```text #define F(X) X ``` задаёт макро с аргументом, а ```text #define F (X) ``` задаёт простое object-like macro со строкой замены `(X)`. В месте использования между именем function-like macro и открывающей скобкой пробельные символы допустимы. Если `(` не следует, идентификатор не считается вызовом данного макро и остаётся в выходном тексте. Для обычного function-like macro число фактических аргументов должно совпадать с числом формальных. Для variadic macro должны присутствовать все фиксированные аргументы, а variadic tail может содержать произвольное число аргументов, включая пустой tail. При разборе списка фактических аргументов вложенные круглые скобки учитываются; запятая внутри них не разделяет аргументы. Квадратные скобки такого свойства не имеют — это является частью принятой семантики macro expansion. Например, ```text #define min(X, Y) ((X) < (Y) ? (X) : (Y)) min(1, 2) ``` даёт ```text ((1) < (2) ? (1) : (2)) ``` Перед подстановкой обычный фактический аргумент сам проходит macro expansion. Поэтому каскадные и вложенные вызовы работают естественно: ```text #define A 7 #define min(X, Y) ((X) < (Y) ? (X) : (Y)) min(min(A, 3), 10) ``` Формальный параметр может встречаться в replacement list произвольное число раз. Это означает, что выражение с побочным эффектом в фактическом аргументе также может быть вычислено несколько раз уже последующим компилятором; препроцессор не пытается исправлять такую программу. Поддерживаются макро без формальных параметров: ```text #define READY() 1 ``` Они раскрываются только как вызов `READY()` (пробел между именем и `(` при использовании допустим), но самостоятельный идентификатор `READY` не раскрывается. Имена формальных параметров должны быть различны. Незавершённый список, неверная пунктуация, недостаточное или избыточное число фактических аргументов диагностируются как ошибки. ### 7.1. Stringification `#` Поддерживается оператор stringification (`#`) для параметров function-like macro: ```text #define STR(X) #X STR(alpha + beta) ``` даёт ```text "alpha + beta" ``` Stringification использует **сырой фактический аргумент до macro expansion**. Поэтому: ```text #define A 7 #define STR(X) #X #define XSTR(X) STR(X) STR(A) -> "A" XSTR(A) -> "7" ``` Ведущие и завершающие пробелы аргумента удаляются. Последовательности пробельных символов внутри аргумента сворачиваются в один пробел, кроме пробелов внутри строковых/символьных токенов соответствующего активного языка. Двойные кавычки и обратные косые черты внутри quoted tokens экранируются так, чтобы результат оставался одной корректной строковой константой. Оператор `#` в replacement list function-like macro обязан непосредственно или через пробельные символы ссылаться на имя формального параметра. Внутри quoted token символ `#` оператором не является. Пустой фактический аргумент допустим и stringify-ится как `""`. ### 7.2. Token concatenation `##` Начиная с 0.0.21 поддерживается оператор token concatenation (`##`) в модели, согласованной с GNU CPP и механизмом `collect_expansion()` / `macroexpand()` macro engine. Оператор объединяет два соседних preprocessing token в один token, после чего получившийся replacement list снова проходит macro expansion. Например: ```text #define CAT(A, B) A ## B CAT(foo, bar) ``` даёт `foobar`. Склеивание может образовывать identifier, preprocessing number или многосимвольный punctuator. Поэтому, например, допустимы: ```text CAT(1.5, e3) -> 1.5e3 CAT(+, =) -> += ``` Если формальный параметр непосредственно примыкает к `##`, его фактический аргумент подставляется **без предварительного macro expansion**. Это тот же raw-argument принцип, который используется для stringification. Для получения сначала expansion, а затем concatenation применяется обычный двухуровневый приём GNU CPP: ```text #define AFTERX(X) X_ ## X #define XAFTERX(X) AFTERX(X) #define TABLESIZE 1024 #define BUFSIZE TABLESIZE AFTERX(BUFSIZE) -> X_BUFSIZE XAFTERX(BUFSIZE) -> X_1024 ``` Пустой фактический аргумент рядом с `##` ведёт себя как placemarker: сам по себе он не добавляет token, а `##` с такой стороны не изменяет оставшийся операнд. Если фактический аргумент содержит несколько preprocessing tokens, склеивается только крайний token, непосредственно соседний с `##`; остальные tokens сохраняются и затем участвуют в общем rescan. `#` и `##` могут использоваться в одном function-like macro, например: ```text #define COMMAND(NAME) #NAME | NAME ## _command ``` При этом `#NAME` использует raw spelling аргумента для stringification, а `NAME ## _command` — тот же raw argument для concatenation. `##` внутри quoted token оператором не является. Комментарии к моменту macro expansion уже заменены whitespace, поэтому они не могут быть созданы склеиванием `/` и `*`. Между `##` и его операндами исходно может находиться whitespace; при склеивании он не участвует. Если два операнда не образуют один допустимый preprocessing token, выдаётся диагностика, а сами исходные tokens сохраняются; наличие whitespace между ними после такой диагностики не является частью контракта. `##` в начале или в конце replacement list является ошибкой определения macro. ### 7.3. Variadic macros: `...` и `__VA_ARGS__` Начиная с 0.0.46 поддерживаются variadic function-like macros в современной C99-совместимой форме: ```text #define LOG(...) output(__VA_ARGS__) #define LOGF(format, ...) output(format, __VA_ARGS__) ``` Маркер `...` может быть единственным параметром либо последним элементом после одного или нескольких фиксированных параметров. Старое GNU-расширение с именованным variadic parameter ```text #define LOG(args...) ... ``` в 0.0.46 намеренно не поддерживается. `__VA_OPT__` также не является частью этого релиза. При вызове все tokens после последнего фиксированного параметра, включая разделяющие их запятые, образуют один logical variable argument и подставляются вместо `__VA_ARGS__`. В обычной позиции этот variable argument предварительно проходит macro expansion так же, как обычный фактический аргумент: ```text #define A 7 #define V(...) <__VA_ARGS__> #define F(first, ...) first | __VA_ARGS__ V(A, 2, 3) -> <7, 2, 3> F(1, A, 3) -> 1 | 7, 3 ``` Variadic tail может быть пустым. Поэтому оба вызова ```text F(1) F(1,) ``` допустимы и подставляют пустой `__VA_ARGS__`. Это **не** означает автоматическое удаление запятой, явно записанной в replacement list. Например для ```text #define E(format, ...) output(format, __VA_ARGS__) ``` вызов `E("ok")` оставляет запятую перед пустым tail. Специальная историческая GNU-семантика `, ## __VA_ARGS__`, удаляющая такую запятую, в контракт 0.0.46 не входит; если она понадобится, её следует вводить отдельным явно документированным расширением. `__VA_ARGS__` участвует в уже существующей семантике `#` и `##` как настоящий macro parameter. Stringification использует raw spelling всего variadic tail: ```text #define STRV(...) #__VA_ARGS__ STRV(A, b + c) -> "A, b + c" ``` При соседстве с `##` variadic argument также подставляется без prescan; затем работают обычные правила placemarker, token concatenation и общего rescan. Например: ```text #define L(...) pre ## __VA_ARGS__ #define R(...) __VA_ARGS__ ## post L(fix) -> prefix R(fix) -> fixpost ``` Если variadic argument содержит несколько preprocessing tokens, склеивается только крайний token, непосредственно соседний с `##`, а остальные tokens сохраняются, как и для обычного параметра. Пустой tail рядом с `##` ведёт себя как placemarker. Имя `__VA_ARGS__` зарезервировано для variable argument и не принимается как обычное имя формального параметра. Оператор `#__VA_ARGS__` допустим только в variadic macro. Dump-режимы сохраняют variadic форму определения, например: ```text #define F(first,...) first | __VA_ARGS__ ``` ### 7.4. `__VA_OPT__` Начиная с 0.0.47 variadic macros поддерживают стандартный условный fragment `__VA_OPT__(pp-tokens)`. Если variable argument после обычной macro substitution не содержит preprocessing tokens, весь `__VA_OPT__(...)` раскрывается в пустую последовательность. Если variable argument непуст, содержимое круглых скобок участвует в replacement list: ```text #define DEBUG(format, ...) \ fprintf(stderr, format __VA_OPT__(,) __VA_ARGS__) DEBUG("ready") -> fprintf(stderr, "ready") DEBUG("x=%d", x) -> fprintf(stderr, "x=%d", x) ``` Решение о непустоте принимается **после expansion variable argument**, а не по его исходному spelling. Поэтому macro, который сам раскрывается в пустую последовательность, не активирует `__VA_OPT__`: ```text #define EMPTY #define HAS(...) [__VA_OPT__(yes)] HAS() -> [] HAS(EMPTY) -> [] HAS(token) -> [yes] ``` Содержимое `__VA_OPT__` может включать сбалансированные вложенные круглые скобки. Закрывающая `)` самого `__VA_OPT__` определяется с учётом их вложенности. Вложенный `__VA_OPT__` внутри другого `__VA_OPT__` намеренно запрещён. `__VA_OPT__` интегрирован с существующими правилами parameter substitution, stringification, token concatenation, placemarker и rescan. Например: ```text #define X 123 #define S(...) #__VA_OPT__(__VA_ARGS__) #define L(...) pre ## __VA_OPT__(__VA_ARGS__) S() -> "" S(X) -> "123" L() -> pre L(X) -> pre123 ``` При `#__VA_OPT__(...)` сначала выполняется parameter substitution внутри fragment, включая prescan обычных параметров, но произвольные macro names самого fragment до stringification дополнительно не rescanning-ятся. Поэтому: ```text #define X 123 #define S(a, ...) #__VA_OPT__(a X) S(X, y) -> "123 X" ``` Если parameter внутри `__VA_OPT__` непосредственно участвует во внутреннем `##`, для него, как обычно, prescan подавляется; paste выполняется до дальнейшего rescan. Внешний `##`, соседний с `__VA_OPT__`, получает крайний token уже подготовленного fragment. Пустой результат `__VA_OPT__` рядом с `##` ведёт себя как placemarker. `__VA_OPT__` допустим только в replacement list variadic function-like macro и должен непосредственно задавать parenthesized fragment. `##` не может быть первым или последним preprocessing token внутри самого `__VA_OPT__`. Историческое GNU-расширение ```text , ## __VA_ARGS__ ``` в `mcpu-cpp` намеренно **не реализуется**. Для условной запятой следует использовать современную форму `__VA_OPT__(,)`. Старое GNU-расширение с именованным variadic parameter `args...` также остаётся неподдерживаемым. ### 7.5. Нормализация пробелов в replacement list Начиная с 0.0.48 `mcpu-cpp` не переносит в результат разворачивания служебное выравнивание многострочного macro. После удаления `\` + newline последовательность пробельных символов, принадлежащая самому replacement list, канонизируется в один ASCII-пробел. Это особенно важно для определений, где обратные косые черты визуально выровнены в одну колонку: ```text #define TRACE(x) \ do \ { \ output(x); \ done(); \ } \ while( 0 ) ``` При разворачивании такое определение выдаёт компактный replacement: ```text do { output(x); done(); } while( 0 ) ``` а не сохраняет десятки пробелов перед каждой бывшей границей физической строки. Нормализация относится **только к whitespace самого replacement list**. `mcpu-cpp` не является formatter-ом исходной программы: пробелы в обычном тексте input сохраняются. Пробелы внутри фактического macro argument также не переформатируются только потому, что argument был подставлен в macro: ```text #define ID(x) x ID(a + b) -> a + b ``` Содержимое string/character literals сохраняется буквально, поэтому: ```text #define S "left right" ``` по-прежнему содержит пять пробелов внутри строки. Наличие whitespace между preprocessing tokens сохраняется как один пробел. Это не позволяет случайно изменить tokenization, например превратить `+ +` в `++`, `- >` в `->` или `< <` в `<<`. Операторы `#` и `##`, placemarkers, `__VA_ARGS__`, `__VA_OPT__` и последующий rescan продолжают использовать свои существующие правила; новая политика меняет только количество обычного replacement-list whitespace. Dump-режимы (`-dM`, `-dD`) показывают ту же каноническую форму replacement list, которая хранится во внутренней таблице macro. ### 7.6. Компактификация невидимых строк и linemarkers Начиная с 0.0.49 `mcpu-cpp` использует для вертикального whitespace ту же модель, что GNU CPP: **удаляем, но не забываем**. Строки, которые после preprocessing не породили ни одного выводимого preprocessing token, не обязаны оставаться физическими пустыми строками в `.E`, однако их исходная позиция продолжает учитываться при построении linemarkers и значении `__LINE__`. Причина невидимости не имеет значения. Это могут быть удалённые directives, неактивные ветви `#if`, однострочные и многострочные comments, обычные пустые строки или их смесь. Emitter сравнивает текущую output source position с позицией следующей реально выдаваемой строки. Если следующая позиция находится менее чем через восемь строк, разрыв представляется обычными newline. Если расстояние равно восьми строкам или больше, вместо длинной последовательности пустых строк выдаётся корректирующий linemarker: ```text # N "file" ``` и следующая содержательная строка сразу относится к source line `N`. Таким образом граница поведения совместима с GNU CPP: gaps 0..7 сохраняются через newline, gap 8 и больше заменяется linemarker. Structural markers входа и возврата из include-файла сохраняют обычный смысл: ```text # 1 "header.h" 1 # 4 "source.c" 2 ``` Если included file не породил никакого output, `mcpu-cpp` не создаёт искусственный marker, сообщающий, до какой внутренней строки header дошёл препроцессор. После enter-marker сразу может следовать return-marker. Реальная позиция снова уточняется только тогда, когда требуется выдать следующий содержательный текст. Эта оптимизация меняет только представление output stream. Source coordinates, `__LINE__`, diagnostics, `#line`, include enter/return semantics и обработка macro остаются привязаны к исходному логическому потоку, а не к количеству физических строк в сжатом `.E`. ## 8. Предопределённые макро Начиная с 0.0.6 был перенесён исторический механизм predefined macros из препроцессора. Этот механизм оформлен как отдельный ABI/environment layer будущего безымянного C-подобного языка. Эти определения не являются декоративными: их имена и значения должны соответствовать либо семантике GNU CPP, либо явно документированному MCPU/LibMPU contract. ### 8.1. Динамические source macros Следующие predefined macros вычисляются в точке использования: | Макро | Раскрытие | |---|---| | `__FILE__` | строковая константа с именем текущего входного файла | | `__LINE__` | десятичный номер текущей строки | | `__BASE_FILE__` | строковая константа с именем главного входного файла translation unit | | `__INCLUDE_LEVEL__` | уровень вложенности `#include`; для главного файла равен `0` | | `__DATE__` | дата запуска препроцессора в форме `"Mmm dd yyyy"` | | `__TIME__` | время запуска препроцессора в форме `"hh:mm:ss"` | `__DATE__` и `__TIME__` получают один timestamp на весь translation unit. Специальное раскрытие помещается в output без повторного macro rescan. Эти имена находятся в общей macro table, поэтому `#undef` и последующий `#define` могут осознанно заменить builtin. ### 8.2. Версия препроцессора Начиная с 0.0.8 standalone preprocessor не определяет GCC-имя `__VERSION__`. Оно относится к compiler environment, которого для будущего high-level языка пока нет. Собственная версия `mcpu-cpp` имеет отдельное однозначное имя: ```text #define __MCPU_CPP_VERSION__ "1.0.3" ``` Значение автоматически берётся из `PACKAGE_VERSION`. Когда появится compiler frontend/driver, его version contract будет определён отдельно и не будет смешиваться с версией standalone preprocessor. ### 8.3. Источники истины ABI `mcpu-cpp` собирается только GNU GCC. Во время `configure` проект использует проверенные приёмы из `LibMPU`/`LibMPUIO` `acsite.m4`: GCC predefined macros определяют native type sizes, byte/word order и machine-register width, а установленный `` является окончательным источником настроек LibMPU. В частности, фиксируются и проверяются: ```text MPU_REAL_IO_LIMIT MPU_MATH_FN_LIMIT MPU_BYTE_ORDER MPU_WORD_ORDER BITS_PER_MACHINE_REGISTER BITS_PER_UNIT_T sizeof(__mpu_size_t) sizeof(__mpu_ptrdiff_t) ``` `configure` дополнительно проверяет, что byte order и `BITS_PER_MACHINE_REGISTER`, записанные в LibMPU, согласованы с GCC target, которым собирается `mcpu-cpp`. `MPU_WORD_ORDER` берётся непосредственно из configured LibMPU profile и описывает порядок слов MCPU data environment. Пределы `MPU_REAL_IO_LIMIT` и `MPU_MATH_FN_LIMIT` имеют разные назначения. Например, библиотека может иметь Real I/O до 65536 бит и математические функции только до 16384 бит. Поэтому `MPU_MATH_FN_LIMIT` не используется как предел существования типов Real. ### 8.4. MCPU architecture и assembler prefixes Целевая архитектура определяется макро: ```text #define _ARCH_MCPU 1 ``` MCPU PTR64 имеет ширину 64 бита, поэтому определены `__SIZEOF_POINTER__`, `__MCPU_POINTER_WIDTH__`, `__INTPTR_TYPE__`, `__UINTPTR_TYPE__`, соответствующие width/max macros. Смысл assembler-prefix macros согласован с GNU CPP, а не с первой буквой имени register view. В синтаксисе `mcpu-as` дополнительного sigil перед register, label или immediate нет. `r` и `c` являются частью MCPU register syntax, а не `REGISTER_PREFIX`. Поэтому: ```text #define __REGISTER_PREFIX__ #define __LOCAL_LABEL_PREFIX__ #define __USER_LABEL_PREFIX__ #define __IMMEDIATE_PREFIX__ ``` все четыре раскрываются в пустую последовательность. `.L...` остаётся compiler naming convention и не является assembler ABI local-label prefix: LOCAL/GLOBAL binding определяется symbol directives. ### 8.5. Byte order и word order Базовые числовые значения порядка байт совместимы с GNU CPP: ```text __ORDER_LITTLE_ENDIAN__ __ORDER_BIG_ENDIAN__ __ORDER_PDP_ENDIAN__ ``` Но целевая среда публикует собственные MCPU names: ```text #define __MCPU_BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__ #define __MCPU_WORD_ORDER__ __ORDER_LITTLE_ENDIAN__ #define __BYTE_ORDER__ __MCPU_BYTE_ORDER__ ``` Фактические значения `__MCPU_BYTE_ORDER__` и `__MCPU_WORD_ORDER__` получают из configured LibMPU profile (`MPU_BYTE_ORDER` и `MPU_WORD_ORDER`). Поэтому они следуют host data representation, с которой собрана LibMPU. Это не меняет отдельный архитектурный контракт кодировки MCPU instruction bytecode. GNU/C-specific имя `__FLOAT_WORD_ORDER__` не определяется: типа `float` в будущем языке MCPU нет. Параметры LibMPU/MCPU environment публикуются в MCPU namespace: ```text __MCPU_MACHINE_REGISTER_WIDTH__ __MCPU_REAL_IO_LIMIT__ __MCPU_MATH_FN_LIMIT__ __MCPU_INT_MAX_WIDTH__ __MCPU_REAL_MAX_WIDTH__ __MCPU_COMPLEX_MAX_WIDTH__ ``` `__MCPU_INT_MAX_WIDTH__` равен `NB_I_MAX * 8`, а Real/Complex maximum width равен configured `MPU_REAL_IO_LIMIT`. `__MCPU_MACHINE_REGISTER_WIDTH__` является значением `BITS_PER_MACHINE_REGISTER` установленной LibMPU. Пределы Real I/O и math functions не смешиваются: `MPU_REAL_IO_LIMIT` определяет существование Real/Complex type family и text conversion, а `MPU_MATH_FN_LIMIT` — наличие математических функций соответствующей ширины. ### 8.6. MCPU size/ssize, `ptrdiff` и pointers Будущий язык не наследует variable-width C names `short`, `int`, `long` и не использует C-style имя `size_t` как часть собственного ABI. Беззнаковый LibMPU size type и знаковый byte-count/error type публикуются симметрично в MCPU namespace. Например для 64-bit configured profile: ```text #define __MCPU_SIZE_TYPE__ uint64 #define __MCPU_SIZE_WIDTH__ 64 #define __MCPU_SIZEOF_SIZE__ 8 #define __MCPU_SIZE_MAX__ 0xffffffffffffffff #define __MCPU_SSIZE_TYPE__ int64 #define __MCPU_SSIZE_WIDTH__ 64 #define __MCPU_SIZEOF_SSIZE__ 8 #define __MCPU_SSIZE_MAX__ 0x7fffffffffffffff ``` Это MCPU-specific family, а не попытка приписать GNU CPP несуществующий стандартный `__SSIZE_*` contract. MCPU pointer ABI от host не зависит: PTR64 всегда имеет ширину 64 бита: ```text #define __INTPTR_TYPE__ int64 #define __UINTPTR_TYPE__ uint64 #define __INTPTR_WIDTH__ 64 #define __UINTPTR_WIDTH__ 64 #define __INTPTR_MAX__ 0x7fffffffffffffff #define __UINTPTR_MAX__ 0xffffffffffffffff #define __SIZEOF_POINTER__ 8 #define __MCPU_POINTER_WIDTH__ 64 ``` Разность MCPU pointers является знаковой и также фиксирована независимо от host: ```text #define __PTRDIFF_TYPE__ int64 #define __PTRDIFF_WIDTH__ 64 #define __SIZEOF_PTRDIFF__ 8 #define __PTRDIFF_MAX__ 0x7fffffffffffffff ``` Computed MIN expressions вроде `(-__PTRDIFF_MAX__ - 1)` в predefined table не создаются. ### 8.7. Character types Обычного C `char` в будущем языке нет. Поэтому `__CHAR_TYPE__` и `__WCHAR_TYPE__` не определяются. Типы языка называются без C/C++ suffix `_t`: ```text #define __CHAR8_TYPE__ char8 #define __CHAR16_TYPE__ char16 #define __CHAR8_WIDTH__ 8 #define __CHAR16_WIDTH__ 16 #define __SIZEOF_CHAR8__ 1 #define __SIZEOF_CHAR16__ 2 ``` Это типы будущего языка. Внутренняя реализация самого `mcpu-cpp` по-прежнему использует LibMPUIO `__mpu_char16_t` и strict UCS-2 text model. ### 8.8. Integer families LibMPU Полная structural metadata integer families строится не по жёстко записанному последнему типу, а до `NB_I_MAX * 8` фактически установленной LibMPU. Для каждой power-of-two ширины от 8 бит определяются TYPE, WIDTH и SIZEOF: ```text #define __INT1024_TYPE__ int1024 #define __UINT1024_TYPE__ uint1024 #define __INT1024_WIDTH__ 1024 #define __UINT1024_WIDTH__ 1024 #define __SIZEOF_INT1024__ 128 #define __SIZEOF_UINT1024__ 128 ``` На текущей LibMPU 1.0.35 `NB_I_MAX == 8192`, поэтому family доходит до `int65536`/`uint65536`, а `__SIZEOF_INT65536__ == 8192`. Decimal-digit metadata определяется для **каждой** разрешённой integer width: ```text __INT_DECIMAL_DIG__ __UINT_DECIMAL_DIG__ ``` Значение вычисляется собственными integer-only helpers `mcpu-cpp` из известной ширины типа. Оно означает точное число десятичных цифр максимального значения соответствующего типа: знак и завершающий NUL в `DECIMAL_DIG` не входят. Для unsigned используется максимум `2^bits - 1`, для signed — `2^(bits-1) - 1`. Это отличается от LibMPU `_int_digs()`, которая предназначена для оценки строкового буфера и включает место для завершающего NUL. Например: ```text #define __INT64_DECIMAL_DIG__ 19 #define __UINT64_DECIMAL_DIG__ 20 #define __INT256_DECIMAL_DIG__ 77 #define __UINT256_DECIMAL_DIG__ 78 ``` Только сами textual maxima намеренно ограничены шириной `bits <= 256`: ```text __INT128_MAX__ __UINT128_MAX__ ``` Максимумы строятся через LibMPU `iuitoa()`. Макро `__INT_MIN__` не создаются: predefined table не должна содержать вычисляемые выражения вида `(-__INT_MAX__ - 1)`. Для widths больше 256 бит отсутствуют только MAX; TYPE/WIDTH/SIZEOF/DECIMAL_DIG сохраняются до полного `NB_I_MAX * 8`. ### 8.9. Real и Complex families LibMPU Real/Complex structural metadata генерируется для каждой power-of-two ширины от 32 бит до фактического configured `MPU_REAL_IO_LIMIT`. Для всех этих типов публикуются TYPE, WIDTH и SIZEOF. Для Complex WIDTH означает параметр типа, а не суммарную storage width: ```text #define __COMPLEX128_TYPE__ complex128 #define __COMPLEX128_WIDTH__ 128 #define __SIZEOF_COMPLEX128__ 32 ``` `complex128` состоит из двух компонентов `real128`, поэтому его storage size равен 32 байтам. При `MPU_REAL_IO_LIMIT == 65536` верх family имеет вид: ```text #define __COMPLEX65536_TYPE__ complex65536 #define __COMPLEX65536_WIDTH__ 65536 #define __SIZEOF_COMPLEX65536__ 16384 ``` Для Real соответственно: ```text #define __REAL65536_TYPE__ real65536 #define __REAL65536_WIDTH__ 65536 #define __SIZEOF_REAL65536__ 8192 ``` Precision metadata определяется для **всех** разрешённых Real widths вплоть до `MPU_REAL_IO_LIMIT`. Имена macros согласованы с LibMPU helpers: ```text __REAL_DECIMAL_DIG__ -> _real_digs(bits/8) __REAL_MANT_DIG__ -> _real_mant_digs(bits/8) ``` `__REAL_DIG__` намеренно отсутствует. Ограничение `bits <= 256` относится только к большим textual numeric constants. Для размеров до 256 бит также определяются: ```text __REAL_MAX__ __REAL_MIN__ __REAL_EPSILON__ __REAL_MAX_EXP__ __REAL_MIN_EXP__ __REAL_MAX_10_EXP__ __REAL_MIN_10_EXP__ ``` Например, на LibMPU 1.0.35 для `real128` текущий profile даёт значения вида: ```text #define __REAL128_EPSILON__ 2.524354896707237777317531409e-29 #define __REAL128_MAX__ 4.197157432934775384808581951e+323228496 #define __REAL128_MIN__ 9.530259619551804292864984035e-323228497 #define __REAL128_MAX_10_EXP__ 323228496 #define __REAL128_MAX_EXP__ 1073741823 #define __REAL128_MIN_10_EXP__ -323228524 #define __REAL128_MIN_EXP__ -1073741822 ``` MAX/MIN/EPSILON создаются самой LibMPU и преобразуются через `real_to_ascii()`. Exponent constants получают значения через LibMPU exponent helpers и integer conversion. Для widths больше 256 бит эти numeric predefines отсутствуют, но TYPE/WIDTH/SIZEOF/DECIMAL_DIG/MANT_DIG продолжаются до `MPU_REAL_IO_LIMIT`. Для каждого разрешённого Real type вплоть до `MPU_REAL_IO_LIMIT` также публикуются две компактные характеристики: ```text #define __SIZEOF_REAL128_EXP__ 4 #define __REAL128_MAX_STRLEN__ 60 ``` `__SIZEOF_REALxxx_EXP__` непосредственно получает `_sizeof_exp(NB_Rxxx)`. `__REALxxx_MAX_STRLEN__` получает `_real_max_string(NB_Rxxx)` и означает максимальное **количество символов** текстового представления, а не количество байт. Поэтому для zero-terminated строки нужно резервировать не менее `__REALxxx_MAX_STRLEN__ + 1` элементов: для `char8` это столько же bytes, а для `char16` физический объём в bytes вдвое больше. Эти два metadata-macro определяются и для Real widths больше 256, поскольку сами их значения малы. ### 8.10. Dump macros: `-dM`, `-dMP` Опция: ```text mcpu-cpp -dM input.c ``` печатает только итоговые **непредопределённые** macros в форме `#define ...`. К этой группе относятся определения из основного файла и включённых headers, а также определения командной строки `-D`. Предопределённые macros самого MCPU-CPP в `-dM` не выводятся. Поэтому `-dM` предназначен прежде всего для короткой инспекции macro-state, созданного пользовательской программой. Опция: ```text mcpu-cpp -dMP input.c ``` добавляет к тому же итоговому состоянию активные predefined macros MCPU-CPP. Вывод имеет две последовательные группы: сначала все predefined macros, затем все непредопределённые macros. Внутри каждой группы определения детерминированно сортируются по имени. Такое разделение удобно системному разработчику для инспекции preprocessing ABI и архитектурных свойств текущей MCPU environment, не смешивая их с пользовательскими определениями. Принадлежность к группе определяется происхождением macro, а не его именем. Macro, заданный через `-D` или `#define`, является обычным даже если его имя похоже на системное. Если predefined macro был удалён через `#undef`, он не печатается. Если после этого то же имя снова определено пользователем, новое определение относится к обычной группе и выводится в её части `-dMP`, а также в `-dM`. Тем самым оба режима показывают именно **итоговый macro-state**. Context-dependent `__FILE__`, `__LINE__`, `__DATE__`, `__TIME__`, `__BASE_FILE__` и `__INCLUDE_LEVEL__` в статическом dump не печатаются. Статические ABI/architecture predefined macros и вычисляемые static Real metadata выводятся в `-dMP`. Если input file указан, он сначала полностью препроцессируется, после чего выводится итоговый macro-state; обычный preprocessed text в режимах `-dM` и `-dMP` не выдаётся. Без input file используется stdin, поэтому пустой stdin с `-dM` даёт пустой dump, а `-dMP` позволяет получить набор активных static predefined macros текущей MCPU environment. `-dD` имеет другую семантику и этим разделением не затрагивается. ### 8.11. Dump definitions: `-dD` Опция: ```text mcpu-cpp -dD input.c ``` сохраняет обычный результат препроцессирования и одновременно выводит встреченные директивы `#define`. Перед началом основного входного текста печатаются статические предопределённые macro definitions. Каждой такой дефиниции предшествует marker: ```text # 0 "" #define NAME value ``` а перед блоком предопределённых macro выводится marker исходного файла вида `# 0 "input.c"`. Context-dependent `__FILE__`, `__LINE__`, `__DATE__`, `__TIME__`, `__BASE_FILE__` и `__INCLUDE_LEVEL__` в начальный built-in block не включаются. ### 8.12. Dump configuration: `-dconfig` Опция: ```text mcpu-cpp -dconfig ``` не требует input file и выводит effective variables configuration layer после чтения runtime config, необязательного system override, домашнего user override или выбранного `--config-file`, включая expansion `$NAME`/`${NAME}`. При `--sys-root=PATH` configuration files не читаются, а dump показывает выбранный из командной строки system root `PATH/include`. Строки сортируются по имени и печатаются в форме: ```text NAME = value; ``` Это позволяет проверить реальные include paths без ручного поиска `/etc/mcpu-cpp.conf`, `/etc/mcpu/mcpu-cpp.conf` и `$HOME/.mcpu/etc/mcpu-cpp.conf`. ### 8.13. Verbose configuration snapshot: `-v` При `-v` MCPU-CPP сохраняет прежний runtime trace для `#lang`, `#include` и `#include_next`, но конфигурационные переменные печатаются только один раз — после чтения всех уровней configuration и применения правил приоритета. Поэтому в verbose output видны только **effective values**, а промежуточные значения из runtime-root, system и user config не дублируются. Config-блок выводится в порядке include policy: language-specific user paths, общий user path, system root и AFTER path. Переменная, отсутствующая во всех уровнях configuration, не печатается. Runtime-derived default `MCPU_CPP_SYSTEM_INCLUDE_PATH` является полноценным самым нижним значением и поэтому виден при `-v`, даже если ни один `mcpu-cpp.conf` не найден **или все config-файлы отключены опцией `--no-config`**. Форма строки: ```text config: NAME=value ``` ### 8.14. Effective search directories: `-dsearch-dirs` Опция: ```text mcpu-cpp -dsearch-dirs ``` не требует input file, печатает effective глобальные каталоги поиска и завершает работу без preprocessing. Формат намеренно прост: ```text search: /path/to/directory ``` Каталоги выводятся в семантическом порядке классов поиска: ```text explicit -I explicit -isystem configured language-specific user directories MCPU_CPP_INCLUDE_PATH MCPU_CPP_SYSTEM_INCLUDE_PATH/ MCPU_CPP_SYSTEM_INCLUDE_PATH explicit -idirafter MCPU_CPP_AFTER_INCLUDE_PATH ``` Language-specific entries печатаются для всех поддерживаемых языков в их каноническом порядке. Во время реального `#include` из этой группы участвует только каталог активного `#lang`. Каталог текущего физического файла в `-dsearch-dirs` не выводится: он существует только динамически для конкретного `#include "..."` и меняется вместе с include stack. `--no-config` не удаляет runtime-derived system root, поэтому без конфигурационных файлов dump всё равно содержит `/include/` и `/include`. `-nostdinc` удаляет из dump effective system `` entries и system root, но не explicit `-isystem`. Не существующий на filesystem каталог всё равно показывается, поскольку он является элементом effective search configuration и просто будет пропущен при реальном поиске файла. `-dsearch-dirs` учитывает `-I`, `-isystem`, `-idirafter`, все уровни config и replacement-семантику `MCPU_CPP_SYSTEM_INCLUDE_PATH`. Опция `-o` вместе с ним является ошибкой. ### 8.15. Условная компиляция Директивы `#if`, `#ifdef`, `#ifndef`, `#elif`, `#else` и `#endif` обрабатываются как управляющие директивы препроцессора и в выходной поток не копируются, в том числе при `-dD`. Неактивные ветви пропускаются без выполнения находящихся в них `#define`, `#undef` и `#include`; вложенные условные группы при этом учитываются корректно. Выражение `#if` сначала обрабатывает оператор `defined`, затем выполняется macro expansion, а оставшиеся идентификаторы имеют значение `0`. Поддерживаются арифметические, битовые, сравнительные и логические операции, `?:` и short-circuit semantics для `&&`, `||` и `?:`. Начиная с 0.0.26 синтаксис выражения разбирается parser-ом, генерируемым ZUBR 4.1.0 из `src/mcpp-expr.zubr`; в том же файле находится UCS-2 lexical analyzer. Предварительная обработка `defined` и macro expansion выполняются до входа в parser. Арифметическая семантика вынесена в `mcpp-semantic.c/h` и не зависит от размеров целых типов host-системы. Generated `mcpp-expr.c` включается в release, поэтому ZUBR требуется только при изменении grammar. #### 8.15.1. Единственная вычислительная разрядность — 64 бита MCPU-CPP является препроцессором, а не компилятором языка общего назначения. Все целочисленные вычисления в директивах условной компиляции выполняются только в 64-разрядной арифметике. Препроцессор не выполняет арифметику LibMPU произвольной разрядности, вещественные или комплексные вычисления. Если программисту не требуется управлять двоичным представлением литерала, достаточно обычных целых констант и необязательного `U`/`u`. Например: ```c #if 2 > 1 #if 0xffffffffffffffffU > 1 ``` Числовой lexeme хранится в UCS-2 до классификации, после чего его ASCII-часть передаётся LibMPU `iatoui()`. Поддерживаются `0b...`, `0...`, decimal и `0x...`. Значение, не помещающееся в 64 бита, является ошибкой. Старые C suffixes `L`, `l`, `LL`, `ll` не поддерживаются. #### 8.15.2. Суффикс разрядности `zNNN[Uu]` MCPU-CPP понимает общий для MCPU-языков суффикс разрядности: ```text zNNN ZNNN zNNNu zNNNU ZNNNu ZNNNU ``` `NNN` — непустая последовательность десятичных цифр и **всегда** читается как десятичное число, даже если начинается с нулей. Поэтому `z8`, `z08` и `z008` задают одну и ту же разрядность 8 бит. В общем синтаксисе MCPU корректная разрядность должна быть степенью двойки от 8 до `MPU_REAL_IO_LIMIT`. MCPU-CPP, однако, сознательно ограничен 64-битными вычислениями: * `z8`, `z16`, `z32`, `z64` и варианты регистра допустимы; * значение `NNN > 64` немедленно является ошибкой: препроцессор не допускает числовые константы разрядности выше 64 бит в директивах условной компиляции; * если `NNN <= 64`, но не задаёт допустимую степень двойки, например `z24`, выводится warning и сам `zNNN` игнорируется; * необязательный следующий `U`/`u` задаёт unsigned и сохраняет своё значение даже если некорректный `zNNN` был проигнорирован. После полного суффикса должна заканчиваться числовая preprocessing token. Оператор или punctuation начинает следующий token, поэтому допустимы `1z32u+2`, `(1z32u)` и `1z32u==1`. Записи вроде `1z32undefined`, `1z32ufoo` и `1z32$foo` являются ошибками и не разбиваются искусственно на число и имя. #### 8.15.3. Нормализация литерала Суффикс разрядности действует **только один раз — при формировании значения самой константы**. Разрядность не сохраняется в semantic value и не участвует в последующих операциях. Для `VALUEzNNN` значение считается знаковым N-битным числом в дополнительном коде: 1. сохраняются младшие `NNN` бит; 2. результат расширяется со знаком до 64 бит. Для `VALUEzNNNu`/`VALUEzNNNU` сохраняются младшие `NNN` бит, после чего выполняется нулевое расширение до 64 бит. Например: ```text 0x7fz8 -> 0x000000000000007f -> 127 0x80z8 -> 0xffffffffffffff80 -> -128 0xffz8 -> 0xffffffffffffffff -> -1 0x80z8u -> 0x0000000000000080 -> 128 0xffz8u -> 0x00000000000000ff -> 255 0x1ffz8 -> 0xffffffffffffffff -> -1 0x1ffz8u -> 0x00000000000000ff -> 255 ``` Последние два примера намеренны: `zNNN` задаёт разрядность **двоичного представления**, а не проверку математического диапазона. Биты старше N отбрасываются до расширения. После этой нормализации никакой `z8`, `z16` или `z32` в вычислительной модели уже не существует. Внутреннее значение содержит только 64-битный битовый образ и признак signed/unsigned. #### 8.15.4. Все последующие операции — 64-битные После нормализации все арифметические, побитовые, сравнительные и логические операции выполняются над 64-битными операндами. Результат операции не усекается обратно до разрядности исходного suffix. Поэтому: ```text 0x7fz8 + 1 -> 128 0xffz8u + 1 -> 256 ``` а не `-128` и `0` соответственно. Аналогично `~0xffz8u` инвертирует все 64 бита и даёт `0xffffffffffffff00`. Для бинарных операций, где signedness имеет значение, наличие unsigned операнда переводит операцию в 64-битную unsigned-интерпретацию. Сравнения возвращают `0` или `1`. Логические `!`, `&&`, `||` также возвращают signed 64-битные `0` или `1`; short-circuit не вычисляет невыбранную часть. Сдвиги выполняются после 64-битной нормализации. Правый сдвиг signed отрицательного значения является арифметическим, unsigned — логическим. Например: ```text 0x80z8 >> 1 -> -64 0x80z8u >> 1 -> 64 ``` Историческое правило MCPU-CPP для отрицательного счётчика сдвига сохраняется: `A << -N` эквивалентно `A >> N`, а `A >> -N` — `A << N`. Таким образом, `zNNN` не превращает препроцессор в компилятор с системой integer promotions разных размеров. Он лишь позволяет явно описать битовый образ исходного литерала; затем выражение вычисляется в единственной простой 64-битной модели. #### 8.15.5. Символьные константы Символьная единица имеет тип `__mpu_uint16_t`, соответствующий внутреннему UCS-2 представлению, и перед вычислением расширяется нулями до 64 бит. Последующая арифметика снова является обычной 64-битной арифметикой. Состояние условной компиляции хранится в отдельном стеке; условная группа не может пересекать границу include-файла. ### 8.16. Диагностические директивы `#error` и `#warning` MCPU-CPP поддерживает стандартные диагностические директивы: ```text #error сообщение #warning сообщение ``` `#error` выдаёт diagnostic уровня error с текущими логическими именем файла и номером строки и немедленно завершает preprocessing с ошибкой. `#warning` выдаёт warning с той же source-location information, после чего preprocessing продолжается. Поэтому предшествующий `#line` влияет на координаты обеих диагностик. Остаток строки после имени директивы **не подвергается macro expansion**. Например: ```c #define MESSAGE expanded #warning MESSAGE ``` печатает `MESSAGE`, а не `expanded`. Это отличает диагностические директивы от `#if` и `#line`, где macro expansion является частью соответствующего контракта. Комментарии удаляются на обычной preprocessing phase до обработки директивы. Начальные и конечные пробелы сообщения удаляются, последовательности пробельных символов между preprocessing tokens сворачиваются в один пробел. Пробелы внутри кавычек сохраняются. Например: ```c #warning one /* comment */ two #warning "a b" ``` дают сообщения соответственно `one two` и `"a b"`. Unicode-текст проходит через внутреннее UCS-2 представление и выводится во внешнюю диагностику в UTF-8. Обе директивы являются управляющими и никогда не копируются в обычный выходной поток или в `-dD`. В неактивной ветви `#if` они полностью игнорируются, поэтому обычная защитная конструкция работает ожидаемо: ```c #if 0 #error this error is inactive #endif ``` ### 8.17. Управление предупреждениями: `-Wcomment`, `-Wall`, `-Werror` MCPU-CPP разделяет обязательные предупреждения, являющиеся частью уже зафиксированной preprocessing-семантики, и дополнительные классы предупреждений, которые включаются пользователем. Управление предупреждениями не изменяет семантику `-dD`, macro expansion, conditional compilation или include search. Опции `-Wcomment` и `-Wcomments` являются полными синонимами и включают два лексических предупреждения: * последовательность `/*`, встретившуюся внутри уже открытого `/* ... */` комментария; * backslash-newline внутри `//` комментария, из-за которого однострочный комментарий физически продолжается на следующую строку. По умолчанию этот дополнительный класс выключен. `-Wall` включает все дополнительные warning classes MCPU-CPP; в версии 0.0.40 таким классом является `-Wcomment`. Формы `-Wno-comment` и `-Wno-comments` выключают его. Как в GNU warning model, более специфическая настройка имеет приоритет над групповой независимо от порядка аргументов. Поэтому обе команды: ```text mcpu-cpp -Wall -Wno-comment file.c mcpu-cpp -Wno-comment -Wall file.c ``` оставляют comment warnings выключенными. Между настройками одинаковой специфичности действует последнее указание, например `-Wno-comment -Wcomment` включает этот класс. `-Werror` не включает никаких новых warning classes. Он повышает до error любое предупреждение, которое в данном запуске действительно было бы выдано, и такой запуск завершается неуспешно. Это относится как к дополнительным comment warnings, так и к уже существующим обязательным предупреждениям MCPU-CPP: * активной директиве `#warning`; * недопустимой, но не превышающей 64 бита ширине `zNNN`; * переопределению macro другим replacement list; * результату `##`, не образующему один preprocessing token. Например: ```text mcpu-cpp -Wcomment -Werror file.c ``` превращает найденный comment warning в error. В то же время один `-Werror` без `-Wcomment`/`-Wall` не заставляет MCPU-CPP искать optional comment warnings. `-Wno-error` возвращает обычную severity warning. Для `-Werror` и `-Wno-error`, имеющих одинаковую специфичность, действует последняя опция командной строки. Так, `-Werror -Wno-error` оставляет warnings предупреждениями, а `-Wno-error -Werror` снова повышает их до errors. В 0.0.40 намеренно не вводятся `-Werror=`, `-Wno-error=`, `-Wundef`, `-Wunused-macros`, `-Wtraditional` и другие компиляторные классы. Warning interface MCPU-CPP остаётся компактным и расширяется только тогда, когда новый класс действительно нужен самому preprocessing language. ### 8.18. Идентификаторы UCS-2 Начиная с 0.0.22 имена preprocessing identifiers больше не ограничены ASCII. Внутри `mcpu-cpp` текст уже представлен строгим UCS-2, а классификация символов выполняется locale-independent функциями LibMPUIO 1.0.4, построенными по Unicode 18.0.0. Первый символ идентификатора должен быть `_` или иметь свойство `XID_Start`; последующие символы должны быть `_`, `$` или иметь свойство `XID_Continue`. Символ `$` является расширением `mcpu-cpp`: он разрешён только после первого символа и не может начинать identifier. Это правило едино для имён и параметров macro, `#undef`, `#ifdef`/`#ifndef`, `defined`, обычного macro expansion, `#`/`##`. Имена остаются case-sensitive. Surrogate code units `U+D800..U+DFFF` не являются допустимыми символами identifiers. Например, допустимы: ```c #define АНДРЕЙ 1 #define résumé 2 #define ΩМЕГА 3 #define VALUE$OLD 4 ``` Например, `VALUE$OLD` допустим, а `$VALUE` недопустим, поскольку `$` не является identifier-start character. Combining marks и не-ASCII decimal digits могут входить в identifier в позициях `XID_Continue`, но не становятся автоматически допустимыми первыми символами. Синтаксис числовых констант от этого не меняется: его правила остаются правилами соответствующего языка, а не Unicode `isdigit`. ### 8.19. Макросы командной строки `-D` и `-U` Начиная с 0.0.23 опции `-D` и `-U` являются полноценными действиями препроцессора. Поддерживаются формы: ```text -DNAME -DNAME=VALUE -D'FUNC(a,b)=a+b' -UNAME ``` `-DNAME` эквивалентна `#define NAME 1`; наличие `=` с пустой правой частью задаёт пустой replacement list. Function-like определения используют тот же macro engine, что и обычный `#define`, включая параметры, `#`, `##` и последующий rescanning. `-U` использует тот же identifier contract, что и `#undef`. Действия `-D`/`-U` выполняются в порядке командной строки после установки predefined macros. Только payload опций `-D` и `-U` интерпретируется как UTF-8 и преобразуется в строгий UCS-2. Имена файлов, `-I`, другие pathname arguments и остальные аргументы командной строки остаются исходными byte strings и не подвергаются Unicode-конвертации. Начиная с 0.0.25 символ `$` разрешён внутри имени macro, но не в первой позиции. При передаче `$` из shell пользователь обязан учитывать правила самого shell: shell обрабатывает `$` **до запуска `mcpu-cpp`**. Одинарные кавычки уже полностью защищают `$`, например: ```sh mcpu-cpp '-DАНДРЕЙ$_Y=62' input.c ``` Без кавычек `$` следует экранировать: ```sh mcpu-cpp -DАНДРЕЙ\$_Y=62 input.c ``` или использовать двойные кавычки с экранированием: ```sh mcpu-cpp -D"АНДРЕЙ\$_Y=62" input.c ``` Вариант без защиты: ```sh mcpu-cpp -DАНДРЕЙ$_Y=62 input.c ``` не передаёт написанное имя буквально: `$...` сначала раскрывается shell и `mcpu-cpp` получает уже изменённый `argv`. Внутри одинарных кавычек обратная косая черта перед `$` не нужна и стала бы обычным символом аргумента. Для command-line `-D` левая часть до первого `=` разбирается как отдельный macro declarator. Если после допустимого имени (или завершённого списка параметров function-like macro) до `=` встречается недопустимый хвост, этот хвост молча отбрасывается и **никогда не превращается в replacement list**. Например: ```text -D'АНДРЕЙ@XYZ=62' ``` эквивалентно: ```c #define АНДРЕЙ 62 ``` а не ошибочной форме `#define АНДРЕЙ @XYZ 62`. Аналогичное правило допустимого identifier-prefix применяется к `-U`. Если же первый символ вообще не является допустимым identifier-start character (например `$` или цифра), определение остаётся ошибочным. При `-dD` определения, пришедшие через `-D`, маркируются отдельно от предопределённых macro: ```text # 0 "" #define NAME value ``` в то время как predefined macros продолжают использовать ``. ### 8.20. Публичный интерфейс командной строки `mcpu-cpp` поддерживает только актуальные опции, описанные `--help`. Устаревшие compatibility-флаги не образуют скрытый интерфейс и диагностируются как `unknown option`. Опция `-E` является исключением: она молча принимается и игнорируется, поскольку может передаваться compiler driver при запуске отдельного препроцессора. Опция `--object-suffix SUFFIX` задаёт суффикс object target, используемый при генерации make-зависимостей; аргумент обязателен. ## 9. Build-system и генераторы Собственные Autoconf-макросы проекта находятся в корневом `acsite.m4`. Каталог `m4/` зарезервирован для внешних/vendor M4-файлов. Такой порядок повторяет принятую в библиотеках MCPU схему и не смешивает собственный configure-код с импортированными макросами. Парсер выражений `#if` генерируется ZUBR 4.1.0 из `src/mcpp-expr.zubr`. Release archive содержит и грамматику, и уже сгенерированный `src/mcpp-expr.c`, поэтому обычная сборка не требует установленного ZUBR. После изменения грамматики developer build использует штатное правило Automake: ```text zubr -vl -s -Bmcpp_ -o mcpp-expr.c mcpp-expr.zubr ``` Перед выпуском release generated C должен соответствовать грамматике, полный test suite и `make distcheck` должны проходить без ошибок. ### 9.1. Developer bootstrap и Git source tree Начиная с 0.0.50 корневой скрипт `./bootstrap` позволяет не хранить в Git файлы, которые полностью воспроизводятся из исходников. Скрипт сначала генерирует `src/mcpp-expr.c` из `src/mcpp-expr.zubr` с помощью ZUBR 4.1.0, затем выполняет `aclocal`, `autoheader`, `automake` и `autoconf` в стиле библиотек LibMPU и LibMPUIO. Опция `--target-dest-dir=DIR` задаёт target ROOTFS для системных Autoconf macro/include directories. Это правило относится именно к developer Git tree. **Release archive остаётся самодостаточным**, как и раньше: он содержит `configure`, `Makefile.in`, helper scripts Automake и уже сгенерированный `src/mcpp-expr.c`, поэтому обычная сборка релиза не требует предварительного запуска `bootstrap` и не требует ZUBR. Корневой `.gitignore` перечисляет воспроизводимые bootstrap-файлы и обычный configure/build state. Он не меняет существующую release/build model, а только позволяет поддерживать более чистый Git repository. ## 10. GNU-compatible features `mcpu-cpp` является самостоятельным препроцессором MCPU, но для ряда хорошо известных операций намеренно повторяет поведение GNU CPP. Совместимость относится к документированным возможностям, а не означает полную CLI- или языковую взаимозаменяемость с GCC. В частности, GNU-compatible поведение используется для: * object-like и function-like macro, повторного macro rescan, `#` и `##`; * variadic macro `...` / `__VA_ARGS__` и стандартного `__VA_OPT__`; * `#if`, `#ifdef`, `#ifndef`, `#elif`, `#else`, `#endif` и `defined`; * `#include`, `#include_next`, `#pragma once`, `#line` и GNU linemarkers; * compact output mapping: до семи невидимых строк представляются newline, а разрыв в восемь и более строк — корректирующим linemarker; * forced files `-include` / `-imacros` и dependency options `-M`, `-MM`, `-MD`, `-MMD`, `-MF`, `-MT`, `-MQ`, `-MG`; * warning controls `-w`, `-Wall`, `-Werror` и поддерживаемых `-Wcomment` forms. MCPU-specific возможности, включая `#lang` / `#endlang`, числовой суффикс `zNNN` и ABI predefined macros, остаются собственными расширениями `mcpu-cpp`.