Expand description
Scopes, and the typedef decision that rests on them.
Design: spec/06-lexer-and-parser.md section 6.4.
C’s grammar is ambiguous without knowing which identifiers are type names, because (A)*B
is a cast when A is a type and a multiplication when it is not. The parser resolves that
here, against scopes it maintains itself, and there is no feedback channel to the lexer.
Feeding the answer back to the lexer is the traditional approach and it makes the lexer’s
state depend on how far the parser has got, which is what makes lookahead and error recovery
painful in the compilers that do it.
§The hazards
Each of these is a real bug in a real compiler, and each has a test below.
A declarator introduces its name at the end of the declarator, not at the start, so
typedef int T; void f(int T, T x); has T as a parameter name and T x is then an error,
while typedef int T; T T; reads the specifier T as the type and then declares a variable
of that name.
Tags occupy a namespace of their own, so struct S does not disturb what a bare S means,
and a typedef name shadowed by an inner declaration comes back when that scope closes.
The scoping itself is ScopeMap, in rucc-base, because semantic
analysis needs the same structure with different values in it.
§What is not here
Two of C’s four namespaces. Labels are function wide rather than block scoped and nothing about them is ambiguous, so the function parser collects them and this stack would only be in the way. Members belong to the record that declares them and are reached through a type rather than through a scope, which makes them semantic analysis’s problem and not a parsing decision at all.
Structs§
- Scopes
- The scopes the parser keeps, across the namespaces it has to hold apart.