intfiction.org

The Interactive Fiction Community Forum
It is currently Tue Jun 18, 2013 10:54 pm

All times are UTC - 6 hours [ DST ]




Post new topic Reply to topic  [ 63 posts ]  Go to page Previous  1, 2, 3, 4, 5 ... 7  Next
Author Message
PostPosted: Fri Sep 16, 2011 5:46 am 
Offline

Joined: Mon Jun 09, 2008 8:58 pm
Posts: 680
Location: Seattle
As of version 3 the Book of Scope is complete. The following functions have been translated: ScopeWithin, PlaceInScope, AddToScope, DoScopeAction, TestScope, LoopOverScope, SearchScope, and DoScopeActionAndRecurse. (I will likely rename some of the I7 constructs therein as the project continues, of course.)

I've had my first taste of dealing with I6 arrays, hidden object properties, and properties which can be arrays or routines at will, all at once thanks to DoScopeActionAndRecurse.

In the interests of I7 readability, I exchanged a while loop with a for loop. I had previously exchanged a switch statement with a else-if chain due to an I7 restriction. I don't believe either of these changes should be a problem. Details on them are in the comments at the beginning of the game file. The modernized functions are about four-fifths of the way down in the source, or, use the navigation features to jump to the Book of Scope.

_________________
Blog at Gamasutra :: Programmer's Guide to Inform 7 :: Seattle I-F


Top
 Profile Send private message  
 
PostPosted: Sat Sep 17, 2011 6:01 am 
Offline
User avatar

Joined: Mon Dec 07, 2009 10:39 am
Posts: 38
Location: UK
Ron Newcomb wrote:
Right now I'm working out ways that I7 should access an I6 array, especially if it's sometimes a routine instead, as with add_to_scope.

How did you get around that?


Top
 Profile Send private message  
 
PostPosted: Sat Sep 17, 2011 9:35 am 
Offline

Joined: Fri Jul 16, 2010 2:09 pm
Posts: 1951
Oooh, maybe we could have an "I6 Arrays" extension...


Top
 Profile Send private message  
 
PostPosted: Sat Sep 17, 2011 3:03 pm 
Offline

Joined: Mon Jun 09, 2008 8:58 pm
Posts: 680
Location: Seattle
Sslaxx wrote:
Ron Newcomb wrote:
Right now I'm working out ways that I7 should access an I6 array, especially if it's sometimes a routine instead, as with add_to_scope.

How did you get around that?

For the type-stacking problem I made a to-decide-if phrase that invokes I6's "ofclass Routine" condition. At the I7 level I call them rules rather than routines, of course, but add_to_scope isn't the only place such a condition is useful.

Arrays are more difficult, but there is precedent for how they should work in I7: lists, which is how arrays are used, and table columns, which actually are arrays. So I'll make repeat loops that set the ct_0 and ct_1 table variables to the array name and index. (Since the I6 parser doesn't use I7 Tables they're free for the taking.) With those I can make "the chosen row" same as for tables, and either "the chosen element" and/or "the (name of kind of value K) element" for ct_0-->ct_1.

Exposing them requires I6 inclusions on every single array like I did for the NOUN_TOKEN value up-thread. I did make a "1-based array" KOV for them: To decide which 1-based array is the multiple-object list: (-multiple_object-). I made the type mainly so I don't accidentally pass the wrong kind of thing to the to-phrases that operate on such. And for documentation purposes too, of course, which this project largely is.

@Mikee: I doubt I'll make an array extension, partly because creating a new one involves two I6 inclusions per array, and partly because I believe arrays are planned for I7 proper.

_________________
Blog at Gamasutra :: Programmer's Guide to Inform 7 :: Seattle I-F


Top
 Profile Send private message  
 
PostPosted: Mon Sep 19, 2011 10:53 am 
Offline

Joined: Sat Jan 23, 2010 4:56 pm
Posts: 2127
I gotta say, looking at the I7 bug site, this project is worthwhile for purely for its stress-testing of the I7 code generator.


Top
 Profile Send private message  
 
PostPosted: Mon Sep 19, 2011 6:12 pm 
Offline

Joined: Mon Jun 09, 2008 8:58 pm
Posts: 680
Location: Seattle
Version 4 is up. A dozen more functions were converted: MakeMatch, MatchTextAgainstObject, MultiSub, MultiAdd, MultiFilter, Refers, WordInProperty, CreatureTest, ReviseMulti, BestGuess, SingleBestGuess, and PrintInferredCommand. Plus a few more have partial starts. The only thing these functions have in common is that they're small or are otherwise low-hanging fruit. Only two are worth noting.

