diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-27 13:55:36 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-27 13:55:36 +0000 |
| commit | a91bd145008432fb8c5d91d94ab6f50473a277f5 (patch) | |
| tree | 87af3ebcd2888e129bad9863574567e1c97bb18b /lib/Lexer/Preprocessor.cc | |
| parent | 24f410430b33aaf1f288b83ea37e18fa959e9d3e (diff) | |
feat(preprocessor): add file-local conditional switches
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'lib/Lexer/Preprocessor.cc')
| -rw-r--r-- | lib/Lexer/Preprocessor.cc | 92 |
1 files changed, 92 insertions, 0 deletions
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 <string_view> +#include <unordered_set> + +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<std::string> Switches; + struct Conditional { bool ParentActive; int Line; }; + std::vector<Conditional> 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<int>(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; +} |
