From a91bd145008432fb8c5d91d94ab6f50473a277f5 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Tue, 27 Oct 2026 13:55:36 +0000 Subject: feat(preprocessor): add file-local conditional switches Co-Authored-By: Claude Opus 5.5 --- lib/Lexer/Lexer.cc | 5 +++ lib/Lexer/Preprocessor.cc | 92 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 lib/Lexer/Preprocessor.cc (limited to 'lib') diff --git a/lib/Lexer/Lexer.cc b/lib/Lexer/Lexer.cc index 885f432..5bf2230 100644 --- a/lib/Lexer/Lexer.cc +++ b/lib/Lexer/Lexer.cc @@ -14,6 +14,11 @@ using namespace cps; namespace { const std::unordered_map Keywords = { + {"PP_DEFINE", tok_pp_define}, + {"PP_UNDEF", tok_pp_undef}, + {"PP_IFDEF", tok_pp_ifdef}, + {"PP_IFNDEF", tok_pp_ifndef}, + {"PP_ENDIF", tok_pp_endif}, {"CONSTANT", tok_constant}, {"INCLUDE", tok_include}, {"INCLUDE_OBJ", tok_include_obj}, diff --git a/lib/Lexer/Preprocessor.cc b/lib/Lexer/Preprocessor.cc new file mode 100644 index 0000000..235dd4b --- /dev/null +++ b/lib/Lexer/Preprocessor.cc @@ -0,0 +1,92 @@ +#include "cps/Preprocessor.h" +#include "cps/Diagnostics.h" +#include +#include + +using namespace cps; + +namespace { +bool nameStart(char C) { + return (C >= 'A' && C <= 'Z') || (C >= 'a' && C <= 'z') || C == '_'; +} + +bool nameContinue(char C) { + return nameStart(C) || (C >= '0' && C <= '9'); +} + +std::string_view trim(std::string_view Text) { + const auto First = Text.find_first_not_of(" \t\r\v\f"); + if (First == std::string_view::npos) return {}; + return Text.substr(First, Text.find_last_not_of(" \t\r\v\f") - First + 1); +} +} + +PreprocessedSource cps::preprocess(std::istream &Input) { + PreprocessedSource Result; + std::unordered_set Switches; + struct Conditional { bool ParentActive; int Line; }; + std::vector Conditions; + bool Active = true; + int LineNumber = 0; + auto Error = [&](const char *Message) { + sourceError(LineNumber, "%s", Message); + Result.HadError = true; + }; + std::string Line; + while (std::getline(Input, Line)) { + ++LineNumber; + Result.LibraryLines.push_back(Switches.count("__LIB") != 0); + auto Code = trim(Line); + size_t WordEnd = 0; + while (WordEnd < Code.size() && nameContinue(Code[WordEnd])) ++WordEnd; + auto Word = Code.substr(0, WordEnd); + bool Define = Word == "PP_DEFINE", Undef = Word == "PP_UNDEF"; + bool Ifdef = Word == "PP_IFDEF", Ifndef = Word == "PP_IFNDEF"; + bool Endif = Word == "PP_ENDIF"; + bool Directive = Define || Undef || Ifdef || Ifndef || Endif; + bool Mask = Directive || !Active; + if (Directive) { + auto Tail = Code.substr(WordEnd); + Tail = trim(Tail.substr(0, Tail.find("//"))); + if (Endif) { + if (!Tail.empty()) Error("PP_ENDIF does not take arguments"); + if (Conditions.empty()) Error("PP_ENDIF without matching PP_IFDEF or PP_IFNDEF"); + else { + Active = Conditions.back().ParentActive; + Conditions.pop_back(); + } + } else if (Ifdef || Ifndef || Active) { + size_t NameEnd = 0; + if (!Tail.empty() && nameStart(Tail[0])) { + NameEnd = 1; + while (NameEnd < Tail.size() && nameContinue(Tail[NameEnd])) ++NameEnd; + } + bool Valid = NameEnd != 0 && NameEnd == Tail.size(); + if (!Valid) { + sourceError(LineNumber, "%.*s requires exactly one switch name", + static_cast(Word.size()), Word.data()); + Result.HadError = true; + } + std::string Name(Tail.substr(0, NameEnd)); + if (Ifdef || Ifndef) { + Conditions.push_back({Active, LineNumber}); + bool Defined = Switches.count(Name) != 0; + Active = Active && Valid && (Ifdef ? Defined : !Defined); + } else if (Valid) { + if (Define) Switches.insert(Name); + else Switches.erase(Name); + } + } + } + if (Mask) + for (char &C : Line) + if (C != '\r') C = ' '; + Result.Text += Line; + if (!Input.eof()) Result.Text += '\n'; + } + for (const auto &Condition : Conditions) { + sourceError(Condition.Line, "Unterminated preprocessor conditional: expected PP_ENDIF"); + Result.HadError = true; + } + return Result; +} -- cgit v1.3.1-4-g156e