WordInProperty is the best example of clarity I hope this project can bring. WordInProperty's former implementation was this:
Code:
    k = obj.∝ l = (obj.#prop)/WORDSIZE-1;
    for (m=0 : m<=l : m++)
        if (wd == k-->m) rtrue;
    rfalse;
But now, it is this:
Code:
   if the word is listed in the names of the obj, yes.

Relatedly is Refers. A minor goal of mine is, when I have to create a new name for a function or whatever, I try to make that name resemble similar stuff from Inform 7. Refers takes two parameters, an object and a "dictionary word". But now there's an I7 type called an "understood word", as they come from Understand..as... statements, and invoking Refers resembles a relation: if the word can refer to the object. I can't use actual relations due to the code they auto-generate, but I can make it possible to think of some things in that way, and further ease translation to pure I7 if this project once completed is taken further.

But most of the time I spent on this version went into thinking about all the different kinds of arrays in there. Some are 1-based with their size in the 0th element (multiple_object), some are 0-based with their size who-knows-where (match_list), one is even 2-based (parse -- but only under the Z-machine, it seems). Some have object elements, some numbers, a few are byte-based ZSCII characters (buffer). At least one is an array of structs (parse), the shape of which depends on Z or Glulx. Sometimes arrays work in concert, like a struct of arrays: see how match_list and match_score share the same index. And then there's array-valued properties, which are just different enough from 1-based arrays to be annoying.

Similar to the ideal of making I6 operations resemble I7's is hiding some of these nitpicky differences. For the I7 author, "1" is always the first element, and size comes from a "size" property. So I spent a great deal of time creating heavily overloaded array accessor phrases that normalize as much of this as possible. And one idea that hopefully bears more fruit than problems is, again, relations.

Type-wise, I've changed arrays from a simple KOV to a relation of index-type to element-type. This is handy since I can use Inform's generic types to make creating accessor functions easier. Given definitions like this:
Code:
To decide which relation of 0-based index to numbers is the match scores list: (-match_scores-).
To decide which relation of 1-based index to objects is the multiple-object list: (-multiple_object-).
To decide which relation of 0-based index to ZSCII letters is the player's input buffer: (-buffer-).
I can create phrases with a parameter of a relation of a 0-based index to a value of kind K and then re-use the K for the element's type.

_________________
Blog at Gamasutra :: Programmer's Guide to Inform 7 :: Seattle I-F


Top
 Profile Send private message  
 
PostPosted: Tue Sep 20, 2011 10:02 am 
Offline

Joined: Fri Jul 16, 2010 2:09 pm
Posts: 1951
This is still so cool!

I don't have anything else to add, just bumping the topic. :D


Top
 Profile Send private message  
 
PostPosted: Mon Sep 26, 2011 4:01 am 
Offline

Joined: Mon Jun 09, 2008 8:58 pm
Posts: 680
Location: Seattle
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.

_________________
Blog at Gamasutra :: Programmer's Guide to Inform 7 :: Seattle I-F


Top
 Profile Send private message  
 
PostPosted: Thu Oct 06, 2011 3:17 am 
Offline

Joined: Mon Jun 09, 2008 8:58 pm
Posts: 680
Location: Seattle
The project's first draft nears completion, with 22 more functions translated in version 6. Highlights this time around include the translation of NounDomain and Adjudicate, neither of which were trivial to do. The GOTOs were removed from TryGivenObject and NounDomain, and though it involved creating a new function to do so, that means that this version has no GOTOs in it currently.

TryNumber was rewritten for efficiency's and readability's sake, namely, using the technique of subtracting a ZSCII character from '0' to translate the ten digits into small numbers.

Some unidentified magic numbers used by PrepositionChain were given names.

UnpackGrammarLine was slightly modified for efficiency. It previously cleared out three arrays before filling them in, but now it fills them in first as far as possible, then clears out only the remaining entries.

And finally but most importantly, some of the array copying, searching, and related loops were replaced by VM-specific opcodes for performance reasons. As well I modified a few loop constructs to use an intermediate global variable to cache the result of array size calculations so it happens once per loop rather than once per iteration. (The original code always did this, but I didn't hit upon a way of mimicing it while preserving readability until now.) The net result of this is that the I7 expression of the parser code should be able to keep up with, or even exceed, the I6 version's performance.

Three more functions remain, including the grand-daddy of them all: the monstrous Parser__parse.

_________________
Blog at Gamasutra :: Programmer's Guide to Inform 7 :: Seattle I-F


Top
 Profile Send private message  
 
PostPosted: Fri Oct 07, 2011 3:54 pm 
Offline

Joined: Mon Dec 07, 2009 5:14 am
Posts: 909
While about 99% of this is way over my head, and I'm still not entirely sure what purpose this will serve, I am always drawn here for the latest progress report. Your efforts are tremendous, and your determination is an inspiration to us all. I salute you.


Top
 Profile Send private message  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 63 posts ]  Go to page Previous  1, 2, 3, 4, 5 ... 7  Next

All times are UTC - 6 hours [ DST ]


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group