My GW-BASIC saga continues. I’m on the third rewrite now of this “simple” translation app. I keep encountering problems that ultimately challenge core design decisions I made early on. Given the time scale and the fact that this is a toy project (and not, say, the Netscape browser), starting over is actually saving me time. On each rewrite, I approach the problem with more clarity than before and, if nothing else, get much more quickly to the point where I throw it all away again. It is a real-ish version of Michael Feathers’ “disappearing code” experiment.
Having seen this problem from a few angles now, I have determined there are at least three “hard sub-problems” that must be cracked in order for a reasonable GW-BASIC to C# translator to emerge:
- Parsing GW-BASIC statements
- Parsing GW-BASIC expressions
- Translation to C#
It seems obvious in retrospect — tautological, almost! — but there is something to be said for experiential learning. At this point it’s like I’m doing a very, very long code kata.
Today I want to drill into sub-problem #2 above, GW-BASIC expression parsing. I did not originally distinguish this from plain old GW-BASIC statement parsing, but the most recent rewrite trigger brought it into focus. As a diligent TDD’er (Geepaw Hill would be proud!), I had a good set of green tests and was about to implement the next feature — the ‘AND’ logical operator. Try as I might, I could not get the new failing test to pass without breaking many other tests in wholly unexpected ways. While Sprache had been a big help and massively increased my parsing productivity, the resultant parsers had become hopelessly complicated over time.
Looking back, there was an earlier sign that I was on the wrong path. Almost every new test I was adding ended up in the “IfThen” statement parsing fixture. But these were clearly expression parsing tests masquerading as IF/THEN tests — if only I had listened to the tests sooner.
With the benefit of hindsight, I am going to proceed by building just an expression parsing library. Once I can show that parsing expressions works in isolation, I suspect that statement parsing will be much simpler. In essence, I can avert the eventual combinatorial explosion of the earlier tests and cover the two concerns separately (turning products into sums, as @jbrains would say).
The ExpressionSample project on GitHub shows the fruits of this labor. It is still a work in progress, but has support for the following:
- Numeric and string literals (2, “xyz”)
- Numeric and string variables (N1, ST$)
- Numeric and string arrays with any number of numeric subscripts (AR(2,N))
- Additive numeric expressions (2+5-7)
- Multiplicative numeric expressions (2*4/8)
- Negation numeric expressions (-X)
- Exponential expressions (2^5)
- Parenthesized numeric and string expressions ((X+Y)*Z, (“x”))
- String concatenation (“x”+Y$)
Annoyingly, it has one bug related to unary minus handling which I have not bothered to address. It will treat the expression “-1^2” as “(-1)^2” instead of “-(1^2)”. Overall it’s been a mostly fun ~8 hours of work so far.
For completeness, I still need to implement:
- Relational operators (=, <, >, etc.)
- Logical operators (AND, OR, etc.)
- Functional operators (MID$, SQR, etc.)
All the same, I remain confident that I will be able to continue on this path without another rewrite. We shall see next week!
Pingback: BASIC solutions: expressions – WriteAsync .NET