Version 5 is up, with 25 more functions translated, leaving 25 more yet to go.
One of the more useful translations is the Keyboard() function, which does the bulk of what we think of as the Reading A Command activity. It handles OOPS and UNDO processing, plus the blank line parser error. I took extra time to place comments into this function highlighting where and what the check, carry out, and report rules would be for each of those commands if anyone wants to promote them to full out-of-world actions, or just get a sense of how they work. ScoreMatchL, part of the does-the-player-mean subsystem, was done similarly in case someone wants to break it out into a rulebook or three. I’m unsure if the first two “rulebooks” are useful, or even necessary, but the third certainly looks promising.
It was during the translation of Keyboard() that I began to think of placing “landmarks” in the code. There’s a lot of code in here with little to distinguish one block of black ink from another block of black ink. Just scrolling around, it’s hard to tell where you are in the source. So I’ve decided that all of the calls to the library message system will also have, in blue text, the message that they’ll print. That text isn’t actually used, it’s just a visual cue and yet another way for one to see what the code is doing.
Another thing I’m trying to do is to remove the GOTO statements (called “jump” in Inform 6). Keyboard() used break and next statements to mimic them, but I removed even those via else-if chains. But ChooseObjects, on the other hand, took some doing. While it was conceptually simple – make the final third of the function the first third – code is fussy. And then it used a while-loop in a peculiar way: by combining a following if-statement into itself, using next-commands to do the looping, and skipping the following code should the loop fail prematurely. It was a clever trick, but doesn’t jive well with making the code clear and readable. The solution I found I’m still unsure of. It’s hard to find test cases that hit it particularly.
However, even if ChooseObjects was a great success, TryGivenObject broke me. TryGivenObject has only two labels, but with four different jumps that aim at them. The code is convoluted and difficult to follow even in the translated version. I certainly intend to come back to it after the rest of the functions are translated, as I’ll have a better understanding of the parser code (along with shorter code, I presume), but until then, Inform 7 now has GOTOs under the trumped-up moniker “control labels”. May God have mercy on my soul.
This version also introduces bit-twiddling to the project. This I’ve had experience with before, but I started afresh rather than importing an earlier solution. I’ve used the verbs include and exclude to do all the bit-twiddling. As imperatives they work much like Inform’s existing increment and decrement phrases. Because of a bug I hit a year or two ago, most bit tests here take the form “if (x & y) == y” rather than the “if (x & y) ~= 0” which is standard. This is so I can test “if x includes y1 + y2” safely. The values for Y are still defined as I6 constants and exposed through KOVs with to-decide phrases. Include/exclude work on any KOV so long as X and Y are the same KOV.
And finally but certainly (unfortunately) not least, my handling of arrays continues to evolve. It turns out that relations, at least the ones that are passed through phrases, are treated more like indexed text and less like objects: they’re passed by copy. Even though I’m using only them as a type, Inform wants to insert code to make copies. And while I’ve been avoiding that via the {-pointer-to:xxx} directive, it doesn’t work when I7 phrases call other I7 phrases. So arrays as relations, while making sense conceptually as a special case of associative arrays, were a blind alley for this project. Instead, arrays are now typed as rulebooks. This makes no sense conceptually, but works mechanically, so shall it be.
The second difference in how arrays are handled are the arrays of structs, such as the parse array and a couple of pronoun arrays. I had originally created the fields as a kind-of-value in their own right, but that disallowed the syntax of “the 1st word of the parsed input” since Inform’s phrases cannot have two parameters right next to each other. I found “the word # 1 of the parsed input” to be annoying, so each field now has a phrase dedicated to it. Inelegant, perhaps, but the client code is more readable as a result, and that’s what matters.
Relatedly, for one phrase, “if x is listed as a (field) in y”, I made a handful of field types specifically for that phrase. This is because the field, of type struct, is now a Unit rather than an empty KOV just for automatic phrase-selection purposes, and that allows me to pass two numbers for the price of one. (It’s just like how hours and minutes are both passed together as a single “time” unit.) The second number is the number of fields (“columns”) that that array has, a number that’s needed in the for-loop that that phrase masks. The effect is that I don’t need to clutter the actual parser code with extra appelations like “with element size 3” for the ct_1 += 3, because it’s hidden in the unit.