Equating "verb x with y" and "verb x and y"?

Howdy,
Apologies if this has been asked before; I couldn’t find a way to search the forum (edit: bah, now there’s a search link; guess I had to register first).

I have a verb “combining it with” that takes two objects as input and returns one. “combine x with y” works fine, but “combine x and y” runs combine twice, once with just x and once with just y, resulting in two “You must supply a second noun.” messages. Is it possible to intercept “combine x and y” before that check happens? Using ‘Understand “combine [something] and [something]” as combining it with’ doesn’t seem to have any effect.

Thanks.

This is the second time in the last few days that someone has mentioned that the “and” in grammar lines is superseded by the parser’s assumption that “and” refers to multiple nouns. Shouldn’t this be considered a bug? If “and” is part of the grammar, surely that should be privileged over the invocation of the multiple object list…?

If the current behavior is in fact the intended behavior, then the manual should definitely mention that “and” should be avoided in command grammar. It doesn’t seem to do that.

–Erik

If it’s the intended behavior, it’s a design defect that should be remedied. (In effect, that makes it a bug.) The grammar line should always be given precedence during parsing. We could probably think of other good examples where a native English speaker (the player) might type a line that the author thought of and wrote grammar for, but that the parser failed to handle.

Here’s one: Commas and periods. It ought to be allowed to use commas and periods in grammar lines. The parser should consult the grammar line first, to determine whether it contains a comma or period, before going on to split the input up into separate commands.

Is that an extreme case? No, because of the natural-sounding input ‘hello, bob’, which at present requires a workaround.

I agree with Erik; it’d be nice if it were possible to override invocation of the multiple-object list (which is one of those hard-code things that in general makes me feel ooky).

For your original question, shammack, the deal as I understand it is that when the player enters a command of the form “Do something to this and that,” Inform effectively breaks it up into two commands, “Do something to this” and “Do something to that,” and tries to execute them in turn. This is the same behavior as “Take all”; when that is entered, Inform figures out what the list of takeable things is and tries to take them all, one by one. It does this by using the “multiple object list,” which is the list of the objects that it’s going to try taking (or whatever the action is).

In this case, we can work around your issue by invoking the multiple object list. We have to detect it when the player is trying to combine more than one thing using “and.” Then, for the first one, we look at the multiple object list and see if it contains exactly two things. If it does, we try combining them. If not, we issue an error message. But the tricky thing is that, if the player had entered “combine x and y,” the game will have done all this while processing the command “combine x,” and it still needs to process “combine y.” So we have to set a flag that tells us whether we’ve already tried this multi-combine once this turn; if so, we ignore the rest of the multi-combines.

So I’d come up with something like this (not tested):

[code]Combining it with is an action applying to two things. Understand “combine [something] with [something]” as combining it with. [and then you have whatever code you want for combining]

Multi-combining-complete is a truth-state that varies. Multi-combining-complete is usually false.
After reading a command: Now multi-combining-complete is false. [This sets the flag to false at the beginning of every turn.]

Multi-combining is an action applying to one thing. Understand “combine [things]” as multi-combining. [The “[things]” token allows the game to understand “combine this and that” as multi-combining.]

Check multi-combining when multi-combining-complete is true: stop the action. [This means after we’ve already tried the multi-combining action on the first thing, we don’t try it again.]

Carry out multi-combining:
let L be the multiple object list;
if the number of objects in L is 0 or the number of objects in L is 1: [I think that if the player types “combine fruit” the multiple object list has 0 entries, but if the player types “combine all on table” and there’s only one thing on the table then the multiple object list has 1 entry]
say “You must specify two things to combine.”;
now multi-combining-complete is true;
stop the action;
otherwise if the number of objects in L is greater than 2:
say “You can only combine two things at once.”;
now multi-combining-complete is true;
stop the action;
otherwise: [this should happen only when there are exactly two things to be combined]
now multi-combining-complete is true;
try combining entry 1 of L with entry 2 of L.

The silently announce items from multiple object lists rule is listed instead of the announce items from multiple object lists rule in the action-processing rules. [The announce items… rule is the one that prints things like “Ball: taken. Bat: taken” when you enter “take ball and bat.” We don’t want that to happen when you’re multi-combining, so we replace it with something that doesn’t do that when you’re multi-combining.]

This is the silently announce items from multiple object lists rule:
unless multi-combining:
if the current item from the multiple object list is not nothing, say “[current item from the multiple object list]: [run paragraph on]”.[/code]

Usual warnings: I am not super expert, I haven’t tested this at all, and I typed some of it straight into the browser window, so there may be goofs (and watch the tab stops). Hopefully it’s something to start with.

The previous thread with some related stuff is here. (I don’t think that thread actually involves a bug, since I was using the multiple object list to hack up a command with three nouns, which Inform usually doesn’t allow.) It’s basically based on the example The Left Hand of Autumn from the manual.

(BTW, there’s a “search” button on the upper right-hand corner of the window, next to the “FAQ” button; but in this case I don’t think it would’ve been easy for you to find what you wanted with a search, anyway.)

