The context sensitivity of C’s grammar
eli.thegreenplace.net
eli.thegreenplace.net
As the original authors wrote the compiler the result of not having the context free grammar was just having a few lines of the code more. Their goal was certainly not an academic "parser" purity. Parsing is one of quite uninteresting parts when you're making UNIX and C some 40 yeas ago.
Regarding historical perspective, unfortunately it isn't so easy to gain. I actually consulted a lot of comp.compiler discussions from the early 1990s, but most links in them point to various non-existent FTP sites :-/
FWIW I did mention 'tcc' in the article, the "tiny c compiler" with rather compact and clean source code, probably smaller than the code for Bison
http://unixarchive.tliquest.net/PDP-11/Distributions/researc...
In 500 K gzipped tar you have at least the whole unix, the whole stdlib and the whole C compiler. For the book that explains the sources see:
Book: Lion's Commentary on the Sixth Ed Unix
Professionals write recursive-descent parsers.
Real men code finite state machines by hand: http://galileo.phys.virginia.edu/classes/551.jvn.fall01/fsm....
But seriously, the mystery and ignorance that continues to surround Yacc to this day needs to be fought. If you're using Yacc, you're doing it wrong. As far as parser-generators go, it is totally obsolete, and even when it wasn't, it was the wrong choice for almost all parsing problems. I learned this one day when someone recoded a Yacc-based parser I wrote in recursive-descent style, and it was less lines of code than my specification.
If you need to write a really simple, or a really complicated, parser, recursive-descent is usually the way to go. You might try Henry Baker's META (http://home.pipeline.com/~hbaker1/Prag-Parse.html) as a first alternative. If you really, really feel the need for a parser-generator, take a look at parsing expressions grammars/packrat parsers: http://pdos.csail.mit.edu/~baford/packrat/
But the real lesson here is to design your syntax to be easy to parse and manipulate in the first place. C's grammar is the clearest testament to how little thought Dennis Ritchie put into the design of C.
I've tried working with other parser generators, but I always keep coming back to hand-coded recursive descent. The biggest advantage IMO is that they're both easier to debug, and can be made to report errors in input much better than parser-generator generated ones.
What is missing from the discussion in a generally good article here is that it isn't so much a "hack to the lexer" but you put information in the symbol table (scope, type) and push out to yacc a symbol with token type attached (variable, typename, etc).
As another commenter here says, C compilers weren't originally done with yacc or other parser generator, they were recursive descent.
As an example, Emacs syntax-highlights both "YY xx" and "int xx" the same way.
To parse this correctly, the parser has to receive a "TYPE-NAME" token for XX and an "IDENTIFIER" token for yy. If the parser receives an IDENTIFIER token for XX, this will result in a parse error.
Or, to put it another way, you resort to uglyfying the lexer to overcome definiciencies in the parser.
(A) * B
This is either A multiplied by B, or type A casting the dereferenced value of B: http://en.wikipedia.org/wiki/The_lexer_hackSignificant whitespace is evil, at least in the context of C where it is not significant anywhere else.
That's basically what C++ did by requiring the space in:
Foo<Bar<Baz> > var;
to make it easy to lexically distinguish the '>>' right-shift operator from the '> >' sequence of two successive template-parameter closing symbols.C++0x is changing that though, due to the unpopularity of making programmers accomodate what looks like a parser-implementation hack.
The article says that this code is "evil", but I disagree. There is a valid pattern that I use quite often (I described it in an article for ACCU a few years ago, too). It's an example from C++ but I think I could apply it to C as well. Consider (with a modification of the example I used in that article):
struct PirateShip { struct Cannons { char* cannon1; char* cannon2; } Cannons; }
Now you can use
PirateShip s; printf("Name of the first cannon: %s\n", s.Cannons.cannon1);
which is very readable and idiomatic (once you get used to it ;) )
More abstract, it provides an idiomatic way to group collections of objects/structs at the source code/syntactic level.
(one could name the struct and the member differently, I realize; the benefit of using the same name is when you define constant members in the inner struct, that way you can reference constants and properties with the same name and you never have to think about which one to use.)
(also again I use this from C++, there may be things that are different in C, but afaik this part is the same).
struct PirateShip {
struct PirateShip {
char* cannon1;
char* cannon2;
}
PirateShip PirateShip;
}
Am I misunderstanding?It generates an amazing lexer/parser, which can do something like a million lines of code a second. But he hasn't open sourced the parser generator nor ported it from Windows. I've been trying to convince him to do so, and he came oh so close recently.
He's been trying to figure out how to make money from it if he Open Sources it. As it is, everyone seems to be totally ignoring it despite its sophistication compared with flex/bison.
typedef unsigned int uint, uint * uintptr;
Here is how to specify a sample grammar to handle the above statement with a new product called LRSTAR. Note, this grammar has no conflicts and is LALR(1). Here is the grammar:
<identifier> => lookup ()
Declaration -> VarDecl1 /','... ';' -> typedef VarDecl2 /','... ';'
VarDecl1 -> Type... <identifier>
VarDecl2 -> Type... <identifier> => defterm(2,{typedef})
Type -> SimpleType... -> Type '*'
SimpleType -> char -> int -> short -> unsigned -> {typedef}
You can download LRSTAR at http://HighperWare.com.