Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 14 | pmbaty | 1 | //===--- RAIIObjectsForParser.h - RAII helpers for the parser ---*- C++ -*-===// |
| 2 | // |
||
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
||
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
||
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
||
| 6 | // |
||
| 7 | //===----------------------------------------------------------------------===// |
||
| 8 | // |
||
| 9 | // This file defines and implements the some simple RAII objects that are used |
||
| 10 | // by the parser to manage bits in recursion. |
||
| 11 | // |
||
| 12 | //===----------------------------------------------------------------------===// |
||
| 13 | |||
| 14 | #ifndef LLVM_CLANG_PARSE_RAIIOBJECTSFORPARSER_H |
||
| 15 | #define LLVM_CLANG_PARSE_RAIIOBJECTSFORPARSER_H |
||
| 16 | |||
| 17 | #include "clang/Parse/ParseDiagnostic.h" |
||
| 18 | #include "clang/Parse/Parser.h" |
||
| 19 | #include "clang/Sema/DelayedDiagnostic.h" |
||
| 20 | #include "clang/Sema/ParsedTemplate.h" |
||
| 21 | #include "clang/Sema/Sema.h" |
||
| 22 | |||
| 23 | namespace clang { |
||
| 24 | // TODO: move ParsingClassDefinition here. |
||
| 25 | // TODO: move TentativeParsingAction here. |
||
| 26 | |||
| 27 | /// A RAII object used to temporarily suppress access-like |
||
| 28 | /// checking. Access-like checks are those associated with |
||
| 29 | /// controlling the use of a declaration, like C++ access control |
||
| 30 | /// errors and deprecation warnings. They are contextually |
||
| 31 | /// dependent, in that they can only be resolved with full |
||
| 32 | /// information about what's being declared. They are also |
||
| 33 | /// suppressed in certain contexts, like the template arguments of |
||
| 34 | /// an explicit instantiation. However, those suppression contexts |
||
| 35 | /// cannot necessarily be fully determined in advance; for |
||
| 36 | /// example, something starting like this: |
||
| 37 | /// template <> class std::vector<A::PrivateType> |
||
| 38 | /// might be the entirety of an explicit instantiation: |
||
| 39 | /// template <> class std::vector<A::PrivateType>; |
||
| 40 | /// or just an elaborated type specifier: |
||
| 41 | /// template <> class std::vector<A::PrivateType> make_vector<>(); |
||
| 42 | /// Therefore this class collects all the diagnostics and permits |
||
| 43 | /// them to be re-delayed in a new context. |
||
| 44 | class SuppressAccessChecks { |
||
| 45 | Sema &S; |
||
| 46 | sema::DelayedDiagnosticPool DiagnosticPool; |
||
| 47 | Sema::ParsingDeclState State; |
||
| 48 | bool Active; |
||
| 49 | |||
| 50 | public: |
||
| 51 | /// Begin suppressing access-like checks |
||
| 52 | SuppressAccessChecks(Parser &P, bool activate = true) |
||
| 53 | : S(P.getActions()), DiagnosticPool(nullptr) { |
||
| 54 | if (activate) { |
||
| 55 | State = S.PushParsingDeclaration(DiagnosticPool); |
||
| 56 | Active = true; |
||
| 57 | } else { |
||
| 58 | Active = false; |
||
| 59 | } |
||
| 60 | } |
||
| 61 | SuppressAccessChecks(SuppressAccessChecks &&Other) |
||
| 62 | : S(Other.S), DiagnosticPool(std::move(Other.DiagnosticPool)), |
||
| 63 | State(Other.State), Active(Other.Active) { |
||
| 64 | Other.Active = false; |
||
| 65 | } |
||
| 66 | void operator=(SuppressAccessChecks &&Other) = delete; |
||
| 67 | |||
| 68 | void done() { |
||
| 69 | assert(Active && "trying to end an inactive suppression"); |
||
| 70 | S.PopParsingDeclaration(State, nullptr); |
||
| 71 | Active = false; |
||
| 72 | } |
||
| 73 | |||
| 74 | void redelay() { |
||
| 75 | assert(!Active && "redelaying without having ended first"); |
||
| 76 | if (!DiagnosticPool.pool_empty()) |
||
| 77 | S.redelayDiagnostics(DiagnosticPool); |
||
| 78 | assert(DiagnosticPool.pool_empty()); |
||
| 79 | } |
||
| 80 | |||
| 81 | ~SuppressAccessChecks() { |
||
| 82 | if (Active) done(); |
||
| 83 | } |
||
| 84 | }; |
||
| 85 | |||
| 86 | /// RAII object used to inform the actions that we're |
||
| 87 | /// currently parsing a declaration. This is active when parsing a |
||
| 88 | /// variable's initializer, but not when parsing the body of a |
||
| 89 | /// class or function definition. |
||
| 90 | class ParsingDeclRAIIObject { |
||
| 91 | Sema &Actions; |
||
| 92 | sema::DelayedDiagnosticPool DiagnosticPool; |
||
| 93 | Sema::ParsingDeclState State; |
||
| 94 | bool Popped; |
||
| 95 | |||
| 96 | ParsingDeclRAIIObject(const ParsingDeclRAIIObject &) = delete; |
||
| 97 | void operator=(const ParsingDeclRAIIObject &) = delete; |
||
| 98 | |||
| 99 | public: |
||
| 100 | enum NoParent_t { NoParent }; |
||
| 101 | ParsingDeclRAIIObject(Parser &P, NoParent_t _) |
||
| 102 | : Actions(P.getActions()), DiagnosticPool(nullptr) { |
||
| 103 | push(); |
||
| 104 | } |
||
| 105 | |||
| 106 | /// Creates a RAII object whose pool is optionally parented by another. |
||
| 107 | ParsingDeclRAIIObject(Parser &P, |
||
| 108 | const sema::DelayedDiagnosticPool *parentPool) |
||
| 109 | : Actions(P.getActions()), DiagnosticPool(parentPool) { |
||
| 110 | push(); |
||
| 111 | } |
||
| 112 | |||
| 113 | /// Creates a RAII object and, optionally, initialize its |
||
| 114 | /// diagnostics pool by stealing the diagnostics from another |
||
| 115 | /// RAII object (which is assumed to be the current top pool). |
||
| 116 | ParsingDeclRAIIObject(Parser &P, ParsingDeclRAIIObject *other) |
||
| 117 | : Actions(P.getActions()), |
||
| 118 | DiagnosticPool(other ? other->DiagnosticPool.getParent() : nullptr) { |
||
| 119 | if (other) { |
||
| 120 | DiagnosticPool.steal(other->DiagnosticPool); |
||
| 121 | other->abort(); |
||
| 122 | } |
||
| 123 | push(); |
||
| 124 | } |
||
| 125 | |||
| 126 | ~ParsingDeclRAIIObject() { |
||
| 127 | abort(); |
||
| 128 | } |
||
| 129 | |||
| 130 | sema::DelayedDiagnosticPool &getDelayedDiagnosticPool() { |
||
| 131 | return DiagnosticPool; |
||
| 132 | } |
||
| 133 | const sema::DelayedDiagnosticPool &getDelayedDiagnosticPool() const { |
||
| 134 | return DiagnosticPool; |
||
| 135 | } |
||
| 136 | |||
| 137 | /// Resets the RAII object for a new declaration. |
||
| 138 | void reset() { |
||
| 139 | abort(); |
||
| 140 | push(); |
||
| 141 | } |
||
| 142 | |||
| 143 | /// Signals that the context was completed without an appropriate |
||
| 144 | /// declaration being parsed. |
||
| 145 | void abort() { |
||
| 146 | pop(nullptr); |
||
| 147 | } |
||
| 148 | |||
| 149 | void complete(Decl *D) { |
||
| 150 | assert(!Popped && "ParsingDeclaration has already been popped!"); |
||
| 151 | pop(D); |
||
| 152 | } |
||
| 153 | |||
| 154 | /// Unregister this object from Sema, but remember all the |
||
| 155 | /// diagnostics that were emitted into it. |
||
| 156 | void abortAndRemember() { |
||
| 157 | pop(nullptr); |
||
| 158 | } |
||
| 159 | |||
| 160 | private: |
||
| 161 | void push() { |
||
| 162 | State = Actions.PushParsingDeclaration(DiagnosticPool); |
||
| 163 | Popped = false; |
||
| 164 | } |
||
| 165 | |||
| 166 | void pop(Decl *D) { |
||
| 167 | if (!Popped) { |
||
| 168 | Actions.PopParsingDeclaration(State, D); |
||
| 169 | Popped = true; |
||
| 170 | } |
||
| 171 | } |
||
| 172 | }; |
||
| 173 | |||
| 174 | /// A class for parsing a DeclSpec. |
||
| 175 | class ParsingDeclSpec : public DeclSpec { |
||
| 176 | ParsingDeclRAIIObject ParsingRAII; |
||
| 177 | |||
| 178 | public: |
||
| 179 | ParsingDeclSpec(Parser &P) |
||
| 180 | : DeclSpec(P.getAttrFactory()), |
||
| 181 | ParsingRAII(P, ParsingDeclRAIIObject::NoParent) {} |
||
| 182 | ParsingDeclSpec(Parser &P, ParsingDeclRAIIObject *RAII) |
||
| 183 | : DeclSpec(P.getAttrFactory()), |
||
| 184 | ParsingRAII(P, RAII) {} |
||
| 185 | |||
| 186 | const sema::DelayedDiagnosticPool &getDelayedDiagnosticPool() const { |
||
| 187 | return ParsingRAII.getDelayedDiagnosticPool(); |
||
| 188 | } |
||
| 189 | |||
| 190 | void complete(Decl *D) { |
||
| 191 | ParsingRAII.complete(D); |
||
| 192 | } |
||
| 193 | |||
| 194 | void abort() { |
||
| 195 | ParsingRAII.abort(); |
||
| 196 | } |
||
| 197 | }; |
||
| 198 | |||
| 199 | /// A class for parsing a declarator. |
||
| 200 | class ParsingDeclarator : public Declarator { |
||
| 201 | ParsingDeclRAIIObject ParsingRAII; |
||
| 202 | |||
| 203 | public: |
||
| 204 | ParsingDeclarator(Parser &P, const ParsingDeclSpec &DS, |
||
| 205 | const ParsedAttributes &DeclarationAttrs, |
||
| 206 | DeclaratorContext C) |
||
| 207 | : Declarator(DS, DeclarationAttrs, C), |
||
| 208 | ParsingRAII(P, &DS.getDelayedDiagnosticPool()) {} |
||
| 209 | |||
| 210 | const ParsingDeclSpec &getDeclSpec() const { |
||
| 211 | return static_cast<const ParsingDeclSpec&>(Declarator::getDeclSpec()); |
||
| 212 | } |
||
| 213 | |||
| 214 | ParsingDeclSpec &getMutableDeclSpec() const { |
||
| 215 | return const_cast<ParsingDeclSpec&>(getDeclSpec()); |
||
| 216 | } |
||
| 217 | |||
| 218 | void clear() { |
||
| 219 | Declarator::clear(); |
||
| 220 | ParsingRAII.reset(); |
||
| 221 | } |
||
| 222 | |||
| 223 | void complete(Decl *D) { |
||
| 224 | ParsingRAII.complete(D); |
||
| 225 | } |
||
| 226 | }; |
||
| 227 | |||
| 228 | /// A class for parsing a field declarator. |
||
| 229 | class ParsingFieldDeclarator : public FieldDeclarator { |
||
| 230 | ParsingDeclRAIIObject ParsingRAII; |
||
| 231 | |||
| 232 | public: |
||
| 233 | ParsingFieldDeclarator(Parser &P, const ParsingDeclSpec &DS, |
||
| 234 | const ParsedAttributes &DeclarationAttrs) |
||
| 235 | : FieldDeclarator(DS, DeclarationAttrs), |
||
| 236 | ParsingRAII(P, &DS.getDelayedDiagnosticPool()) {} |
||
| 237 | |||
| 238 | const ParsingDeclSpec &getDeclSpec() const { |
||
| 239 | return static_cast<const ParsingDeclSpec&>(D.getDeclSpec()); |
||
| 240 | } |
||
| 241 | |||
| 242 | ParsingDeclSpec &getMutableDeclSpec() const { |
||
| 243 | return const_cast<ParsingDeclSpec&>(getDeclSpec()); |
||
| 244 | } |
||
| 245 | |||
| 246 | void complete(Decl *D) { |
||
| 247 | ParsingRAII.complete(D); |
||
| 248 | } |
||
| 249 | }; |
||
| 250 | |||
| 251 | /// ExtensionRAIIObject - This saves the state of extension warnings when |
||
| 252 | /// constructed and disables them. When destructed, it restores them back to |
||
| 253 | /// the way they used to be. This is used to handle __extension__ in the |
||
| 254 | /// parser. |
||
| 255 | class ExtensionRAIIObject { |
||
| 256 | ExtensionRAIIObject(const ExtensionRAIIObject &) = delete; |
||
| 257 | void operator=(const ExtensionRAIIObject &) = delete; |
||
| 258 | |||
| 259 | DiagnosticsEngine &Diags; |
||
| 260 | public: |
||
| 261 | ExtensionRAIIObject(DiagnosticsEngine &diags) : Diags(diags) { |
||
| 262 | Diags.IncrementAllExtensionsSilenced(); |
||
| 263 | } |
||
| 264 | |||
| 265 | ~ExtensionRAIIObject() { |
||
| 266 | Diags.DecrementAllExtensionsSilenced(); |
||
| 267 | } |
||
| 268 | }; |
||
| 269 | |||
| 270 | /// ColonProtectionRAIIObject - This sets the Parser::ColonIsSacred bool and |
||
| 271 | /// restores it when destroyed. This says that "foo:" should not be |
||
| 272 | /// considered a possible typo for "foo::" for error recovery purposes. |
||
| 273 | class ColonProtectionRAIIObject { |
||
| 274 | Parser &P; |
||
| 275 | bool OldVal; |
||
| 276 | public: |
||
| 277 | ColonProtectionRAIIObject(Parser &p, bool Value = true) |
||
| 278 | : P(p), OldVal(P.ColonIsSacred) { |
||
| 279 | P.ColonIsSacred = Value; |
||
| 280 | } |
||
| 281 | |||
| 282 | /// restore - This can be used to restore the state early, before the dtor |
||
| 283 | /// is run. |
||
| 284 | void restore() { |
||
| 285 | P.ColonIsSacred = OldVal; |
||
| 286 | } |
||
| 287 | |||
| 288 | ~ColonProtectionRAIIObject() { |
||
| 289 | restore(); |
||
| 290 | } |
||
| 291 | }; |
||
| 292 | |||
| 293 | /// Activates OpenMP parsing mode to preseve OpenMP specific annotation |
||
| 294 | /// tokens. |
||
| 295 | class ParsingOpenMPDirectiveRAII { |
||
| 296 | Parser &P; |
||
| 297 | bool OldVal; |
||
| 298 | |||
| 299 | public: |
||
| 300 | ParsingOpenMPDirectiveRAII(Parser &P, bool Value = true) |
||
| 301 | : P(P), OldVal(P.OpenMPDirectiveParsing) { |
||
| 302 | P.OpenMPDirectiveParsing = Value; |
||
| 303 | } |
||
| 304 | |||
| 305 | /// This can be used to restore the state early, before the dtor |
||
| 306 | /// is run. |
||
| 307 | void restore() { P.OpenMPDirectiveParsing = OldVal; } |
||
| 308 | |||
| 309 | ~ParsingOpenMPDirectiveRAII() { restore(); } |
||
| 310 | }; |
||
| 311 | |||
| 312 | /// RAII object that makes '>' behave either as an operator |
||
| 313 | /// or as the closing angle bracket for a template argument list. |
||
| 314 | class GreaterThanIsOperatorScope { |
||
| 315 | bool &GreaterThanIsOperator; |
||
| 316 | bool OldGreaterThanIsOperator; |
||
| 317 | public: |
||
| 318 | GreaterThanIsOperatorScope(bool >IO, bool Val) |
||
| 319 | : GreaterThanIsOperator(GTIO), OldGreaterThanIsOperator(GTIO) { |
||
| 320 | GreaterThanIsOperator = Val; |
||
| 321 | } |
||
| 322 | |||
| 323 | ~GreaterThanIsOperatorScope() { |
||
| 324 | GreaterThanIsOperator = OldGreaterThanIsOperator; |
||
| 325 | } |
||
| 326 | }; |
||
| 327 | |||
| 328 | class InMessageExpressionRAIIObject { |
||
| 329 | bool &InMessageExpression; |
||
| 330 | bool OldValue; |
||
| 331 | |||
| 332 | public: |
||
| 333 | InMessageExpressionRAIIObject(Parser &P, bool Value) |
||
| 334 | : InMessageExpression(P.InMessageExpression), |
||
| 335 | OldValue(P.InMessageExpression) { |
||
| 336 | InMessageExpression = Value; |
||
| 337 | } |
||
| 338 | |||
| 339 | ~InMessageExpressionRAIIObject() { |
||
| 340 | InMessageExpression = OldValue; |
||
| 341 | } |
||
| 342 | }; |
||
| 343 | |||
| 344 | class OffsetOfStateRAIIObject { |
||
| 345 | Sema::OffsetOfKind &OffsetOfState; |
||
| 346 | Sema::OffsetOfKind OldValue; |
||
| 347 | |||
| 348 | public: |
||
| 349 | OffsetOfStateRAIIObject(Parser &P, Sema::OffsetOfKind Value) |
||
| 350 | : OffsetOfState(P.OffsetOfState), OldValue(P.OffsetOfState) { |
||
| 351 | OffsetOfState = Value; |
||
| 352 | } |
||
| 353 | |||
| 354 | ~OffsetOfStateRAIIObject() { OffsetOfState = OldValue; } |
||
| 355 | }; |
||
| 356 | |||
| 357 | /// RAII object that makes sure paren/bracket/brace count is correct |
||
| 358 | /// after declaration/statement parsing, even when there's a parsing error. |
||
| 359 | class ParenBraceBracketBalancer { |
||
| 360 | Parser &P; |
||
| 361 | unsigned short ParenCount, BracketCount, BraceCount; |
||
| 362 | public: |
||
| 363 | ParenBraceBracketBalancer(Parser &p) |
||
| 364 | : P(p), ParenCount(p.ParenCount), BracketCount(p.BracketCount), |
||
| 365 | BraceCount(p.BraceCount) { } |
||
| 366 | |||
| 367 | ~ParenBraceBracketBalancer() { |
||
| 368 | P.AngleBrackets.clear(P); |
||
| 369 | P.ParenCount = ParenCount; |
||
| 370 | P.BracketCount = BracketCount; |
||
| 371 | P.BraceCount = BraceCount; |
||
| 372 | } |
||
| 373 | }; |
||
| 374 | |||
| 375 | class PoisonSEHIdentifiersRAIIObject { |
||
| 376 | PoisonIdentifierRAIIObject Ident_AbnormalTermination; |
||
| 377 | PoisonIdentifierRAIIObject Ident_GetExceptionCode; |
||
| 378 | PoisonIdentifierRAIIObject Ident_GetExceptionInfo; |
||
| 379 | PoisonIdentifierRAIIObject Ident__abnormal_termination; |
||
| 380 | PoisonIdentifierRAIIObject Ident__exception_code; |
||
| 381 | PoisonIdentifierRAIIObject Ident__exception_info; |
||
| 382 | PoisonIdentifierRAIIObject Ident___abnormal_termination; |
||
| 383 | PoisonIdentifierRAIIObject Ident___exception_code; |
||
| 384 | PoisonIdentifierRAIIObject Ident___exception_info; |
||
| 385 | public: |
||
| 386 | PoisonSEHIdentifiersRAIIObject(Parser &Self, bool NewValue) |
||
| 387 | : Ident_AbnormalTermination(Self.Ident_AbnormalTermination, NewValue), |
||
| 388 | Ident_GetExceptionCode(Self.Ident_GetExceptionCode, NewValue), |
||
| 389 | Ident_GetExceptionInfo(Self.Ident_GetExceptionInfo, NewValue), |
||
| 390 | Ident__abnormal_termination(Self.Ident__abnormal_termination, NewValue), |
||
| 391 | Ident__exception_code(Self.Ident__exception_code, NewValue), |
||
| 392 | Ident__exception_info(Self.Ident__exception_info, NewValue), |
||
| 393 | Ident___abnormal_termination(Self.Ident___abnormal_termination, NewValue), |
||
| 394 | Ident___exception_code(Self.Ident___exception_code, NewValue), |
||
| 395 | Ident___exception_info(Self.Ident___exception_info, NewValue) { |
||
| 396 | } |
||
| 397 | }; |
||
| 398 | |||
| 399 | /// RAII class that helps handle the parsing of an open/close delimiter |
||
| 400 | /// pair, such as braces { ... } or parentheses ( ... ). |
||
| 401 | class BalancedDelimiterTracker : public GreaterThanIsOperatorScope { |
||
| 402 | Parser& P; |
||
| 403 | tok::TokenKind Kind, Close, FinalToken; |
||
| 404 | SourceLocation (Parser::*Consumer)(); |
||
| 405 | SourceLocation LOpen, LClose; |
||
| 406 | |||
| 407 | unsigned short &getDepth() { |
||
| 408 | switch (Kind) { |
||
| 409 | case tok::l_brace: return P.BraceCount; |
||
| 410 | case tok::l_square: return P.BracketCount; |
||
| 411 | case tok::l_paren: return P.ParenCount; |
||
| 412 | default: llvm_unreachable("Wrong token kind"); |
||
| 413 | } |
||
| 414 | } |
||
| 415 | |||
| 416 | bool diagnoseOverflow(); |
||
| 417 | bool diagnoseMissingClose(); |
||
| 418 | |||
| 419 | public: |
||
| 420 | BalancedDelimiterTracker(Parser& p, tok::TokenKind k, |
||
| 421 | tok::TokenKind FinalToken = tok::semi) |
||
| 422 | : GreaterThanIsOperatorScope(p.GreaterThanIsOperator, true), |
||
| 423 | P(p), Kind(k), FinalToken(FinalToken) |
||
| 424 | { |
||
| 425 | switch (Kind) { |
||
| 426 | default: llvm_unreachable("Unexpected balanced token"); |
||
| 427 | case tok::l_brace: |
||
| 428 | Close = tok::r_brace; |
||
| 429 | Consumer = &Parser::ConsumeBrace; |
||
| 430 | break; |
||
| 431 | case tok::l_paren: |
||
| 432 | Close = tok::r_paren; |
||
| 433 | Consumer = &Parser::ConsumeParen; |
||
| 434 | break; |
||
| 435 | |||
| 436 | case tok::l_square: |
||
| 437 | Close = tok::r_square; |
||
| 438 | Consumer = &Parser::ConsumeBracket; |
||
| 439 | break; |
||
| 440 | } |
||
| 441 | } |
||
| 442 | |||
| 443 | SourceLocation getOpenLocation() const { return LOpen; } |
||
| 444 | SourceLocation getCloseLocation() const { return LClose; } |
||
| 445 | SourceRange getRange() const { return SourceRange(LOpen, LClose); } |
||
| 446 | |||
| 447 | bool consumeOpen() { |
||
| 448 | if (!P.Tok.is(Kind)) |
||
| 449 | return true; |
||
| 450 | |||
| 451 | if (getDepth() < P.getLangOpts().BracketDepth) { |
||
| 452 | LOpen = (P.*Consumer)(); |
||
| 453 | return false; |
||
| 454 | } |
||
| 455 | |||
| 456 | return diagnoseOverflow(); |
||
| 457 | } |
||
| 458 | |||
| 459 | bool expectAndConsume(unsigned DiagID = diag::err_expected, |
||
| 460 | const char *Msg = "", |
||
| 461 | tok::TokenKind SkipToTok = tok::unknown); |
||
| 462 | bool consumeClose() { |
||
| 463 | if (P.Tok.is(Close)) { |
||
| 464 | LClose = (P.*Consumer)(); |
||
| 465 | return false; |
||
| 466 | } else if (P.Tok.is(tok::semi) && P.NextToken().is(Close)) { |
||
| 467 | SourceLocation SemiLoc = P.ConsumeToken(); |
||
| 468 | P.Diag(SemiLoc, diag::err_unexpected_semi) |
||
| 469 | << Close << FixItHint::CreateRemoval(SourceRange(SemiLoc, SemiLoc)); |
||
| 470 | LClose = (P.*Consumer)(); |
||
| 471 | return false; |
||
| 472 | } |
||
| 473 | |||
| 474 | return diagnoseMissingClose(); |
||
| 475 | } |
||
| 476 | void skipToEnd(); |
||
| 477 | }; |
||
| 478 | } // end namespace clang |
||
| 479 | |||
| 480 | #endif |