Language Issues: Praise and Critique of XPL0
|Top Previous Next|
After having spent many years programming in Pascal and other languages, I had both pleasant and unpleasant feelings about using XPL0 again. I thought it would be worthwhile to mention both while they are fresh in my mind.
Praise: There are a number of features of XPL0 that are useful and superior to other languages. Here is a summary of the ones I'm aware of. They are mostly comparisons to Pascal, although some apply to other languages.
2. Case Statement. XPL0's case statement is very flexible. Each case can accept any Boolean expression as the argument. Other languages like Pascal can only accept a constant expression for the case argument. This make XPL0 more flexible, and enables the language to handle more complex comparisons that would need to be handled with more awkward if-then-else constructs in other languages.
3. Conditional Assignment. XPL0 has a conditional assignment statement that simplifies picking values under differing circumstances. Pascal and some other languages don't have this capability, which makes assigning values slightly more complicated.
Critique: After having spent many years programming in Pascal and other languages, it was something of a shock to program in XPL0 again. I was struck by how easy it was to get stung by minor programming errors and how hard it was to find them once you made them. In the process I realized there were lots of things that could be done to XPL0 to make it more useful, less error prone and easier to program. Here is a list of things I noticed:
1. Device Dependencies. All device dependent functions should be made into intrinsics. Many XPL programs are written so they are completely device dependent making them non-portable. For example, lots of programs call various BIOS and DOS functions via software interrupts directly from XPL. This makes it impossible to run the program on different operating systems without extensive revisions to the XPL program itself.
The intrinsics were designed to handle hardware dependencies. If all hardware dependent routines were put into intrinsics, then XPL programs would be far more portable. Only about 100 intrinsics are in common use so there is plenty of room to convert all the common hardware dependent functions to intrinsics.
2. Common Units. Common units should be used in intrinsics. For example, the current Sound intrinsic specifies the duration of a sound in units of 54 milliseconds. This reflects the underlying hardware of the PC timer chip. The problem is that if the sound generator changes, all programs using the Sound intrinsic must change. The correct method would be to specify the sound duration in milliseconds with an intrinsic converting them to the underlying units. Other examples of this problem include specifying colors in the arcane CGA/EGA/VGA palette index system instead of RGB.
3. Intrinsic Arguments. Intrinsics do no argument checking of any kind. As a result, you can pass the wrong number or type of arguments to an intrinsic, and the compiler will not report the problem. Since it is easy to forget which type of argument to expect, one of the most common errors in XPL0 is passing the wrong argument type to an intrinsic. For example, XPL0 will happily let you pass an integer argument to an intrinsic that requires a real argument. When the intrinsic pops the supposed real argument off the stack, the stack will be misaligned, causing a catastrophic error. Just testing XPL code, I repeatedly nailed myself with code like this:
The compiler already knows if an intrinsic returns a integer or a real, and will flag a mixed mode error if you assign the result to the wrong kind of variable. There is no reason it couldn't check the arguments passed to an intrinsic. All it needs is a list of the types. The intrinsic declaration could provide argument information that could be used by the compiler to test arguments. For example, intrinsic declarations could have optional parentheses after the name that could contain a list of argument types, for example:
This construct is backward compatible, because if the parentheses are there, the compiler would know the number of arguments and their types, and it could check them. If the parentheses were not there, the compiler would revert to its original behavior. That would make the extension backward compatible.
4. Procedure/Function Arguments. The compiler should also check procedure and functions arguments for the correct type and number. Although passing the wrong argument to an ordinary procedure is not as catastrophic as passing the wrong arguments to an intrinsic, it is still a common source of bugs and errors.
5. Intrinsic Declarations. Intrinsic declarations shouldn't be handled by the user. As multiple versions of XPL0 are generated, multiple versions of the intrinsics are distributed. Since the intrinsics are usually put in a common directory like C:\CXPL, the directory has a tendency to accumulate debris. This leads to the problem that the intrinsics may not match the actual code in the runtime package. Since the module that contains the intrinsics code knows exactly which intrinsics are available for the specific version of the language, it would be a simple matter for it to generate a string containing all intrinsic declarations. This could then be injected into the code at the beginning of a program. It could also be used for other IDE purposes, like showing the user a pop-up list of available intrinsics. All these features are now available in the EXPL.
6. Short Circuit Boolean Expressions. In Pascal you have the option of aborting a Boolean expression if the result is known before all the terms are evaluated. This lets you test and access an array in the same expression. Here is an example:
if I<Limit and S(I)=^A then DoSomething;
In Pascal this expression would examine "I" to make sure it hadn't exceeded the length of string S and abort before S(I) was tested. In XPL0, the whole expression is evaluated even if "I" exceeds Limit. In order to test Limit in XPL0 you must resort to a more complicated expression:
if I<Limit then
if S(I)=^A then DoSomething;
It seems like the compiler could handle the situation quite easily. The only difference between the first and second expression is that the "and" operator is replaced with "then if." All the compiler needs to do is generate the code for the second example whenever it sees the pattern displayed in the first expression.