As long as we're talking philosophy, there's another important issue to point out: error handling. Notice that although the parser correctly rejects (almost) every malformed expression we can throw at it, with a meaningful error message, we haven't really had to do much work to make that happen. In fact, in the whole parser per se (from Ident through Expression) there are only two calls to the error routine, Expected. Even those aren't necessary … if you'll look again in Term and Expression, you'll see that those statements can't be reached. I put them in early on as a bit of insurance, but they're no longer needed. Why don't you delete them now?
So how did we get this nice error handling virtually for free? It's simply that I've carefully avoided reading a character directly using GetChar. Instead, I've relied on the error handling in GetName, GetNum, and Match to do all the error checking for me. Astute readers will notice that some of the calls to Match (for example, the ones in Add and Subtract) are also unnecessary … we already know what the character is by the time we get there … but it maintains a certain symmetry to leave them in, and the general rule to always use Match instead of GetChar is a good one.
I mentioned an “almost” above. There is a case where our error handling leaves a bit to be desired. So far we haven't told our parser what and end-of-line looks like, or what to do with embedded white space. So a space character (or any other character not part of the recognized character set) simply causes the parser to terminate, ignoring the unrecognized characters.
It could be argued that this is reasonable behavior at this point. In a “real” compiler, there is usually another statement following the one we're working on, so any characters not treated as part of our expression will either be used for or rejected as part of the next one.
But it's also a very easy thing to fix up, even if it's only temporary. All we have to do is assert that the expression should end with an end-of-line , i.e., a carriage return.
To see what I'm talking about, try the input line
1+2 <space> 3+4
See how the space was treated as a terminator? Now, to make the compiler properly flag this, add the line
if Look <> CR then Expected('Newline');
in the main program, just after the call to Expression. That catches anything left over in the input stream. Don't forget to define CR in the const statement:
CR = ^M;
As usual, recompile the program and verify that it does what it's supposed to.