Interactive Parsing

version 3 by Jon Ingold

  • Home page
  • Beginning
  • Previous



  • Section - Letter F

    Include (-

    advance_warning = -1;

    ! ------------IP CHANGE HERE-----------------
    if (pre_command == INACTIVE)
    {
    ! ------------IP CHANGE DONE-----------------

    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
    k = parser_results-->INP1_PRES; @push k; @push parameters;
    parameters = 1; parser_results-->INP1_PRES = 0;
    l = NounDomain(actors_location, actor, NOUN_TOKEN);
    @pull parameters; @pull k; parser_results-->INP1_PRES = k;
    #Ifdef DEBUG;
    if (parser_trace >= 2) {
    print " [Advanced to ~noun~ token: ";
    if (l == REPARSE_CODE) print "re-parse request]^";
    else {
    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;
    }
    }
    }

    ! ------------IP CHANGE HERE-----------------
    }
    ! ------------IP CHANGE DONE-----------------

    ! 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 3 - Tokens

    Include (-
    for (pcount=1 : : pcount++) {
    pattern-->pcount = PATTERN_NULL; scope_token = 0;

    token = line_token-->(pcount-1);
    lookahead = line_token-->pcount;

    #Ifdef DEBUG;
    if (parser_trace >= 2)
    print " [line ", line, " token ", pcount, " word ", wn, " : ", (DebugToken) token,
    "]^";
    #Endif; ! DEBUG

    if (token ~= ENDIT_TOKEN) {

    ! ------------IP CHANGE HERE-----------------
             MatchUpTo(wn-1, EX_NOUN, "Parse Token going round the loop."); ! we've gotten this far, right?
    ! ------------IP CHANGE DONE-----------------

    scope_reason = PARSING_REASON;
    AnalyseToken(token);

    l = ParseToken(found_ttype, found_tdata, pcount-1, token);
    while ((l >= GPR_NOUN) && (l < -1)) l = ParseToken(ELEMENTARY_TT, l + 256);

    scope_reason = PARSING_REASON;

    if (l == GPR_PREPOSITION) {
    if (found_ttype~=PREPOSITION_TT && (found_ttype~=ELEMENTARY_TT ||
    found_tdata~=TOPIC_TOKEN)) params_wanted--;

    l = true;
    }
    else
    if (l < 0) l = false;
    else
    if (l ~= GPR_REPARSE) {
    if (l == GPR_NUMBER) {
    if (nsns == 0) special_number1 = parsed_number;
    else special_number2 = parsed_number;
    nsns++; l = 1;
    }
    if (l == GPR_MULTIPLE) l = 0;
    parser_results-->(parameters+INP1_PRES) = l;
    parameters++;
    pattern-->pcount = l;
    l = true;
    }

    #Ifdef DEBUG;
    if (parser_trace >= 3) {
    print " [token resulted in ";
    if (l == REPARSE_CODE) print "re-parse request]^";
    if (l == 0) print "failure with error type ", etype, "]^";
    if (l == 1) print "success]^";
    }
    #Endif; ! DEBUG

    if (l == REPARSE_CODE) jump ReParse;
    if (l == false) break;
    }
    else {

    ! If the player has entered enough already but there's still
    ! text to wade through: store the pattern away so as to be able to produce
    ! a decent error message if this turns out to be the best we ever manage,
    ! and in the mean time give up on this line

    ! However, if the superfluous text begins with a comma or "then" then
    ! take that to be the start of another instruction

    if (wn <= num_words) {
    l = NextWord();
    if (l == THEN1__WD or THEN2__WD or THEN3__WD or comma_word) {
    held_back_mode = 1; hb_wn = wn-1;
    }
    else {
                

                MatchUpTo(wn-2, EX_ENDOFLINE, "Grammar line's run out but text hasn't");
                ! why is it -2? Because we've just done a NextWord on the line above ( +1) and we're on a word that didn't match ( + 1). So we matched 2.
                
    for (m=0 : m<32 : m++) pattern2-->m = pattern-->m;
    pcount2 = pcount;
    etype = UPTO_PE;
    break;
    }
    }

    ! Now, we may need to revise the multiple object because of the single one
    ! we now know (but didn't when the list was drawn up).

    if (parameters >= 1 && parser_results-->INP1_PRES == 0) {
    l = ReviseMulti(parser_results-->INP2_PRES);
    if (l ~= 0) { etype = l; parser_results-->ACTION_PRES = action_to_be; break; }
    }
    if (parameters >= 2 && parser_results-->INP2_PRES == 0) {
    l = ReviseMulti(parser_results-->INP1_PRES);
    if (l ~= 0) { etype = l; break; }
    }

    ! To trap the case of "take all" inferring only "yourself" when absolutely
    ! nothing else is in the vicinity...

    if (take_all_rule == 2 && parser_results-->INP1_PRES == actor) {
    best_etype = NOTHING_PE;
    jump GiveError;
    }

    #Ifdef DEBUG;
    if (parser_trace >= 1) print "[Line successfully parsed]^";
    #Endif; ! DEBUG

    ! The line has successfully matched the text. Declare the input error-free...

    oops_from = 0;

    ! ...explain any inferences made (using the pattern)...
            
    if (inferfrom ~= 0) {
    PrintInferredCommand(inferfrom);
    ClearParagraphing();
    }

    ! ...copy the action number, and the number of parameters...

    parser_results-->ACTION_PRES = action_to_be;
    parser_results-->NO_INPS_PRES = parameters;

    ! ...reverse first and second parameters if need be...

    if (action_reversed && parameters == 2) {
    i = parser_results-->INP1_PRES;
    parser_results-->INP1_PRES = parser_results-->INP2_PRES;
    parser_results-->INP2_PRES = i;
    if (nsns == 2) {
    i = special_number1; special_number1 = special_number2;
    special_number2 = i;
    }
    }

    ! ...and to reset "it"-style objects to the first of these parameters, if
    ! there is one (and it really is an object)...

    if (parameters > 0 && parser_results-->INP1_PRES >= 2)
    PronounNotice(parser_results-->INP1_PRES);

    ! ...and return from the parser altogether, having successfully matched
    ! a line.

    if (held_back_mode == 1) {
    wn=hb_wn;
    jump LookForMore;
    }

    ! ------------IP CHANGE HERE-----------------
    MatchUpTo(wn-1, EX_ENDOFLINE, "Parser matched"); ! wn as next word has taken us past the end of input
    ! ------------IP CHANGE DONE-----------------

    rtrue;

    } ! end of if(token ~= ENDIT_TOKEN) else
    } ! end of for(pcount++)

    .LineFailed;
    ! The line has failed to match.
    ! We continue the outer "for" loop, trying the next line in the grammar.

    if (etype > best_etype) best_etype = etype;
    if (etype ~= ASKSCOPE_PE && etype > nextbest_etype) nextbest_etype = etype;

    ! ...unless the line was something like "take all" which failed because
    ! nothing matched the "all", in which case we stop and give an error now.

    if (take_all_rule == 2 && etype==NOTHING_PE) break;

    } ! end of for(line++)

    ! The grammar is exhausted: every line has failed to match.

    -) instead of "Parser Letter G" in "Parser.i6t".

    Section 4 - Errors

    Include (-

    ! If the player was the actor (eg, in "take dfghh") the error must be printed,
    ! and fresh input called for. In three cases the oops word must be jiggled.


    if ((etype ofclass Routine) || (etype ofclass String)) {
    if (ParserError(etype) ~= 0) jump ReType;
    } else {
            if (verb_wordnum == 0 && etype == CANTSEE_PE) etype = VERB_PE;
            players_command = 100 + WordCount(); ! The snippet variable ``player's command''
    BeginActivity(PRINTING_A_PARSER_ERROR_ACT);
    if (ForActivity(PRINTING_A_PARSER_ERROR_ACT)) jump SkipParserError;
    }
    pronoun_word = pronoun__word; pronoun_obj = pronoun__obj;

    if (etype == STUCK_PE) { L__M(##Miscellany, 27); oops_from = 1; }

    if (etype == UPTO_PE) { L__M(##Miscellany, 28);
    for (m=0 : m<32 : m++) pattern-->m = pattern2-->m;
    pcount = pcount2; PrintCommand(0); L__M(##Miscellany, 56);
    }
    if (etype == NUMBER_PE) L__M(##Miscellany, 29);
    if (etype == CANTSEE_PE) { L__M(##Miscellany, 30); oops_from=saved_oops; }
    if (etype == TOOLIT_PE) L__M(##Miscellany, 31);
    if (etype == NOTHELD_PE) { L__M(##Miscellany, 32); oops_from=saved_oops; }
    if (etype == MULTI_PE) L__M(##Miscellany, 33);
    if (etype == MMULTI_PE) L__M(##Miscellany, 34);
    if (etype == VAGUE_PE) L__M(##Miscellany, 35);
    if (etype == EXCEPT_PE) L__M(##Miscellany, 36);
    if (etype == ANIMA_PE) L__M(##Miscellany, 37);
    if (etype == VERB_PE)
        {

    ! ------------IP CHANGE HERE-----------------
            MatchUpTo(0, EX_VERB, "Verb Error");
    ! ------------IP CHANGE DONE-----------------

            L__M(##Miscellany, 38);
        }
    if (etype == SCENERY_PE) L__M(##Miscellany, 39);
    if (etype == ITGONE_PE) {
    if (pronoun_obj == NULL)
    L__M(##Miscellany, 35);
    else L__M(##Miscellany, 40);
    }
    if (etype == JUNKAFTER_PE) L__M(##Miscellany, 41);
    if (etype == TOOFEW_PE) L__M(##Miscellany, 42, multi_had);
    if (etype == NOTHING_PE) {
    if (parser_results-->ACTION_PRES == ##Remove &&
    parser_results-->INP2_PRES ofclass Object) {
    noun = parser_results-->INP2_PRES; ! ensure valid for messages
    if (noun has animate) L__M(##Take, 6, noun);
    else if (noun hasnt container or supporter) L__M(##Insert, 2, noun);
    else if (noun has container && noun hasnt open) L__M(##Take, 9, noun);
    else if (children(noun)==0) L__M(##Search, 6, noun);
    else parser_results-->ACTION_PRES = 0;
    }
    if (parser_results-->ACTION_PRES ~= ##Remove) {
    if (multi_wanted==100) L__M(##Miscellany, 43);
    else L__M(##Miscellany, 44);
    }
    }
    if (etype == ASKSCOPE_PE) {
    scope_stage = 3;
    if (indirect(scope_error) == -1) {
    best_etype = nextbest_etype;
    if (~~((etype ofclass Routine) || (etype ofclass String)))
    EndActivity(PRINTING_A_PARSER_ERROR_ACT);
    jump GiveError;
    }
    }
    if (etype == NOTINCONTEXT_PE) L__M(##Miscellany, 73);

    .SkipParserError;
    if ((etype ofclass Routine) || (etype ofclass String)) jump ReType;
    say__p = 1;
    EndActivity(PRINTING_A_PARSER_ERROR_ACT);

    -) instead of "Parser Letter I" in "Parser.i6t".

    Include (-
    ! And go (almost) right back to square one...

    ! ------------IP CHANGE HERE-----------------
        if (pre_command == PERFORMING)
            pre_command = ERROR_GIVEN;
    ! ------------IP CHANGE DONE-----------------

    jump ReType;

    ! ...being careful not to go all the way back, to avoid infinite repetition
    ! of a deferred command causing an error.
    -) instead of "Parser Letter J" in "Parser.i6t".

    Section - Letter K


    Include (-
    ! At this point, the return value is all prepared, and we are only looking
    ! to see if there is a "then" followed by subsequent instruction(s).

    .LookForMore;

    if (wn > num_words)
        {

    ! ------------IP CHANGE HERE-----------------
            MatchUpTo(num_words, EX_ENDOFLINE, "We've run out of text to read");
    ! ------------IP CHANGE DONE-----------------

         rtrue;
        }

    i = NextWord();
    if (i == THEN1__WD or THEN2__WD or THEN3__WD or comma_word) {
    if (wn > num_words) {
    held_back_mode = false;
    return;
    }
    i = WordAddress(verb_wordnum);
    j = WordAddress(wn);
    for (: i<j : i++) i->0 = ' ';
    i = NextWord();
    if (i == AGAIN1__WD or AGAIN2__WD or AGAIN3__WD) {
    ! Delete the words "then again" from the again buffer,
    ! in which we have just realised that it must occur:
    ! prevents an infinite loop on "i. again"

    i = WordAddress(wn-2)-buffer;
    if (wn > num_words) j = INPUT_BUFFER_LEN-1;
    else j = WordAddress(wn)-buffer;
    for (: i<j : i++) buffer3->i = ' ';
    }
    VM_Tokenise(buffer,parse);
    held_back_mode = true;
    return;
    }

    best_etype = UPTO_PE;
    jump GiveError;

    -) instead of "Parser Letter K" in "Parser.i6t".

    Chapter 2 - Parse Token


    Section - Parse Token A

    Include (-

    token_filter = 0;
    parser_inflection = name;

    switch (given_ttype) {
    ELEMENTARY_TT:
    switch (given_tdata) {
    SPECIAL_TOKEN:
    l = TryNumber(wn);
    special_word = NextWord();
    #Ifdef DEBUG;
    if (l ~= -1000)
    if (parser_trace >= 3) print " [Read special as the number ", l, "]^";
    #Endif; ! DEBUG
    if (l == -1000) {
    #Ifdef DEBUG;
    if (parser_trace >= 3) print " [Read special word at word number ", wn, "]^";
    #Endif; ! DEBUG
    l = special_word;
    }
    parsed_number = l;
    return GPR_NUMBER;

    NUMBER_TOKEN:
    l=TryNumber(wn++);
    if (l == -1000) {
    etype = NUMBER_PE;
    return GPR_FAIL;
    }
    #Ifdef DEBUG;
    if (parser_trace>=3) print " [Read number as ", l, "]^";
    #Endif; ! DEBUG
    parsed_number = l;
    return GPR_NUMBER;

    CREATURE_TOKEN:
    if (action_to_be == ##Answer or ##Ask or ##AskFor or ##Tell)
    scope_reason = TALKING_REASON;

    TOPIC_TOKEN:
    consult_from = wn;
    if ((line_ttype-->(token_n+1) ~= PREPOSITION_TT) &&
    (line_token-->(token_n+1) ~= ENDIT_TOKEN))
    RunTimeError(13);
    do o = NextWordStopped();
    until (o == -1 || PrepositionChain(o, token_n+1) ~= -1);
    wn--;
    consult_words = wn-consult_from;
    if (consult_words == 0) return GPR_FAIL;
    if (action_to_be == ##Ask or ##Answer or ##Tell) {
    o = wn; wn = consult_from; parsed_number = NextWord();
    wn = o; return 1;
    }
    if (o==-1 && (line_ttype-->(token_n+1) == PREPOSITION_TT))
    return GPR_FAIL; ! don't infer if required preposition is absent
    return GPR_PREPOSITION;
    }

    PREPOSITION_TT:
    ! Is it an unnecessary alternative preposition, when a previous choice
    ! has already been matched?
    if ((token->0) & $10) return GPR_PREPOSITION;

    ! If we've run out of the player's input, but still have parameters to
    ! specify, we go into "infer" mode, remembering where we are and the
    ! preposition we are inferring...

    if (wn > num_words) {
    if (inferfrom==0 && parameters<params_wanted) {
    inferfrom = pcount; inferword = token;
    pattern-->pcount = REPARSE_CODE + VM_DictionaryAddressToNumber(given_tdata);
    }

    ! If we are not inferring, then the line is wrong...

    if (inferfrom == 0)
    {
    ! this is where we end up when typing "PUT BOOK". How do we
    ! know we're not going to infer?
    return -1;
    }

    ! If not, then the line is right but we mark in the preposition...

    pattern-->pcount = REPARSE_CODE + VM_DictionaryAddressToNumber(given_tdata);
    return GPR_PREPOSITION;
    }

    o = NextWord();

    pattern-->pcount = REPARSE_CODE + VM_DictionaryAddressToNumber(o);

    ! Whereas, if the player has typed something here, see if it is the
    ! required preposition... if it's wrong, the line must be wrong,
    ! but if it's right, the token is passed (jump to finish this token).

    if (o == given_tdata)
        {


    ! ------------IP CHANGE HERE-----------------
    !glk_set_window((+main-window+).ref_number);
    !print "Matched a solo preposition : word num = ", wn-1, ".^";
            MatchUpTo(wn - 1, EX_NOUN, "Matched solo prep.^");
    ! ------------IP CHANGE DONE-----------------


            return GPR_PREPOSITION;
        }
    if (PrepositionChain(o, token_n) ~= -1)
        {

    ! ------------IP CHANGE HERE-----------------
    !glk_set_window((+main-window+).ref_number);
    !print "Matched a alternate preposition : word num = ", wn-1, ".^";
            MatchUpTo(wn - 1, EX_NOUN, "Matched chained prep.^");
    ! ------------IP CHANGE DONE-----------------

            return GPR_PREPOSITION;
        }
    return -1;

    GPR_TT:
    l = indirect(given_tdata);
    #Ifdef DEBUG;
    if (parser_trace >= 3) print " [Outside parsing routine returned ", l, "]^";
    #Endif; ! DEBUG
    return l;

    SCOPE_TT:
    scope_token = given_tdata;
    scope_stage = 1;
    #Ifdef DEBUG;
    if (parser_trace >= 3) print " [Scope routine called at stage 1]^";
    #Endif; ! DEBUG
    l = indirect(scope_token);
    #Ifdef DEBUG;
    if (parser_trace >= 3) print " [Scope routine returned multiple-flag of ", l, "]^";
    #Endif; ! DEBUG
    if (l == 1) given_tdata = MULTI_TOKEN; else given_tdata = NOUN_TOKEN;

    ATTR_FILTER_TT:
    token_filter = 1 + given_tdata;
    given_tdata = NOUN_TOKEN;

    ROUTINE_FILTER_TT:
    token_filter = given_tdata;
    given_tdata = NOUN_TOKEN;

    } ! end of switch(given_ttype)

    token = given_tdata;

    -) instead of "Parse Token Letter A" in "Parser.i6t".

    Section - Parse Token F

    [ NOTE, it would appear there's actually no changes in this section at all. ]

    Include (-

    ! Happy or unhappy endings:

    .PassToken;

    if (many_flag) {
    single_object = GPR_MULTIPLE;
    multi_context = token;
    }
    else {
    if (indef_mode == 1 && indef_type & PLURAL_BIT ~= 0) {
    if (indef_wanted < INDEF_ALL_WANTED && indef_wanted > 1) {
    multi_had = 1; multi_wanted = indef_wanted;
    etype = TOOFEW_PE;
    jump FailToken;
    }
    }
    }
    return single_object;

    .FailToken;

    ! If we were only guessing about it being a plural, try again but only
    ! allowing singulars (so that words like "six" are not swallowed up as
    ! Descriptors)

    if (allow_plurals && indef_guess_p == 1) {
    #Ifdef DEBUG;
    if (parser_trace >= 4) print " [Retrying singulars after failure ", etype, "]^";
    #Endif;
    prev_indef_wanted = indef_wanted;
    allow_plurals = false;
    wn = desc_wn;

    jump TryAgain;
    }

    if ((indef_wanted > 0 || prev_indef_wanted > 0) && (~~multiflag)) etype = MULTI_PE;

    ! ------------IP CHANGE HERE-----------------
    ! - NOTE, it would appear there's actually no changes in this section at all.

        ! MatchUpTo(wn - 2, EX_NOUN, "Parser token failed (using wn-2. Is this arbitrary?)");
        ! why -2? Assumes we've failed on the first word (I think this is true, otherwise we get an "UPTO" error instead.
    ! ------------IP CHANGE DONE-----------------

    return GPR_FAIL;

    ]; ! end of ParseToken__


    -) instead of "Parse Token Letter F" in "Parser.i6t"

    Section 2 - Descriptors

    Include (-
    [ Descriptors o x flag cto type n;
    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 = INDEF_ALL_WANTED;
    if (take_all_rule == 1) take_all_rule = 2;
    indef_type = indef_type | PLURAL_BIT;
    }
    if (allow_plurals) {
    if (NextWordStopped() ~= -1) { wn--; n = TryNumber(wn-1); } else { n=0; wn--; }
    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 (flag == 1 && NextWordStopped() ~= OF1__WD or OF2__WD or OF3__WD or OF4__WD)
    wn--; ! Skip 'of' after these
    }
    wn--;

    ! ------------IP CHANGE HERE-----------------
        MatchUpTo(wn-1, EX_NOUN, "Descriptors");
    ! ------------IP CHANGE DONE-----------------


    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 1 - NounDomain


    Include (-

    [ MatchUpTo wordnum type debugprint;
        if (type == EX_ENDOFLINE && inferfrom == 0)
            command_in_full = true;
        if (word_understood_to < wordnum || type == EX_VERB)
        {
            #ifdef DEBUG_ADVANCE_PARSING;
                glk_set_window( (+main-window+).ref_number );
                print "Establishing ", wordnum, " words and token type ", type, ".^";
                print (string) debugprint, "^";
            #endif;
            
            word_understood_to = wordnum;
            next_token_type = type;
        }
    ];

    [ 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; }

    ! ------------IP CHANGE HERE-----------------
    if (number_matched > 1 && pre_command == PERFORMING)
    {
            !glk_set_window((+main-window+).ref_number);
            !print "Multilmatch : wn = ", wn, " but we record wn-1 = ", wn-1, ".^";

            MatchUpTo(wn-1, EX_PREPOSITION, "Multiple match in NounDomain. We return the first and move on.");
            return match_list-->0;
    }
    ! ------------IP CHANGE DONE-----------------

    ! 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)
                {

    ! ------------IP CHANGE HERE-----------------

                        !glk_set_window((+main-window+).ref_number);
                        !print "Splurge text on end of line : wn = ", wn, " but we record wn-2 = ", wn-2, ".^";

                    MatchUpTo(wn-1, EX_ENDOFLINE, "End of text input (Noun domain)");
    ! ------------IP CHANGE DONE-----------------

                    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.)

    ! ------------IP CHANGE HERE-----------------

    #ifdef DEBUG_ADVANCE_PARSING;
        glk_set_window((+main-window+).ref_number);
        print "Noun Domain: matched ", (the) i, " with wn = ", wn-1, ". Inferfrom == ", inferfrom, " or ", pcount, "?";
    #endif;

         MatchUpTo(wn-1, EX_NOUN, "NounDomain"); ! really? or preposition?

    ! ------------IP CHANGE DONE-----------------

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

    ! ------------IP CHANGE HERE-----------------
        next_expected_word = i;
    ! ------------IP CHANGE DONE-----------------

    return i;
    }


    ! ------------IP CHANGE HERE-----------------
            if (pre_command == PERFORMING) rfalse;
    ! ------------IP CHANGE DONE-----------------


    ! 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)
        {

    ! ------------IP CHANGE HERE-----------------
            MatchUpTo(num_words, EX_NOUN, "ND: This one's never reached.");
    ! ------------IP CHANGE DONE-----------------

            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;

    ! ------------IP CHANGE HERE-----------------
    ! Under IP, we don't do questions in this way. Instead, we record where in the input line the player's text is going to
    ! go, and then we give them an input line to edit

        if ((+current text input window+) == (+key-window+))
        {
            CreateEditPoint(CharacterNumber(match_from) - 1);
            pre_command = RETURN_TO_INPUT_LINE;
            return REPARSE_CODE;
        }

    ! ------------IP CHANGE DONE-----------------


    #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);

    ! ------------IP CHANGE HERE-----------------
    ! Under IP, we don't do questions in this way. Instead, we record where in the input line the player's text is going to
    ! go, and then we give them an input line to edit

        if ((+current text input window+) == (+key-window+))
        {
            ! we had a sneaky line break.
            print "^";
            CreateEditPoint((buffer-->0) + 1);
            pre_command = RETURN_TO_INPUT_LINE;
            return REPARSE_CODE;
        }



    ! ------------IP CHANGE DONE-----------------



    #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".


    Section - Error Reporting

    Include (-

    [ L__M act n x1 x2 rv flag;

    ! ------------IP CHANGE HERE-----------------
    #ifndef SHOW_PARSER_ERRORS;
    if (pre_command == INACTIVE)
    {
    #endif;

    ! ------------IP CHANGE DONE-----------------

    @push sw__var;
    sw__var = act;
    if (n == 0) n = 1;
    @push action;
    lm_act = act;
    lm_n = n;
    lm_o = x1;
    lm_o2 = x2;
    switch (act) {
    ##Miscellany: rv = (+ whether or not intervened in miscellaneous message +);
    ##ListMiscellany: rv = (+ whether or not intervened in miscellaneous list message +);
    default: rv = (+ whether or not intervened in action message +);
    }
    action = sw__var;
    if (rv == false) rv = RunRoutines(LibraryMessages, before);
    @pull action;
    if (rv == false) LanguageLM(n, x1, x2);
    @pull sw__var;

    ! ------------IP CHANGE HERE-----------------
    #ifndef SHOW_PARSER_ERRORS;
    }
    #endif;
    ! ------------IP CHANGE DONE-----------------

    ];

    -) instead of "Printing Mechanism" in "Language.i6t".



    Interactive Parsing ends here.

    ---- DOCUMENTATION ----

    Chapter: Overview

    Section: What does this extension do?

    Interactive Parsing removes the traditional IF command prompt, in which the players types a command, hits return, and then gets feedback, in favour of a more modern, real-time command prompt, in which the player's command in constantly processed and interpreted while they are typing.

    The system colour-codes the words typed by the player to reflect if the game is understanding them. It also suggests corrections for words, if the player has mistyped, and these corrections are automatically written into the input line.


    Section: Why would I want to do this?

    The text-game interface is notoriously difficult for beginners to "get into". Part of the reason for this is poor feedback on misunderstood commands, coupled with the negative play experience of having a command not be understood. (The player, after all, knows English better than the computer; so it should be the computer's problem, and not the player's, to understand.)

    Interactive Parsing attempts to remove the frustration involved with finding and entering a successful command, by ensuring that players are discouraged to type commands that don't work, and pointed in the direction of commands that do.

    The design of the system follows that of search engines and mobile phone keyboards, with "opt-out" spell-correction rather than "opt-in", and predictive suggestions.

    Chapter: Getting Started

    Section: Setting Up

    To begin using Interactive Parsing, you will first need to download a copy of Erik Temple's extension, "Text Input-Output Control" from the following page:

        http://inform7.com/extensions/Erik%20Temple/Text%20Window%20Input-Output%20Control/index.html

    and also "Flexible Windows":

        http://inform7.com/extensions/Jon%20Ingold/Flexible%20Windows/index.html

    You can then simply include the Interactive Parsing extension at the top of your game-file. The system should automatically begin suggesting and correcting based on your game's dictionary.

    Section: Changing Input Mode

    You can toggle the input mode using the "inputchange" command. This flips the game between input modes. This command is available in the released game as well, but can be disabled by including the line:

        Understand the command "inputchange" as something new.

    Section: Disabling Interactive Parsing

    You can disable the extension easily by including the line:

        Use interactive parsing override.

    at the top of your game file - this is provided on an option because interactive parsing tends to run slowly in the I7 IDE, and it does not interact with the Skein and Transcript functions. So during development you may want to leave the features of Interactive Parser turned off, and include it only at the end of a project.

    You may well want to include the use option in a "(not for release)" section in your source code.


    Section: Glulx only

    Interactive Parsing only works for Glulx. While it might be possible to do a similar process for the Z-Machine, the lack of window control means the player would be entering text into the status line. This is unattractive, and Glulx is, these days, a fast, viable interpreter with web-based options with considerably more memory to spare.

    Section: Dictionary Size Constants

    The system requires a few constants to store information. These are set to sensible defaults, but if any are exceeded by a large game, the system should print a warning message on starting up with instructions on how to fix it.

    The most important, and most likely to be needed, is:

        Use maximum dictionary size of at least 1000.
        
    This tells the game how big the dictionary is.

    Less importantly, there is a constant to determine letter-by-letter storage:

        Use average word length of at least 6.
        
    It's unlikely you'll need to extend this (6 is already much large than the default dictionary average.)

    The game also stores, turn by turn, a list of words that are in scope, words that are verbs, and words that refer to visible items. The limits for the number of these can also be increased.

    Note that errors displayed for these will only occur if and when the limit is exceeded, so you will need to test the game fully to be sure they have not been reached!

        Use maximum words in scope of at least 256.
        Use maximum verb words of at least 300.
        Use maximum compass names of at least 35.


    Section: Overheads

    The system performs parsing and dictionary searches throughout the player's typing session, so it will inevitably slow down the player's ability to type into the game at times. However, the system has been heavily optimised, and in test cases runs with no more perceptible slowdown than a mobile-phone typing interface.

    The system may become more expensive in larger games, and the author would be interested to know if and when it becomes impractical. (Though note, testing in the IDE is not representative of testing in the released game.)

    The system also uses a lot of storage, but since Glulx has an enormous brain, this shouldn't cause too much of a problem.

    Section: Interaction with other extensions

    Note this extension replaces large amounts of the parser, so it may not work with other extensions which are designed to alter parsing behaviour. However, it should be easy to fix any conflicts. Refer to the extension authors, or the "Parser Rewrites" section below.

    Section: Credits

    Interactive Parsing is built out of two other, fine extensions - Text Input-Output Control by Erik Temple, which handles the separate input window (and is a required file to run this extensions) - and Mistype by Cedric Knight, from which several tips, tricks, algorithms and general inspiration have been drawn.

    This extension also relies on Basic Screen Effects by Emily Short, and Flexible Windows, by myself and Erik Temple.

    Section: Special Keys

    The following keys have particular behaviour in the interface. You may wish to document these for the user.

        Space - accept a suggestion if the typed word is not in the dictionary
        Tab - accept a suggestion regardless of what the player typed
        Up / Page Up - go back through command history
        Down / Home - go forward through command history
        Right / Left - move the cursor across the input line
        Escape - accept typed version word ignoring suggestions

    Chapter: Fine Tuning

    Section: Long Words

    By default, the Inform library truncates all dictionary words to 9 characters long. This can cause the suggestion system to accept, suggest and write into the transcript words which are cut-off, such as "ROADRUNNE" or "MISCHEVIO". To correct this, we need to tell the library what these words look like, in full. This is done using a table, which is already defined in the extension to cover long library words (such as "SUPERBRIEF" and "TWENTY-ONE"). It has one column, "word", in which we put the (text string) of the word in full. Don't forget, this is a "continuation" of a table, not a new one.

        Table of Longer Words (continued)
        word
        "mischievous"
        "roadrunner"

    Note if the game contains two long words which share the first nine-letters, this won't work, and can't be worked around. Apologies, but there's just no data in the compiled game-file to detect this.

    To help you find words that need a longer definition, the use option "long word check report" is provided. Compiling with this option enabled will cause the game to list on start-up all the long words in the dictionary which don't have an alternative in table. Some will be internal names (such as "main-window" and "key-window", which are the names of the Glulx window objects); and some will be valid 9-letter words 9such as "southeast" and "southwest"). But any that appear cut-off here and can be entered by the player should be given a table entry.

    Release versions will never print this text, which is for debugging only.


    Section: Boosting Words

    Sometimes, the system will find two or more words that are equally good matches. We can force some suggestions to take priority. (Note, this shouldn't be used much, as there's no support for any contextual data. I've mainly implemented it to ensure "DROP" wins over "DRAG". But here's how it works, in case you need it.)

    The game contains a table called the "Table of Useful Words". By default it looks as follows:

        Table of Useful Words
        word name
        "look"
        "drop"
        "close"
        "attack"

    The defaults exist to promote LOOK over LOCK, DROP over DRAG, CLOSE over CLEAN and CLIMB, ATTACK over ATTACH.

    By default, when two equal-length words match the player's input (for example, CL matches CLEAN and CLIMB, and LO matches LOOK and LOCK), the system will pick the first one alphabetically. To change this, add a word to the table above. This word will then score a one-point bonus ever tme it's matched.



    Section: Single-Letter Verb Associations

    To improve speed, the game has a fixed table mapping single keystrokes to verbs (since these are always context-independent). This table is called the Table of Single-Letter Verb Associations, and it looks as follows:

        Table of Single-Letter Verb Associations
        ascii value letter text
        97 "a" "attack"
        98 "b" "buy"
        99 "c" "cut"
        100 "d" "down"
        101 "e" "east"
        102 "f" "feel"
        103 "g" "get"
        104 "h" "hello"
        105 "i" "inventory"
        106 "j" "jump"
        107 "k" "kick"
        108 "l" "look"
        109 "m" "move"
        110 "n" "north"
        111 "o" "open"
        112 "p" "put"
        113 "q" "quit"
        114 "r" "read"
        115 "s" "south"
        116 "t" "take"
        117 "u" "up"
        118 "v" "version"
        119 "w" "west"
        120 "x" "examine"
        121 "y" "yes"
        122 "z" "wait"

    If your game features different verbs, or you disagree with any of the key-mappings provided, you can change this at the start of the game using a When play begins rule:

        When play begins:
            now the text corresponding to a letter of "a" in the Table of Single-Letter Verb Associations is "admonish".

    Chapter: How it Works

    Section: Continuous Tokenisation and Colour-Coding

    At its heart, Interactive Parsing works by reanalysing the input buffer after every keystroke and tokenising what it finds. (Tokenising means, looking up in the game dictionary). Tokenisation is fast, and returns information on whether the typed words are recognised or not. This information is turned into colour-coding and relayed back to the player as colour. This is the simplest level of analysis.

    We also run the full game parser on what the player has typed, and record how many words of the input are successfully matched. If the whole line is understood, the "Press Return" text is displayed on the input line. Otherwise, the number of words understood is stored to be used as a benchmark for suggested word alternatives.

    Section: Near-Matches, Mistypes and Suggestions

    When the player has typed a word which is not understood, the game attempts to deduce if this is a mistype, or the beginning of, an expected dictionary word. If one can be found which is close to what the player has typed in, it will be suggested as an alternative by the correction/prediction system.

    The algorithm for comparing typed input against dictionary words uses a pre-analysis system for additional speed, based on prime numbers. A quick explanation follows, because I think it's quite clever.

    The algorithm aims to score how close a typed word (say, "XAEMI") is to a dictionary word ("EXAMINE"). The first version of this algorithm looped through the typed letters, then for each of these through the dictionary letters, and if a matching letter was found, the word was scored based on the distance between the typed and dictionary positions. (This is the algorithm used by the Mistype extension.)

    However, this meant two full loops as well as printing every dictionary word to a text buffer before every comparison.

    The second version of this algorithm uses a preprocess of the dictionary. While the game is starting up, it loops through each word in the dictionary and converts each letter into a prime number (A = 2, B = 3, C = 5, up to Z = 101). It then multiplies the prime-values of the word in blocks of three and stores these in a large look-up table.

    So for example,

        EXAMINE

    becomes

        11, 89, 2, 41, 23, 43, 11

    giving the set of multiples:

        979, 1958, 7298, 1886, 40549, 10879, 473

    When the game then comes to check this against what the player types, all it needs to do is loop through the typed letters and see which of their prime-values divide into which of these multiples. This saves printing the word to the buffer, and only requires use to loop over the local neighbourhood of each typed letter.

    Section: Pre-parsing

    When a new close word has been found, the game creates a copy of the input buffer as if the player had typed in their input with this new suggestion. The full game parser is then run on this input, and we record how many words it matches in the player's input.

    If more words are matched than were with what the player actually typed, this suggestion is considered valid and it will be offered to the player, unless one can be found which is closer to what the player typed.

    For speed, the system checks sensible words first: words associated with visible objects and prepositions. At the start of the input line, it will only check verbs and compass directions.

    Results of pre-parsing are cached so the parser does not need to be run again over the same suggested input (if, say, the player types in one additional letter).

    However, pre-parsing is still the slowest part of the process; and when the game cannot find any matches for what the player has typed it gets very slow, as the game must try a very large number of dictionary words. To resolve this problem, we use a threading system.


    Section: Threaded Input and Background Word-checking

    The extension makes use of Glulx's timer capability to provide background processing on the player's input.

    When any new letter is typed, an suggestion/predictive analysis is begun, but while it executes it checks (every hundred cycles or so) to see if a key has been pressed. If it has, analysis exits, the keypress is dealt with, and the new data is used to either continue or restart the analysis process.

    This isn't as smooth as I'd like - Glulx cannot check to see if a key has been pressed without imposing at least a 1 millisecond time delay, so it cannot check for keypresses in a fluid way, but has to do so at set intervals.

    Section: Further Optimisation

    The next biggest saving would probably be to start caching the Scope Lists produced by the parser, as these are recalculated for every new word tried in a certain position on the input line. However, this would require a fair bit more parser hacking.

    Chapter: Changelog and Known Issues

    Section: Changelog

    Version 3:
        * Added functionality to correctly detect, suggest and write in words of more than 9 letters, using the Table of Longer Words.
        * Added support for talking to animate characters and providing them with instructions. The system now correctly suggests and parses this kind of input, although note it will not suggest the comma after the name of the animate thing. (It would be good if it did, maybe, but the player has either typed the comma or not, so the game doesn't have much information to go on.)

    Version 2:
        * Added command line recall (up/down arrow keys)
        * Added command line editing (left/right arrow keys)
        * Added handling for disambiguating input, by recalling the previous command line and placing the cursor at the appropriate edit point (!)

    Section: Known Issues

    Interactive Parsing fails on mobile devices running Quixe.

    This extension is incompatible with any other that alters any parsing routines (including Disambiguation Control). However, the extension attempts to clearly mark what parser changes have been made, so it should be possible to upgrade other extensions to achieve compatibility.

    Section: Parser Rewrites

    All parser rewrites are in Book 5 and are flagged by the comments:

        ! ------------IP CHANGE HERE-----------------

        ! ------------IP CHANGE DONE-----------------

    At last count, there are 36 changes, most consisting of a single additional line.