5 ms·
I go a little deeper and more esoteric with my big gripe with C++... why can't it be parsed with a simple LALR(1) parser? Donald Knuth summed this up much more
by beat 11y ago
I go a little deeper and more esoteric with my big gripe with C++... why can't it be parsed with a simple LALR(1) parser?
Donald Knuth summed this up much more elegantly than I could ever hope to do so, in his six character critique of C++:
a < b > c;
What the hell does that mean? What it means is that you cannot determine the meaning of those six characters without knowing in advance the types of a, b, and c. Is it an odd-but-valid C comparison? (a is less than b and b is greater than c) Or is it a template declaration? You cannot know these things without context.
Back when I studied compiler theory and was reading the Dragon Book, the simplicity, elegance, and precision of the LALR(1) parser design really touched me. And it was implemented on every Unix box with lex and yacc. This is how you write a compiler! This is how you understand structure precisely! To be syntactically valid, a statement must have one and only one meaning.
C++ cannot be parsed without interpreting variable types at parse time. LALR(1) does not work. It never has worked. C++ has been broken on this front since the 1980s, when it was just a preprocessor that cross-compiled to C. Why? What possible benefit is there to giving up that core bit of parser discipline?
"Because it's powerful!", someone sneers, as if that condescension justified bad design. "Because templates are so hard!" another whispers. But what about this?
a [< b >] c;
If the template declaration tokens were [< >] rather than < >, they wouldn't overload the comparison operators. The meaning of Knuth's example would be unambiguous, and both could be parsed without grief - both by machines, and by human eyeballs. This isn't difficulty. It's just not thinking through the consequences.
But that's okay. Smoke enough of that << and >> crack, and you'll soon forget those tokens are bitwise shift operators, not i/o. Sigh.
- deleted 11y ago[deleted]
- antiquark 11y agoIt depends on the context though. Code like vector<int> years; list<string> names; is pretty easy to wrap your brain around.
- zzalpha 11y agoOf all the complaints I can think of regarding C++, the one I care least about is the difficulty in disambiguating the grammar... Languages exist to help humans, not compilers. Of course, C++ often fails in that regard as well. Aside, a pox on the house of bottom up parsers. Anyone trying to produce useful semantic errors out of a bottom up parser understands how god awful they are to work with. There's a reason both GCC and clang use hand rolled recursive descent parsers...
- beat 11y agoMy first professional exposure to C++ was porting code across every Unix platform available in the late 90s, using a bunch of proprietary compilers. It was hell. Nothing was consistent. And of course, there were no unit test suites, and the programmers thought that if it compiled on HP-UX 9, it'd compile anywhere. So I have a real attitude about how the parsers work. I have yet to see a reasonable explanation why C++ needs a type-aware compiler to accomplish its goals. I can't think of any other languages with this problem.
- roel_v 11y ago"I have yet to see a reasonable explanation why C++ needs a type-aware compiler to accomplish its goals." Are you saying strong typing is a problem with C++ as a language? Or am I misunderstanding?
- jkeler 11y agoYou are probably talking about static typing, not strong. Strong typing implies that there are no implicit type conversions which is not true for C++.
- roel_v 11y agoOh god not this again...
- to3m 11y agoYou can't assign a category to the tokens in something like "T * p" unless you know whether "T" is a type or not. Suppose you have the following line of code: T *p; It means one thing if it were preceded by this, making the line a variable declaration: struct T; typedef struct T T; It means quite another if it were preceded by this, making the line an expression: int T=5,p=0; (This particular problem also affects C.)
- roel_v 11y ago
- deleted 11y ago[deleted]
- 1amzave 11y agoWhile this is a legitimate complaint, it also applies (though in fewer ways) to C. How do you parse this? (x)(y); Is that a call of function (or function pointer) `x` with argument `y`? Or is it a cast of `y` to type `x`? You need to have kept track of of all the typedefs in the code prior to that point to know.
- beat 11y agoGood point. But keep in mind that C predates C++ by over 15 years. There were tremendous advances in theory in that time. It's not completely fair to compare a hacker's tool from 1970 with a state of the art academic exercise from 1987.
- unscaled 11y agoEarly versions of C date to 1972, while early versions of C++ (called C with classes back then) date 1979, only 7 years later, probably a few rooms down the hall from where C was originally developed. True, templates came much later, but by then C++ was already hopelessly unparsable by LALR(1) parsers. But in truth, it was this way from moment 0, since it was based on C-syntax. Making it parsable by yacc was a lost cause, since yacc followed C and not the other way around.
- koenigdavidmj 11y agoAnother one: x * y; Is that a multiplication, whose result is being thrown away, or a declaration of a variable y of type pointer-to-x?
- ArkyBeagle 11y agoBy itself it's almost certainly a function call with a discarded return. The result is not assigned to anything.
- masklinn 11y ago> Is it an odd-but-valid C comparison? (a is less than b and b is greater than c) That's not the C comparison, that's a Python chained comparison. A C comparison will parse this as `(a < b) > c`, and you'll get false for a=3, b=5 and c=1.
- raverbashing 11y agoThis is pretty much solvable with a preprocessor and some heuristics and you get to keep your LALR parser Also IMHO a<b>c should not exist as a comparison. Does this mean a<b AND b>c? Then spell it out, it's a rare case and it doesn't contribute to readability (as opposed to a<b<c for example)
- masklinn 11y ago> Also IMHO a<b>c should not exist as a comparison. Does this mean a<b AND b>c? Then spell it out, it's a rare case and it doesn't contribute to readability (as opposed to a<b<c for example) Both are probably broken anyway. The first one will compute as (a < b) > c, and the second one as (a < b) < c. Unless you're using Python which has chained comparison, which has its own pitfalls e.g. `a < b == c > d` may not do what you want (it's equivalent to `a < b and b == c and c > d`, not `(a < b) == (c > d)`)