blob: 36458fffeca930b7c653e6eadb76819d273035e1 [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
Zhenyao Mod526f982014-05-13 14:51:19 -070049MacroExpander::MacroExpander(Lexer *lexer,
50 MacroSet *macroSet,
51 Diagnostics *diagnostics)
52 : mLexer(lexer),
53 mMacroSet(macroSet),
54 mDiagnostics(diagnostics)
alokp@chromium.org04d7d222012-05-16 19:24:07 +000055{
56}
57
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000058MacroExpander::~MacroExpander()
59{
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +000060 for (std::size_t i = 0; i < mContextStack.size(); ++i)
alokp@chromium.org6c0c2d82012-07-18 15:49:56 +000061 {
62 delete mContextStack[i];
63 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000064}
65
Zhenyao Mod526f982014-05-13 14:51:19 -070066void MacroExpander::lex(Token *token)
alokp@chromium.org04d7d222012-05-16 19:24:07 +000067{
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000068 while (true)
69 {
70 getToken(token);
71
72 if (token->type != Token::IDENTIFIER)
73 break;
74
75 if (token->expansionDisabled())
76 break;
77
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +000078 MacroSet::const_iterator iter = mMacroSet->find(token->text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000079 if (iter == mMacroSet->end())
80 break;
81
82 const Macro& macro = iter->second;
83 if (macro.disabled)
84 {
85 // If a particular token is not expanded, it is never expanded.
86 token->setExpansionDisabled(true);
87 break;
88 }
89 if ((macro.type == Macro::kTypeFunc) && !isNextTokenLeftParen())
90 {
91 // If the token immediately after the macro name is not a '(',
92 // this macro should not be expanded.
93 break;
94 }
95
96 pushMacro(macro, *token);
97 }
98}
99
Zhenyao Mod526f982014-05-13 14:51:19 -0700100void MacroExpander::getToken(Token *token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000101{
102 if (mReserveToken.get())
103 {
104 *token = *mReserveToken;
105 mReserveToken.reset();
106 return;
107 }
108
109 // First pop all empty macro contexts.
110 while (!mContextStack.empty() && mContextStack.back()->empty())
111 {
112 popMacro();
113 }
114
115 if (!mContextStack.empty())
116 {
117 *token = mContextStack.back()->get();
118 }
119 else
120 {
121 mLexer->lex(token);
122 }
123}
124
Zhenyao Mod526f982014-05-13 14:51:19 -0700125void MacroExpander::ungetToken(const Token &token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000126{
127 if (!mContextStack.empty())
128 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700129 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000130 context->unget();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000131 assert(context->replacements[context->index] == token);
132 }
133 else
134 {
135 assert(!mReserveToken.get());
136 mReserveToken.reset(new Token(token));
137 }
138}
139
140bool MacroExpander::isNextTokenLeftParen()
141{
142 Token token;
143 getToken(&token);
144
145 bool lparen = token.type == '(';
146 ungetToken(token);
147
148 return lparen;
149}
150
Zhenyao Mod526f982014-05-13 14:51:19 -0700151bool MacroExpander::pushMacro(const Macro &macro, const Token &identifier)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000152{
153 assert(!macro.disabled);
154 assert(!identifier.expansionDisabled());
155 assert(identifier.type == Token::IDENTIFIER);
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000156 assert(identifier.text == macro.name);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000157
158 std::vector<Token> replacements;
159 if (!expandMacro(macro, identifier, &replacements))
160 return false;
161
162 // Macro is disabled for expansion until it is popped off the stack.
163 macro.disabled = true;
164
Zhenyao Mod526f982014-05-13 14:51:19 -0700165 MacroContext *context = new MacroContext;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000166 context->macro = &macro;
167 context->replacements.swap(replacements);
168 mContextStack.push_back(context);
169 return true;
170}
171
172void MacroExpander::popMacro()
173{
174 assert(!mContextStack.empty());
175
Zhenyao Mod526f982014-05-13 14:51:19 -0700176 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000177 mContextStack.pop_back();
178
179 assert(context->empty());
180 assert(context->macro->disabled);
181 context->macro->disabled = false;
182 delete context;
183}
184
Zhenyao Mod526f982014-05-13 14:51:19 -0700185bool MacroExpander::expandMacro(const Macro &macro,
186 const Token &identifier,
187 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000188{
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000189 replacements->clear();
Olli Etuahoe6432c82015-09-08 14:21:38 +0300190
191 // In the case of an object-like macro, the replacement list gets its location
192 // from the identifier, but in the case of a function-like macro, the replacement
193 // list gets its location from the closing parenthesis of the macro invocation.
194 // This is tested by dEQP-GLES3.functional.shaders.preprocessor.predefined_macros.*
195 SourceLocation replacementLocation = identifier.location;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000196 if (macro.type == Macro::kTypeObj)
197 {
198 replacements->assign(macro.replacements.begin(),
199 macro.replacements.end());
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000200
201 if (macro.predefined)
202 {
Zhenyao Mob5e17752014-10-22 10:57:10 -0700203 const char kLine[] = "__LINE__";
204 const char kFile[] = "__FILE__";
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000205
206 assert(replacements->size() == 1);
207 Token& repl = replacements->front();
208 if (macro.name == kLine)
209 {
alokp@chromium.orgf1155922012-06-28 23:34:30 +0000210 std::ostringstream stream;
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000211 stream << identifier.location.line;
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000212 repl.text = stream.str();
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000213 }
214 else if (macro.name == kFile)
215 {
alokp@chromium.orgf1155922012-06-28 23:34:30 +0000216 std::ostringstream stream;
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000217 stream << identifier.location.file;
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000218 repl.text = stream.str();
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000219 }
220 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000221 }
222 else
223 {
224 assert(macro.type == Macro::kTypeFunc);
225 std::vector<MacroArg> args;
226 args.reserve(macro.parameters.size());
Olli Etuahoe6432c82015-09-08 14:21:38 +0300227 if (!collectMacroArgs(macro, identifier, &args, &replacementLocation))
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000228 return false;
229
230 replaceMacroParams(macro, args, replacements);
231 }
232
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000233 for (std::size_t i = 0; i < replacements->size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000234 {
235 Token& repl = replacements->at(i);
236 if (i == 0)
237 {
238 // The first token in the replacement list inherits the padding
239 // properties of the identifier token.
240 repl.setAtStartOfLine(identifier.atStartOfLine());
241 repl.setHasLeadingSpace(identifier.hasLeadingSpace());
242 }
Olli Etuahoe6432c82015-09-08 14:21:38 +0300243 repl.location = replacementLocation;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000244 }
245 return true;
246}
247
Zhenyao Mod526f982014-05-13 14:51:19 -0700248bool MacroExpander::collectMacroArgs(const Macro &macro,
249 const Token &identifier,
Olli Etuahoe6432c82015-09-08 14:21:38 +0300250 std::vector<MacroArg> *args,
251 SourceLocation *closingParenthesisLocation)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000252{
253 Token token;
254 getToken(&token);
255 assert(token.type == '(');
256
257 args->push_back(MacroArg());
258 for (int openParens = 1; openParens != 0; )
259 {
260 getToken(&token);
261
262 if (token.type == Token::LAST)
263 {
Shannon Woods7f2d7942013-11-19 15:07:58 -0500264 mDiagnostics->report(Diagnostics::PP_MACRO_UNTERMINATED_INVOCATION,
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000265 identifier.location, identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000266 // Do not lose EOF token.
267 ungetToken(token);
268 return false;
269 }
270
271 bool isArg = false; // True if token is part of the current argument.
272 switch (token.type)
273 {
274 case '(':
275 ++openParens;
276 isArg = true;
277 break;
278 case ')':
279 --openParens;
280 isArg = openParens != 0;
Olli Etuahoe6432c82015-09-08 14:21:38 +0300281 *closingParenthesisLocation = token.location;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000282 break;
283 case ',':
284 // The individual arguments are separated by comma tokens, but
285 // the comma tokens between matching inner parentheses do not
286 // seperate arguments.
Zhenyao Mod526f982014-05-13 14:51:19 -0700287 if (openParens == 1)
288 args->push_back(MacroArg());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000289 isArg = openParens != 1;
290 break;
291 default:
292 isArg = true;
293 break;
294 }
295 if (isArg)
296 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700297 MacroArg &arg = args->back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000298 // Initial whitespace is not part of the argument.
Zhenyao Mod526f982014-05-13 14:51:19 -0700299 if (arg.empty())
300 token.setHasLeadingSpace(false);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000301 arg.push_back(token);
302 }
303 }
304
Zhenyao Mod526f982014-05-13 14:51:19 -0700305 const Macro::Parameters &params = macro.parameters;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000306 // If there is only one empty argument, it is equivalent to no argument.
307 if (params.empty() && (args->size() == 1) && args->front().empty())
308 {
309 args->clear();
310 }
311 // Validate the number of arguments.
312 if (args->size() != params.size())
313 {
314 Diagnostics::ID id = args->size() < macro.parameters.size() ?
Shannon Woods7f2d7942013-11-19 15:07:58 -0500315 Diagnostics::PP_MACRO_TOO_FEW_ARGS :
316 Diagnostics::PP_MACRO_TOO_MANY_ARGS;
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000317 mDiagnostics->report(id, identifier.location, identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000318 return false;
319 }
320
321 // Pre-expand each argument before substitution.
322 // This step expands each argument individually before they are
323 // inserted into the macro body.
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000324 for (std::size_t i = 0; i < args->size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000325 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700326 MacroArg &arg = args->at(i);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000327 TokenLexer lexer(&arg);
328 MacroExpander expander(&lexer, mMacroSet, mDiagnostics);
329
330 arg.clear();
331 expander.lex(&token);
332 while (token.type != Token::LAST)
333 {
334 arg.push_back(token);
335 expander.lex(&token);
336 }
337 }
338 return true;
339}
340
Zhenyao Mod526f982014-05-13 14:51:19 -0700341void MacroExpander::replaceMacroParams(const Macro &macro,
342 const std::vector<MacroArg> &args,
343 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000344{
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000345 for (std::size_t i = 0; i < macro.replacements.size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000346 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700347 const Token &repl = macro.replacements[i];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000348 if (repl.type != Token::IDENTIFIER)
349 {
350 replacements->push_back(repl);
351 continue;
352 }
353
354 // TODO(alokp): Optimize this.
355 // There is no need to search for macro params every time.
356 // The param index can be cached with the replacement token.
357 Macro::Parameters::const_iterator iter = std::find(
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000358 macro.parameters.begin(), macro.parameters.end(), repl.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000359 if (iter == macro.parameters.end())
360 {
361 replacements->push_back(repl);
362 continue;
363 }
364
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000365 std::size_t iArg = std::distance(macro.parameters.begin(), iter);
Zhenyao Mod526f982014-05-13 14:51:19 -0700366 const MacroArg &arg = args[iArg];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000367 if (arg.empty())
368 {
369 continue;
370 }
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000371 std::size_t iRepl = replacements->size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000372 replacements->insert(replacements->end(), arg.begin(), arg.end());
373 // The replacement token inherits padding properties from
374 // macro replacement token.
375 replacements->at(iRepl).setHasLeadingSpace(repl.hasLeadingSpace());
376 }
alokp@chromium.org04d7d222012-05-16 19:24:07 +0000377}
378
379} // namespace pp
380