blob: a4519f2d464e24781359f1e12ea74245b780436a [file] [log] [blame]
alokp@chromium.org04d7d222012-05-16 19:24:07 +00001//
2// Copyright (c) 2011 The ANGLE Project Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6
7#include "MacroExpander.h"
8
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +00009#include <algorithm>
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +000010#include <sstream>
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000011
daniel@transgaming.comb3077d02013-01-11 04:12:09 +000012#include "DiagnosticsBase.h"
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000013#include "Token.h"
14
alokp@chromium.org04d7d222012-05-16 19:24:07 +000015namespace pp
16{
17
Corentin Wallez2bd9c442016-09-20 16:39:18 -040018namespace
19{
20
21const size_t kMaxContextTokens = 10000;
22
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000023class TokenLexer : public Lexer
24{
25 public:
26 typedef std::vector<Token> TokenVector;
27
Zhenyao Mod526f982014-05-13 14:51:19 -070028 TokenLexer(TokenVector *tokens)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000029 {
30 tokens->swap(mTokens);
31 mIter = mTokens.begin();
32 }
33
Corentin Walleze5a1f272015-08-21 02:58:25 +020034 void lex(Token *token) override
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000035 {
36 if (mIter == mTokens.end())
37 {
38 token->reset();
39 token->type = Token::LAST;
40 }
41 else
42 {
43 *token = *mIter++;
44 }
45 }
46
47 private:
48 PP_DISALLOW_COPY_AND_ASSIGN(TokenLexer);
49
50 TokenVector mTokens;
51 TokenVector::const_iterator mIter;
52};
53
Corentin Wallez2bd9c442016-09-20 16:39:18 -040054} // anonymous namespace
55
Olli Etuaho1b2f1622016-03-04 15:06:51 +020056MacroExpander::MacroExpander(Lexer *lexer, MacroSet *macroSet, Diagnostics *diagnostics)
Corentin Wallez2bd9c442016-09-20 16:39:18 -040057 : mLexer(lexer), mMacroSet(macroSet), mDiagnostics(diagnostics), mTotalTokensInContexts(0)
alokp@chromium.org04d7d222012-05-16 19:24:07 +000058{
59}
60
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000061MacroExpander::~MacroExpander()
62{
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +000063 for (std::size_t i = 0; i < mContextStack.size(); ++i)
alokp@chromium.org6c0c2d82012-07-18 15:49:56 +000064 {
65 delete mContextStack[i];
66 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000067}
68
Zhenyao Mod526f982014-05-13 14:51:19 -070069void MacroExpander::lex(Token *token)
alokp@chromium.org04d7d222012-05-16 19:24:07 +000070{
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000071 while (true)
72 {
73 getToken(token);
74
75 if (token->type != Token::IDENTIFIER)
76 break;
77
78 if (token->expansionDisabled())
79 break;
80
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +000081 MacroSet::const_iterator iter = mMacroSet->find(token->text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000082 if (iter == mMacroSet->end())
83 break;
84
85 const Macro& macro = iter->second;
86 if (macro.disabled)
87 {
88 // If a particular token is not expanded, it is never expanded.
89 token->setExpansionDisabled(true);
90 break;
91 }
92 if ((macro.type == Macro::kTypeFunc) && !isNextTokenLeftParen())
93 {
94 // If the token immediately after the macro name is not a '(',
95 // this macro should not be expanded.
96 break;
97 }
98
99 pushMacro(macro, *token);
100 }
101}
102
Zhenyao Mod526f982014-05-13 14:51:19 -0700103void MacroExpander::getToken(Token *token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000104{
105 if (mReserveToken.get())
106 {
107 *token = *mReserveToken;
108 mReserveToken.reset();
109 return;
110 }
111
112 // First pop all empty macro contexts.
113 while (!mContextStack.empty() && mContextStack.back()->empty())
114 {
115 popMacro();
116 }
117
118 if (!mContextStack.empty())
119 {
120 *token = mContextStack.back()->get();
121 }
122 else
123 {
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400124 assert(mTotalTokensInContexts == 0);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000125 mLexer->lex(token);
126 }
127}
128
Zhenyao Mod526f982014-05-13 14:51:19 -0700129void MacroExpander::ungetToken(const Token &token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000130{
131 if (!mContextStack.empty())
132 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700133 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000134 context->unget();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000135 assert(context->replacements[context->index] == token);
136 }
137 else
138 {
139 assert(!mReserveToken.get());
140 mReserveToken.reset(new Token(token));
141 }
142}
143
144bool MacroExpander::isNextTokenLeftParen()
145{
146 Token token;
147 getToken(&token);
148
149 bool lparen = token.type == '(';
150 ungetToken(token);
151
152 return lparen;
153}
154
Zhenyao Mod526f982014-05-13 14:51:19 -0700155bool MacroExpander::pushMacro(const Macro &macro, const Token &identifier)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000156{
157 assert(!macro.disabled);
158 assert(!identifier.expansionDisabled());
159 assert(identifier.type == Token::IDENTIFIER);
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000160 assert(identifier.text == macro.name);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000161
Corentin Wallezd2f195b2016-09-19 15:53:33 -0400162 macro.expansionCount++;
163
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000164 std::vector<Token> replacements;
165 if (!expandMacro(macro, identifier, &replacements))
166 return false;
167
168 // Macro is disabled for expansion until it is popped off the stack.
169 macro.disabled = true;
170
Zhenyao Mod526f982014-05-13 14:51:19 -0700171 MacroContext *context = new MacroContext;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000172 context->macro = &macro;
173 context->replacements.swap(replacements);
174 mContextStack.push_back(context);
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400175 mTotalTokensInContexts += context->replacements.size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000176 return true;
177}
178
179void MacroExpander::popMacro()
180{
181 assert(!mContextStack.empty());
182
Zhenyao Mod526f982014-05-13 14:51:19 -0700183 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000184 mContextStack.pop_back();
185
186 assert(context->empty());
187 assert(context->macro->disabled);
Corentin Wallezd2f195b2016-09-19 15:53:33 -0400188 assert(context->macro->expansionCount > 0);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000189 context->macro->disabled = false;
Corentin Wallezd2f195b2016-09-19 15:53:33 -0400190 context->macro->expansionCount--;
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400191 mTotalTokensInContexts -= context->replacements.size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000192 delete context;
193}
194
Zhenyao Mod526f982014-05-13 14:51:19 -0700195bool MacroExpander::expandMacro(const Macro &macro,
196 const Token &identifier,
197 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000198{
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000199 replacements->clear();
Olli Etuahoe6432c82015-09-08 14:21:38 +0300200
201 // In the case of an object-like macro, the replacement list gets its location
202 // from the identifier, but in the case of a function-like macro, the replacement
203 // list gets its location from the closing parenthesis of the macro invocation.
204 // This is tested by dEQP-GLES3.functional.shaders.preprocessor.predefined_macros.*
205 SourceLocation replacementLocation = identifier.location;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000206 if (macro.type == Macro::kTypeObj)
207 {
208 replacements->assign(macro.replacements.begin(),
209 macro.replacements.end());
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000210
211 if (macro.predefined)
212 {
Zhenyao Mob5e17752014-10-22 10:57:10 -0700213 const char kLine[] = "__LINE__";
214 const char kFile[] = "__FILE__";
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000215
216 assert(replacements->size() == 1);
217 Token& repl = replacements->front();
218 if (macro.name == kLine)
219 {
alokp@chromium.orgf1155922012-06-28 23:34:30 +0000220 std::ostringstream stream;
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000221 stream << identifier.location.line;
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000222 repl.text = stream.str();
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000223 }
224 else if (macro.name == kFile)
225 {
alokp@chromium.orgf1155922012-06-28 23:34:30 +0000226 std::ostringstream stream;
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000227 stream << identifier.location.file;
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000228 repl.text = stream.str();
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000229 }
230 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000231 }
232 else
233 {
234 assert(macro.type == Macro::kTypeFunc);
235 std::vector<MacroArg> args;
236 args.reserve(macro.parameters.size());
Olli Etuahoe6432c82015-09-08 14:21:38 +0300237 if (!collectMacroArgs(macro, identifier, &args, &replacementLocation))
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000238 return false;
239
240 replaceMacroParams(macro, args, replacements);
241 }
242
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000243 for (std::size_t i = 0; i < replacements->size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000244 {
245 Token& repl = replacements->at(i);
246 if (i == 0)
247 {
248 // The first token in the replacement list inherits the padding
249 // properties of the identifier token.
250 repl.setAtStartOfLine(identifier.atStartOfLine());
251 repl.setHasLeadingSpace(identifier.hasLeadingSpace());
252 }
Olli Etuahoe6432c82015-09-08 14:21:38 +0300253 repl.location = replacementLocation;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000254 }
255 return true;
256}
257
Zhenyao Mod526f982014-05-13 14:51:19 -0700258bool MacroExpander::collectMacroArgs(const Macro &macro,
259 const Token &identifier,
Olli Etuahoe6432c82015-09-08 14:21:38 +0300260 std::vector<MacroArg> *args,
261 SourceLocation *closingParenthesisLocation)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000262{
263 Token token;
264 getToken(&token);
265 assert(token.type == '(');
266
267 args->push_back(MacroArg());
268 for (int openParens = 1; openParens != 0; )
269 {
270 getToken(&token);
271
272 if (token.type == Token::LAST)
273 {
Shannon Woods7f2d7942013-11-19 15:07:58 -0500274 mDiagnostics->report(Diagnostics::PP_MACRO_UNTERMINATED_INVOCATION,
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000275 identifier.location, identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000276 // Do not lose EOF token.
277 ungetToken(token);
278 return false;
279 }
280
281 bool isArg = false; // True if token is part of the current argument.
282 switch (token.type)
283 {
284 case '(':
285 ++openParens;
286 isArg = true;
287 break;
288 case ')':
289 --openParens;
290 isArg = openParens != 0;
Olli Etuahoe6432c82015-09-08 14:21:38 +0300291 *closingParenthesisLocation = token.location;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000292 break;
293 case ',':
294 // The individual arguments are separated by comma tokens, but
295 // the comma tokens between matching inner parentheses do not
296 // seperate arguments.
Zhenyao Mod526f982014-05-13 14:51:19 -0700297 if (openParens == 1)
298 args->push_back(MacroArg());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000299 isArg = openParens != 1;
300 break;
301 default:
302 isArg = true;
303 break;
304 }
305 if (isArg)
306 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700307 MacroArg &arg = args->back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000308 // Initial whitespace is not part of the argument.
Zhenyao Mod526f982014-05-13 14:51:19 -0700309 if (arg.empty())
310 token.setHasLeadingSpace(false);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000311 arg.push_back(token);
312 }
313 }
314
Zhenyao Mod526f982014-05-13 14:51:19 -0700315 const Macro::Parameters &params = macro.parameters;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000316 // If there is only one empty argument, it is equivalent to no argument.
317 if (params.empty() && (args->size() == 1) && args->front().empty())
318 {
319 args->clear();
320 }
321 // Validate the number of arguments.
322 if (args->size() != params.size())
323 {
324 Diagnostics::ID id = args->size() < macro.parameters.size() ?
Shannon Woods7f2d7942013-11-19 15:07:58 -0500325 Diagnostics::PP_MACRO_TOO_FEW_ARGS :
326 Diagnostics::PP_MACRO_TOO_MANY_ARGS;
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000327 mDiagnostics->report(id, identifier.location, identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000328 return false;
329 }
330
331 // Pre-expand each argument before substitution.
332 // This step expands each argument individually before they are
333 // inserted into the macro body.
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400334 size_t numTokens = 0;
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000335 for (std::size_t i = 0; i < args->size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000336 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700337 MacroArg &arg = args->at(i);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000338 TokenLexer lexer(&arg);
Olli Etuaho1b2f1622016-03-04 15:06:51 +0200339 MacroExpander expander(&lexer, mMacroSet, mDiagnostics);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000340
341 arg.clear();
342 expander.lex(&token);
343 while (token.type != Token::LAST)
344 {
345 arg.push_back(token);
346 expander.lex(&token);
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400347 numTokens++;
348 if (numTokens + mTotalTokensInContexts > kMaxContextTokens)
349 {
350 mDiagnostics->report(Diagnostics::PP_OUT_OF_MEMORY, token.location, token.text);
351 return false;
352 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000353 }
354 }
355 return true;
356}
357
Zhenyao Mod526f982014-05-13 14:51:19 -0700358void MacroExpander::replaceMacroParams(const Macro &macro,
359 const std::vector<MacroArg> &args,
360 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000361{
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000362 for (std::size_t i = 0; i < macro.replacements.size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000363 {
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400364 if (!replacements->empty() &&
365 replacements->size() + mTotalTokensInContexts > kMaxContextTokens)
366 {
367 const Token &token = replacements->back();
368 mDiagnostics->report(Diagnostics::PP_OUT_OF_MEMORY, token.location, token.text);
369 return;
370 }
371
Zhenyao Mod526f982014-05-13 14:51:19 -0700372 const Token &repl = macro.replacements[i];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000373 if (repl.type != Token::IDENTIFIER)
374 {
375 replacements->push_back(repl);
376 continue;
377 }
378
379 // TODO(alokp): Optimize this.
380 // There is no need to search for macro params every time.
381 // The param index can be cached with the replacement token.
382 Macro::Parameters::const_iterator iter = std::find(
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000383 macro.parameters.begin(), macro.parameters.end(), repl.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000384 if (iter == macro.parameters.end())
385 {
386 replacements->push_back(repl);
387 continue;
388 }
389
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000390 std::size_t iArg = std::distance(macro.parameters.begin(), iter);
Zhenyao Mod526f982014-05-13 14:51:19 -0700391 const MacroArg &arg = args[iArg];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000392 if (arg.empty())
393 {
394 continue;
395 }
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000396 std::size_t iRepl = replacements->size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000397 replacements->insert(replacements->end(), arg.begin(), arg.end());
398 // The replacement token inherits padding properties from
399 // macro replacement token.
400 replacements->at(iRepl).setHasLeadingSpace(repl.hasLeadingSpace());
401 }
alokp@chromium.org04d7d222012-05-16 19:24:07 +0000402}
403
404} // namespace pp
405