From bc98f4224b4ae2f4e0cc59ddca11972c203fdba6 Mon Sep 17 00:00:00 2001 From: datadiode Date: Wed, 28 Aug 2024 07:34:10 +0200 Subject: [PATCH 01/10] Allow reexpansion of currently expanding macros during argument evaluation. This fixes #31 as reproduced in TEST_CASE(define_define_11) without breaking code from @ydamigos's comment to #225 as reproduced in TEST_CASE(define_define_11a). --- simplecpp.cpp | 33 ++++++--------------------------- test.cpp | 12 ++++++++++++ 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 3cce780e..5a327403 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -102,8 +102,6 @@ static const simplecpp::TokenString ONCE("once"); static const simplecpp::TokenString HAS_INCLUDE("__has_include"); -static const simplecpp::TokenString INNER_COMMA(",,"); - template static std::string toString(T t) { // NOLINTNEXTLINE(misc-const-correctness) - false positive @@ -1622,10 +1620,6 @@ namespace simplecpp { rawtok = rawtok2->next; } output->takeTokens(output2); - for (Token* tok = output->front(); tok; tok = tok->next) { - if (tok->str() == INNER_COMMA) - tok->setstr(","); - } return rawtok; } @@ -1795,25 +1789,9 @@ namespace simplecpp { tok = expandHash(tokens, rawloc, tok, macros, expandedmacros, parametertokens); } else { if (!expandArg(tokens, tok, rawloc, macros, expandedmacros, parametertokens)) { - bool expanded = false; - const MacroMap::const_iterator it = macros.find(tok->str()); - if (it != macros.end() && expandedmacros.find(tok->str()) == expandedmacros.end()) { - const Macro &m = it->second; - if (!m.functionLike()) { - Token* mtok = tokens->back(); - m.expand(tokens, rawloc, tok, macros, expandedmacros); - for (mtok = mtok->next; mtok; mtok = mtok->next) { - if (mtok->op == ',') - mtok->setstr(INNER_COMMA); - } - expanded = true; - } - } - if (!expanded) { - tokens->push_back(new Token(*tok)); - if (tok->macro.empty() && (par > 0 || tok->str() != "(")) - tokens->back()->macro = name(); - } + tokens->push_back(new Token(*tok)); + if (tok->macro.empty() && (par > 0 || tok->str() != "(")) + tokens->back()->macro = name(); } if (tok->op == '(') @@ -2138,9 +2116,10 @@ namespace simplecpp { return true; for (const Token *partok = parametertokens[argnr]->next; partok != parametertokens[argnr + 1U];) { 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())) + if (it != macros.end() && !partok->isExpandedFrom(&it->second) && (partok->str() == name() || expandedmacros.find(partok->str()) == expandedmacros.end())) { + std::set expandedmacros; // temporary amnesia to allow reexpansion of currently expanding macros during argument evaluation partok = it->second.expand(output, loc, partok, macros, expandedmacros); - else { + } else { output->push_back(newMacroToken(partok->str(), loc, isReplaced(expandedmacros), partok)); output->back()->macro = partok->macro; partok = partok->next; diff --git a/test.cpp b/test.cpp index d00658ad..3e857b24 100644 --- a/test.cpp +++ b/test.cpp @@ -717,6 +717,17 @@ static void define_define_11() ASSERT_EQUALS("\n\n\n\nP2DIR ;", preprocess(code)); } +static void define_define_11a() +{ + const char code[] = "#define A_B_C 0x1\n" + "#define A_ADDRESS 0x00001000U\n" + "#define A ((uint32_t ) A_ADDRESS)\n" + "#define CONCAT(x, y, z) x ## _ ## y ## _ ## z\n" + "#define TEST_MACRO CONCAT(A, B, C)\n" + "TEST_MACRO\n"; + ASSERT_EQUALS("\n\n\n\n\n0x1", preprocess(code)); +} + static void define_define_12() { const char code[] = "#define XY(Z) Z\n" @@ -2855,6 +2866,7 @@ int main(int argc, char **argv) TEST_CASE(define_define_9); // line break in nested macro call TEST_CASE(define_define_10); TEST_CASE(define_define_11); + TEST_CASE(define_define_11a); TEST_CASE(define_define_12); // expand result of ## TEST_CASE(define_define_13); TEST_CASE(define_define_14); From eed5b7c5203c8067da4f8578dfd8bdec78579eb9 Mon Sep 17 00:00:00 2001 From: datadiode Date: Thu, 29 Aug 2024 07:19:08 +0200 Subject: [PATCH 02/10] Fix Clang-Tidy errors --- simplecpp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 5a327403..d15da9f8 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2117,8 +2117,8 @@ namespace simplecpp { for (const Token *partok = parametertokens[argnr]->next; partok != parametertokens[argnr + 1U];) { 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 expandedmacros; // temporary amnesia to allow reexpansion of currently expanding macros during argument evaluation - partok = it->second.expand(output, loc, partok, macros, expandedmacros); + const std::set expandedmacros2; // temporary amnesia to allow reexpansion of currently expanding macros during argument evaluation + partok = it->second.expand(output, loc, partok, macros, expandedmacros2); } else { output->push_back(newMacroToken(partok->str(), loc, isReplaced(expandedmacros), partok)); output->back()->macro = partok->macro; From 91f42e62fc97abf386538d59bfb95447fb89f99f Mon Sep 17 00:00:00 2001 From: datadiode Date: Thu, 29 Aug 2024 08:28:10 +0200 Subject: [PATCH 03/10] Temporarily bypass macro expansion during hash expansion (#296) --- simplecpp.cpp | 3 ++- test.cpp | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index d15da9f8..d5abb310 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2140,7 +2140,8 @@ namespace simplecpp { */ const Token *expandHash(TokenList *output, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { TokenList tokenListHash(files); - tok = expandToken(&tokenListHash, loc, tok->next, macros, expandedmacros, parametertokens); + const MacroMap macros2; // temporarily bypass macro expansion + tok = expandToken(&tokenListHash, loc, tok->next, macros2, expandedmacros, parametertokens); std::ostringstream ostr; ostr << '\"'; for (const Token *hashtok = tokenListHash.cfront(); hashtok; hashtok = hashtok->next) diff --git a/test.cpp b/test.cpp index 3e857b24..b5850baf 100644 --- a/test.cpp +++ b/test.cpp @@ -1030,6 +1030,11 @@ static void hash() preprocess("#define A(x) (x)\n" "#define B(x) A(#x)\n" "B(123)")); + + ASSERT_EQUALS("\n\nprintf ( \"bar(3)\" \"\\n\" ) ;", + preprocess("#define bar(x) x % 2\n" + "#define foo(x) printf(#x \"\\n\")\n" + "foo(bar(3));")); } static void hashhash1() // #4703 From f7736110d200ccb3f592cf3e73e712ab2618b1e7 Mon Sep 17 00:00:00 2001 From: datadiode Date: Thu, 29 Aug 2024 17:30:01 +0200 Subject: [PATCH 04/10] Expand varargs when referenced as per the nonstandard gcc/clang extension for empty varargs, to yield gcc-like behavior also in cases where varargs isn't empty (#66, #130) --- simplecpp.cpp | 8 ++++++++ test.cpp | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/simplecpp.cpp b/simplecpp.cpp index d5abb310..9adbfd21 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1895,6 +1895,14 @@ namespace simplecpp { if (sameline(tok, tok->next) && tok->next && tok->next->op == '#' && tok->next->next && tok->next->next->op == '#') { if (!sameline(tok, tok->next->next->next)) throw invalidHashHash::unexpectedNewline(tok->location, name()); + if (variadic && tok->op == ',' && tok->next->next->next->str() == args.back()) { + Token *const comma = newMacroToken(tok->str(), loc, isReplaced(expandedmacros), tok); + output->push_back(comma); + tok = expandToken(output, loc, tok->next->next->next, macros, expandedmacros, parametertokens2); + if (output->back() == comma) + output->deleteToken(comma); + continue; + } TokenList new_output(files); if (!expandArg(&new_output, tok, parametertokens2)) output->push_back(newMacroToken(tok->str(), loc, isReplaced(expandedmacros), tok)); diff --git a/test.cpp b/test.cpp index b5850baf..f340ff8c 100644 --- a/test.cpp +++ b/test.cpp @@ -1074,6 +1074,16 @@ static void hashhash4() // nonstandard gcc/clang extension for empty varargs ASSERT_EQUALS("\n\na ( 1 ) ;", preprocess(code)); } +static void hashhash4a() +{ + const char code[] = "#define GETMYID(a) ((a))+1\n" + "#define FIGHT_FOO(c, ...) foo(c, ##__VA_ARGS__)\n" + "#define FIGHT_BAR(c, args...) bar(c, ##args)\n" + "FIGHT_FOO(1, GETMYID(a));\n" + "FIGHT_BAR(1, GETMYID(b));"; + ASSERT_EQUALS("\n\n\nfoo ( 1 , ( ( a ) ) + 1 ) ;\nbar ( 1 , ( ( b ) ) + 1 ) ;", preprocess(code)); +} + static void hashhash5() { ASSERT_EQUALS("x1", preprocess("x##__LINE__")); @@ -2909,6 +2919,7 @@ int main(int argc, char **argv) TEST_CASE(hashhash2); TEST_CASE(hashhash3); TEST_CASE(hashhash4); + TEST_CASE(hashhash4a); // #66, #130 TEST_CASE(hashhash5); TEST_CASE(hashhash6); TEST_CASE(hashhash7); // # ## # (C standard; 6.10.3.3.p4) From 0a9b23000a726b62ef279a257169d1af48023c9b Mon Sep 17 00:00:00 2001 From: datadiode Date: Thu, 29 Aug 2024 19:00:39 +0200 Subject: [PATCH 05/10] Fix a Clang-Tidy error --- simplecpp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 9adbfd21..f7bb98a5 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2146,7 +2146,7 @@ namespace simplecpp { * @param parametertokens parameters given when expanding this macro * @return token after the X */ - const Token *expandHash(TokenList *output, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { + const Token *expandHash(TokenList *output, const Location &loc, const Token *tok, const MacroMap &, const std::set &expandedmacros, const std::vector ¶metertokens) const { TokenList tokenListHash(files); const MacroMap macros2; // temporarily bypass macro expansion tok = expandToken(&tokenListHash, loc, tok->next, macros2, expandedmacros, parametertokens); From de037486abec6bff6aca17846c77db41e3a06e50 Mon Sep 17 00:00:00 2001 From: datadiode Date: Fri, 30 Aug 2024 09:27:34 +0200 Subject: [PATCH 06/10] Fix a Clang-Tidy error --- simplecpp.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index f7bb98a5..85ea95b5 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1786,7 +1786,7 @@ namespace simplecpp { // A##B => AB tok = expandHashHash(tokens, rawloc, tok, macros, expandedmacros, parametertokens); } else if (tok->op == '#' && sameline(tok, tok->next) && tok->next->op != '#') { - tok = expandHash(tokens, rawloc, tok, macros, expandedmacros, parametertokens); + tok = expandHash(tokens, rawloc, tok, expandedmacros, parametertokens); } else { if (!expandArg(tokens, tok, rawloc, macros, expandedmacros, parametertokens)) { tokens->push_back(new Token(*tok)); @@ -1947,7 +1947,7 @@ namespace simplecpp { tok = expandHashHash(output, loc, tok->previous, macros, expandedmacros, parametertokens2); } else { // #123 => "123" - tok = expandHash(output, loc, tok->previous, macros, expandedmacros, parametertokens2); + tok = expandHash(output, loc, tok->previous, expandedmacros, parametertokens2); } } @@ -2141,12 +2141,11 @@ namespace simplecpp { * @param output destination tokenlist * @param loc location for expanded token * @param tok The # token - * @param macros all macros * @param expandedmacros set with expanded macros, with this macro * @param parametertokens parameters given when expanding this macro * @return token after the X */ - const Token *expandHash(TokenList *output, const Location &loc, const Token *tok, const MacroMap &, const std::set &expandedmacros, const std::vector ¶metertokens) const { + const Token *expandHash(TokenList *output, const Location &loc, const Token *tok, const std::set &expandedmacros, const std::vector ¶metertokens) const { TokenList tokenListHash(files); const MacroMap macros2; // temporarily bypass macro expansion tok = expandToken(&tokenListHash, loc, tok->next, macros2, expandedmacros, parametertokens); From 064de8486941fdde7b6093289d0ee7759e180d90 Mon Sep 17 00:00:00 2001 From: datadiode Date: Fri, 30 Aug 2024 10:41:09 +0200 Subject: [PATCH 07/10] Prevent simplifyPath_cppcheck() from wasting time on looking for a hypothetical network host --- CMakeLists.txt | 5 +++++ test.cpp | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 30ee1431..88c46b9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,11 @@ option(DISABLE_CPP03_SYNTAX_CHECK "Disable the C++03 syntax check." OFF) include(CheckCXXCompilerFlag) +if (WIN32) + # prevent simplifyPath_cppcheck() from wasting time on looking for a hypothetical network host + add_definitions(-DUNCHOST=$ENV{COMPUTERNAME}) +endif() + function(add_compile_options_safe FLAG) string(MAKE_C_IDENTIFIER "HAS_CXX_FLAG${FLAG}" mangled_flag) check_cxx_compiler_flag(${FLAG} ${mangled_flag}) diff --git a/test.cpp b/test.cpp index f340ff8c..e6210ecb 100644 --- a/test.cpp +++ b/test.cpp @@ -16,6 +16,9 @@ #include #include +#define STRINGIZE_(x) #x +#define STRINGIZE(x) STRINGIZE_(x) + static int numberOfFailedAssertions = 0; #define ASSERT_EQUALS(expected, actual) (assertEquals((expected), (actual), __LINE__)) @@ -2593,8 +2596,8 @@ static void simplifyPath_cppcheck() ASSERT_EQUALS("src/", simplecpp::simplifyPath("src/abc/../")); // Handling of UNC paths on Windows - ASSERT_EQUALS("//src/test.cpp", simplecpp::simplifyPath("//src/test.cpp")); - ASSERT_EQUALS("//src/test.cpp", simplecpp::simplifyPath("///src/test.cpp")); + ASSERT_EQUALS("//" STRINGIZE(UNCHOST) "/test.cpp", simplecpp::simplifyPath("//" STRINGIZE(UNCHOST) "/test.cpp")); + ASSERT_EQUALS("//" STRINGIZE(UNCHOST) "/test.cpp", simplecpp::simplifyPath("///" STRINGIZE(UNCHOST) "/test.cpp")); } static void simplifyPath_New() From 5547385820d6f152e7816e02e2f8ab08abe0425e Mon Sep 17 00:00:00 2001 From: datadiode Date: Fri, 30 Aug 2024 21:56:43 +0200 Subject: [PATCH 08/10] FIXED testcase: stringize_misc.c --- run-tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/run-tests.py b/run-tests.py index c053ed32..2f28bf0f 100644 --- a/run-tests.py +++ b/run-tests.py @@ -71,7 +71,6 @@ def cleanup(out): 'c99-6_10_3_4_p6.c', 'expr_usual_conversions.c', # condition is true: 4U - 30 >= 0 'stdint.c', - 'stringize_misc.c', # GCC.. 'diagnostic-pragma-1.c', From 5e16ae7afa7db6bbf4dfd19e3a5bb15c367fc2f3 Mon Sep 17 00:00:00 2001 From: datadiode Date: Sat, 31 Aug 2024 09:47:43 +0200 Subject: [PATCH 09/10] Revert the no longer needed fix from https://github.com/danmar/simplecpp/commit/f1086d2 while retaining the relevant TEST_CASE(define_define_17) --- simplecpp.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 85ea95b5..babf6113 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1561,10 +1561,7 @@ namespace simplecpp { rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location)); rawtok = rawtok->next; } - bool first = true; - if (valueToken && valueToken->str() == rawtok1->str()) - first = false; - if (expand(&output2, rawtok1->location, rawtokens2.cfront(), macros, expandedmacros, first)) + if (expand(&output2, rawtok1->location, rawtokens2.cfront(), macros, expandedmacros)) rawtok = rawtok1->next; } else { rawtok = expand(&output2, rawtok->location, rawtok, macros, expandedmacros); @@ -1809,10 +1806,8 @@ namespace simplecpp { return sameline(lpar,tok) ? tok : nullptr; } - const Token * expand(TokenList * const output, const Location &loc, const Token * const nameTokInst, const MacroMap ¯os, std::set expandedmacros, bool first=false) const { - - if (!first) - expandedmacros.insert(nameTokInst->str()); + const Token * expand(TokenList * const output, const Location &loc, const Token * const nameTokInst, const MacroMap ¯os, std::set expandedmacros) const { + expandedmacros.insert(nameTokInst->str()); usageList.push_back(loc); From 912d924a693d994e938a570d1ee08303c655882f Mon Sep 17 00:00:00 2001 From: datadiode Date: Mon, 2 Sep 2024 15:37:52 +0200 Subject: [PATCH 10/10] Have stringize operator preserve standard required whitespace (#241) / Fix an integer truncation warning in testrunner --- simplecpp.cpp | 18 ++++++++++++------ simplecpp.h | 9 ++++++--- test.cpp | 8 +++++++- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index babf6113..928e123c 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -886,7 +886,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, } if (prefix.empty()) - push_back(new Token(s, location)); // push string without newlines + push_back(new Token(s, location, isspace(stream.peekChar()))); // push string without newlines else back()->setstr(prefix + s); @@ -916,7 +916,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, } } - push_back(new Token(currentToken, location)); + push_back(new Token(currentToken, location, isspace(stream.peekChar()))); if (multiline) location.col += currentToken.size(); @@ -1546,9 +1546,9 @@ namespace simplecpp { // Copy macro call to a new tokenlist with no linebreaks const Token * const rawtok1 = rawtok; TokenList rawtokens2(inputFiles); - rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location)); + rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location, rawtok->whitespaceahead)); rawtok = rawtok->next; - rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location)); + rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location, rawtok->whitespaceahead)); rawtok = rawtok->next; int par = 1; while (rawtok && par > 0) { @@ -1558,7 +1558,7 @@ namespace simplecpp { --par; else if (rawtok->op == '#' && !sameline(rawtok->previous, rawtok)) throw Error(rawtok->location, "it is invalid to use a preprocessor directive as macro parameter"); - rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location)); + rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location, rawtok->whitespaceahead)); rawtok = rawtok->next; } if (expand(&output2, rawtok1->location, rawtokens2.cfront(), macros, expandedmacros)) @@ -2128,6 +2128,8 @@ namespace simplecpp { partok = partok->next; } } + if (tok->whitespaceahead && output->back()) + output->back()->whitespaceahead = true; return true; } @@ -2146,8 +2148,12 @@ namespace simplecpp { tok = expandToken(&tokenListHash, loc, tok->next, macros2, expandedmacros, parametertokens); std::ostringstream ostr; ostr << '\"'; - for (const Token *hashtok = tokenListHash.cfront(); hashtok; hashtok = hashtok->next) + for (const Token *hashtok = tokenListHash.cfront(), *next; hashtok; hashtok = next) { + next = hashtok->next; ostr << hashtok->str(); + if (next && hashtok->whitespaceahead) + ostr << ' '; + } ostr << '\"'; output->push_back(newMacroToken(escapeString(ostr.str()), loc, isReplaced(expandedmacros))); return tok; diff --git a/simplecpp.h b/simplecpp.h index 88a14014..672f29a5 100755 --- a/simplecpp.h +++ b/simplecpp.h @@ -94,13 +94,13 @@ namespace simplecpp { */ class SIMPLECPP_LIB Token { public: - Token(const TokenString &s, const Location &loc) : - location(loc), previous(nullptr), next(nullptr), string(s) { + Token(const TokenString &s, const Location &loc, bool wsahead = false) : + whitespaceahead(wsahead), location(loc), previous(nullptr), next(nullptr), string(s) { flags(); } Token(const Token &tok) : - macro(tok.macro), op(tok.op), comment(tok.comment), name(tok.name), number(tok.number), location(tok.location), previous(nullptr), next(nullptr), string(tok.string), mExpandedFrom(tok.mExpandedFrom) { + macro(tok.macro), op(tok.op), comment(tok.comment), name(tok.name), number(tok.number), whitespaceahead(tok.whitespaceahead), location(tok.location), previous(nullptr), next(nullptr), string(tok.string), mExpandedFrom(tok.mExpandedFrom) { } void flags() { @@ -132,6 +132,7 @@ namespace simplecpp { bool comment; bool name; bool number; + bool whitespaceahead; Location location; Token *previous; Token *next; @@ -153,6 +154,8 @@ namespace simplecpp { void setExpandedFrom(const Token *tok, const Macro* m) { mExpandedFrom = tok->mExpandedFrom; mExpandedFrom.insert(m); + if (tok->whitespaceahead) + whitespaceahead = true; } bool isExpandedFrom(const Macro* m) const { return mExpandedFrom.find(m) != mExpandedFrom.end(); diff --git a/test.cpp b/test.cpp index e6210ecb..1e42fad2 100644 --- a/test.cpp +++ b/test.cpp @@ -47,7 +47,7 @@ static int assertEquals(const std::string &expected, const std::string &actual, return (expected == actual); } -static int assertEquals(const unsigned int &expected, const unsigned int &actual, int line) +static int assertEquals(const long long &expected, const long long &actual, int line) { return assertEquals(std::to_string(expected), std::to_string(actual), line); } @@ -1038,6 +1038,12 @@ static void hash() preprocess("#define bar(x) x % 2\n" "#define foo(x) printf(#x \"\\n\")\n" "foo(bar(3));")); + + ASSERT_EQUALS("\n\n\n\"Y Y\"", + preprocess("#define X(x,y) x y\n" + "#define STR_(x) #x\n" + "#define STR(x) STR_(x)\n" + "STR(X(Y,Y))")); } static void hashhash1() // #4703