blob: cc115f9113d87ada0ae568ab11fe5262656e7cd0 [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
Corentin Wallez054f7ed2016-09-20 17:15:59 -04007#include "compiler/preprocessor/MacroExpander.h"
alokp@chromium.org04d7d222012-05-16 19:24:07 +00008
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +00009#include <algorithm>
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000010
Corentin Wallez054f7ed2016-09-20 17:15:59 -040011#include "common/debug.h"
12#include "compiler/preprocessor/DiagnosticsBase.h"
13#include "compiler/preprocessor/Token.h"
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000014
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{
Jamie Madillf832c9d2016-12-12 17:38:48 -050025 public:
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000026 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
Jamie Madillf832c9d2016-12-12 17:38:48 -050047 private:
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000048 TokenVector mTokens;
49 TokenVector::const_iterator mIter;
50};
51
Corentin Wallez2bd9c442016-09-20 16:39:18 -040052} // anonymous namespace
53
Olli Etuaho78b0c912016-11-21 14:23:06 +000054class MacroExpander::ScopedMacroReenabler final : angle::NonCopyable
55{
56 public:
57 ScopedMacroReenabler(MacroExpander *expander);
58 ~ScopedMacroReenabler();
59
60 private:
61 MacroExpander *mExpander;
62};
63
64MacroExpander::ScopedMacroReenabler::ScopedMacroReenabler(MacroExpander *expander)
65 : mExpander(expander)
66{
67 mExpander->mDeferReenablingMacros = true;
68}
69
70MacroExpander::ScopedMacroReenabler::~ScopedMacroReenabler()
71{
72 mExpander->mDeferReenablingMacros = false;
Olli Etuaho47c27e82017-01-17 15:29:35 +000073 for (auto macro : mExpander->mMacrosToReenable)
Olli Etuaho78b0c912016-11-21 14:23:06 +000074 {
Olli Etuaho47c27e82017-01-17 15:29:35 +000075 // Copying the string here by using substr is a check for use-after-free. It detects
76 // use-after-free more reliably than just toggling the disabled flag.
77 ASSERT(macro->name.substr() != "");
Olli Etuaho78b0c912016-11-21 14:23:06 +000078 macro->disabled = false;
79 }
80 mExpander->mMacrosToReenable.clear();
81}
82
Olli Etuahof1cf5e62016-11-22 17:36:49 +000083MacroExpander::MacroExpander(Lexer *lexer,
84 MacroSet *macroSet,
85 Diagnostics *diagnostics,
86 int allowedMacroExpansionDepth)
Olli Etuaho78b0c912016-11-21 14:23:06 +000087 : mLexer(lexer),
88 mMacroSet(macroSet),
89 mDiagnostics(diagnostics),
90 mTotalTokensInContexts(0),
Olli Etuahof1cf5e62016-11-22 17:36:49 +000091 mAllowedMacroExpansionDepth(allowedMacroExpansionDepth),
Olli Etuaho78b0c912016-11-21 14:23:06 +000092 mDeferReenablingMacros(false)
alokp@chromium.org04d7d222012-05-16 19:24:07 +000093{
94}
95
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000096MacroExpander::~MacroExpander()
97{
Olli Etuaho78b0c912016-11-21 14:23:06 +000098 ASSERT(mMacrosToReenable.empty());
Corentin Wallez054f7ed2016-09-20 17:15:59 -040099 for (MacroContext *context : mContextStack)
alokp@chromium.org6c0c2d82012-07-18 15:49:56 +0000100 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400101 delete context;
alokp@chromium.org6c0c2d82012-07-18 15:49:56 +0000102 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000103}
104
Zhenyao Mod526f982014-05-13 14:51:19 -0700105void MacroExpander::lex(Token *token)
alokp@chromium.org04d7d222012-05-16 19:24:07 +0000106{
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000107 while (true)
108 {
109 getToken(token);
110
111 if (token->type != Token::IDENTIFIER)
112 break;
113
114 if (token->expansionDisabled())
115 break;
116
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000117 MacroSet::const_iterator iter = mMacroSet->find(token->text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000118 if (iter == mMacroSet->end())
119 break;
120
Olli Etuaho47c27e82017-01-17 15:29:35 +0000121 std::shared_ptr<Macro> macro = iter->second;
122 if (macro->disabled)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000123 {
124 // If a particular token is not expanded, it is never expanded.
125 token->setExpansionDisabled(true);
126 break;
127 }
Corentin Wallez449a8032016-10-26 08:05:54 -0400128
129 // Bump the expansion count before peeking if the next token is a '('
130 // otherwise there could be a #undef of the macro before the next token.
Olli Etuaho47c27e82017-01-17 15:29:35 +0000131 macro->expansionCount++;
132 if ((macro->type == Macro::kTypeFunc) && !isNextTokenLeftParen())
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000133 {
134 // If the token immediately after the macro name is not a '(',
135 // this macro should not be expanded.
Olli Etuaho47c27e82017-01-17 15:29:35 +0000136 macro->expansionCount--;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000137 break;
138 }
139
140 pushMacro(macro, *token);
141 }
142}
143
Zhenyao Mod526f982014-05-13 14:51:19 -0700144void MacroExpander::getToken(Token *token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000145{
146 if (mReserveToken.get())
147 {
148 *token = *mReserveToken;
149 mReserveToken.reset();
150 return;
151 }
152
153 // First pop all empty macro contexts.
154 while (!mContextStack.empty() && mContextStack.back()->empty())
155 {
156 popMacro();
157 }
158
159 if (!mContextStack.empty())
160 {
161 *token = mContextStack.back()->get();
162 }
163 else
164 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400165 ASSERT(mTotalTokensInContexts == 0);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000166 mLexer->lex(token);
167 }
168}
169
Zhenyao Mod526f982014-05-13 14:51:19 -0700170void MacroExpander::ungetToken(const Token &token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000171{
172 if (!mContextStack.empty())
173 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700174 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000175 context->unget();
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400176 ASSERT(context->replacements[context->index] == token);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000177 }
178 else
179 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400180 ASSERT(!mReserveToken.get());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000181 mReserveToken.reset(new Token(token));
182 }
183}
184
185bool MacroExpander::isNextTokenLeftParen()
186{
187 Token token;
188 getToken(&token);
189
190 bool lparen = token.type == '(';
191 ungetToken(token);
192
193 return lparen;
194}
195
Olli Etuaho47c27e82017-01-17 15:29:35 +0000196bool MacroExpander::pushMacro(std::shared_ptr<Macro> macro, const Token &identifier)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000197{
Olli Etuaho47c27e82017-01-17 15:29:35 +0000198 ASSERT(!macro->disabled);
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400199 ASSERT(!identifier.expansionDisabled());
200 ASSERT(identifier.type == Token::IDENTIFIER);
Olli Etuaho47c27e82017-01-17 15:29:35 +0000201 ASSERT(identifier.text == macro->name);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000202
203 std::vector<Token> replacements;
Olli Etuaho47c27e82017-01-17 15:29:35 +0000204 if (!expandMacro(*macro, identifier, &replacements))
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000205 return false;
206
207 // Macro is disabled for expansion until it is popped off the stack.
Olli Etuaho47c27e82017-01-17 15:29:35 +0000208 macro->disabled = true;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000209
Zhenyao Mod526f982014-05-13 14:51:19 -0700210 MacroContext *context = new MacroContext;
Olli Etuaho47c27e82017-01-17 15:29:35 +0000211 context->macro = macro;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000212 context->replacements.swap(replacements);
213 mContextStack.push_back(context);
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400214 mTotalTokensInContexts += context->replacements.size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000215 return true;
216}
217
218void MacroExpander::popMacro()
219{
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400220 ASSERT(!mContextStack.empty());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000221
Zhenyao Mod526f982014-05-13 14:51:19 -0700222 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000223 mContextStack.pop_back();
224
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400225 ASSERT(context->empty());
226 ASSERT(context->macro->disabled);
227 ASSERT(context->macro->expansionCount > 0);
Olli Etuaho78b0c912016-11-21 14:23:06 +0000228 if (mDeferReenablingMacros)
229 {
230 mMacrosToReenable.push_back(context->macro);
231 }
232 else
233 {
234 context->macro->disabled = false;
235 }
Corentin Wallezd2f195b2016-09-19 15:53:33 -0400236 context->macro->expansionCount--;
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400237 mTotalTokensInContexts -= context->replacements.size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000238 delete context;
239}
240
Zhenyao Mod526f982014-05-13 14:51:19 -0700241bool MacroExpander::expandMacro(const Macro &macro,
242 const Token &identifier,
243 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000244{
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000245 replacements->clear();
Olli Etuahoe6432c82015-09-08 14:21:38 +0300246
247 // In the case of an object-like macro, the replacement list gets its location
248 // from the identifier, but in the case of a function-like macro, the replacement
249 // list gets its location from the closing parenthesis of the macro invocation.
250 // This is tested by dEQP-GLES3.functional.shaders.preprocessor.predefined_macros.*
251 SourceLocation replacementLocation = identifier.location;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000252 if (macro.type == Macro::kTypeObj)
253 {
Jamie Madillf832c9d2016-12-12 17:38:48 -0500254 replacements->assign(macro.replacements.begin(), macro.replacements.end());
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000255
256 if (macro.predefined)
257 {
Zhenyao Mob5e17752014-10-22 10:57:10 -0700258 const char kLine[] = "__LINE__";
259 const char kFile[] = "__FILE__";
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000260
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400261 ASSERT(replacements->size() == 1);
Jamie Madillf832c9d2016-12-12 17:38:48 -0500262 Token &repl = replacements->front();
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000263 if (macro.name == kLine)
264 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400265 repl.text = ToString(identifier.location.line);
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000266 }
267 else if (macro.name == kFile)
268 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400269 repl.text = ToString(identifier.location.file);
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000270 }
271 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000272 }
273 else
274 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400275 ASSERT(macro.type == Macro::kTypeFunc);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000276 std::vector<MacroArg> args;
277 args.reserve(macro.parameters.size());
Olli Etuahoe6432c82015-09-08 14:21:38 +0300278 if (!collectMacroArgs(macro, identifier, &args, &replacementLocation))
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000279 return false;
280
281 replaceMacroParams(macro, args, replacements);
282 }
283
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000284 for (std::size_t i = 0; i < replacements->size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000285 {
Jamie Madillf832c9d2016-12-12 17:38:48 -0500286 Token &repl = replacements->at(i);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000287 if (i == 0)
288 {
289 // The first token in the replacement list inherits the padding
290 // properties of the identifier token.
291 repl.setAtStartOfLine(identifier.atStartOfLine());
292 repl.setHasLeadingSpace(identifier.hasLeadingSpace());
293 }
Olli Etuahoe6432c82015-09-08 14:21:38 +0300294 repl.location = replacementLocation;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000295 }
296 return true;
297}
298
Zhenyao Mod526f982014-05-13 14:51:19 -0700299bool MacroExpander::collectMacroArgs(const Macro &macro,
300 const Token &identifier,
Olli Etuahoe6432c82015-09-08 14:21:38 +0300301 std::vector<MacroArg> *args,
302 SourceLocation *closingParenthesisLocation)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000303{
304 Token token;
305 getToken(&token);
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400306 ASSERT(token.type == '(');
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000307
308 args->push_back(MacroArg());
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400309
Olli Etuaho78b0c912016-11-21 14:23:06 +0000310 // Defer reenabling macros until args collection is finished to avoid the possibility of
311 // infinite recursion. Otherwise infinite recursion might happen when expanding the args after
312 // macros have been popped from the context stack when parsing the args.
313 ScopedMacroReenabler deferReenablingMacros(this);
314
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400315 int openParens = 1;
316 while (openParens != 0)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000317 {
318 getToken(&token);
319
320 if (token.type == Token::LAST)
321 {
Jamie Madillf832c9d2016-12-12 17:38:48 -0500322 mDiagnostics->report(Diagnostics::PP_MACRO_UNTERMINATED_INVOCATION, identifier.location,
323 identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000324 // Do not lose EOF token.
325 ungetToken(token);
326 return false;
327 }
328
Jamie Madillf832c9d2016-12-12 17:38:48 -0500329 bool isArg = false; // True if token is part of the current argument.
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000330 switch (token.type)
331 {
Jamie Madillf832c9d2016-12-12 17:38:48 -0500332 case '(':
333 ++openParens;
334 isArg = true;
335 break;
336 case ')':
337 --openParens;
338 isArg = openParens != 0;
339 *closingParenthesisLocation = token.location;
340 break;
341 case ',':
342 // The individual arguments are separated by comma tokens, but
343 // the comma tokens between matching inner parentheses do not
344 // seperate arguments.
345 if (openParens == 1)
346 args->push_back(MacroArg());
347 isArg = openParens != 1;
348 break;
349 default:
350 isArg = true;
351 break;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000352 }
353 if (isArg)
354 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700355 MacroArg &arg = args->back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000356 // Initial whitespace is not part of the argument.
Zhenyao Mod526f982014-05-13 14:51:19 -0700357 if (arg.empty())
358 token.setHasLeadingSpace(false);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000359 arg.push_back(token);
360 }
361 }
362
Zhenyao Mod526f982014-05-13 14:51:19 -0700363 const Macro::Parameters &params = macro.parameters;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000364 // If there is only one empty argument, it is equivalent to no argument.
365 if (params.empty() && (args->size() == 1) && args->front().empty())
366 {
367 args->clear();
368 }
369 // Validate the number of arguments.
370 if (args->size() != params.size())
371 {
Jamie Madillf832c9d2016-12-12 17:38:48 -0500372 Diagnostics::ID id = args->size() < macro.parameters.size()
373 ? Diagnostics::PP_MACRO_TOO_FEW_ARGS
374 : Diagnostics::PP_MACRO_TOO_MANY_ARGS;
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000375 mDiagnostics->report(id, identifier.location, identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000376 return false;
377 }
378
379 // Pre-expand each argument before substitution.
380 // This step expands each argument individually before they are
381 // inserted into the macro body.
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400382 size_t numTokens = 0;
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400383 for (auto &arg : *args)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000384 {
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000385 TokenLexer lexer(&arg);
Olli Etuahof1cf5e62016-11-22 17:36:49 +0000386 if (mAllowedMacroExpansionDepth < 1)
387 {
388 mDiagnostics->report(Diagnostics::PP_MACRO_INVOCATION_CHAIN_TOO_DEEP, token.location,
389 token.text);
390 return false;
391 }
392 MacroExpander expander(&lexer, mMacroSet, mDiagnostics, mAllowedMacroExpansionDepth - 1);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000393
394 arg.clear();
395 expander.lex(&token);
396 while (token.type != Token::LAST)
397 {
398 arg.push_back(token);
399 expander.lex(&token);
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400400 numTokens++;
401 if (numTokens + mTotalTokensInContexts > kMaxContextTokens)
402 {
403 mDiagnostics->report(Diagnostics::PP_OUT_OF_MEMORY, token.location, token.text);
404 return false;
405 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000406 }
407 }
408 return true;
409}
410
Zhenyao Mod526f982014-05-13 14:51:19 -0700411void MacroExpander::replaceMacroParams(const Macro &macro,
412 const std::vector<MacroArg> &args,
413 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000414{
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000415 for (std::size_t i = 0; i < macro.replacements.size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000416 {
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400417 if (!replacements->empty() &&
418 replacements->size() + mTotalTokensInContexts > kMaxContextTokens)
419 {
420 const Token &token = replacements->back();
421 mDiagnostics->report(Diagnostics::PP_OUT_OF_MEMORY, token.location, token.text);
422 return;
423 }
424
Zhenyao Mod526f982014-05-13 14:51:19 -0700425 const Token &repl = macro.replacements[i];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000426 if (repl.type != Token::IDENTIFIER)
427 {
428 replacements->push_back(repl);
429 continue;
430 }
431
432 // TODO(alokp): Optimize this.
433 // There is no need to search for macro params every time.
434 // The param index can be cached with the replacement token.
Jamie Madillf832c9d2016-12-12 17:38:48 -0500435 Macro::Parameters::const_iterator iter =
436 std::find(macro.parameters.begin(), macro.parameters.end(), repl.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000437 if (iter == macro.parameters.end())
438 {
439 replacements->push_back(repl);
440 continue;
441 }
442
Jamie Madillf832c9d2016-12-12 17:38:48 -0500443 std::size_t iArg = std::distance(macro.parameters.begin(), iter);
Zhenyao Mod526f982014-05-13 14:51:19 -0700444 const MacroArg &arg = args[iArg];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000445 if (arg.empty())
446 {
447 continue;
448 }
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000449 std::size_t iRepl = replacements->size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000450 replacements->insert(replacements->end(), arg.begin(), arg.end());
451 // The replacement token inherits padding properties from
452 // macro replacement token.
453 replacements->at(iRepl).setHasLeadingSpace(repl.hasLeadingSpace());
454 }
alokp@chromium.org04d7d222012-05-16 19:24:07 +0000455}
456
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400457MacroExpander::MacroContext::MacroContext() : macro(0), index(0)
458{
459}
460
461bool MacroExpander::MacroContext::empty() const
462{
463 return index == replacements.size();
464}
465
466const Token &MacroExpander::MacroContext::get()
467{
468 return replacements[index++];
469}
470
471void MacroExpander::MacroContext::unget()
472{
473 ASSERT(index > 0);
474 --index;
475}
476
alokp@chromium.org04d7d222012-05-16 19:24:07 +0000477} // namespace pp