Skip to content

Commit 3c0144b

Browse files
dmcppcheckclaude
andcommitted
Add HideSetExpander, macro expansion with hide sets
It is used when DUI::hideSetExpansion is set. 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. Not handled yet: #, ##, __VA_OPT__ and defined in a replacement list, the comma before an empty __VA_ARGS__, and the old Token::macro rules. The tests that need them are marked with HIDESET_EXPANSION_TODO(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent f9a112d commit 3c0144b

3 files changed

Lines changed: 508 additions & 7 deletions

File tree

‎simplecpp.cpp‎

Lines changed: 205 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,15 @@
2727
#include <cstdlib>
2828
#include <cstring>
2929
#include <ctime>
30+
#include <deque>
3031
#include <fstream>
3132
#include <iostream>
3233
#include <istream>
34+
#include <iterator>
3335
#include <limits>
3436
#include <list>
3537
#include <map>
38+
#include <memory>
3639
#include <set>
3740
#include <sstream>
3841
#include <stack>
@@ -1852,7 +1855,10 @@ namespace simplecpp {
18521855
tok->location = loc;
18531856
}
18541857

1858+
class HideSetExpander;
1859+
18551860
class Macro {
1861+
friend class HideSetExpander;
18561862
public:
18571863
explicit Macro(std::vector<std::string> &f) : nameTokDef(nullptr), valueToken(nullptr), endToken(nullptr), files(f), tokenListDefine(f), variadic(false), variadicOpt(false), valueDefinedInCode_(false) {}
18581864

@@ -2937,6 +2943,199 @@ namespace simplecpp {
29372943
};
29382944
}
29392945

