"Understand "X" as [object]" in I6 - thoughts?

I’ve been thinking about trying to work on an I6 extension that implements phrase synonyms; the reason being I’m tired of crafting parse_name routines for all the different ways a player might refer to an object with a slightly complicated name :slight_smile: Inform 7 does that really nicely, with ‘understand “the artist formerly known as Prince” as Prince’ and stuff like that.

The only I6 extension that kind of does this is “pname” by Neil Cerutti - but it works with individual names, and you have to put them in a certain order and put some properties on them (like whether they are optional), which still sounds complicated. (And you have to replace a bunch of routines, which won’t do since his version was written for 6/10.)

What I’m thinking is the following: there could be an attribute ‘phrase_name’ with a list of strings, and if you want those complicated things to be recognized, say it in the parse_name; something like

Object myobject "can of soda"
  with parse_name [; return ComplicatedParse(self); ],
  phrase_name "can of soda", "can of pop";

(obviously this is a toy example that could be easily dealt with by parse_name)
And the ComplicatedParse would:

  • Look at the words in the name property and compute what would be computed without parse_name.
  • For every string in phrase_name, look in the buffer array and try to find that string; if it is there, your score is the number of words (counted as the number of spaces+1) in that string.
  • Return the best score you have.

Would that be the right approach? Any thoughts? In particular:

  • How does I7 do it, i.e. what kind of I6 code is generated? Does I7 do it by generating I6 code for the right parse_name routine for the object, or is it more obscure?
  • Does it sound like it’d slow down the parsing too much? (Is that kind of thing why an I7 game is slower than an I6 game?)
  • If I do it like that, the words won’t be added to the dictionary, right? Will it create any problems, especially if extensions such as “mistype” are used? Should I try to add every single word in a phrase_name string in the dictionary, and how would I go about doing that?

Thanks a lot for your help and thoughts in advance :smiley:

You can look at the I6 code easily enough; it’s in project.inform/Build/auto.inf after you do a compile.

The can is a thing.
Understand "can of soda" as the can.
Understand "can of pop" as the can.
    wn = original_wn;
    try_from_wn = wn; f = false; n = 0;
    while (true) {
        ! On pass 1 only, advance wn past name property words
        ! (but do not do this for ##TheSame, when wn is undefined)
        if ((parser_action ~= ##TheSame) && (pass == 1)) {
            while (WordInProperty(NextWordStopped(), self, name)) f = true;
            wn--; try_from_wn = wn;
        }
        if (pass == 1 or 2) {
        }
        if ((parser_action ~= ##TheSame) && (pass == 1)) {
            while (WordInProperty(NextWordStopped(), self, name)) f = true;
            wn--; try_from_wn = wn;
        }
        if (NextWordStopped() ~= 'can') jump Fail_1;
        if (NextWordStopped() ~= 'of') jump Fail_1;
        if (NextWordStopped() ~= 'soda') jump Fail_1;
        try_from_wn = wn; f = true; continue;
        .Fail_1; wn = try_from_wn;
        if (NextWordStopped() ~= 'can') jump Fail_2;
        if (NextWordStopped() ~= 'of') jump Fail_2;
        if (NextWordStopped() ~= 'pop') jump Fail_2;
        try_from_wn = wn; f = true; continue;
        .Fail_2; wn = try_from_wn;
        break;
    } ! End of endless loop
    while (WordInProperty(NextWordStopped(), self, name)) n++;
    if ((f) || (n>0)) n = n + try_from_wn - original_wn;

This isn’t particularly readable but it’s not complicated either. At every word, it sets a bookmark (try_from_wn) and then checks a sequence of words. On any failure, it resets to the bookmark and checks the next sequence. If all the sequences fail, it calls WordInProperty() which checks a single word against the object’s name property.

(The pass variable has to do with the special ##TheSame and ##PluralFound checks. See the DM4, or ignore it and let’s just talk about pass 3.)

This code is about as efficient as it could be, given the way the parser is set up. I don’t believe it’s a significant source of slowdown, even in large I7 games.

(But it is a significant source of file size bloat. In Hadean Lands there are about 850 of these generated parser_name routines, mostly boilerplate code; they occupy about 310Kb of ROM, or 15% of the game file. I’d happily write my own more-efficient boilerplate to cut that down. Inform doesn’t provide a good way to do this; you have to edit the I6 code and recompile. I haven’t bothered.)

As for replicating this behavior in I6: I don’t have code for this on hand, but it’s probably doable. There may well be an extension lying around for it. (I haven’t looked at pname, maybe this is exactly what it does.)

I’d set up a property that contains dict words, rather than strings. That way everything is in the dictionary.

Object myobject "can of soda"
  with parse_name ComplicatedParse,
  phrase_name 'can' 'of' 'soda' '/' 'can' 'of' 'pop';

Thanks a lot, Zarf!

I see, the code is indeed not that complicated. I can see how it’d take a lot of space though!

And I looked at pname, and it actually does the same thing as your solution, although with the ability to put logic in it (’.x’ for optional, ‘.or’ for either of two nouns). Then I’m wondering why it’s so complicated and why it needed to replace so many routines in the library; I think it is to make the parser look at the pname attribute without having to write e.g. parse_name ComplicatedParse for every object. In any case, it won’t work with a library other than 6.10.

I’m going to (re)implement this very convenient solution in an extension that wouldn’t need to replace routines in the Library. (I’m also thinking about adding a ‘.name’ to mean ‘any word in the name property’, for shorthand.) I’ll post the result here.

Thanks again!

Behold, the new I6 extension PhraseNames !

The idea is the same as in “pname.h” by Neil Cerutti, except it works with any version of the library and it’s super easy to include, and the tokens are slightly different : a phrase ends with ‘./’, optional words correspond to the tokens ‘.opt’ or ‘.opt(’ ‘group’ ‘of’ ‘words’ ‘.)’, there’s ‘.name’ to match any word from the name property, and ‘.or’ for an alternative to the previous word.

Example of use:

    Object woodenchair "wooden chair"
      with parse_name ComplicatedParsing,     ! mandatory
           name 'chair' 'seat',
           phrase_name '.name' '.opt(' 'made' 'of' 'wood' '.or' 'oak' '.)' './'
                       'wooden' '.or' 'oak' '.name';

recognizes “seat made of oak”, “chair made of oak”, “chair made of wood”, “seat made of wood”, “wooden chair”, “wooden seat”, “oak seat”, “oak chair”, “chair” and “seat”.

I might try in a future version to define ‘.or(’ (ie make it so that group of words can be alternatives to single words), and refactor my code so that one could also define phrase_name2, phrase_name3… to get past the “32 entries” restriction on property arrays.

Any comments or bug reports are welcome!