Учебный проект: свой препроцессор C++
(раскрытие #include) с использованием:
std::regex- сырых строковых литералов (
R"( ... )") - рекурсии
std::filesystem
Проект демонстрирует прохождение тестов и реальное использование препроцессора в цепочке сборки.
Мы пишем собственный препроцессор, а затем используем его:
- чтобы склеить исходники другой версии проекта в один
.cpp, - скомпилировать этот файл,
- и запустить тесты.
То есть препроцессор работает на практике.
-
V0 — оркестратор (launcher):
- компилирует V1,
- запускает тесты V1,
- запускает V1 в режиме
--flatten, - компилирует результат в V2,
- запускает тесты V2.
-
V1 — минимальная реализация препроцессора по ТЗ
- режим
--flatten(склейка проекта в один.cpp).
- режим
-
V2 — улучшенная версия:
- убирает UTF-8 BOM,
- нормализует CRLF,
- нормализует пути,
- кэширует найденные include.
V0
├─ компилирует V1
├─ запускает V1 (тесты)
├─ запускает V1 --flatten
│ └─ получает build/v2_flat.cpp
├─ компилирует build/v2_flat.cpp → v2.exe
└─ запускает v2.exe (тесты)
Главное:
build/v2_flat.cppсоздаётся препроцессором V1.- Это делается рекурсией + regex, ровно теми же приёмами, которые изучались в уроке.
- В сборочной цепочке используется режим
--flatten.
В режиме --flatten препроцессор раскрывает ТОЛЬКО директивы вида:
#include "..."Директивы вида:
#include <...>НЕ раскрываются и копируются в выходной файл без изменений.
-
системные заголовки (
<iostream>,<vector>и т.д.) должны обрабатываться настоящим компилятором (g++); -
поиск и раскрытие системных include не входит в задание;
-
это соответствует реальной практике склейки пользовательского кода:
- unity build — компиляция проекта как одного большого файла (часто для ускорения сборки);
- amalgamation — распространение проекта в виде одного
.cpp(классический пример — SQLite).
Итог:
препроцессор склеивает пользовательский код, компилятор обрабатывает стандартную библиотеку.
cpp-preprocessor2/
│
├─ README.md
├─ v0.cpp # Оркестратор: V1 → flatten → V2
│
├─ v1_parts/
│ ├─ v1_main.cpp # main() V1: тесты и режим --flatten
│ └─ v1_preprocess_impl.h
│ ├─ Preprocess # режим ТЗ
│ ├─ PreprocessOne_TZ # рекурсия для ТЗ
│ ├─ FlattenProject # режим --flatten
│ └─ PreprocessOne_Flatten # рекурсивная склейка "..."
│
├─ v2_parts/
│ ├─ v2_main.cpp # main() V2: тесты
│ └─ v2_preprocess_impl.h # улучшенная реализация
│
├─ common/
│ └─ tests_common.h # общие тесты (используются V1 и V2)
│
└─ build/
└─ v2_flat.cpp # GENERATED: результат --flatten (создаётся V1)
| V1 | V2 |
|---|---|
|
|
Улучшенная реализация препроцессора: |
Чтобы русский текст корректно отображался в консоли:
chcp 65001Далее:
set PATH=C:\Qt\Tools\mingw1310_64\bin;%PATH%
g++ -std=gnu++17 v0.cpp -o v0.exe
v0.exeКлючевые строки:
g++ -std=gnu++17 v1_parts/v1_main.cpp -o v1.exe
v1.exe
V1: минимальная реализация + flatten (только #include "...")
Анализируем и компилируем решение...
Запускаем тесты...
Успех!
v1.exe --flatten v2_parts/v2_main.cpp build/v2_flat.cpp v2_parts common
g++ -std=gnu++17 build/v2_flat.cpp -o v2.exe
v2.exe
V2: улучшенная версия (BOM/CRLF/normalize/кэш) + flatten
Анализируем и компилируем решение...
Запускаем тесты...
Успех!v0.exe
├─ system("g++ ... v1_main.cpp -> v1.exe")
├─ system("v1.exe") // тесты V1
├─ system("v1.exe --flatten ...") // склейка V2
│ └─ v1_main.cpp: main()
│ └─ FlattenProject(...)
│ └─ PreprocessOne_Flatten(...)
│ ├─ regex_match("#include \"...\"")
│ └─ рекурсия
├─ system("g++ ... build/v2_flat.cpp -> v2.exe")
└─ system("v2.exe") // тесты V2