2946+
namespace simplecpp {
2947+
/**
2948+
* Macro expansion with hide sets (Prosser's algorithm, used by DUI::hideSetExpansion).
2949+
* Each token has a hide set: the macros that it must not be expanded into.
2950+
* The result of an expansion is pushed back onto the input and scanned again, so a
2951+
* function-like macro whose arguments follow the expansion in the source is found by
2952+
* normal rescanning.
2953+
* Work in progress: #, ##, __VA_OPT__ and "defined" in a replacement list are not handled yet.
2954+
*/
2955+
class HideSetExpander {
2956+
public:
2957+
HideSetExpander(const MacroMap &macros, std::vector<std::string> &files)
2958+
: macros(macros), files(files) {}
2959+
2960+
HideSetExpander(const HideSetExpander&) = delete;
2961+
HideSetExpander &operator=(const HideSetExpander&) = delete;
2962+
2963+
/**
2964+
* Expand the macro at rawtok.
2965+
* @param output destination tokenlist
2966+
* @param rawtok the macro name in the source
2967+
* @return the first source token after the expansion
2968+
* @throws Macro::Error if the macro can not be expanded
2969+
*/
2970+
const Token *expand(TokenList &output, const Token *rawtok) {
2971+
loc = rawtok->location;
2972+
Input input(rawtok->next);
2973+
input.pending.push_back(new Token(*rawtok));
2974+
expandInput(output, input);
2975+
return input.raw;
2976+
}
2977+
2978+
private:
2979+
/** The input of an expansion: tokens to scan again, then the source */
2980+
struct Input {
2981+
explicit Input(const Token *raw) : raw(raw) {}
2982+
Input(const Input&) = delete;
2983+
Input &operator=(const Input&) = delete;
2984+
~Input() {
2985+
for (const Token * const tok : pending)
2986+
delete tok;
2987+
}
2988+
2989+
/** The next token, read from the source if there are no pending tokens; nullptr at the end */
2990+
Token *read() {
2991+
if (!pending.empty()) {
2992+
std::unique_ptr<Token> tok(pending.front());
2993+
pending.pop_front();
2994+
return tok.release();
2995+
}
2996+
if (!raw)
2997+
return nullptr;
2998+
if (raw->op == '#' && !sameline(raw->previous, raw))
2999+
throw Macro::invalidDirectiveAsMacroParameter(raw->location);
3000+
const Token * const tok = raw;
3001+
raw = raw->next;
3002+
return new Token(*tok);
3003+
}
3004+
3005+
/** The next token, without reading it */
3006+
const Token *peek() const {
3007+
return pending.empty() ? raw : pending.front();
3008+
}
3009+
3010+
/** Put tokens in front of the pending tokens */
3011+
void pushFront(TokenList &tokens) {
3012+
std::deque<Token *> front;
3013+
while (tokens.front()) {
3014+
front.push_back(new Token(*tokens.front()));
3015+
tokens.deleteToken(tokens.front());
3016+
}
3017+
pending.insert(pending.begin(), front.begin(), front.end());
3018+
}
3019+
3020+
/** tokens to scan again, the next one is at the front */
3021+
std::deque<Token *> pending;
3022+
/** next token in the source, it is only read for the arguments of a macro call */
3023+
const Token *raw;
3024+
};
3025+
3026+
static Macro::Error unsupported(const Location &loc, const std::string &what) {
3027+
return {loc, what + " is not supported by the hide-set expansion yet"};
3028+
}
3029+
3030+
/** Expand the pending tokens of the input. Source tokens are only read for macro arguments. */
3031+
void expandInput(TokenList &output, Input &input) {
3032+
while (!input.pending.empty()) {
3033+
Token * const tok = input.read();
3034+
const MacroMap::const_iterator it = tok->name ? macros.find(tok->str()) : macros.end();
3035+
if (it == macros.end() || tok->isInHideSet(it->first)) {
3036+
output.push_back(tok);
3037+
continue;
3038+
}
3039+
const Macro &macro = it->second;
3040+
3041+
if (macro.functionLike()) {
3042+
const Token * const next = input.peek();
3043+
if (!next || next->op != '(') {
3044+
output.push_back(tok);
3045+
continue;
3046+
}
3047+
}
3048+
3049+
macro.usageList.emplace_back(loc);
3050+
if (macro.expandBuiltin(output, loc, tok)) {
3051+
delete tok;
3052+
continue;
3053+
}
3054+
3055+
std::set<TokenString> hideSet;
3056+
std::vector<TokenList> arguments;
3057+
if (macro.functionLike()) {
3058+
TokenList call(files);
3059+
call.push_back(tok);
3060+
readCall(call, input);
3061+
const std::vector<const Token *> delimiters = macro.getMacroParameters(call.cfront(), false);
3062+
// the hide set of the name and the ) have the macro calls that this call
3063+
// was produced by, the arguments are not affected by them
3064+
const std::set<TokenString> &hs1 = call.cfront()->hideSet();
3065+
const std::set<TokenString> &hs2 = delimiters.back()->hideSet();
3066+
std::set_intersection(hs1.begin(), hs1.end(), hs2.begin(), hs2.end(), std::inserter(hideSet, hideSet.begin()));
3067+
arguments = expandArguments(macro, delimiters);
3068+
} else {
3069+
hideSet = tok->hideSet();
3070+
delete tok;
3071+
}
3072+
hideSet.insert(macro.name());
3073+
3074+
TokenList result(files);
3075+
substitute(result, macro, arguments, hideSet);
3076+
input.pushFront(result);
3077+
}
3078+
}
3079+
3080+
/** Read "( ... )" after the macro name in call */
3081+
static void readCall(TokenList &call, Input &input) {
3082+
int par = 0;
3083+
while (Token * const tok = input.read()) {
3084+
call.push_back(tok);
3085+
if (tok->op == '(')
3086+
++par;
3087+
else if (tok->op == ')' && --par == 0)
3088+
return;
3089+
}
3090+
}
3091+
3092+
/** Each argument of the call, fully macro expanded */
3093+
std::vector<TokenList> expandArguments(const Macro &macro, const std::vector<const Token *> &delimiters) {
3094+
std::vector<TokenList> arguments;
3095+
for (std::size_t i = 0; i < macro.args.size(); ++i) {
3096+
arguments.emplace_back(files);
3097+
if (i + 1U >= delimiters.size())
3098+
continue; // empty variadic argument
3099+
Input input(nullptr);
3100+
for (const Token *tok = delimiters[i]->next; tok != delimiters[i + 1U]; tok = tok->next)
3101+
input.pending.push_back(new Token(*tok));
3102+
expandInput(arguments.back(), input);
3103+
}
3104+
return arguments;
3105+
}
3106+
3107+
/** The replacement list of the macro, with the parameters replaced by the arguments */
3108+
void substitute(TokenList &result, const Macro &macro, const std::vector<TokenList> &arguments, const std::set<TokenString> &hideSet) const {
3109+
if (macro.variadicOpt)
3110+
throw unsupported(loc, "__VA_OPT__");
3111+
for (const Token *tok = macro.valueToken; tok != macro.endToken; tok = tok->next) {
3112+
if (tok->op == '#')
3113+
throw unsupported(loc, "# and ##");
3114+
if (tok->str() == DEFINED)
3115+
throw unsupported(loc, "defined");
3116+
const unsigned int argnr = tok->name ? macro.getArgNum(tok->str()) : ~0U;
3117+
if (argnr < arguments.size()) {
3118+
for (const Token *argtok = arguments[argnr].cfront(); argtok; argtok = argtok->next)
3119+
result.push_back(new Token(*argtok));
3120+
} else {
3121+
result.push_back(new Token(tok->str(), loc, tok->whitespaceahead));
3122+
result.back()->macro = macro.name();
3123+
}
3124+
}
3125+
for (Token *tok = result.front(); tok; tok = tok->next) {
3126+
tok->location = loc;
3127+
for (const TokenString &name : hideSet)
3128+
tok->addToHideSet(name);
3129+
}
3130+
}
3131+
3132+
const MacroMap &macros;
3133+
std::vector<std::string> &files;
3134+
/** location of the macro name in the source, all tokens of the expansion get it */
3135+
Location loc;
3136+
};
3137+
}
3138+
29403139
namespace simplecpp {
29413140

29423141
#ifdef __CYGWIN__
@@ -3865,7 +4064,12 @@ static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token
38654064
if (it != macros.end()) {
38664065
simplecpp::TokenList value(files);
38674066
try {
3868-
tok1 = it->second.expand(value, tok, macros, files, outputList);
4067+
if (dui.hideSetExpansion) {
4068+
simplecpp::HideSetExpander expander(macros, files);
4069+
tok1 = expander.expand(value, tok);
4070+
} else {
4071+
tok1 = it->second.expand(value, tok, macros, files, outputList);
4072+
}
38694073
} catch (const simplecpp::Macro::invalidDirectiveAsMacroParameter &err) {
38704074
if (outputList) {
38714075
simplecpp::Output out{

‎simplecpp.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,12 @@ namespace simplecpp {
222222
void addToHideSet(const TokenString &macroName) {
223223
mHideSet.insert(macroName);
224224
}
225+
const std::set<TokenString> &hideSet() const {
226+
return mHideSet;
227+
}
228+
void setHideSet(std::set<TokenString> hideSet) {
229+
mHideSet = std::move(hideSet);
230+
}
225231

226232
void printAll() const;
227233
void printOut() const;

0 commit comments

Comments
 (0)