blob: 3c0423e7240eb9cbf21bd421f705df9f3c83b17b [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
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000018class TokenLexer : public Lexer
19{
20 public:
21 typedef std::vector<Token> TokenVector;
22
Zhenyao Mod526f982014-05-13 14:51:19 -070023 TokenLexer(TokenVector *tokens)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000024 {
25 tokens->swap(mTokens);
26 mIter = mTokens.begin();
27 }
28
Corentin Walleze5a1f272015-08-21 02:58:25 +020029 void lex(Token *token) override
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000030 {
31 if (mIter == mTokens.end())
32 {
33 token->reset();
34 token->type = Token::LAST;
35 }
36 else
37 {
38 *token = *mIter++;
39 }
40 }
41
42 private:
43 PP_DISALLOW_COPY_AND_ASSIGN(TokenLexer);
44
45 TokenVector mTokens;
46 TokenVector::const_iterator mIter;
47};
48
Olli Etuaho1b2f1622016-03-04 15:06:51 +020049MacroExpander::MacroExpander(Lexer *lexer, MacroSet *macroSet, Diagnostics *diagnostics)
50 : mLexer(lexer), mMacroSet(macroSet), mDiagnostics(diagnostics)
alokp@chromium.org04d7d222012-05-16 19:24:07 +000051{
52}
53
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000054MacroExpander::~MacroExpander()
55{
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +000056 for (std::size_t i = 0; i < mContextStack.size(); ++i)
alokp@chromium.org6c0c2d82012-07-18 15:49:56 +000057 {
58 delete mContextStack[i];
59 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000060}
61
Zhenyao Mod526f982014-05-13 14:51:19 -070062void MacroExpander::lex(Token *token)
alokp@chromium.org04d7d222012-05-16 19:24:07 +000063{
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000064 while (true)
65 {
66 getToken(token);
67
68 if (token->type != Token::IDENTIFIER)
69 break;
70
71 if (token->expansionDisabled())
72 break;
73
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +000074 MacroSet::const_iterator iter = mMacroSet->find(token->text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000075 if (iter == mMacroSet->end())
76 break;
77
78 const Macro& macro = iter->second;
79 if (macro.disabled)
80 {
81 // If a particular token is not expanded, it is never expanded.
82 token->setExpansionDisabled(true);
83 break;
84 }
85 if ((macro.type == Macro::kTypeFunc) && !isNextTokenLeftParen())
86 {
87 // If the token immediately after the macro name is not a '(',
88 // this macro should not be expanded.
89 break;
90 }
91
92 pushMacro(macro, *token);
93 }
94}
95
Zhenyao Mod526f982014-05-13 14:51:19 -070096void MacroExpander::getToken(Token *token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000097{
98 if (mReserveToken.get())
99 {
100 *token = *mReserveToken;
101 mReserveToken.reset();
102 return;
103 }
104
105 // First pop all empty macro contexts.
106 while (!mContextStack.empty() && mContextStack.back()->empty())
107 {
108 popMacro();
109 }
110
111 if (!mContextStack.empty())
112 {
113 *token = mContextStack.back()->get();
114 }
115 else
116 {
117 mLexer->lex(token);
118 }
119}
120
Zhenyao Mod526f982014-05-13 14:51:19 -0700121void MacroExpander::ungetToken(const Token &token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000122{
123 if (!mContextStack.empty())
124 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700125 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000126 context->unget();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000127 assert(context->replacements[context->index] == token);
128 }
129 else
130 {
131 assert(!mReserveToken.get());
132 mReserveToken.reset(new Token(token));
133 }
134}
135
136bool MacroExpander::isNextTokenLeftParen()
137{
138 Token token;
139 getToken(&token);
140
141 bool lparen = token.type == '(';
142 ungetToken(token);
143
144 return lparen;
145}
146
Zhenyao Mod526f982014-05-13 14:51:19 -0700147bool MacroExpander::pushMacro(const Macro &macro, const Token &identifier)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000148{
149 assert(!macro.disabled);
150 assert(!identifier.expansionDisabled());
151 assert(identifier.type == Token::IDENTIFIER);
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000152 assert(identifier.text == macro.name);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000153
154 std::vector<Token> replacements;
155 if (!expandMacro(macro, identifier, &replacements))
156 return false;
157
158 // Macro is disabled for expansion until it is popped off the stack.
159 macro.disabled = true;
160
Zhenyao Mod526f982014-05-13 14:51:19 -0700161 MacroContext *context = new MacroContext;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000162 context->macro = &macro;
163 context->replacements.swap(replacements);
164 mContextStack.push_back(context);
165 return true;
166}
167
168void MacroExpander::popMacro()
169{
170 assert(!mContextStack.empty());
171
Zhenyao Mod526f982014-05-13 14:51:19 -0700172 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000173 mContextStack.pop_back();
174
175 assert(context->empty());
176 assert(context->macro->disabled);
177 context->macro->disabled = false;
178 delete context;
179}
180
Zhenyao Mod526f982014-05-13 14:51:19 -0700181bool MacroExpander::expandMacro(const Macro &macro,
182 const Token &identifier,
183 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000184{
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000185 replacements->clear();
Olli Etuahoe6432c82015-09-08 14:21:38 +0300186
187 // In the case of an object-like macro, the replacement list gets its location
188 // from the identifier, but in the case of a function-like macro, the replacement
189 // list gets its location from the closing parenthesis of the macro invocation.
190 // This is tested by dEQP-GLES3.functional.shaders.preprocessor.predefined_macros.*
191 SourceLocation replacementLocation = identifier.location;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000192 if (macro.type == Macro::kTypeObj)
193 {
194 replacements->assign(macro.replacements.begin(),
195 macro.replacements.end());
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000196
197 if (macro.predefined)
198 {
Zhenyao Mob5e17752014-10-22 10:57:10 -0700199 const char kLine[] = "__LINE__";
200 const char kFile[] = "__FILE__";
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000201
202 assert(replacements->size() == 1);
203 Token& repl = replacements->front();
204 if (macro.name == kLine)
205 {
alokp@chromium.orgf1155922012-06-28 23:34:30 +0000206 std::ostringstream stream;
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000207 stream << identifier.location.line;
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000208 repl.text = stream.str();
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000209 }
210 else if (macro.name == kFile)
211 {
alokp@chromium.orgf1155922012-06-28 23:34:30 +0000212 std::ostringstream stream;
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000213 stream << identifier.location.file;
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000214 repl.text = stream.str();
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000215 }
216 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000217 }
218 else
219 {
220 assert(macro.type == Macro::kTypeFunc);
221 std::vector<MacroArg> args;
222 args.reserve(macro.parameters.size());
Olli Etuahoe6432c82015-09-08 14:21:38 +0300223 if (!collectMacroArgs(macro, identifier, &args, &replacementLocation))
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000224 return false;
225
226 replaceMacroParams(macro, args, replacements);
227 }
228
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000229 for (std::size_t i = 0; i < replacements->size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000230 {
231 Token& repl = replacements->at(i);
232 if (i == 0)
233 {
234 // The first token in the replacement list inherits the padding
235 // properties of the identifier token.
236 repl.setAtStartOfLine(identifier.atStartOfLine());
237 repl.setHasLeadingSpace(identifier.hasLeadingSpace());
238 }
Olli Etuahoe6432c82015-09-08 14:21:38 +0300239 repl.location = replacementLocation;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000240 }
241 return true;
242}
243
Zhenyao Mod526f982014-05-13 14:51:19 -0700244bool MacroExpander::collectMacroArgs(const Macro &macro,
245 const Token &identifier,
Olli Etuahoe6432c82015-09-08 14:21:38 +0300246 std::vector<MacroArg> *args,
247 SourceLocation *closingParenthesisLocation)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000248{
249 Token token;
250 getToken(&token);
251 assert(token.type == '(');
252
253 args->push_back(MacroArg());
254 for (int openParens = 1; openParens != 0; )
255 {
256 getToken(&token);
257
258 if (token.type == Token::LAST)
259 {
Shannon Woods7f2d7942013-11-19 15:07:58 -0500260 mDiagnostics->report(Diagnostics::PP_MACRO_UNTERMINATED_INVOCATION,
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000261 identifier.location, identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000262 // Do not lose EOF token.
263 ungetToken(token);
264 return false;
265 }
266
267 bool isArg = false; // True if token is part of the current argument.
268 switch (token.type)
269 {
270 case '(':
271 ++openParens;
272 isArg = true;
273 break;
274 case ')':
275 --openParens;
276 isArg = openParens != 0;
Olli Etuahoe6432c82015-09-08 14:21:38 +0300277 *closingParenthesisLocation = token.location;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000278 break;
279 case ',':
280 // The individual arguments are separated by comma tokens, but
281 // the comma tokens between matching inner parentheses do not
282 // seperate arguments.
Zhenyao Mod526f982014-05-13 14:51:19 -0700283 if (openParens == 1)
284 args->push_back(MacroArg());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000285 isArg = openParens != 1;
286 break;
287 default:
288 isArg = true;
289 break;
290 }
291 if (isArg)
292 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700293 MacroArg &arg = args->back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000294 // Initial whitespace is not part of the argument.
Zhenyao Mod526f982014-05-13 14:51:19 -0700295 if (arg.empty())
296 token.setHasLeadingSpace(false);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000297 arg.push_back(token);
298 }
299 }
300
Zhenyao Mod526f982014-05-13 14:51:19 -0700301 const Macro::Parameters &params = macro.parameters;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000302 // If there is only one empty argument, it is equivalent to no argument.
303 if (params.empty() && (args->size() == 1) && args->front().empty())
304 {
305 args->clear();
306 }
307 // Validate the number of arguments.
308 if (args->size() != params.size())
309 {
310 Diagnostics::ID id = args->size() < macro.parameters.size() ?
Shannon Woods7f2d7942013-11-19 15:07:58 -0500311 Diagnostics::PP_MACRO_TOO_FEW_ARGS :
312 Diagnostics::PP_MACRO_TOO_MANY_ARGS;
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000313 mDiagnostics->report(id, identifier.location, identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000314 return false;
315 }
316
317 // Pre-expand each argument before substitution.
318 // This step expands each argument individually before they are
319 // inserted into the macro body.
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000320 for (std::size_t i = 0; i < args->size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000321 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700322 MacroArg &arg = args->at(i);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000323 TokenLexer lexer(&arg);
Olli Etuaho1b2f1622016-03-04 15:06:51 +0200324 MacroExpander expander(&lexer, mMacroSet, mDiagnostics);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000325
326 arg.clear();
327 expander.lex(&token);
328 while (token.type != Token::LAST)
329 {
330 arg.push_back(token);
331 expander.lex(&token);
332 }
333 }
334 return true;
335}
336
Zhenyao Mod526f982014-05-13 14:51:19 -0700337void MacroExpander::replaceMacroParams(const Macro &macro,
338 const std::vector<MacroArg> &args,
339 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000340{
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000341 for (std::size_t i = 0; i < macro.replacements.size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000342 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700343 const Token &repl = macro.replacements[i];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000344 if (repl.type != Token::IDENTIFIER)
345 {
346 replacements->push_back(repl);
347 continue;
348 }
349
350 // TODO(alokp): Optimize this.
351 // There is no need to search for macro params every time.
352 // The param index can be cached with the replacement token.
353 Macro::Parameters::const_iterator iter = std::find(
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000354 macro.parameters.begin(), macro.parameters.end(), repl.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000355 if (iter == macro.parameters.end())
356 {
357 replacements->push_back(repl);
358 continue;
359 }
360
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000361 std::size_t iArg = std::distance(macro.parameters.begin(), iter);
Zhenyao Mod526f982014-05-13 14:51:19 -0700362 const MacroArg &arg = args[iArg];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000363 if (arg.empty())
364 {
365 continue;
366 }
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000367 std::size_t iRepl = replacements->size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000368 replacements->insert(replacements->end(), arg.begin(), arg.end());
369 // The replacement token inherits padding properties from
370 // macro replacement token.
371 replacements->at(iRepl).setHasLeadingSpace(repl.hasLeadingSpace());
372 }
alokp@chromium.org04d7d222012-05-16 19:24:07 +0000373}
374
375} // namespace pp
376