Asking which do you mean triggered

The more I look at this, the weirder it seems:

[code]Instead of asking someone about:
If the topic understood matches “[any thing]”:
say “match.”;

Every turn: say “We made it to the every turn stage.”;

Test is a room.

Bob is a man in Test.

There is a blue ball in test. There is a green ball in test.

Test me with “ask Bob about ball/blue/ask Bob about blue ball”
[/code]

so, not only does the phrase “(snippet) matches (topic)” trigger the asking which do you mean activity, but it halts the turn sequence no matter when it happened. It seems like I could use this to my advantage (consider this thread where I discuss halting the turn sequence after a parsing-related activity: https://intfiction.org/t/order-of-turn-sequence-rules-and-some-bragging/4312/1)… but, damn that’s weird! Am I missing something? How does it work?

And another thing! I thought that when you respond to a “which do you mean” question, your text is inserted into the command before the ambiguously named object and then the command is reparsed… but if that were true, then the response to the second command should say “match.” It does not. What’s really happening?

My assumption was at least partially correct, as evidenced by this rule:

After reading a command: showme the player's command;

Now the transcript looks like this:

So why doesn’t the instead rule run?

It gets weirder. It seems that the Instead rule resumes running after doctoring the player’s command, but the topic understood is not updated and the return value of the phrase is false, even though there were matches:

I think perhaps I can raise a flag that forces a true reparsing of the modified command. But it might be tricky to hide it in an extension - users of the extension might have to be aware of it.

It looks to me as though the topic understood is updated to the first word of whatever you enter at the disambiguation prompt.

I don’t think that’s it. I think the topic understood remains the same (in this case, 401) but the player’s command changes without being reparsed. If the command were fully reparsed, the topic understood would be 402.

[code]Instead of asking someone about:
showme the topic understood;
If the topic understood matches “[any thing]”:
say “match.”;
otherwise:
say “no match.”;
showme the topic understood;
showme the topic understood as a number;

To decide which number is (snip - a snippet) as a number: (- {snip} -);

Every turn: say “We made it to the every turn stage.”;

After reading a command: showme the player’s command;

Test is a room.

Bob is a man in Test.

There is a blue ball in test. There is a green ball in test.

Test me with “actions/ask Bob about ball/blue/ask bob about blue ball”[/code]

Errrrr, I’m bugged. I thought I could use a “for reading the player’s command” rule to stop the disambiguation prompt, but it seems that the Keyboard() call is a sub-call of the activity, not the other way around. I don’t really want to hack NounDomain, but I’m starting to feel like I need to.

I don’t speak I6 – I was just judging by the output from this:

[code]Instead of asking someone about:
say “Topic understood: [topic understood].”;
If the topic understood matches “[any thing]”:
say “match.”;
say “Topic understood: [topic understood].”;
showme the player’s command.

Every turn: say “We made it to the every turn stage.”;

Test is a room.

Bob is a man in Test.

There is a blue ball in test. There is a green ball in test.

Test me with “ask Bob about ball/blue/ask Bob about ball/blue ball/ask Bob about ball/ball blue/ask Bob about blue ball/ask Bob about ball ball ball ball ball/blue ball”[/code]

As you can see, the topic understood when disambiguation is not required is always what it’s supposed to be, but most of the time when disambiguation is required the topic understood becomes the first word of the disambiguation response. Except for the last case, which I hadn’t tried before, when it just omits the last word of the new player’s command. Hm.

Anyway, this could be the result of the 401/402 business; as I said, I don’t understand that. Trying to figure out Ron’s Original Parser extension is on my list (somewhere after “Write a bunch of philosophy papers”).

A snippet is actually just a range of words. The value is just (start word number) * 100 + (number of words). So 401 means 1 word starting at word 4. The last example you gave would be 405. When the command is changed to “ask bob about blue ball ball ball ball ball,” it points to the word “blue” and the first four "ball"s that follow it. If the command were to be reparsed, the topic understood would become 406.

OK, so it doesn’t adjust the range of words in the command, meaning that the “topic understood” as words becomes whatever words were where the original topic understood was.

You’ve probably already checked this, but when you’re disambiguating on the person (if you have Bob Smith and Bob Jones), it does reset the topic understood; it seems to me like it’s figuring out what the topic understood is, and then checking that it can be understood as a noun. But as I said, I don’t know what’s going on.

Right. That’s because the process normally happens when parsing a command. So if the command is:

“Ask bob jones about ball ball”

It first parses the command into the action of asking Bob Jones about “ball ball”, i.e. action = “asking it about”, noun = Bob Jones, topic understood = 502.

Then my special rule takes over and parses the snippet “ball ball.”

It seems there is an expectation when doing disambiguation that we’re still at the stage of parsing a command. So everything works fine when that’s what we’re doing: the command “ask bob about ball ball” (action = asking it about, noun = [Bob Smith or Bob Jones], topic understood = 402) becomes “ask jones bob about ball ball” after the player specifies “jones,” and then it’s parsed into the correct action.

But when the Instead rule does the disambiguation, we’re in the middle of an action already, so the parser is not invoked as it should be. No new action is created, and thus the topic understood is not altered.

So I’m thinking KeyboardPrimitive might be a good place to hack in, since it’s short and self-contained. How dangerous would it be to change it so that it does nothing when a “suppress reading a command” flag is raised?

Alternatively, I could recreate an entire turn sequence within the Asking Which Do You Mean activity. Would that be more or less insane?

Well, hacking KeyboardPrimitive didn’t work - it just sends the parser into an infinite loop of “I beg your pardon?”

Adding this to the beginning of Keyboard:

[code]Global suppress_keyboard = 0;

[ Keyboard a_buffer a_table nw i w w2 x1 x2;
if (suppress_keyboard) return 0;
[/code]

…results in a lot of errors:

I tried copying the previous command the same way the “oops” behavior does, but I still got the same errors. What’s the problem here?

I gave up and decided to hack NounDomain instead. This seems to work… see if you can break it!

[spoiler][code]An thing can be recently matched.

To loop over the match list with (func - phrase thing -> nothing): (- LoopOverMatchList({func}–>1); -);

Include (-
[ LoopOverMatchList func i;
for (i = 0; i < number_matched; i++) {
indirect(func, match_list–>i);
}
];
-)

Suppression of disambiguation is a truth state that varies.
The suppression of disambiguation variable translates into I6 as “suppress_disambig”.
To suppress disambiguation: now suppression of disambiguation is true.
To allow disambiguation: now suppression of disambiguation is false.

To decide whether marking the match list: decide on whether or not the suppression of disambiguation is true.

Include (-
Global suppress_disambig = 0;

! Other than the creation of this one global variable, the only change to NounDomain is the insertion of a single line

[ NounDomain domain1 domain2 context
first_word i j k l answer_words marker;
#Ifdef DEBUG;
if (parser_trace >= 4) {
print " [NounDomain called at word ", wn, “^”;
print " ";
if (indef_mode) {
print "seeking indefinite object: ";
if (indef_type & OTHER_BIT) print "other ";
if (indef_type & MY_BIT) print "my ";
if (indef_type & THAT_BIT) print "that ";
if (indef_type & PLURAL_BIT) print "plural ";
if (indef_type & LIT_BIT) print "lit ";
if (indef_type & UNLIT_BIT) print "unlit ";
if (indef_owner ~= 0) print “owner:”, (name) indef_owner;
new_line;
print " number wanted: ";
if (indef_wanted == INDEF_ALL_WANTED) print “all”; else print indef_wanted;
new_line;
print " most likely GNAs of names: ", indef_cases, “^”;
}
else print “seeking definite object^”;
}
#Endif; ! DEBUG

match_length = 0; number_matched = 0; match_from = wn;

SearchScope(domain1, domain2, context);

#Ifdef DEBUG;
if (parser_trace >= 4) print "   [ND made ", number_matched, " matches]^";
#Endif; ! DEBUG

wn = match_from+match_length;

! If nothing worked at all, leave with the word marker skipped past the
! first unmatched word...

if (number_matched == 0) { wn++; rfalse; }

! Suppose that there really were some words being parsed (i.e., we did
! not just infer).  If so, and if there was only one match, it must be
! right and we return it...

if (match_from <= num_words) {
    if (number_matched == 1) {
        i=match_list-->0;
        return i;
    }

    ! ...now suppose that there was more typing to come, i.e. suppose that
    ! the user entered something beyond this noun.  If nothing ought to follow,
    ! then there must be a mistake, (unless what does follow is just a full
    ! stop, and or comma)

    if (wn <= num_words) {
        i = NextWord(); wn--;
        if (i ~=  AND1__WD or AND2__WD or AND3__WD or comma_word
               or THEN1__WD or THEN2__WD or THEN3__WD
               or BUT1__WD or BUT2__WD or BUT3__WD) {
            if (lookahead == ENDIT_TOKEN) rfalse;
        }
    }
}

! Now look for a good choice, if there's more than one choice...

number_of_classes = 0;

if (number_matched == 1) i = match_list-->0;
if (number_matched > 1) {
	i = true;
    if (number_matched > 1)
    	for (j=0 : j<number_matched-1 : j++)
			if (Identical(match_list-->j, match_list-->(j+1)) == false)
				i = false;
	if (i) dont_infer = true;
    i = Adjudicate(context);
    if (i == -1) rfalse;
    if (i == 1) rtrue;       !  Adjudicate has made a multiple
                         !  object, and we pass it on
}

! If i is non-zero here, one of two things is happening: either
! (a) an inference has been successfully made that object i is
!     the intended one from the user's specification, or
! (b) the user finished typing some time ago, but we've decided
!     on i because it's the only possible choice.
! In either case we have to keep the pattern up to date,
! note that an inference has been made and return.
! (Except, we don't note which of a pile of identical objects.)

if (i ~= 0) {
	if (dont_infer) return i;
    if (inferfrom == 0) inferfrom=pcount;
    pattern-->pcount = i;
    return i;
}

! This is the only line changed in NounDomain:
if (suppress_disambig) return REPARSE_CODE;

! If we get here, there was no obvious choice of object to make.  If in
! fact we've already gone past the end of the player's typing (which
! means the match list must contain every object in scope, regardless
! of its name), then it's foolish to give an enormous list to choose
! from - instead we go and ask a more suitable question...

if (match_from > num_words) jump Incomplete;

! Now we print up the question, using the equivalence classes as worked
! out by Adjudicate() so as not to repeat ourselves on plural objects...

BeginActivity(ASKING_WHICH_DO_YOU_MEAN_ACT);
if (ForActivity(ASKING_WHICH_DO_YOU_MEAN_ACT)) jump SkipWhichQuestion;
j = 1; marker = 0;
for (i=1 : i<=number_of_classes : i++) {
	while (((match_classes-->marker) ~= i) && ((match_classes-->marker) ~= -i))
		marker++;
	if (match_list-->marker hasnt animate) j = 0;
}
if (j) L__M(##Miscellany, 45); else L__M(##Miscellany, 46);

j = number_of_classes; marker = 0;
for (i=1 : i<=number_of_classes : i++) {
    while (((match_classes-->marker) ~= i) && ((match_classes-->marker) ~= -i)) marker++;
    k = match_list-->marker;

    if (match_classes-->marker > 0) print (the) k; else print (a) k;

    if (i < j-1)  print (string) COMMA__TX;
    if (i == j-1) {
		#Ifdef SERIAL_COMMA;
		if (j ~= 2) print ",";
    	#Endif; ! SERIAL_COMMA
    	print (string) OR__TX;
    }
}
L__M(##Miscellany, 57);

.SkipWhichQuestion; EndActivity(ASKING_WHICH_DO_YOU_MEAN_ACT);

! ...and get an answer:

.WhichOne;
#Ifdef TARGET_ZCODE;
for (i=2 : i<INPUT_BUFFER_LEN : i++) buffer2->i = ’ ';
#Endif; ! TARGET_ZCODE
answer_words=Keyboard(buffer2, parse2);

! Conveniently, parse2-->1 is the first word in both ZCODE and GLULX.
first_word = (parse2-->1);

! Take care of "all", because that does something too clever here to do
! later on:

if (first_word == ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD) {
    if (context == MULTI_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN) {
        l = multiple_object-->0;
        for (i=0 : i<number_matched && l+i<MATCH_LIST_WORDS : i++) {
            k = match_list-->i;
            multiple_object-->(i+1+l) = k;
        }
        multiple_object-->0 = i+l;
        rtrue;
    }
    L__M(##Miscellany, 47);
    jump WhichOne;
}

! Look for a comma, and interpret this as a fresh conversation command
! if so:

for (i=1 : i<=answer_words : i++)
	if (WordFrom(i, parse2) == comma_word) {
        VM_CopyBuffer(buffer, buffer2);
        jump RECONSTRUCT_INPUT;		
	}

! If the first word of the reply can be interpreted as a verb, then
! assume that the player has ignored the question and given a new
! command altogether.
! (This is one time when it's convenient that the directions are
! not themselves verbs - thus, "north" as a reply to "Which, the north
! or south door" is not treated as a fresh command but as an answer.)

#Ifdef LanguageIsVerb;
if (first_word == 0) {
    j = wn; first_word = LanguageIsVerb(buffer2, parse2, 1); wn = j;
}
#Endif; ! LanguageIsVerb
if (first_word ~= 0) {
    j = first_word->#dict_par1;
    if ((0 ~= j&1) && ~~LanguageVerbMayBeName(first_word)) {
        VM_CopyBuffer(buffer, buffer2);
        jump RECONSTRUCT_INPUT;
    }
}

! Now we insert the answer into the original typed command, as
! words additionally describing the same object
! (eg, > take red button
!      Which one, ...
!      > music
! becomes "take music red button".  The parser will thus have three
! words to work from next time, not two.)

#Ifdef TARGET_ZCODE;
k = WordAddress(match_from) - buffer; l=buffer2->1+1;
for (j=buffer + buffer->0 - 1 : j>=buffer+k+l : j-- ) j->0 = 0->(j-l);
for (i=0 : i<l : i++) buffer->(k+i) = buffer2->(2+i);
buffer->(k+l-1) = ' ';
buffer->1 = buffer->1 + l;
if (buffer->1 >= (buffer->0 - 1)) buffer->1 = buffer->0;
#Ifnot; ! TARGET_GLULX
k = WordAddress(match_from) - buffer;
l = (buffer2-->0) + 1;
for (j=buffer+INPUT_BUFFER_LEN-1 : j>=buffer+k+l : j-- ) j->0 = j->(-l);
for (i=0 : i<l : i++) buffer->(k+i) = buffer2->(WORDSIZE+i);
buffer->(k+l-1) = ' ';
buffer-->0 = buffer-->0 + l;
if (buffer-->0 > (INPUT_BUFFER_LEN-WORDSIZE)) buffer-->0 = (INPUT_BUFFER_LEN-WORDSIZE);
#Endif; ! TARGET_

! Having reconstructed the input, we warn the parser accordingly
! and get out.

.RECONSTRUCT_INPUT;

num_words = WordCount();
wn = 1;
#Ifdef LanguageToInformese;
LanguageToInformese();
! Re-tokenise:
VM_Tokenise(buffer,parse);
#Endif; ! LanguageToInformese
num_words = WordCount();
players_command = 100 + WordCount();
actors_location = ScopeCeiling(player);
FollowRulebook(Activity_after_rulebooks-->READING_A_COMMAND_ACT, true);

return REPARSE_CODE;

! Now we come to the question asked when the input has run out
! and can't easily be guessed (eg, the player typed "take" and there
! were plenty of things which might have been meant).

.Incomplete;

if (context == CREATURE_TOKEN) L__M(##Miscellany, 48);
else                           L__M(##Miscellany, 49);

#Ifdef TARGET_ZCODE;
for (i=2 : i<INPUT_BUFFER_LEN : i++) buffer2->i=' ';
#Endif; ! TARGET_ZCODE
answer_words = Keyboard(buffer2, parse2);

first_word=(parse2-->1);
#Ifdef LanguageIsVerb;
if (first_word==0) {
    j = wn; first_word=LanguageIsVerb(buffer2, parse2, 1); wn = j;
}
#Endif; ! LanguageIsVerb

! Once again, if the reply looks like a command, give it to the
! parser to get on with and forget about the question...

if (first_word ~= 0) {
    j = first_word->#dict_par1;
    if (0 ~= j&1) {
        VM_CopyBuffer(buffer, buffer2);
        return REPARSE_CODE;
    }
}

! ...but if we have a genuine answer, then:
!
! (1) we must glue in text suitable for anything that's been inferred.

if (inferfrom ~= 0) {
    for (j=inferfrom : j<pcount : j++) {
        if (pattern-->j == PATTERN_NULL) continue;
        #Ifdef TARGET_ZCODE;
        i = 2+buffer->1; (buffer->1)++; buffer->(i++) = ' ';
        #Ifnot; ! TARGET_GLULX
        i = WORDSIZE + buffer-->0;
        (buffer-->0)++; buffer->(i++) = ' ';
        #Endif; ! TARGET_

        #Ifdef DEBUG;
        if (parser_trace >= 5)
        	print "[Gluing in inference with pattern code ", pattern-->j, "]^";
        #Endif; ! DEBUG

        ! Conveniently, parse2-->1 is the first word in both ZCODE and GLULX.

        parse2-->1 = 0;

        ! An inferred object.  Best we can do is glue in a pronoun.
        ! (This is imperfect, but it's very seldom needed anyway.)

        if (pattern-->j >= 2 && pattern-->j < REPARSE_CODE) {
            PronounNotice(pattern-->j);
            for (k=1 : k<=LanguagePronouns-->0 : k=k+3)
                if (pattern-->j == LanguagePronouns-->(k+2)) {
                    parse2-->1 = LanguagePronouns-->k;
                    #Ifdef DEBUG;
                    if (parser_trace >= 5)
                    	print "[Using pronoun '", (address) parse2-->1, "']^";
                    #Endif; ! DEBUG
                    break;
                }
        }
        else {
            ! An inferred preposition.
            parse2-->1 = VM_NumberToDictionaryAddress(pattern-->j - REPARSE_CODE);
            #Ifdef DEBUG;
            if (parser_trace >= 5)
            	print "[Using preposition '", (address) parse2-->1, "']^";
            #Endif; ! DEBUG
        }

        ! parse2-->1 now holds the dictionary address of the word to glue in.

        if (parse2-->1 ~= 0) {
            k = buffer + i;
            #Ifdef TARGET_ZCODE;
            @output_stream 3 k;
             print (address) parse2-->1;
            @output_stream -3;
            k = k-->0;
            for (l=i : l<i+k : l++) buffer->l = buffer->(l+2);
            i = i + k; buffer->1 = i-2;
            #Ifnot; ! TARGET_GLULX
            k = Glulx_PrintAnyToArray(buffer+i, INPUT_BUFFER_LEN-i, parse2-->1);
            i = i + k; buffer-->0 = i - WORDSIZE;
            #Endif; ! TARGET_
        }
    }
}

! (2) we must glue the newly-typed text onto the end.

#Ifdef TARGET_ZCODE;
i = 2+buffer->1; (buffer->1)++; buffer->(i++) = ' ';
for (j=0 : j<buffer2->1 : i++,j++) {
    buffer->i = buffer2->(j+2);
    (buffer->1)++;
    if (buffer->1 == INPUT_BUFFER_LEN) break;
}
#Ifnot; ! TARGET_GLULX
i = WORDSIZE + buffer-->0;
(buffer-->0)++; buffer->(i++) = ' ';
for (j=0 : j<buffer2-->0 : i++,j++) {
    buffer->i = buffer2->(j+WORDSIZE);
    (buffer-->0)++;
    if (buffer-->0 == INPUT_BUFFER_LEN) break;
}
#Endif; ! TARGET_

! (3) we fill up the buffer with spaces, which is unnecessary, but may
!     help incorrectly-written interpreters to cope.

#Ifdef TARGET_ZCODE;
for (: i<INPUT_BUFFER_LEN : i++) buffer->i = ' ';
#Endif; ! TARGET_ZCODE

return REPARSE_CODE;

]; ! end of NounDomain

-) instead of “Noun Domain” in “Parser.i6t”.

To mark (item - an object) as recently matched (this is marking snippet matches):
Now item is recently matched.

Instead of asking someone about:
showme the topic understood;
mark “[any thing]” that matches the topic understood;
showme the topic understood;
showme the topic understood as a number;
showme the list of recently matched things;

Marking whether objects match is a truth state that varies.

To mark (T - a topic) that matches (snip - a snippet):
Now everything is not recently matched;
say “starting matching.”;
suppress disambiguation;
if snip matches T, loop over the match list with marking snippet matches;
allow disambiguation;
say “done matching.”;

A turn sequence rule when marking the match list (this is the stop the turn sequence when marking whether objects match rule):
rule fails.

The stop the turn sequence when marking whether objects match rule is listed before the every turn stage rule in the turn sequence rules.

To decide which number is (snip - a snippet) as a number: (- {snip} -);

Every turn: say “We made it to the every turn stage.”;

After reading a command: showme the player’s command;

Test is a room.

Bob is a man in Test.

There is a blue ball in test. There is a green ball in test.

Test me with “actions/ask Bob about ball/blue/ask bob about blue ball”[/code][/spoiler]