Different approaches to interpretation
That's what I would call execution, decompiling and debugging. Debugging is really just execution with some extra features like viewing the upcoming command and the program state. Decompiling does not require real execution of commands, but you still have to read them one by one and put out text representing each one.They are similar, but different. |
There are some problems with this solution, though:
- If you extract step 3 but not step 1 into the strategies, you'll have to pass information from step 1 into the strategy. To retrieve the information to pass (and to determine if there's any info to pass at all), you will have to at least see if the command is an extended opcode. Now the code to interpret your instruction set is split across at least three classes. This makes changes to the instruction set (which will be inevitable at early stages of development) a pain to implement.
- If you do extract step 1, all your interpreter class will be is a container of script data and an infinite loop on a strategy. Now the strategies do ALL the work, when they were only supposed to abstract away the differences between the three modes. However, that is still better than option one, so if you feel my method is stupid, this is probably your way to go.
- The execution and decompiling logic is no longer separate from the language-specific code and therefore not reusable as easily as it could be.
- Either way, you're passing around a lot of data between the strategies and the interpreter. This looks awful in code and is no fun to implement.
Describing the problem like that suggests implementing the decorator pattern:
- We'll define a common interface for all interpreters. We will implement the byte code pattern and the core loop in a BaseInterpreter. The actual logic of executing and decompiling commands of a specific language will be implemented in subclasses of that.
- The debugger and decompiler will implement the Interpreter interface as well, but only take another interpreter as input.
- The debugger will override the core loop to add steps 2 and 4 from the table above, but defer the actual execution to the interpreter that was passed in during construction.
- The decompiler will override the same method, but it will not defer to execution and rely on the code generating functions instead.
(ADDENDUM: Having now written the rest of the series, building a decompiler from the parts used by the compiler is just as easy. However, you still need the command-to-text function for the debugger, so building a decompiler with it won't hurt.)
No comments:
Post a Comment