Repository navigation
Conversation
|
@chrchr-github @pfultz2 @web-flow @glankk @ludviggunne @wjakobsson I do not plan to merge this PR "as is" . This is a proof-of-concept PR written by AI. But can you please give me feedback about the "philosophy". I want to restructure macro expansion in simplecpp to follow the standard better and use best-practices for C preprocessors. When I wrote simplecpp I did not have a complete understanding of macro expansion. Issue #742 has a section "Problems with the current macro expansion" that explains the problems. If we agree about the goal and philosophy then I will try to start splitting out some refactorings from this PR that are small and safe. I will thereby reduce the size of this PR. And then when it's smaller we can make up a plan about how we convert macro expansion in simplecpp as safely as possible. |
C11 6.10.3.5 examples 3, 4, 5 and 7, the C23 __VA_OPT__ examples and invalid ## pastes. Known bugs are tested with the new TODO_ASSERT_EQUALS. Tests for Token::macro, the locations of expanded tokens and the macro usage, which are used by cppcheck. A new helper expandedTokens() writes each output token with its macro name and location; the tokenMacro tests use it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
checkHashHashOperands(), checkPunctuatorPaste() and checkUniversalCharacterPaste() only depend on the tokens, not on the expansion state, so a new expander can use them too. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… error Only an encoding prefix such as L, u8 or R gives a valid token, as in L ## "s". Before, cat(a,"s") gave the invalid token a"s". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stringify() only depends on the tokens, so a new expander can use it too. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ors() No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
combineOperators() did not copy whitespaceahead from the second character, so str(a == 0) gave "a ==0" (C11 6.10.3.5 EXAMPLE 4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It now returns the argument delimiters of a valid call, or throws wrongNumberOfParameters, so a new expander can use it. Tests are added for the argument count. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e an error gcc outputs the name, simplecpp reports wrong number of parameters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No behaviour change. A test is added for defined at the end of the file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The whitespace is copied in Macro::newMacroToken() instead. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
setExpandedFrom(), markExpandedFrom() and isExpandedFrom() are renamed to setHideSet(), addToHideSet() and isInHideSet(). No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The names are what Prosser's algorithm uses, and they can not dangle after #undef. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DUI::hideSetExpansion (simplecpp option -hideset-expansion) selects it. It implements Prosser's algorithm for object-like and function-like macros: arguments are expanded once, and the result of an expansion is pushed back onto the input, so a call whose arguments follow in the source is found by normal rescanning. testrunner runs all tests with both macro expansions; HIDESET_EXPANSION_TODO() skips a test that the new one can not handle yet. The macro usage in the tests is sorted, its order depends on the standard library. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# needs the arguments that are not expanded. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# is an operator in a function-like macro when it is followed by a parameter; the argument as written in the call is turned into a string with stringify(). The whitespace after a parameter comes after its argument, and the expansion is followed by the whitespace after the macro call. Output: a # from a macro expansion at the start of a line gets a space before it, so it is not read as a directive, as gcc and clang do (clang tests hash_space.c and macro_not_define.c). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s replaced by ## needs the operands before they are joined. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As in C11 6.10.3.3: an argument that is an operand of ## is not macro expanded, an empty argument is a placemarker, and the pasted token is rescanned. The ## checks of the old expansion are used. Like the old expansion, ", ## __VA_ARGS__" removes the comma when there are no variable arguments, and a string literal followed by a name is not pasted. With the hide-set expansion, 141 of the 155 clang/gcc testsuite tests pass (140 with the old expansion). The new test macro TODO_OLD_EXPANSION_ASSERT_EQUALS() is for known bugs of the old expansion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MacroCall has what the replacement of a macro call depends on: the hide set, the arguments and the whitespace after the call. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Token::macro is set in one place. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…roName() They do not depend on how the old expansion stores the arguments, so the hide-set expansion can use them. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
findDefinedOperand() and definedMacroName() are used, as in the old expansion. 5 more tests pass with the hide-set expansion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… list __VA_OPT__ will use it for its content. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As in C23 6.10.5.1: __VA_OPT__(content) is replaced by nothing when the variable arguments are macro expanded to nothing, otherwise by its content, which is substituted like the replacement list. #__VA_OPT__(...) gives a string literal, and __VA_OPT__ can be an operand of ##. These give the same result as gcc with the hide-set expansion, but not with the old one: F(E) where E expands to nothing, #__VA_OPT__(a b), and a ## __VA_OPT__(b c) ## d with no variable arguments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
getOperand() was getting long. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #742 (draft, work in progress).
Adds a macro expander that uses Prosser's hide-set algorithm, next to the old
Macro::expand(). It is off by default and selected withDUI::hideSetExpansion(simplecpp option-hideset-expansion). testrunner runs every test with both expansions;HIDESET_EXPANSION_TODO()skips a test that the new expansion can't handle yet.How well the new macro expansion works
clang and gcc preprocessor testsuites (
run-tests.py; a test passes when the output is the same as clang's or gcc's):macro_paste_hard.candmacro_rescan_varargs.cfail with the old expansion (todo list ofrun-tests.py) and pass with the new one.testrunner: 323 tests, all run with both expansions. With the new expansion 6 are skipped:
Token::macroand location rules__VA_ARGS__is removedReal code: for 1,687 files (/usr/include, cppcheck lib and test, simplecpp testsuite) the output is the same with both expansions, except for 14 files of the clang/gcc testsuites. In 11 of them the new expansion gives clang's or gcc's result and the old one doesn't; in the other 3 both are wrong in the same way.
Correct with the new expansion, wrong with the old one (the results are the same as gcc's; the tests use
TODO_OLD_EXPANSION_ASSERT_EQUALS()):#define f(x) x/f(f)(1)f(1)1#define R()/Rat the end of the fileRELLIP_FUNC(F LPAREN 'a', 'b' RPAREN)(clangmacro_rescan_varargs.c)'a' + 'b'F ( 'a' , 'b' )#define r(x,y) x ## y/{ r(,) }(C11 6.10.3.5 EXAMPLE 3 and 5){ }#define A x######x/A(gccpr58844-1.c)xxx## x#define F(...) <__VA_OPT__(x)>/F(E),Eexpands to nothing<><x>#define F(a,...) #__VA_OPT__(a b)/F(X,2)with#define X 1"1 b""X" b#define F(a,...) a ## __VA_OPT__(b c) ## d/F(x)xdx# "d"#define HASH #/HASH define foo bar# define foo baron line 2#on line 1 (the location of the#define)simplecpp issues fixed by the new macro expansion
The examples in these issues give the same result as gcc with the new expansion; they are added as tests (
TODO_OLD_EXPANSION_ASSERT_EQUALS()where the old expansion is wrong).EVAL ( HELLO EMPTY() () )HELLO ( )"Hello World"EVAL(REPEAT(4, M, ~))(cloak.h idioms)REPEAT ( 3 , M , ~ ) 30 1 2 3PAR(PREFIX_ ## kind, (__VA_ARGS__))PREFIX_SCALAR ( int , x )int xMACRO_COMPOSE(FOO, BAR(1))withA ## BFOOBAR ( 1 )1PREFIX##NAME##EXTENSIONwith empty arguments##int foo ;a ## ## c(consecutive##)x# "y"xy# __VA_OPT__(# x),# __VA_OPT__(x ## __VA_ARGS__)"x" "Hello" world"\"x\"" "Helloworld"#define HASH #/HASH define foo bar#on line 1# define foo baron line 2For the #713 iteration idioms, a hide set only applies while an expansion is rescanned: a macro name that is found in the hide set of its own macro is never replaced ("painted", C11 6.10.3.4p2), but other tokens can be expanded again when they are rescanned later, for example as part of an argument.
Not fixed by this PR:
defined(X ## Y)with parentheses in#if(#653, #218, #75) is not handled by either expansion; it can be fixed infindDefinedOperand(), which both use. An unterminated macro call (#414) is still not an error.Commits
Preparation, can be split out into separate PRs:
__VA_OPT__examples, invalid##, andToken::macro, expanded token locations andMacroUsage(used by cppcheck). Known bugs use the newTODO_ASSERT_EQUALS().##checks,stringify(), the argument count check ingetMacroParameters(),expandBuiltin(),findDefinedOperand()/definedMacroName(),copyMacroCall(),setLocation(), andcombineWithNext()incombineOperators().##of a name or number with a string literal is an error (cat(a,"s"));#keeps the space after an operator of two characters (str(a == 0)); a#from a macro at the start of an output line gets a space before it, as gcc and clang do.Hide sets and the new expansion:
Token::mExpandedFrombecomes the hide setmHideSet, with macro names instead ofMacropointers.HideSetExpander, added step by step: object-like and function-like macros,__VA_ARGS__and the built-in macros; then#,##(with placemarkers, as in C11 6.10.3.3),definedin a replacement list, and__VA_OPT__(C23 6.10.5.1). A hide set only applies while the expansion is rescanned.All commits except the fixes above have no behaviour change with the default (old) expansion.
Still to do
__VA_ARGS__(MSVC-like, done by the old expansion)Token::macroand location rules and follow them (cppcheck uses them)hideSetExpansion🤖 Generated with Claude Code