From 2fb673983a721383c4e9018ffc3f03dd4e7096f2 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 4 Aug 2026 16:15:45 -0500 Subject: [PATCH 1/2] Expand function macros that have arguments at the end --- run-tests.py | 1 - simplecpp.cpp | 35 +++++++++++++++++++++++++++++++++-- test.cpp | 17 +++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/run-tests.py b/run-tests.py index 20f59a49..71d59b15 100644 --- a/run-tests.py +++ b/run-tests.py @@ -80,7 +80,6 @@ def cleanup(out: str) -> str: 'macro_rescan_varargs.c', # todo, high priority - 'c99-6_10_3_4_p5.c', 'c99-6_10_3_4_p6.c', 'expr_usual_conversions.c', # condition is true: 4U - 30 >= 0 'stdint.c', diff --git a/simplecpp.cpp b/simplecpp.cpp index 4b24e311..a90e09a6 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2310,12 +2310,43 @@ namespace simplecpp { return false; if (variadic && argnr + 1U >= parametertokens.size()) // empty variadic parameter return true; - for (const Token *partok = parametertokens[argnr]->next; partok != parametertokens[argnr + 1U];) { + const Token * const argEnd = parametertokens[argnr + 1U]; + for (const Token *partok = parametertokens[argnr]->next; partok != argEnd;) { const MacroMap::const_iterator it = macros.find(partok->str()); if (it != macros.end() && !partok->isExpandedFrom(&it->second) && (partok->str() == name() || expandedmacros.find(partok->str()) == expandedmacros.end())) { std::set expandedmacros2(expandedmacros); // temporary amnesia to allow reexpansion of currently expanding macros during argument evaluation expandedmacros2.erase(name()); - partok = it->second.expand(output, loc, partok, macros, std::move(expandedmacros2)); + TokenList temp(files); + partok = it->second.expand(temp, loc, partok, macros, expandedmacros2); + // Expand while the expansion result ends with the name of a + // function-like macro whose arguments are supplied by the + // remaining argument tokens + while (partok && partok->previous) { + const Macro * const calledMacro = rescanMacro(temp, partok->previous, macros, expandedmacros2); + if (!calledMacro) + break; + TokenList temp2(files); + temp2.push_back(new Token(temp.cback()->str(), partok->location)); + unsigned int par = 0; + const Token *tok2 = partok; + for (; tok2 && tok2 != argEnd; tok2 = tok2->next) { + temp2.push_back(new Token(*tok2)); + if (tok2->op == '(') + ++par; + else if (tok2->op == ')') { + --par; + if (par == 0U) + break; + } + } + if (!tok2 || tok2 == argEnd) + break; + output.takeTokens(temp); + output.deleteToken(output.back()); + calledMacro->expand(temp, loc, temp2.cfront(), macros, expandedmacros2); + partok = tok2->next; + } + output.takeTokens(temp); } else { output.push_back(newMacroToken(partok->str(), loc, isReplaced(expandedmacros), partok)); output.back()->macro = partok->macro; diff --git a/test.cpp b/test.cpp index 247f63e1..8223f6f6 100644 --- a/test.cpp +++ b/test.cpp @@ -989,6 +989,22 @@ static void define26() "f ( ) ( )", preprocess(code2)); } +static void define27() +{ + // an expansion result inside a macro argument that is a function-like macro + // name must be rescanned against the remaining argument tokens + const char code[] = "#define o1(a) a()\n" + "#define b(c, d) c##d\n" + "#define l(c, d) b(c, 1)\n" + "#define m(e, f)\n" + "#define g(h, i) m(h, )\n" + "#define j(h, i) g(h, )\n" + "#define k() l(j(, ), )\n" + "#define n(a) j(l(o, )(a), )\n" + "n(k)\n"; + ASSERT_EQUALS("", preprocess(code)); +} + static void define_invalid_1() { @@ -4563,6 +4579,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(define24); TEST_CASE(define25); TEST_CASE(define26); + TEST_CASE(define27); TEST_CASE(define_invalid_1); TEST_CASE(define_invalid_2); TEST_CASE(define_invalid_3); From ab690bb61a809b4115d1b0144d1fdc68c8130b59 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 5 Aug 2026 10:48:03 -0500 Subject: [PATCH 2/2] Add reusable expandTrailingCalls --- simplecpp.cpp | 74 ++++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index a90e09a6..a01ec030 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2174,24 +2174,34 @@ namespace simplecpp { return &it->second; } - const Token *recursiveExpandToken(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { - // Expand while the expansion result ends with the name of a function-like - // macro whose arguments are supplied by the tokens that follow it. Each round - // consumes that macro call from the token stream, so tok always advances. + /** Expand while the expansion result in @p temp ends with the name of a + * function-like macro whose arguments are supplied by the tokens after @p tok. + * Each round consumes that macro call from the token stream, so tok always + * advances. The @p gatherCall callback copies the "( ... )" tokens that follow + * @p lpar into its output list and returns the matching ')', or nullptr when no + * complete call is available. Returns the last consumed token. */ + template + const Token *expandTrailingCalls(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, GatherCall gatherCall) const { while (const Macro * const calledMacro = rescanMacro(temp, tok, macros, expandedmacros)) { TokenList temp2(files); - temp2.push_back(new Token(temp.cback()->str(), tok->location)); + temp2.push_back(new Token(temp.cback()->str(), tok->next->location)); - const Token * const tok2 = appendTokens(temp2, loc, tok->next, macros, expandedmacros, parametertokens); - if (!tok2) + const Token * const closingPar = gatherCall(temp2, tok->next); + if (!closingPar) break; output.takeTokens(temp); output.deleteToken(output.back()); calledMacro->expand(temp, loc, temp2.cfront(), macros, expandedmacros); - tok = tok2; + tok = closingPar; } output.takeTokens(temp); - return tok->next; + return tok; + } + + const Token *recursiveExpandToken(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { + return expandTrailingCalls(output, temp, loc, tok, macros, expandedmacros, [&](TokenList &temp2, const Token *lpar) { + return appendTokens(temp2, loc, lpar, macros, expandedmacros, parametertokens); + })->next; } const Token *expandToken(TokenList &output, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { @@ -2317,36 +2327,28 @@ namespace simplecpp { std::set expandedmacros2(expandedmacros); // temporary amnesia to allow reexpansion of currently expanding macros during argument evaluation expandedmacros2.erase(name()); TokenList temp(files); - partok = it->second.expand(temp, loc, partok, macros, expandedmacros2); - // Expand while the expansion result ends with the name of a - // function-like macro whose arguments are supplied by the - // remaining argument tokens - while (partok && partok->previous) { - const Macro * const calledMacro = rescanMacro(temp, partok->previous, macros, expandedmacros2); - if (!calledMacro) - break; - TokenList temp2(files); - temp2.push_back(new Token(temp.cback()->str(), partok->location)); - unsigned int par = 0; - const Token *tok2 = partok; - for (; tok2 && tok2 != argEnd; tok2 = tok2->next) { - temp2.push_back(new Token(*tok2)); - if (tok2->op == '(') - ++par; - else if (tok2->op == ')') { - --par; - if (par == 0U) - break; + partok = it->second.expand(temp, loc, partok, macros, std::move(expandedmacros2)); + if (partok->op == '(' && temp.cback() && temp.cback()->name) { + // the expansion result may end with the name of a function-like + // macro whose arguments are supplied by the remaining argument tokens + std::set expandedmacros3(expandedmacros); + expandedmacros3.erase(name()); + partok = expandTrailingCalls(output, temp, loc, partok->previous, macros, expandedmacros3, [&](TokenList &temp2, const Token *lpar) -> const Token * { + unsigned int par = 0; + for (const Token *tok2 = lpar; tok2 != argEnd; tok2 = tok2->next) { + temp2.push_back(new Token(*tok2)); + if (tok2->op == '(') + ++par; + else if (tok2->op == ')') { + if (--par == 0U) + return tok2; + } } - } - if (!tok2 || tok2 == argEnd) - break; + return nullptr; + })->next; + } else { output.takeTokens(temp); - output.deleteToken(output.back()); - calledMacro->expand(temp, loc, temp2.cfront(), macros, expandedmacros2); - partok = tok2->next; } - output.takeTokens(temp); } else { output.push_back(newMacroToken(partok->str(), loc, isReplaced(expandedmacros), partok)); output.back()->macro = partok->macro;