In cp_parser_declaration_seq_opt , declarations are handled by cp_parser_declaration . So normally, token it should process is only “end of file” (CPP_EOF). “}” and “;” come from abnormal cases. In such high level function, this extra “}” just means the parse can’t go on, but let above cp_parser_translation_unit to generate error message (usually, it because the error in declaration, so more corrective error message should have been produced).
6221 static void
6222 cp_parser_declaration_seq_opt ( cp_parser * parser) in parser.c
6223 {
6224 while (true)
6225 {
6226 cp_token *token;
6227
6228 token = cp_lexer_peek_token (parser->lexer);
6229
6230 if (token->type == CPP_CLOSE_BRACE
6231 || token->type == CPP_EOF)
6232 break ;
6233
6234 if (token->type == CPP_SEMICOLON)
6235 {
6236 /* A declaration consisting of a single semicolon is
6237 invalid. Allow it unless we're being pedantic. */
6238 if (pedantic && !in_system_header )
6239 pedwarn ("extra `;'");
6240 cp_lexer_consume_token (parser->lexer);
6241 continue ;
6242 }
6243
6244 /* The C lexer modifies PENDING_LANG_CHANGE when it wants the
6245 parser to enter or exit implicit `extern "C"' blocks. */
6246 while (pending_lang_change > 0)
6247 {
6248 push_lang_context (lang_name_c);
6249 --pending_lang_change ;
6250 }
6251 while (pending_lang_change < 0)
6252 {
6253 pop_lang_context ();
6254 ++pending_lang_change ;
6255 }
6256
6257 /* Parse the declaration itself. */
6258 cp_parser_declaration (parser);
6259 }
6260 }
pending_lang_change will be used if the system header files doesn’t support C+ as well as C. For our assuming target, it is not true. So lines from 6246 to 6255 in cp_parser_declaration_seq_opt are skipped as result.
According to [3], the abbreviate syntax tree for declaration is shown in below.
declaration
├ block- declaration
├ function-definition
├ template-declaration
├ explicit-instantiation
├ linkage-specification
├ namespace-definition
GNU Ext Ⅼ __extension__ declaration
To better understand how parser works and how the tree is constructed and what it contains, it is better go through the procedure of compiling a real program. To see the ability of the compiler to accommodate C++ standard, we need select the program carefully to let it cover most topic of the language.
Here we use the notable generic library LOKI as the example. The source files we select are those of SmallObj which defines high performance architecture to allocate and deallocate small size objects. Here we skip the step performed by preprocessor, previous section has explain this operation very clearly.
In the source file SmallObject.cc, after the duplicate inclusion defense macro, are the statements:
21 #include "Threads.h" in Smallobject.c
22 #include "Singleton.h"
23 #include <cstddef>
24 #include <vector>
The first two included files are part of the project, the rest are system headers. It is one of the practice recommended by some company (include the company I serve) – includes the closest header file first, the rearer it appears the more common its purpose.
The procedure of reading in included file is covered by previous section too, we also can skip it safely. First in file Threads.h, we see following code snippet (ignore macros):
34 namespace Loki
35 {
36 ////////////////////////////////////////////////////////////////////////////////
37 // class template SingleThreaded
38 // Implementation of the ThreadingModel policy used by various classes
39 // Implements a single-threaded model; no synchronization
40 ////////////////////////////////////////////////////////////////////////////////
41
42 template <class Host>
43 class SingleThreaded
44 {
45 public :
46 struct Lock
47 {
48 Lock() {}
49 Lock(const Host&) {}
50 };
51
52 typedef Host VolatileType;
53
54 typedef int IntType;
55
56 static IntType AtomicAdd(volatile IntType& lval, IntType val)
57 { return lval += val; }
58
59 static IntType AtomicSubtract(volatile IntType& lval, IntType val)
60 { return lval -= val; }
61
62 static IntType AtomicMultiply(volatile IntType& lval, IntType val)
63 { return lval *= val; }
64
65 static IntType AtomicDivide(volatile IntType& lval, IntType val)
66 { return lval /= val; }
67
68 static IntType AtomicIncrement(volatile IntType& lval)
69 { return ++lval; }
70
71 static IntType AtomicDivide(volatile IntType& lval)
72 { return --lval; }
73
74 static void AtomicAssign(volatile IntType & lval, IntType val)
75 { lval = val; }
76
77 static void AtomicAssign(IntType & lval, volatile IntType & val)
78 { lval = val; }
79 };
…
117 }
The secret and effect of the code is explain brilliantly in 《 modern C++ design 》 (author Andrei Alexandrescu). It is amazing.
The first statement seen by parser is “namespace Loki”, in cp_parser_declaration , it will be handle by following code:
cp_parser_declaration (continue)
6335 /* If the next token is `namespace', check for a named or unnamed
6336 namespace definition. */
6337 else if (token1.keyword == RID_NAMESPACE
6338 && (/* A named namespace definition. */
6339 (token2.type == CPP_NAME
6340 && (cp_lexer_peek_nth_token (parser->lexer, 3)->type
6341 == CPP_OPEN_BRACE))
6342 /* An unnamed namespace definition. */
6343 || token2.type == CPP_OPEN_BRACE))
6344 cp_parser_namespace_definition (parser);
The abbreviate syntax tree for the namespace-definition is given below. A named namespace can be defined across the source files. All definitions will be combined tegother. Among them, the first definition seen by the compiler is original-namespace-definition, those seen later are extension-namespace-definition. But both definitions just have identical looking. On the contrary anonymous namespace can only be defined within a single file.
namespace-definition
├ named-namespace-definition
| Ⅼ original-namespace-definition
| Ⅼ namespace identifier { namespace-body }
| Ⅼ declaration-seq [opt]
| extension-namespace-definition
| Ⅼ namespace original-namespace-name { namespace-body }
| Ⅼ identifier
Ⅼ unnamed-namespace-definition
Ⅼ namespace { namespace-body }
9538 static void
9539 cp_parser_namespace_definition (cp_parser* parser) in parser.c
9540 {
9541 tree identifier;
9542
9543 /* Look for the `namespace' keyword. */
9544 cp_parser_require_keyword (parser, RID_NAMESPACE, "`namespace'");
9545
9546 /* Get the name of the namespace. We do not attempt to distinguish
9547 between an original-namespace-definition and an
9548 extension-namespace-definition at this point. The semantic
9549 analysis routines are responsible for that. */
9550 if (cp_lexer_next_token_is (parser->lexer, CPP_NAME))
9551 identifier = cp_parser_identifier (parser);
9552 else
9553 identifier = NULL_TREE;
9554
9555 /* Look for the `{' to start the namespace. */
9556 cp_parser_require (parser, CPP_OPEN_BRACE, "`{'");
9557 /* Start the namespace. */
9558 push_namespace (identifier);
9559 /* Parse the body of the namespace. */
9560 cp_parser_namespace_body (parser);
9561 /* Finish the namespace. */
9562 pop_namespace ();
9563 /* Look for the final `}'. */
9564 cp_parser_require (parser, CPP_CLOSE_BRACE, "`}'");
9565 }
Section Create namespace “std” covers the detail of pushing the scope of namespace. In short, after namespace Loki is pushed into global namespace, we should get the brief layout of tree as below figure. See that now namespace Loki is pointed by scope_chain as current binding scope.
Figure 46 : brief layout after pushing Loki namespace
cp_parser_namespace_body has below definition.
9572 static void
9573 cp_parser_namespace_body (cp_parser* parser) in parser.c
9574 {
9575 cp_parser_declaration_seq_opt (parser);
9576 }
We re-enter cp_parser_declaration_seq_opt .