"Allows finer control over the disambiguation process used by Inform to decide what the player was referring to. Less guesswork, more questions asking for more input. Also removes the multiple-object-rejection in favour of asking for more information."
The message-id-requested is a number that varies.
This is the disambiguation printing rule:
if the message-id-requested is:
-- 1: say "Please be more specific - " (A);
-- 2: say "Whom [regarding the player][do] [we] want[actor-or-player] to [print-or-construct]: " (B);
-- 3: say "What [regarding the player][do] [we] want[actor-or-player] to [print-or-construct]: " (C);
-- 4: say "Whom [regarding the player][do] [we] want[actor-or-player] to [print-or-construct]?[paragraph break]" (D);
-- 5: say "What [regarding the player][do] [we] want[actor-or-player] to [print-or-construct]?[paragraph break]" (E);
-- 6: say "whom [regarding the player][do] [we] want[actor-or-player] to [print-or-construct]: " (F);
-- 7: say "what [regarding the player][do] [we] want[actor-or-player] to [print-or-construct]: " (G);
-- 8: say "whom [regarding the player][do] [we] want[actor-or-player] to [print-or-construct]?[paragraph break]" (H);
-- 9: say "what [regarding the player][do] [we] want[actor-or-player] to [print-or-construct]?[paragraph break]" (I);
-- 10: say "?[line break]" (J);
-- 11: say "?[paragraph break]" (K);
-- 12: say "[We] [can] only have one item here. Which exactly?" (L);
-- 20: say "take things from" (M);
-- 21: say "put things in" (N);
-- 22: say "put things on" (O);
To say actor-or-player: (-ActorOrPlayer(); -).
To say print-or-construct: (- PrintOrConstruct(); -).
Include (-
[ ParserMessage n m;
! print "--- ", n, " ----^";
! we swap messages if we're in indef mode
if (indef_wanted > 0)
{
m = 0;
switch(n)
{
2: m = 6;
3: m = 7;
4: m = 8;
5: m = 9;
}
if (m > 0) ! m had better never equal any of the values listed above!!
{
ParserMessage(1);
ParserMessage(m);
rtrue;
}
}
(+message-id-requested+) = n;
(+disambiguation printing rule+)();
];
[ ActorOrPlayer i;
i = 1;
if (actor~=player) print " ", (the) actor;
#ifdef FIRST_PERSON;
if (actor == player) print " me";
#endif;
];
[ PrintOrConstruct ;
if (~~look_ahead) PrintCommand();
else if (action_to_be == ##Remove) ParserMessage(20);
else if (action_to_be == ##Insert) ParserMessage(21);
else if (action_to_be == ##PutOn) ParserMessage(22);
! to get here, the user must have created a new action with the Multi_held or multi_inside tokens.
! We'll have to let the parser do the best it can
! if we get strange messages for new actions, this might be the place to add something in!!
else PrintCommand();
];
-)
[ ******************************************************************************************************** ]
Volume - Backend Stuff
Chapter - The Match list
[ The match list stores the objects that met the players input. This is what we intend to disambiguate between. ]
Section - Building the match list as an I7 list
To decide which list of objects is the match list:
let L be a list of objects;
repeat with N running from 1 to 100:
let i be item N from the match list;
if i is no-object:
break;
add i to L;
decide on L.
To decide which thing is item (N - a number) from the match list:
(- MatchListEntry({N}) -).
Section - Guesswork
[ Sometimes the parser is informed: other times it's guessing blind. Compare
> TAKE KEY
Which key do you mean..?
and
>TAKE
This extension allows us to separate the two cases by asking if we're "guessing".
This is critical on the lower levels to how the parsing is carried out: it affects whether the parser should be allowed to offer opinions on what's going on.
We're guessing if a nameless object which is always in scope appears in the possible noun list.
]
No-object is a privately-named thing. After deciding the scope of the player: place no-object in scope.
Should the game suggest doing something with no-object: never.
Rule for deciding whether all includes no-object: it does not.
To decide if guessing:
(-
(GuessingI6())
-)
section - i6 routines for accessing the match list
[
The match list contains the objects the parser is currently considering for the noun it's thinking about
We use the following routines for rules that say, "when comparing the x and the y" or "when also considering the k"
]
Include (-
[ MatchListGuessing;
return IncludedInMatchList(0);
];
[ IncludedInMatchList
obj excl_flag
i
;
if (obj == 0) obj = (+no-object+);
for (i = 0: i< number_matched : i++)
{ !print " ", (the) match_list-->i;
if (match_list-->i == obj)
if (excl_flag == 0 || (excl_flag == 1 && number_matched == 2)) rtrue;
}
rfalse;
];
[MatchListEntry N;
if ( N > number_matched) return (+no-object+);
return match_list-->(N-1);
];
-).
Book - Parser replacements
Chapter - Noun Domain
[
Does the heavy lifting:
* produces a match list for the current input
* sets the guessing flag to true or flag
* runs the adjudicate routine on the match list and input
* decides whether to fail the line, pass it or ask for additional input
* if additional input is required, it gathers it and then stitches it back into the original line of input
* this is done twice - once for stitching text onto the end of the line, once for dropping it in the middle
Extensively rewritten!
Lots of replicated code so this isn't very tidy. I may try to break it up into routines later.
]
Include (-
Global look_ahead = 0;
Global guessed_first_noun = false;
-) after "Grammar Line Variables" in "Parser.i6t".
Include (-
Array printed_text -> 123;
Global guessing;
[GuessingI6; return guessing; ];
! ----------------------------------------------------------------------------
! NounDomain does the most substantial part of parsing an object name.
!
! It is given two "domains" - usually a location and then the actor who is
! looking - and a context (i.e. token type), and returns:
!
! 0 if no match at all could be made,
! 1 if a multiple object was made,
! k if object k was the one decided upon,
! REPARSE_CODE if it asked a question of the player and consequently rewrote
! the player's input, so that the whole parser should start again
! on the rewritten input.
!
! In the case when it returns 1<k<REPARSE_CODE, it also sets the variable
! length_of_noun to the number of words in the input text matched to the
! noun.
! In the case k=1, the multiple objects are added to multiple_object by
! hand (not by MultiAdd, because we want to allow duplicates).
! ----------------------------------------------------------------------------
[ MultiContext tkn;
! return true if the grammar token is one that allows for multiple objects
! ie. grammar line expects multiple objects
if (tkn == MULTI_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN)
rtrue;
rfalse;
];
[ WorthGuessingNoun act;
! Version 7 - I think this routine is no longer used, having been replaced by the I7 rules
if (act ==
! list of actions for which guessing the only available non-scenery-type noun is worth doing
##Take or ##Drop or ##PutOn or ##Insert or ##Remove
) rtrue;
rfalse;
];
[ NounDomain domain1 domain2 context first_word i j k l
answer_words marker flag;
#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 == 100) print "all"; else print indef_wanted;
new_line;
print " most likely GNAs of names: ", indef_cases, "^";
}
else print "seeking definite object^";
}
#endif;
! initialise variables
match_length=0;
number_matched=0;
match_from=wn;
! build the match list
SearchScope(domain1, domain2, context);
#ifdef DEBUG;
if (parser_trace>=4) print " [ND made ", number_matched, " matches]^";
#endif;
! use the match-list to determine if we were guessing or not
! seems backwards to use the results of parsing to decide if there's no text, but
! that's the way it's done.
guessing = MatchListGuessing();
#ifdef DEBUG;
if (parser_trace>=4)
{
if (guessing)
{
print "[ND guessing]^";
}
else
{ print "[ND informed]^";
for (i = 0: i< number_matched: i++)
if (match_list-->i~=0) print "^", (the) match_list-->i, "..?^";
}
}
#endif;
wn=match_from+match_length;
! If nothing worked at all, leave with the word marker skipped past the
! first unmatched word, and ditch this line
if (number_matched==0) { wn++; rfalse; }
! Suppose that there really were some words being parsed (i.e., we did
! not just guess). 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)
!!! this, note, would be a great place to check PUT <x> DOWN and catch that problem!!
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;
}
! DC Improvement
! We add the ability for the grammar line to check, here and now, if the next word in the input
! is the preposition the game is expecting for this line. This lets us fail the grammar line quickly
! if we're matching the wrong line entirely. The standard parser does do this, and can lead to Inform
! making guesses and disambiguating in contexts when it's no good to even try.
#ifdef COBJ_DEBUG;
print "Next token lookahead: ", line_ttype-->pcount , "!^";
#endif;
if (line_ttype-->pcount == PREPOSITION_TT)
{
if (~~PrepositionChain(i, pcount) ~= -1)
{
! print "(We've failed to match our preposition. Woohoo! Let's move on)";
rfalse;
}
}
! otherwise, if the next token is another noun, we run the parser to gather results for the second noun, so we
! can do a ChooseObjects pass - using the I7 rules - using full lines, rather than partially matched ones.
if (line_ttype-->pcount == ELEMENTARY_TT or ATTR_FILTER_TT or ROUTINE_FILTER_TT)
{
SafeSkipDescriptors();
! save the current match state, since we're experimenting here
@push token_filter; @push wn; @push match_length; @push match_from; @push number_matched;
! now get all the matches for the second noun
match_length = 0; number_matched = 0; match_from = wn;
if (line_ttype-->pcount == ELEMENTARY_TT)
{
! cheapest way of filling the match list
SearchScope(actor, actors_location, line_tdata-->pcount);
}
else
{
! more complex tokens need a bit more work
if (line_ttype-->pcount == ATTR_FILTER_TT)
{ token_filter = 1 + line_tdata-->pcount;
}
else
{ token_filter = line_tdata-->pcount;
}
SearchScope(actor, actors_location, NOUN_TOKEN);
}
#ifdef DEBUG;
if (parser_trace >= 4)
print number_matched, " possible continuation nouns";
#endif;
i = number_matched;
! reset the position of the parser from before this pass
@pull number_matched; @pull match_from; @pull match_length; @pull wn; @pull token_filter;
! Are there no matches for the second noun? If so, we can give up here and now.
if (i == 0)
{
#ifdef DEBUG;
if (parser_trace >= 4)
print "(Failed to match follow-on words. Moving to next line.)^";
#endif;
rfalse;
}
}
}
}
! Now, if there's more than one choice, let's see if we can do better
number_of_classes=0;
if (number_matched > 1)
{
(+list-outcomes+) = false; ! we set the list-writer to false and see if the outcomes can make it true again
! Now we run Adjudicate, which in turn runs ChooseObjects and the I7 routines
! these routines will score the options, and delete inappropriate ones
i=Adjudicate(context);
! Did we fail to get anything from Adjudicate?
if (i == -1)
{
! If we're guessing, then there was nothing valid. So let's ask the player to explain themselves.
if (guessing)
{
if (indef_possambig)
{
if (parser_trace == 5)
print " [Failed to find anything using an ambiguous input.]^";
rfalse;
}
jump Incomplete;
}
! otherwise, if we weren't guessing, then what the player typed made no sense after all, so fail the line.
rfalse;
}
if (i==1) ! A multiple object was matched.
{
! If we're not looking for a multiple noun, then we have a fundamental problem
if (~~MultiContext(context))
print "[BUG in Disambiguation: Multiple object made it out of Adjudicate!]^";
rtrue;
}
}
! If i is non-zero, then we're looking at an object
! 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 && ~~guessing)
{
! we're not guessing. We're always allow to default
! when we're not guessing
return i;
}
! if we are guessing, we're only allowed to default if list-outcomes made it to true
if (guessing && (+list-outcomes+) == false)
{
! in which case, we jump to incomplete and act all surprised.
! print "[*** Jumping to incomplete due to rubbish matches ***]^";
jump Incomplete;
}
! this is where we record that we guessed this one, rather than being told about it
! we need to record this fact for the insertion of "it" that'll happen later, if there's a parse-line
! reconstruction.
! for a first-pass, let's just set a flag.
if (inferfrom==0)
{
inferfrom=pcount;
if (guessing)
{
! this was our first point of guesswork on this line, as the interfrom was still zero
if (parser_trace >= 4) print "[Setting guessed first noun to true]^";
guessed_first_noun = true;
}
}
pattern-->pcount = i;
return i;
}
! 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 we're not guessing, but at the end of the line, we still want to add
! extra text to the end of the input, rather that in the middle somewhere
! We have to do these two differently, because of the way text is spliced together
if (match_from > num_words && ~~guessing) jump Incomplete;
.ListOutPoint;
! 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 SkipWhichQuestionB;
#ifdef NO_SUGGESTIONS;
ParserMessage(1);
if (context==CREATURE_TOKEN) ParserMessage(8); else ParserMessage(9);
#ifnot;
if (number_of_classes > TRUNCATE_LIST || (+list-outcomes+) == false)
{
ParserMessage(1);
if (context==CREATURE_TOKEN) ParserMessage(8); else ParserMessage(9);
jump SkipWhichQuestionB;
}
else
{
if (context==CREATURE_TOKEN) ParserMessage(2); else ParserMessage(3);
PrintMatchClasses(match_list, number_of_classes, 0);
ParserMessage(10);
}
.SkipWhichQuestionB;
EndActivity(ASKING_WHICH_DO_YOU_MEAN_ACT);
! ...and get an answer:
.WhichOne;
for (i=2:i<120:i++) buffer2->i=' ';
answer_words=Keyboard(buffer2, parse2);
if (guessing==1) jump DoneIncompleteInput;
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 (MultiContext(context)) ! we stick all of these guys into the multiple object list. Do we really want to do it this way?
! this list just then gets "used" as a normal reply (i guess it's okay; isn't it?!?)
{ l=multiple_object-->0;
for (i=0:i<number_matched && l+i<63:i++)
{ k=match_list-->i;
multiple_object-->(i+1+l) = k;
}
multiple_object-->0 = i+l;
rtrue;
}
ParserMessage(12);
! PARSER_CLARIF_INTERNAL_RM('C');
jump WhichOne;
}
! 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;
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();
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;
BeginActivity(ASKING_WHICH_DO_YOU_MEAN_ACT);
if (ForActivity(ASKING_WHICH_DO_YOU_MEAN_ACT)) jump AfterWhichMessage;
if (context==CREATURE_TOKEN)
ParserMessage(4); else ParserMessage(5);
.AfterWhichMessage;
EndActivity(ASKING_WHICH_DO_YOU_MEAN_ACT);
.AskedIncomplete;
#Ifdef TARGET_ZCODE;
for (i=2 : i<INPUT_BUFFER_LEN : i++) buffer2->i=' ';
#Endif; ! TARGET_ZCODE
answer_words = Keyboard(buffer2, parse2);
.DoneIncompleteInput;
first_word=(parse2-->1);
#ifdef LanguageIsVerb;
if (first_word==0)
{ j = wn; first_word=LanguageIsVerb(buffer2, parse2, 1); wn = j;
}
#endif;
! 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.)
! BUG 24/7/10:
! The following code provides for cases where the parser has filled in the first word
! from no input entirely and asked for clarification on the second word.
! It needs to glue something into the "gap" to cover the missing noun, otherwise the
! line will no longer match.
! Problem is, if the parser had some text here -- which seems to happen in the case
! of an adjudication between identical copy items -- then it shoves "it" on the end, and
! this fails to match.
! So ideally this block of code will ONLY fire if there was NOTHING in the input line for the first noun.
! How do we check for this? Can we use one of the guessing-type flags set above??
! First pass: get something to print out which case we're in
! Tests are : >GIVE ITEM X / >PERSON Y when the player has 2 Xs
! SNARK / > PERSON Y, when there is a rule for "Suggesting" item X for snarking (no duplicate X required.)
if (pattern-->j >= 2 && pattern-->j < REPARSE_CODE)
{
if (guessed_first_noun)
{
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 >= 4)
print "[Using pronoun as guessed_first was true.]^";
if (parser_trace >= 5)
print "[Using pronoun '", (address) parse2-->1, "']^";
#Endif; ! DEBUG
break;
}
else if (parser_trace >=4 ) print "[Not using pronoun as guessed_first was false.]^";
}
}
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;
];
[ PrintMatchClasses ar max imark i marker k ;
!print "(max ", max, " imark ", imark, ")^";
marker = imark;
for (i=1 : i<=max : i++) {
while (((match_classes-->marker) ~= i) && ((match_classes-->marker) ~= -i))
{
! print match_classes-->marker, " / from marker ", marker, "^";
marker++;
}
k = ar-->marker;
if (match_classes-->marker > 0) print (the) k; else print (a) k;
if (i < max-1) print ", ";
if (i == max-1) {
#Ifdef SERIAL_COMMA;
print ",";
#Endif; ! SERIAL_COMMA
PARSER_CLARIF_INTERNAL_RM('H');
}
}
];
[ PARSER_CLARIF_INTERNAL_R; ];
-) instead of "Noun Domain" in "Parser.i6t".
chapter - adjudicate
[
Edited to provide better multiple object error behaviour.
]
Include (-
[ BestScore its_score best i;
best = -1000;
for (i=0 : i<number_matched : i++) {
its_score = match_scores-->i; if (its_score > best) best = its_score;
}
return best;
];
[ Adjudicate context i j k good_flag good_ones last n ultimate flag offset best_score;
#Ifdef DEBUG;
if (parser_trace >= 4) {
print " [Adjudicating match list of size ", number_matched,
" in context ", context, "^";
print " ";
if (indef_mode) {
print "indefinite type: ";
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 == 100) print "all"; else print indef_wanted;
new_line;
print " most likely GNAs of names: ", indef_cases, "^";
}
else print "definite object^";
}
#Endif; ! DEBUG
j = number_matched-1; good_ones = 0; last = match_list-->0;
for (i=0 : i<=j : i++) {
n = match_list-->i;
match_scores-->i = good_ones;
ultimate = ScopeCeiling(n);
if (context==HELD_TOKEN && parent(n)==actor)
{ good_ones++; last=n; }
if (context==MULTI_TOKEN && ultimate==ScopeCeiling(actor)
&& n~=actor && n hasnt concealed !&& n hasnt scenery
)
{ good_ones++; last=n; }
if (context==MULTIHELD_TOKEN && parent(n)==actor)
{ good_ones++; last=n; }
if (context==MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN)
{ if (advance_warning==-1)
{ if (context==MULTIEXCEPT_TOKEN)
{ good_ones++; last=n;
}
if (context==MULTIINSIDE_TOKEN)
{ if (parent(n)~=actor) { good_ones++; last=n; }
}
}
else
{ if (context==MULTIEXCEPT_TOKEN && n~=advance_warning)
{ good_ones++; last=n; }
if (context==MULTIINSIDE_TOKEN && n in advance_warning)
{ good_ones++; last=n; }
}
}
if (context==CREATURE_TOKEN && CreatureTest(n)==1)
{ good_ones++; last=n; }
match_scores-->i = 1000*(good_ones - match_scores-->i);
}
if (good_ones == 1)
{
! these are good, regardless of our rules (this is duplication, but it'll do for now)
(+list-outcomes+) = true;
return last;
}
! If there is ambiguity about what was typed, but it definitely wasn't
! animate as required, then return anything; higher up in the parser
! a suitable error will be given. (This prevents a question being asked.)
! if (context==CREATURE_TOKEN && good_ones==0) return match_list-->0;
if (indef_mode==0) indef_type=0;
ScoreMatchL(context);
if (number_matched == 0) return -1;
!print "(Made ", number_matched, " indef ", indef_mode, " & ", indef_wanted, ".^";
if (indef_mode == 0 && guessing == false)
{ ! Is there now a single highest-scoring object?
i = SingleBestGuess();
if (i >= 0)
{
#ifdef DEBUG;
if (parser_trace>=4)
print " Single best-scoring object returned.]^";
#endif;
return i;
}
}
best_score = BestScore();
! ------If we've made a multiple object we investigate it, term by term, to check that everything in it should be in it
! note that we load up the multiple object list not from 0 but from wherever it happens to be
! and, even worse, we load it up out of order, chuck away the match list scores and chuck away the match list itself
! and the only ray of hope in all this is it assembles the multiple object list in order of preference
! (although why it does that I have no idea, because I'm sure all this info is later ignored.)
if (indef_mode==1 && indef_type & PLURAL_BIT ~= 0)
{
#ifndef PUT_IN_FOR_NOW;
if (~~MultiContext(context))
{
indef_mode = 0; !print "JUMPING MULTI";
jump PostMultiObject;
}
#endif;
!print "DOING MULTI";
i=0; offset=multiple_object-->0;
for (j=BestGuess():j~=-1 && i<indef_wanted
&& i+offset<63:j=BestGuess())
{ flag=0;
BeginActivity(DECIDING_WHETHER_ALL_INC_ACT, j);
if ((ForActivity(DECIDING_WHETHER_ALL_INC_ACT, j)) == 0) {
if (j hasnt concealed && j hasnt worn) flag=1;
if (context==MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN
&& parent(j)~=actor) flag=0;
if (action_to_be == ##Take or ##Remove && parent(j)==actor) flag=0;
k=ChooseObjects(j,flag);
if (k==1) flag=1; else { if (k==2) flag=0; }
} else {
flag = 0; if (RulebookSucceeded()) flag = 1;
}
EndActivity(DECIDING_WHETHER_ALL_INC_ACT, j);
if (flag==1)
{ i++; multiple_object-->(i+offset) = j;
#ifdef DEBUG;
if (parser_trace>=4) print " Accepting it^";
#endif;
}
else
{ i=i;
#ifdef DEBUG;
if (parser_trace>=4) print " Rejecting it^";
#endif;
}
}
if (i<indef_wanted && indef_wanted<100)
{ etype=TOOFEW_PE; multi_wanted=indef_wanted;
multi_had=i;
return -1;
}
multiple_object-->0 = i+offset;
multi_context=context;
#ifdef DEBUG;
if (parser_trace>=4)
print " Made multiple object of size ", i, "]^";
#endif;
return 1;
}
.PostMultiObject;
! We set match classes in a routine so we can do it again below
n = SetMatchClasses(match_list, 0, number_matched);
#ifdef DEBUG;
if (parser_trace>=4)
{ print " ** Grouped into ", n, " possibilities by name:^";
for (i=0:i<number_matched:i++)
if (match_classes-->i > 0)
print " ", (The) match_list-->i,
" (", match_list-->i, ") --- group ",
match_classes-->i, "^";
}
#endif;
if (indef_mode == 0)
{ if (n > 1)
{ k = -1;
for (i=0:i<number_matched:i++)
{ if (match_scores-->i > k)
{ k = match_scores-->i; ! current highest
j = match_classes-->i; j=j*j; ! j = classes^2 ?!? (fix sign?)
flag = 0;
}
else
if (match_scores-->i == k) ! we found a draw
{ if ((match_classes-->i) * (match_classes-->i) ~= j)
flag = 1; ! we found a different class
}
}
if (flag)
{
#ifdef DEBUG;
if (parser_trace>=4)
print " Unable to choose best group, so ask player.]^";
#endif;
! if (guessing)
! {
#ifdef DEBUG;
if (parser_trace>=4)
print "[Highest score ", k, " -- deleting all lower scores.]^";
#endif;
for (i=0:i<number_matched:i++)
{ while (match_scores-->i < k)
{ if (i == number_matched-1) { number_matched--; break; }
for (j=i:j<number_matched:j++)
{ match_list-->j = match_list-->(j+1);
match_scores-->j = match_scores-->(j+1);
}
number_matched--;
}
}
#ifdef DEBUG;
if (parser_trace>=4)
print "[", number_matched, " entries remaining.]^";
#endif;
SetMatchClasses(match_list, 0, number_matched);
! }
return 0;
}
#ifdef DEBUG;
if (parser_trace>=4)
print " Best choices are all from the same group.^";
#endif;
}
}
! When the player is really vague, or there's a single collection of
! indistinguishable objects to choose from, choose the one the player
! most recently acquired, or if the player has none of them, then
! the one most recently put where it is.
!if (n == 1) dont_infer = true;
return BestGuess();
];
[ SetMatchClasses ar st en max i n flag j;
max = en + st - 1;
for (i = st :i <= max :i++) match_classes-->i=0;
n=1;
for (i = st :i <= max : i++)
if (match_classes-->i==0)
{ match_classes-->i=n++; flag=0;
for (j=i+1 : j<=max : j++)
if (match_classes-->j==0
&& Identical(ar-->i, ar-->j)==1)
{ flag=1;
match_classes-->j=match_classes-->i;
}
if (flag==1) match_classes-->i = 1-n;
}
n--; number_of_classes = n;
return n;
];
-) instead of "Adjudicate" in "Parser.i6t".
Section - ScoreMatchL
Include (-
Constant SCORE__CHOOSEOBJ = 1000;
Constant SCORE__IFGOOD = 500;
Constant SCORE__UNCONCEALED = 100;
Constant SCORE__BESTLOC = 60;
Constant SCORE__NEXTBESTLOC = 40;
Constant SCORE__NOTCOMPASS = 20;
Constant SCORE__NOTSCENERY = 10;
Constant SCORE__NOTACTOR = 5;
Constant SCORE__GNA = 1;
Constant SCORE__DIVISOR = 20;
Constant PREFER_HELD;
[ ScoreMatchL context its_owner its_score obj i j threshold met a_s l_s fda ;
! if (indef_type & OTHER_BIT ~= 0) threshold++;
if (indef_type & MY_BIT ~= 0) threshold++;
if (indef_type & THAT_BIT ~= 0) threshold++;
if (indef_type & LIT_BIT ~= 0) threshold++;
if (indef_type & UNLIT_BIT ~= 0) threshold++;
if (indef_owner ~= nothing) threshold++;
#Ifdef DEBUG;
if (parser_trace >= 4) print " Scoring match list: indef mode ", indef_mode, " type ",
indef_type, ", satisfying ", threshold, " requirements:^";
#Endif; ! DEBUG
#ifdef PREFER_HELD;
a_s = SCORE__BESTLOC; l_s = SCORE__NEXTBESTLOC;
if (action_to_be == ##Take or ##Remove) {
a_s = SCORE__NEXTBESTLOC; l_s = SCORE__BESTLOC;
}
context = context; ! silence warning
#ifnot;
a_s = SCORE__NEXTBESTLOC; l_s = SCORE__BESTLOC;
if (context == HELD_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN) {
a_s = SCORE__BESTLOC; l_s = SCORE__NEXTBESTLOC;
}
#endif; ! PREFER_HELD
for (i=0: i<number_matched: i++) {
obj = match_list-->i; its_owner = parent(obj); its_score=0;
! if (indef_type & OTHER_BIT ~=0
! && obj~=itobj or himobj or herobj) met++;
if (indef_type & MY_BIT ~=0 && its_owner==actor) met++;
if (indef_type & THAT_BIT ~=0 && its_owner==actors_location) met++;
if (indef_type & LIT_BIT ~=0 && obj has light) met++;
if (indef_type & UNLIT_BIT ~=0 && obj hasnt light) met++;
if (indef_owner~=0 && its_owner == indef_owner) met++;
if (met < threshold)
{
#ifdef DEBUG;
if (parser_trace >= 4)
print " ", (The) match_list-->i,
" (", match_list-->i, ") in ", (the) its_owner,
" is rejected (doesn't match descriptors)^";
#endif;
match_list-->i=-1;
}
else
{ its_score = 0;
fda = ChooseObjects(obj, 2, context);
if (fda > -1) its_score = SCORE__CHOOSEOBJ * fda;
else match_list-->i = -1;
if (obj hasnt concealed) its_score = its_score + SCORE__UNCONCEALED;
if (its_owner==actor) its_score = its_score + a_s;
else
if (its_owner==actors_location) its_score = its_score + l_s;
else
if (its_owner~=compass) its_score = its_score + SCORE__NOTCOMPASS;
if (obj hasnt scenery) its_score = its_score + SCORE__NOTSCENERY;
if (obj ~= actor) its_score = its_score + SCORE__NOTACTOR;
! A small bonus for having the correct GNA,
! for sorting out ambiguous articles and the like.
if (indef_cases & (PowersOfTwo_TB-->(GetGNAOfObject(obj))))
its_score = its_score + SCORE__GNA;
match_scores-->i = match_scores-->i + its_score;
#ifdef DEBUG;
if (parser_trace >= 4)
{
print " ", (The) obj,
" (", obj, ") in ", (the) its_owner;
if (match_list-->i == -1) print " : deleted by ChooseObjects^";
else print " : ", match_scores-->i, " points^";
}
#endif;
}
}
! remove dead entries from the match list
for (i=0:i<number_matched:i++)
{ while (match_list-->i == -1)
{ if (i == number_matched-1) { number_matched--; break; }
for (j=i:j<number_matched:j++)
{ match_list-->j = match_list-->(j+1);
match_scores-->j = match_scores-->(j+1);
}
number_matched--;
}
}
if (guessing == true) ! If bypass disambiguation were working, it would be checked here (MW)
for (i=0: i<number_matched:i++)
{
if (match_list-->i~=-1)
{ ! we need to rescore everything using the more straightforward system
! this gives scores for everything except actor's held / player held, which tends to separate stuff out
its_score = 0;
obj = match_list-->i;
its_owner = parent(obj);
fda = ChooseObjects(obj, 2, context);
if (fda > -1) its_score = its_score + SCORE__CHOOSEOBJ * fda;
if (obj hasnt concealed) its_score = its_score + SCORE__UNCONCEALED;
if (its_owner~=compass) its_score = its_score + SCORE__NOTCOMPASS;
! if (obj hasnt scenery) its_score = its_score + SCORE__NOTSCENERY;
if (obj ~= actor) its_score = its_score + SCORE__NOTACTOR;
! A small bonus for having the correct GNA,
! for sorting out ambiguous articles and the like.
if (indef_cases & (PowersOfTwo_TB-->(GetGNAOfObject(obj))))
its_score = its_score + SCORE__GNA;
match_scores-->i = its_score;
#ifdef DEBUG;
if (parser_trace >= 4)
print " ", (The) match_list-->i,
" (", match_list-->i, ") rescored at: ", match_scores-->i, " points^";
#endif;
}
}
];
-) instead of "ScoreMatchL" in "Parser.i6t".
Section - Descriptors
Include (-
Global mf;
[ Descriptors allow_multiple o x flag cto type n;
mf = 0;
ResetDescriptors();
if (wn > num_words) return 0;
for (flag=true:flag:)
{ o=NextWordStopped(); flag=false;
for (x=1:x<=LanguageDescriptors-->0:x=x+4)
if (o == LanguageDescriptors-->x)
{ flag = true;
type = LanguageDescriptors-->(x+2);
if (type ~= DEFART_PK) indef_mode = true;
indef_possambig = true;
indef_cases = indef_cases & (LanguageDescriptors-->(x+1));
if (type == POSSESS_PK)
{ cto = LanguageDescriptors-->(x+3);
switch(cto)
{ 0: indef_type = indef_type | MY_BIT;
1: indef_type = indef_type | THAT_BIT;
default: indef_owner = PronounValue(cto);
if (indef_owner == NULL) indef_owner = InformParser;
}
}
if (type == light)
indef_type = indef_type | LIT_BIT;
if (type == -light)
indef_type = indef_type | UNLIT_BIT;
}
if (o==OTHER1__WD or OTHER2__WD or OTHER3__WD)
{ indef_mode=1; flag=1;
indef_type = indef_type | OTHER_BIT; }
if (o==ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD)
{ indef_mode=1; flag=1; indef_wanted=100;
if (take_all_rule == 1)
take_all_rule = 2;
indef_type = indef_type | PLURAL_BIT;
if (mf==0) mf = wn-1;
}
if (allow_plurals && allow_multiple)
{ n=TryNumber(wn-1);
if (n==1) { indef_mode=1; flag=1; }
if (n>1) { indef_guess_p=1;
indef_mode=1; flag=1; indef_wanted=n;
indef_nspec_at=wn-1;
indef_type = indef_type | PLURAL_BIT;
if (mf==0) mf = wn-1;
}
}
if (flag==1
&& NextWordStopped() ~= OF1__WD or OF2__WD or OF3__WD or OF4__WD)
wn--; ! Skip 'of' after these
}
wn--;
! if ((indef_wanted > 0) && (~~allow_multiple)) return MULTI_PE;
return 0;
];
[ SafeSkipDescriptors;
@push indef_mode; @push indef_type; @push indef_wanted;
@push indef_guess_p; @push indef_possambig; @push indef_owner;
@push indef_cases; @push indef_nspec_at;
Descriptors();
@pull indef_nspec_at; @pull indef_cases;
@pull indef_owner; @pull indef_possambig; @pull indef_guess_p;
@pull indef_wanted; @pull indef_type; @pull indef_mode;
];
-) instead of "Parsing Descriptors" in "Parser.i6t".
Section - Parser Lookaheads should tell Choose Objects to behave differently
Include (-
advance_warning = -1; indef_mode = false;
for (i=0,m=false,pcount=0 : line_token-->pcount ~= ENDIT_TOKEN : pcount++) {
scope_token = 0;
if (line_ttype-->pcount ~= PREPOSITION_TT) i++;
if (line_ttype-->pcount == ELEMENTARY_TT) {
if (line_tdata-->pcount == MULTI_TOKEN) m = true;
if (line_tdata-->pcount == MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN && i == 1) {
! First non-preposition is "multiexcept" or
! "multiinside", so look ahead.
#Ifdef DEBUG;
if (parser_trace >= 2) print " [Trying look-ahead]^";
#Endif; ! DEBUG
! We need this to be followed by 1 or more prepositions.
pcount++;
if (line_ttype-->pcount == PREPOSITION_TT) {
! skip ahead to a preposition word in the input
do {
l = NextWord();
} until ((wn > num_words) ||
(l && (l->#dict_par1) & 8 ~= 0));
if (wn > num_words) {
#Ifdef DEBUG;
if (parser_trace >= 2)
print " [Look-ahead aborted: prepositions missing]^";
#Endif;
jump LineFailed;
}
do {
if (PrepositionChain(l, pcount) ~= -1) {
! advance past the chain
if ((line_token-->pcount)->0 & $20 ~= 0) {
pcount++;
while ((line_token-->pcount ~= ENDIT_TOKEN) &&
((line_token-->pcount)->0 & $10 ~= 0))
pcount++;
} else {
pcount++;
}
} else {
! try to find another preposition word
do {
l = NextWord();
} until ((wn >= num_words) ||
(l && (l->#dict_par1) & 8 ~= 0));
if (l && (l->#dict_par1) & 8) continue;
! lookahead failed
#Ifdef DEBUG;
if (parser_trace >= 2)
print " [Look-ahead aborted: prepositions don't match]^";
#endif;
jump LineFailed;
}
l = NextWord();
} until (line_ttype-->pcount ~= PREPOSITION_TT);
! put back the non-preposition we just read
wn--;
if ((line_ttype-->pcount == ELEMENTARY_TT) &&
(line_tdata-->pcount == NOUN_TOKEN)) {
l = Descriptors(); ! skip past THE etc
if (l~=0) etype=l; ! don't allow multiple objects
! set flags for choose objects to use
look_ahead = true;
cobj_flag = 0;
l = NounDomain(actors_location, actor, NOUN_TOKEN);
! unset them again, so we can pretend this never happened
look_ahead = 0;
cobj_flag = 0;
#Ifdef DEBUG;
if (parser_trace >= 2) {
print " [Advanced to ~noun~ token: ";
if (l == REPARSE_CODE) print "re-parse request]^";
if (l == 1) print "but multiple found]^";
if (l == 0) print "error ", etype, "]^";
if (l >= 2) print (the) l, "]^";
}
#Endif; ! DEBUG
if (l == REPARSE_CODE) jump ReParse;
if (l >= 2) advance_warning = l;
}
}
break;
}
}
}
! Slightly different line-parsing rules will apply to "take multi", to
! prevent "take all" behaving correctly but misleadingly when there's
! nothing to take.
take_all_rule = 0;
if (m && params_wanted == 1 && action_to_be == ##Take)
take_all_rule = 1;
! And now start again, properly, forearmed or not as the case may be.
! As a precaution, we clear all the variables again (they may have been
! disturbed by the call to NounDomain, which may have called outside
! code, which may have done anything!).
inferfrom = 0;
parameters = 0;
nsns = 0; special_word = 0;
multiple_object-->0 = 0;
etype = STUCK_PE;
wn = verb_wordnum+1;
-) instead of "Parser Letter F" in "Parser.i6t".
Section - reset cobj_flags on reparsing (the library should do this anyway, I think)
Include (-
if (held_back_mode == 1) {
held_back_mode = 0;
VM_Tokenise(buffer, parse);
jump ReParse;
}
.ReType;
cobj_flag = 0;
BeginActivity(READING_A_COMMAND_ACT); if (ForActivity(READING_A_COMMAND_ACT)==false) {
Keyboard(buffer,parse);
players_command = 100 + WordCount();
num_words = WordCount();
} if (EndActivity(READING_A_COMMAND_ACT)) jump ReType;
.ReParse;
! added in for the purposes of the bug fix
guessed_first_noun = false;
cobj_flag = 0;
parser_inflection = name;
! Initially assume the command is aimed at the player, and the verb
! is the first word
num_words = WordCount();
wn = 1;
#Ifdef LanguageToInformese;
LanguageToInformese();
! Re-tokenise:
VM_Tokenise(buffer,parse);
#Endif; ! LanguageToInformese
num_words = WordCount();
k=0;
#Ifdef DEBUG;
if (parser_trace >= 2) {
print "[ ";
for (i=0 : i<num_words : i++) {
#Ifdef TARGET_ZCODE;
j = parse-->(i*2 + 1);
#Ifnot; ! TARGET_GLULX
j = parse-->(i*3 + 1);
#Endif; ! TARGET_
k = WordAddress(i+1);
l = WordLength(i+1);
print "~"; for (m=0 : m<l : m++) print (char) k->m; print "~ ";
if (j == 0) print "?";
else {
#Ifdef TARGET_ZCODE;
if (UnsignedCompare(j, HDR_DICTIONARY-->0) >= 0 &&
UnsignedCompare(j, HDR_HIGHMEMORY-->0) < 0)
print (address) j;
else print j;
#Ifnot; ! TARGET_GLULX
if (j->0 == $60) print (address) j;
else print j;
#Endif; ! TARGET_
}
if (i ~= num_words-1) print " / ";
}
print " ]^";
}
#Endif; ! DEBUG
verb_wordnum = 1;
actor = player;
actors_location = ScopeCeiling(player);
usual_grammar_after = 0;
.AlmostReParse;
scope_token = 0;
action_to_be = NULL;
! Begin from what we currently think is the verb word
.BeginCommand;
wn = verb_wordnum;
verb_word = NextWordStopped();
! If there's no input here, we must have something like "person,".
if (verb_word == -1) {
best_etype = STUCK_PE;
jump GiveError;
}
! Now try for "again" or "g", which are special cases: don't allow "again" if nothing
! has previously been typed; simply copy the previous text across
if (verb_word == AGAIN2__WD or AGAIN3__WD) verb_word = AGAIN1__WD;
if (verb_word == AGAIN1__WD) {
if (actor ~= player) {
best_etype = ANIMAAGAIN_PE;
jump ReType;
}
#Ifdef TARGET_ZCODE;
if (buffer3->1 == 0) {
PARSER_COMMAND_INTERNAL_RM('D'); new_line;
jump ReType;
}
#Ifnot; ! TARGET_GLULX
if (buffer3-->0 == 0) {
PARSER_COMMAND_INTERNAL_RM('D'); new_line;
jump ReType;
}
#Endif; ! TARGET_
for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer->i = buffer3->i;
VM_Tokenise(buffer,parse);
num_words = WordCount();
players_command = 100 + WordCount();
jump ReParse;
}
! Save the present input in case of an "again" next time
if (verb_word ~= AGAIN1__WD)
for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer3->i = buffer->i;
if (usual_grammar_after == 0) {
j = verb_wordnum;
i = RunRoutines(actor, grammar);
#Ifdef DEBUG;
if (parser_trace >= 2 && actor.grammar ~= 0 or NULL)
print " [Grammar property returned ", i, "]^";
#Endif; ! DEBUG
if ((i ~= 0 or 1) && (VM_InvalidDictionaryAddress(i))) {
usual_grammar_after = verb_wordnum; i=-i;
}
if (i == 1) {
parser_results-->0 = action;
parser_results-->1 = noun;
parser_results-->2 = second;
rtrue;
}
if (i ~= 0) { verb_word = i; wn--; verb_wordnum--; }
else { wn = verb_wordnum; verb_word = NextWord(); }
}
else usual_grammar_after = 0;
-) instead of "Parser Letter A" in "Parser.i6t".
[ ************************************************************************************************************************************************************ ]
Volume - the System for Disambiguation
Book - Choose Objects entrypoint
Chapter - replacing I7's choose objects routine
Matching a creature token is a truth state that varies.
List-outcomes is a truth state that varies.
Include (-
Constant COBJ_BITS_SIZE = (MATCH_LIST_WORDS*MATCH_LIST_WORDS/8);
! the highest value returned by CheckDPMR (see the Standard Rules)
Constant HIGHEST_DPMR_SCORE = 9;
Array alt_match_list --> (MATCH_LIST_WORDS+1);
#ifdef TARGET_GLULX;
[ COBJ__Copy words from to i;
for (i=0: i<words: i++)
to-->i = from-->i;
];
#ifnot;
[ COBJ__Copy words from to bytes;
bytes = words * 2;
@copy_table from to bytes;
];
#endif;
! swap alt_match_list with match_list/number_matched
[ COBJ__SwapMatches i x;
! swap the counts
x = number_matched;
number_matched = alt_match_list-->0;
alt_match_list-->0 = x;
! swap the values
if (x < number_matched) x = number_matched;
for (i=x: i>0: i-- ) {
x = match_list-->(i-1);
match_list-->(i-1) = alt_match_list-->i;
alt_match_list-->i = x;
}
];
! ChooseObjects comes with three strategies:
! 1) We're trying the first noun of a line like TAKE FISH WITH POLE, with the query on the first noun.
! We use scope/matching to build a list of possible second nouns, and loop across all of them, collecting the best score
! 2) We're doing nothing complicated: either TAKE ROCK, or TAKE ROCK WITH MAGNET with the query on the second noun
! We just build an action pattern and try it.
! 3) We're doing the reverse of 1 - the query is on the second noun, but the first noun is variable
! Either because we're doing a lookahead (PUT CAT IN BAG checks BAG before CAT)
! or we're looking at a multiple object (GIVE ALL TO JIM or GIVE COINS TO JIM)
[ ChooseObjects obj code context l i swn spcount a b c old_ad otf gdata;
#ifdef COBJ_DEBUG;
print "(entering with ", (the) obj, " and flag ", cobj_flag, " lookahead? ", look_ahead, ")^";
#endif;
! we ditch the no-object tracking object immediately
if (obj == (+no-object+)) return -1;
! we record this for use by the expectation rules - currently redundant, but may well reappear
if (context == CREATURE_TOKEN)
(+matching a creature token+) = 1;
else
(+matching a creature token+) = 0;
! we shouldn't really be here under I7 rules with an all...
if (code<2) rfalse;
! by default, the smart score should be always ignore
c = -1;
! if multiple object exists for the first parameter and we're now looking for the second
! then copy the mult into the alt list, and use code 3
if (parameters > 0 && multiple_object-->0 > 0)
{
#ifdef COBJ_DEBUG;
print "[We've found a multiple list in the first object.]^";
#endif;
CopyMultipleObjectList();
cobj_flag = 3;
}
if (cobj_flag == 1 && parameters > 0)
{
#ifdef COBJ_DEBUG;
print "[now scoring the second: drop into simple mode.]^";
#endif;
cobj_flag = 2;
}
if (cobj_flag == 1)
{
.CodeOne;
if (parameters > 0)
print_ret "[Bug in C-Ob: this should be unreachable. (Are we here directly from a jump statement?]^";
#ifdef COBJ_DEBUG;
print "[scoring ", (the) obj, " (first) in ", alt_match_list-->0, " combinations]^";
#endif;
return ScoreCombos(0, 1, obj, context);
}
if (cobj_flag == 2) {
!Works for TAKE FISH(?) or WEAR HAT(?)
! also TIE FISH(!) TO CARPET(?)
!Both occasions we know everything we need to in advance
.CodeTwo;
#ifdef COBJ_DEBUG;
print "[scoring ", (the) obj, " (simple)]^";
#endif;
if (parameters==0)
{
b = GuessScoreDabCombo(obj, 0, context, 1);
c = SmartScoreDabCombo(obj, 0);
}
else
{
b = GuessScoreDabCombo(parser_results-->2, obj, context, 2);
c = SmartScoreDabCombo(parser_results-->2, obj);
}
return ResolveChooseScores(b, c);
}
if (cobj_flag == 3)
{
! we loop over the possible first nouns and score them
! same as code 1 above only backwards!
.CodeThree;
#ifdef COBJ_DEBUG;
print "[scoring ", alt_match_list-->0, " combinations for ", (the) obj, " (second)^";
#endif;
return ScoreCombos(-1, 2, obj, context); ! testing for no. two
}
.ChooseStrategy;
#ifdef COBJ_DEBUG;
print "[choosing a cobj strategy: ";
#endif;
swn = wn;
spcount = pcount;
if (look_ahead ~= 0)
{
wn = verb_wordnum + 1;
pcount = 0;
SkipPrepositions();
if ((line_ttype-->pcount == ELEMENTARY_TT or ATTR_FILTER_TT or ROUTINE_FILTER_TT) && (
line_tdata-->pcount ~= SPECIAL_TOKEN or NUMBER_TOKEN or TOPIC_TOKEN or ENDIT_TOKEN
))
{
! Advance past the prepositions in the input
while (wn < swn-1)
{
l = NextWord();
if ( l && (l->#dict_par1) &8 == 0 ) ! if *not* preposition
{
if (l == ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD)
{
CreateAltList();
#ifdef COBJ_DEBUG;
print "all generated ", alt_match_list-->0, " possible first nouns]^";
#endif;
}
else
{
wn--;
BuildTheLookAheadList();
#ifdef COBJ_DEBUG;
print alt_match_list-->0, " possible first nouns]^";
#endif;
}
wn = swn;
pcount = spcount;
cobj_flag = 3;
jump CodeThree;
}
}
}
print "[Lookahead mode failed to achieve anything. Not sure how this is possible?]^";
}
! so now we just look ahead ourselves and see what happens next in the grammar line
if (line_ttype-->pcount == PREPOSITION_TT or ELEMENTARY_TT or ROUTINE_FILTER_TT or ATTR_FILTER_TT)
{
SkipPrepositions();
if ((line_ttype-->pcount == ELEMENTARY_TT or ROUTINE_FILTER_TT or ATTR_FILTER_TT) && (
line_tdata-->pcount ~= SPECIAL_TOKEN or NUMBER_TOKEN or TOPIC_TOKEN or ENDIT_TOKEN
))
{
! Advance past the last preposition
while (wn < num_words)
{
l = NextWord();
if ( l && (l->#dict_par1) &8 ) ! if preposition
{
if (l == ALL1__WD or ALL2__WD or ALL3__WD or ALL4__WD or ALL5__WD) continue;
BuildtheLookAheadList();
#ifdef COBJ_DEBUG;
print alt_match_list-->0, " possible second nouns]^";
#endif;
wn = swn;
cobj_flag = 1;
pcount = spcount;
jump CodeOne;
}
}
}
}
#ifdef COBJ_DEBUG;
print "nothing interesting]^";
#endif;
! reset everything
pcount = spcount;
wn = swn;
cobj_flag = 2;
jump CodeTwo;
];
[ BuildTheLookAheadList otf gdata;
! remembers filters and reset if we need to
otf = token_filter;
token_filter = 0;
gdata = line_tdata-->pcount;
if (line_ttype-->pcount == ROUTINE_FILTER_TT)
{
print "(checking routine filter)^";
token_filter = line_tdata-->pcount;
gdata = NOUN_TOKEN;
}
if (line_ttype-->pcount == ATTR_FILTER_TT)
{
print "(checking attribute filter)^";
token_filter = 1 + line_tdata-->pcount;
gdata = NOUN_TOKEN;
}
SafeSkipDescriptors();
! save the current match state
@push match_length; @push match_from;
alt_match_list-->0 = number_matched;
COBJ__Copy(number_matched, match_list, alt_match_list+WORDSIZE);
! now get all the matches we can
match_length = 0; number_matched = 0; match_from = wn;
SearchScope(actor, actors_location, gdata);
! restore match variables
COBJ__SwapMatches();
@pull match_from; @pull match_length;
token_filter = otf;
];
[ SkipPrepositions;
while (line_ttype-->pcount == PREPOSITION_TT) pcount++;
];
[ ScoreCombos
defaultscore varier obj context l i spcount b c n s
;
l = defaultscore; ! note the difference. Because now ...?
for (i=1: i<=alt_match_list-->0: i++)
{
if (varier == 2)
{
s = obj;
n = alt_match_list-->i;
}
if (varier == 1) ! varier = thing we're testing NOT thing which varies (what dumbass wrote this?)
{
n = obj;
s = alt_match_list-->i;
}
b = GuessScoreDabCombo(n, s, context, varier);
c = SmartScoreDabCombo(n, s);
spcount = ResolveChooseScores(b, c);
! print (the) n, " / ", (the) s, " => b ", b, " c ", c , " => ", spcount, ".^";
if (spcount == HIGHEST_DPMR_SCORE)
{
#ifdef COBJ_DEBUG;
print "[scored ", spcount, " - best possible]^";
#endif;
return spcount;
}
if (spcount>l) l = spcount;
}
#ifdef COBJ_DEBUG;
print "[scored ", spcount, "]^";
#endif;
return l;
];
[ CreateAltList i;
! we copy the whole scope into the alt_match_list
LoopOverScope(AddToAltMatch);
];
[ AddToAltMatch obj;
(alt_match_list-->0)++;
alt_match_list-->(alt_match_list-->0) = obj;
];
[ CopyMultToMatch i;
for (i = 0: i < multiple_object-->0 : i++)
{ match_list-->i = multiple_object-->(i+1);
#ifdef COBJ_DEBUG;
if (i > 0) print (the) match_list-->i, " / ";
#endif;
}
return multiple_object-->0;
];
[ CopyMultipleObjectList i;
for (i = 0: i<=multiple_object-->0 : i++)
{ alt_match_list-->i = multiple_object-->i;
#ifdef COBJ_DEBUG;
if (i > 0) print (the) alt_match_list-->i, " / ";
#endif;
}
! new_line;
];
-) instead of "Choose Objects" in "Parser.i6t".
Chapter - should the game choose
Include (-
[ SmartScoreDabCombo a b result;
if (guessing) rfalse;
@push action; @push act_requester; @push noun; @push second;
action = action_to_be;
act_requester = player;
if (action_reversed) { noun = b; second = a; }
else { noun = a; second = b; }
result = SmartCheckDPMR();
#ifdef COBJ_DEBUG;
print "[smart: ", (the) noun, " / ", (the) second, " => ", result, "]^";
#endif;
@pull second; @pull noun; @pull act_requester; @pull action;
return result;
];
[ SmartCheckDPMR result sinp1 sinp2 rv;
sinp1 = inp1; sinp2 = inp2; inp1 = noun; inp2 = second;
rv = FollowRulebook( (+ should the game choose rules+) );
inp1 = sinp1; inp2 = sinp2;
if (RulebookSucceeded()) { ! was (rv) &&
result = ResultOfRule();
if (result == (+ it is an excellent choice outcome +) ) return 9;
if (result == (+ it is a good choice outcome +) ) return 8;
if (result == (+ it is a passable choice outcome +) ) return 7;
! if (result == (+ it is possible outcome +) ) return 6;
if (result == (+ never outcome +) ) return -1;
}
rfalse;
];
-).
Chapter - Should the game suggest
Include (-
[ GuessScoreDabCombo a b context varying_item result;
@push action; @push act_requester; @push noun; @push second;
action = action_to_be;
act_requester = player;
if (action_reversed) { noun = b; second = a; }
else { noun = a; second = b; }
result = GuessCheckDPMR();
if (context == CREATURE_TOKEN)
{
#ifdef COBJ_DEBUG;
print "^(checking a creature context with v-i ", varying_item, ")^";
#endif;
if ( (varying_item == 1 && a has animate)
|| (varying_item == 2 && b has animate)
)
{
#ifdef COBJ_DEBUG;
print "(adjudicating creature context from ", result, " up to ", MaxOf(result, 3), ".)^";
#endif;
result = MaxOf(result, 3);
}
}
#ifdef COBJ_DEBUG;
print "[guess: ", (the) noun, " / ", (the) second, " => ", result, "]^";
#endif;
@pull second; @pull noun; @pull act_requester; @pull action;
return result;
];
[ MaxOf a b;
if (a > b) return a; return b;
];
[ GuessCheckDPMR result sinp1 sinp2 rv;
! if (inp1 == 0 && inp2 ~= 0 && multiple_object-->0 > 0) print "(Made a multiple list!)^";
sinp1 = inp1; sinp2 = inp2; inp1 = noun; inp2 = second;
rv = FollowRulebook( (+Should the game suggest rules+) );
inp1 = sinp1; inp2 = sinp2;
if (RulebookSucceeded()) { ! was (rv) &&
result = ResultOfRule();
if (result == (+ it is an excellent suggestion outcome +) ) return 4;
if (result == (+ it is a good suggestion outcome +) ) return 3;
if (result == (+ it is a passable suggestion outcome +) ) return 2;
if (result == (+ never outcome +) )
{
if (~~guessing) return -2; ! only ever return this when not guessing.
! Otherwise, same as impossible, which falls through
}
}
! so either we had no information or the (result == (+ it is a bad suggestion outcome +) )
! if guessing, we want no list and no default
if (guessing) return -1;
! if not guessing, we want list and default but not good ones.
! recall that this is the bottom of the scoring tree so we can afford to give it a score.
return 1;
];
-);
[version 10 -- removed this, since it has never worked, and it is interfering with calls to "yes" and "no" elsewhere in the code]
[Chapter - bypass disambiguate
[
Bypass disambiguation rules allow us to use the old parser mechanism of favouring held objects in cases where it's faster and less annoying.
By default we never do this.
Bypass disambiguation when smelling a rose: yes.
[ note: actually, this doesn't yet work, because it doesn't copy in the action pattern. TO DO! ]
]
The bypass disambiguation rules are a rulebook.
The bypass disambiguation rules have outcomes yes and no.
Include(-
[ ChooseObjectsBypassDisambiguate rv;
rv = FollowRulebook( (+bypass disambiguation rules+) );
if (RulebookSucceeded() && ResultOfRule() == (+yes outcome+))
{
! print "(bypass disambig: Rulebook succeeded.)^";
rfalse;
}
! print "(bypass disambig: Rulebook not succeeded.)^";
rtrue;
];
-).]
Chapter - we resolve scores from the three types of test
Include
(-
[ ResolveChooseScores expectation_score preference_score;
! print "Resolving: ", noun_set_score, " / ", expectation_score , " / ", preference_score, "^";
! if guessing, this is easy
if (guessing)
{
if (expectation_score >= 3) (+list-outcomes+) = true; ! we've scored high enough to list what we found
return expectation_score;
}
! otherwise, we take individual score, then noun subset score, then guess score
if (expectation_score == -2) return -1;
! if the combination is declared "always impossible"
! this takes absolute priority over everything else and we delete
if (expectation_score > 0) ! 0 => it was a "bad" suggestion, which isn't good enough to print a list
(+list-outcomes+) = true; ! this is automatically high enough to qualify now, because either we found a preference rule
! (okay, so later these might include "unlikely" but they don't for now)
! or we found bad-at-worst, which we still list
if (preference_score ~= 0)
return preference_score; ! preference_score =-1 is a good return score for individual stuff
if (expectation_score ~= -1) return expectation_score;
! otherwise we use the guess score, but we never delete!
rfalse; ! so we tell the score system nothing
];
-)
Disambiguation Control ends here.