Ah, brilliant; that looks like what I need. Thanks! I agree that this seems like a bug/bad design decision.

Another approach – simpler, but untidy – is to go through the command after reading and just change each occurence of “and” to “with”. (If the command begins with “combine” or a synonym.)

You have to mind your synonyms, and things would to hell if you had a “ball and chain” object (not that I’ve ever tried doing that anyway). But it could be a good-enough solution.

I tried a more general way, applying three small changes to the parser (in “Parser Letter A”, “Parser Letter G”, and “Parse Token Letter E” in the Parser.i6t template file. Because the replaced code sections are too long to post here, I put them into an (experimental) extension together with a very small example (changes to the original code are marked #####).

dl.dropbox.com/u/2691966/Connectives.i7x

I have no idea whether this is really usable for larger projects, but it seems to work, for starters.

– Christian

Edit: Okay, I boiled it down to only a few additional lines in “Parse Token Letter E”. There are no additional global variables defined anymore. Here’s the remaining code:

[code][Parse Token Letter E]
Include (-
! Object(s) specified now: is that the end of the list, or have we reached
! “and”, “but” and so on? If so, create a multiple-object list if we
! haven’t already (and are allowed to).

.NextInList;

o = NextWord();

! ##### Check if the next token is a connective; if so, ignore the standard multiple object
!       treatment.

j = true;
AnalyseToken(line_token-->pcount);
if (found_ttype == PREPOSITION_TT
    && o == AND1__WD or AND2__WD or AND3__WD or BUT1__WD or BUT2__WD or BUT3__WD
    && PrepositionChain(o, pcount) ~= -1)
        j = false;
        
if (j && o == AND1__WD or AND2__WD or AND3__WD or BUT1__WD or BUT2__WD or BUT3__WD or comma_word) {

    #Ifdef DEBUG;
    if (parser_trace >= 3) print "  [Read connective '", (address) o, "']^";
    #Endif; ! DEBUG

    if (~~token_allows_multiple) {
        if (multiflag) jump PassToken; ! give UPTO_PE error
        etype=MULTI_PE;
        jump FailToken;
    }

    if (o == BUT1__WD or BUT2__WD or BUT3__WD) and_parity = 1-and_parity;

    if (~~many_flag) {
        multiple_object-->0 = 1;
        multiple_object-->1 = single_object;
        many_flag = true;
        #Ifdef DEBUG;
        if (parser_trace >= 3) print "  [Making new list from ", (the) single_object, "]^";
        #Endif; ! DEBUG
    }
    dont_infer = true; inferfrom=0;           ! Don't print (inferences)
    jump ObjectList;                          ! And back around
}

wn--;   ! Word marker back to first not-understood word

-) instead of “Parse Token Letter E” in “Parser.i6t”.[/code]

Example:

[code]“Mixit”

The Kitchen is a room.

Some milk is a thing, here.

Some honey is a thing, here.

Some pesto is a thing, here. Understand “walnut and cilantro pesto” and “pesto” as the pesto.

Mixing is an action applying to two things. Report mixing: say “You mix [the noun] and [the second noun].”

Understand “mix [something] with/and [something]” as mixing.

Test me with “mix milk and honey / mix milk with honey / take milk and honey / mix walnut and cilantro pesto and honey”.[/code]

Result:

Fantastic. ChristianB’s extension seems to be working perfectly, with no messy side effects so far. Thanks again, everyone.

Good to hear. But I found at least one restriction concerning grouped tokens:

Understand "mix [something] and/with [something]" as mixing.

This works fine, whereas…

Understand "mix [something] with/and [something]" as mixing.

… throws the standard message:

Does anyone know how to address the post-slash part of a grouped grammar line token in I6?

Edit: Problem solved (I hope). The I6 routine PrepositionChain() came in handy. (I changed the code in my previous post if you like to try it out).

dl.dropbox.com/u/2691966/Connectives.i7x

So much of I7 is well-developed now, and ready for a whole new generation of IF. But the parser seems to be hanging on to a lot of I6 conventions that don’t seem to make much sense any more. I’ve said it before - I think the parser needs a total rewrite. I know it’s a big job and I don’t expect anyone to volunteer, but it would be great if it happened. I may do it myself someday, but not too soon…

At this point maybe the best thing would be to catalog all the problems with the current parser. Some things are in uservoice, but maybe it needs its very own page. Does anyone have a place for that?

What I see so far:

  1. Punctuation, especially commas, should be allowed in grammar lines.
  2. “and” should be allowed as a grammar token.
  3. Perhaps handling of multiple objects should be completely reconsidered.
  4. More parser features (like when disambiguation occurs, what noun matches are ruled out before they even reach the disambiguation stage, multiple object lists and the name of an NPC who is being given a command) should be handled at the I7 level, so new grammar lines and rules can be applied to them.
    4a. Disambiguation might be an activity. The match list should be made available and rules should be able to add and remove items from it. It should be possible to generate a match list from a grammar line such as “if the player’s command includes ‘[any known thing]’,” and it should be possible to optionally invoke disambiguation from such a grammar line.
    4b. (a personal pet peeve) It should be possible to manipulate the player’s command using the underlying buffer system, not indexed text. This might provide access to the text of “again” and “oops” commands as well.
    4c. The major functions of NounDomain and Adjudicate should be implemented as rulebooks or activities.
    4d. Some means of expressing a complete command, including not only action, noun and second noun, but also the person asked, should be expressed in a sensible I7 data set. This may be difficult because there’s a possibility for recursion as in Rematch. But once it was handled, I think a lot of things (such as parsing commas) might be easier.
  5. The first word of a command (after the comma) should not be treated specially, creating a need for the special “understand the command…” syntax of I7. Maybe this is too much to ask, but I’m throwing it in there.

You might be answering a question I was planning on asking here but was trying to investigate before I did so. How do I hook into the parser in order to change things like what you mentioned here? I’m assuming (hoping) the parser was designed in a modular fashion so that you essentially have “hooks” into so that you can modify it selectively. Your comment makes me think this is where I probably have to learn to jump into the Inform 6 logic. However, this doesn’t seem like a job for “I6 inclusions” but maybe I’m wrong.

Parts of it are modular, but most of those parts are already exposed to I7 as “Understand” lines. The bulk of the parser is one huge long gigantic routine, so big it has to be divided up into “letters” A through K. There are a few important functions outside that, such as NounDomain and Adjudicate, but they’re pretty monstrous themselves.

Like I said, the parser is my least favorite part of I7.

As for the question of “how,” have a look at Parser.i6t. You can find it in Appendix B here:

inform7.com/sources/src/i6templa … index.html

This section of the manual explains how to replace sections of the template layer:

inform7.com/learn/man/doc442.html

Thanks for the links. I hadn’t come across those documents yet. I will say this: Inform 7 covers up some very ugly internals. It does quite a good job at it, too. The problem seems to be when those internals bubble up to the top, which I suppose the first manifestation of is the “I6 inclusions” and the second manifestation is a desire to interact at various points in the parsing. I do agree that an overhaul of the parsing implementation would probably have been warranted even before Inform 7 was released or at least certainly before it got this far. (I say that because the implementation to rewire the parser may lead to a lot of language design decisions in terms of how constructs are expressed. That’s the fun part of natural language overlays.)

Following up on this, I have encountered a sort of related problem: I have to write separate rules for “instead of combining x with y” and “instead of combining y with x”. I want the order of the nouns to be completely irrelevant.

The way I was thinking of dealing with this was to write all my “instead of combining…” rules with the nouns in a standard order (say, alphabetical), and then add a rule to sort the nouns into that order before attempting to combine them. But I’m not sure where in the process that rule needs to go for it to happen before the nouns get passed to the “combining it with” rules, but after checking to make sure that both nouns are valid targets for combining.

I hope that explanation makes sense. Can anyone point me in the right direction, or is there a better way of handling this?

Well, it could be either after all the checking-that-both-ingredients-are-valid, or before all of that. You can arrange either setup using instead rules, if you’re careful.

However, this is easier if you break it down into multiple phases. Instead rules block out the whole action. Check/carry-out lets you write two phases of rules, but in this case I’d add more by creating a “virtual” action (an action with no grammar, so it can only be invoked by code).

I would set up something like this:

“instead of combining x with y”: redirect non-chemical cases to other actions (e.g., the player might type “mix soup with stick”, which should be sent off to the soup-stirring action rather than treated as a reaction)
“check combining x with y”: eliminate completely unsuitable objects (things that are not reagents)
“carry out combining x with y”: try reacting x with y, or try reacting y with x, depending on the canonical order

“check reacting x with y”: eliminate specific combinations that the protagonist refuses to try
“carry out reacting x with y”: stinks and bangs occur here

Sometimes this phrase comes in handy:

Definition: An object is involved if it is the noun or it is the second noun.

Thanks. I didn’t realize you could “nest” actions like that. That should come in handy (along with that definition tip).

So am I right in thinking that:

  • a “before” rule will execute before the check phase
  • an “instead” rule will execute after the check phase succeeds, but before the carry out phase (and will prevent the carry out phase from executing unless “continue the action” is used)
  • an “after” rule will execute after the carry out phase succeeds, but before the report phase?

If so, is it possible to have something execute only after successful completion of the report phase?

Sorry for so many questions. I have a really hard time finding this sort of specific info in the documentation.

The rulebooks execute in this order:

  1. before
  2. instead
  3. check
  4. carry out
  5. after
  6. report

Instead rules and after rules both stop the action, unless you tack on a “continue the action” to them. (Instead rules stop it with the result ”failure” and after rules stop it with the result “success”.)

I often write a “last report” rule if I want to say something after the normal reporting messages. Depending on the circumstances, an Every Turn rule may sometimes be more appropriate.