Thursday, April 26, 2007

TAS Must Die, Chapter 17

I started working on code generation. For me, this is converting a list of pcodes to a list of executables, executables being classes that implement an interface called Executable. If you can imagine it, it requires implementors to define a method called "execute()".

Preliminary work on the assign, emit, and emitVariable executables went well but did point out flaws in my symbol management, or lack thereof. There are a few places where I 'forget' that some operands might be constants and these won't be found in the run-time datastructure of iscript variables. I could cheese out and check the values for a leading number or quote and figure it out that way. But fixing the problem is the right answer. This will reverberate all the way into the operator precedence parser, I fear, and ultimately cause me to add two more rows and columns to the precedence table.

It's probably just as well, my current mechanism, as I think about, probably doesn't complain about having a constant as an l-value. I don't know what would happen if I did.

Once I have the symbol issue resolved, I'll be sailing again.

Last night's main trauma was an issue caused by my BinaryOperatorPcode class. This class manages an operator, two operands, and the result of an operation. Think

result <- op1 operator op2

So I have this class which can display the pcode for any binary operator - plus, minus, divide, multiple, and, or, etc. It works great. The problem comes when I want to generate the Executable instance. But which executable! It's one thing to print a string like "+" or "*". It's quite another to create an instance of a class that does the actual work.

So I have a pcode class with any one of 15 or so binary operators in it. How DO I determine the proper Executable. One way is to use some hideous switch() statement in the pcode... I hate these, and running if-else-if constructs for two reasons. First, such statements imply bad OO design. Sometimes they are necessary but can be hidden in a Factory or Builder of some sort. Second, with a little work, you frequently find that there's no need for the switch() at all. switch() is an easy symptom-fix.

I think that this is the case here. When in the operator parser, I know each operator specifically since I have to shift and reduce based upon each operator's specific precedence. Invoking the "BinaryOperatorRedux" class was convenient but ultimately unhelpful with regards to generating the very specific Executable instances.

I'll revamp my precedence table and remove all of the BinaryOperatorRedux references. I'll replace them with references to operator-specific reduction classes. Since operator manipulation is identical for all binary reductions, specific classes will almost certainly subclass an augmented BinaryOperatorRedux class, supplying a "protected Pcode getPcode()" method. And once I have an operator-specific pcode class I don't have to do any logic to figure out the appropriate Executable. MultiplyPcode will probably produce MultiplyExecutable, after all.

The cost of this is a larger precedence table and many more classes. But the classes are all trivial. Each operator-specific reduction class adds one 'run time' line of code to the system - the code that tells which Pcode class is proper. Each operator-specifc pcode class again adds one 'run time' line of code - the code that tells which Executable is appropriate. Each operator-specific Executable class will add one 'run time' line of code to the system - the code that does the actual work.

As I typed this, I became sure that I have a good answer. I'll add a fair number of one-liner classes and reduce run-time complexity by eliminating switch() statements.

No comments:

Post a Comment