Original Parser

version 1 by Ron Newcomb

  • Home page
  • Beginning
  • Previous
  • Next



  • Chapter - original

    [ Parser__parse
        syntax line num_lines line_address i j k token l m;

        return ((+ Parser__parse +)-->1)();

        cobj_flag = 0;
        parser_results-->ACTION_PRES = 0;
        parser_results-->NO_INPS_PRES = 0;
        parser_results-->INP1_PRES = 0;
        parser_results-->INP2_PRES = 0;
        meta = false;


    !Chapter - Parser Letter A

    ![Get the input, do OOPS and AGAIN.]

    !Include (-
    if (held_back_mode == 1) {
    held_back_mode = 0;
    VM_Tokenise(buffer, parse);
    jump ReParse;
    }

    .ReType; ! while the rule failed.... test-at-bottom loop

    cobj_flag = 0;
    actors_location = ScopeCeiling(player);
    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; ! if reject the player's command,

    .ReParse;

    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) {
    L__M(##Miscellany, 20); ! "To repeat a command like ~frog, jump~, just say ~again~, not ~frog, again~."
    jump ReType;
    }
    #Ifdef TARGET_ZCODE;
    if (buffer3->1 == 0) {
    L__M(##Miscellany, 21); ! "You can hardly repeat that."
    jump ReType;
    }
    #Ifnot; ! TARGET_GLULX
    if (buffer3-->0 == 0) {
    L__M(##Miscellany, 21); ! "You can hardly repeat that."
    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-->ACTION_PRES = action;
    parser_results-->NO_INPS_PRES = 0;
    parser_results-->INP1_PRES = noun;
    parser_results-->INP2_PRES = second;
    if (noun) parser_results-->NO_INPS_PRES = 1;
    if (second) parser_results-->NO_INPS_PRES = 2;
    rtrue;
    }
    if (i ~= 0) { verb_word = i; wn--; verb_wordnum--; }
    else { wn = verb_wordnum; verb_word = NextWord(); }
    }
    else usual_grammar_after = 0;


    !Chapter - Parser Letter B

    ![Is the command a direction name, and so an implicit GO? If so, go to (K).]

    !Include (-
    #Ifdef LanguageIsVerb;
    if (verb_word == 0) {
    i = wn; verb_word = LanguageIsVerb(buffer, parse, verb_wordnum);
    wn = i;
    }
    #Endif; ! LanguageIsVerb

    ! If the first word is not listed as a verb, it must be a direction
    ! or the name of someone to talk to

    if (verb_word == 0 || ((verb_word->#dict_par1) & (+ being a verb +)) == 0) {

    ! So is the first word an object contained in the special object "compass"
    ! (i.e., a direction)? This needs use of NounDomain, a routine which
    ! does the object matching, returning the object number, or 0 if none found,
    ! or REPARSE_CODE if it has restructured the parse table so the whole parse
    ! must be begun again...

    wn = verb_wordnum; indef_mode = false; token_filter = 0; parameters = 0;
    @push actor; @push action; @push action_to_be;
    actor = player; meta = false; action = ##Go; action_to_be = ##Go;
    l = NounDomain(compass, 0, 0);
    @pull action_to_be; @pull action; @pull actor;
    if (l == REPARSE_CODE) jump ReParse;

    ! If it is a direction, send back the results:
    ! action=GoSub, no of arguments=1, argument 1=the direction.

    if ((l~=0) && (l ofclass K3_direction)) {
    parser_results-->ACTION_PRES = ##Go;
    parser_results-->NO_INPS_PRES = 1;
    parser_results-->INP1_PRES = l;
    jump LookForMore;
    }

    } ! end of first-word-not-a-verb


    !Chapter - Parser Letter C

    ![Is anyone being addressed?]

    !Include (-
        ! Only check for a comma (a "someone, do something" command) if we are
        ! not already in the middle of one. (This simplification stops us from
        ! worrying about "robot, wizard, you are an idiot", telling the robot to
        ! tell the wizard that he is an idiot.)
        
        if (actor == player) {
            for (j=2 : j<=num_words : j++) {
                i=NextWord();
                if (i == comma_word) jump Conversation;
            }
        }
        jump NotConversation;
        
        ! NextWord nudges the word number wn on by one each time, so we've now
        ! advanced past a comma. (A comma is a word all on its own in the table.)
        
        .Conversation;
        
        j = wn - 1;
        if (j == 1) {
            L__M(##Miscellany, 22); ! "You can’t begin with a comma."
            jump ReType;
        }
        
        ! Use NounDomain (in the context of "animate creature") to see if the
        ! words make sense as the name of someone held or nearby
        
        wn = 1; lookahead = HELD_TOKEN;
        scope_reason = TALKING_REASON;
        l = NounDomain(player, actors_location,6);
        scope_reason = PARSING_REASON;
        if (l == REPARSE_CODE) jump ReParse;
        if (l == 0) {
            if (verb_word && ((verb_word->#dict_par1) & (+ being a verb +))) jump NotConversation;
            L__M(##Miscellany, 23); ! "You seem to want to talk to someone, but I can’t see whom."
            jump ReType;
        }
        
        .Conversation2;
        
        ! The object addressed must at least be "talkable" if not actually "animate"
        ! (the distinction allows, for instance, a microphone to be spoken to,
        ! without the parser thinking that the microphone is human).
        
        if (l hasnt animate && l hasnt talkable) {
            L__M(##Miscellany, 24, l); ! "You can’t talk to ", (the) x1, "."
            jump ReType;
        }
        
        ! Check that there aren't any mystery words between the end of the person's
        ! name and the comma (eg, throw out "dwarf sdfgsdgs, go north").
        
        if (wn ~= j) {
            if (verb_word && ((verb_word->#dict_par1) & (+ being a verb +))) jump NotConversation;
            L__M(##Miscellany, 25); ! "To talk to someone, try ~someone, hello~ or some such.";
            jump ReType;
        }
        
        ! The player has now successfully named someone. Adjust "him", "her", "it":
        
        PronounNotice(l);
        
        ! Set the global variable "actor", adjust the number of the first word,
        ! and begin parsing again from there.
        
        verb_wordnum = j + 1;
        
        ! Stop things like "me, again":
        
        if (l == player) {
            wn = verb_wordnum;
            if (NextWordStopped() == AGAIN1__WD or AGAIN2__WD or AGAIN3__WD) {
                L__M(##Miscellany, 20); ! "To repeat a command like ~frog, jump~, just say ~again~, not ~frog, again~.";
                jump ReType;
            }
        }
        
        actor = l;
        actors_location = ScopeCeiling(l);
        #Ifdef DEBUG;
        if (parser_trace >= 1)
            print "[Actor is ", (the) actor, " in ", (name) actors_location, "]^";
        #Endif; ! DEBUG
        jump BeginCommand;


    !Chapter - Parser Letter D

    ![Get the verb: try all the syntax lines for that verb.]

    !Include (-
        .NotConversation;
        if (verb_word == 0 || ((verb_word->#dict_par1) & (+being a verb+)) == 0) {
            if (actor == player) {
                verb_word = UnknownVerb(verb_word);
                if (verb_word ~= 0) jump VerbAccepted;
            }
            best_etype = VERB_PE;
            jump GiveError;
        }
        .VerbAccepted;

    ! We now definitely have a verb, not a direction, whether we got here by the
    ! "take ..." or "person, take ..." method. Get the meta flag for this verb:

    meta = ((verb_word->#dict_par1) & (+being out of world+))/(+being out of world+);

    ! You can't order other people to "full score" for you, and so on...

    if (meta == 1 && actor ~= player) {
    best_etype = VERB_PE;
    meta = 0;
    jump GiveError;
    }

    ! Now let i be the corresponding verb number...

    i = DictionaryWordToVerbNum(verb_word);

    ! ...then look up the i-th entry in the verb table to get the address of the verb's syntax
    ! table...

    syntax = VM_CommandTableAddress(i);

    ! ...and then see how many lines (ie, different patterns corresponding to the
    ! same verb) are stored in the parse table...

    num_lines = (syntax->0) - 1;

    ! ...and now go through them all, one by one.
    ! To prevent pronoun_word 0 being misunderstood,

    pronoun_word = NULL; pronoun_obj = NULL;

    #Ifdef DEBUG;
    if (parser_trace >= 1)
    print "[Parsing for the verb '", (address) verb_word, "' (", num_lines+1, " lines)]^";
    #Endif; ! DEBUG

    best_etype = STUCK_PE; nextbest_etype = STUCK_PE;
    multiflag = false;

    ! "best_etype" is the current failure-to-match error - it is by default
    ! the least informative one, "don't understand that sentence".
    ! "nextbest_etype" remembers the best alternative to having to ask a
    ! scope token for an error message (i.e., the best not counting ASKSCOPE_PE).
    ! multiflag is used here to prevent inappropriate MULTI_PE errors
    ! in addition to its unrelated duties passing information to action routines


    !Chapter - Parser Letter E

    ![Break down a syntax line into analysed tokens.]

    !Include (-
    line_address = syntax + 1;

    for (line=0 : line<=num_lines : line++) {

    ! for (i=0 : i<32 : i++) { ! this is done inside UnpackGrammarLine(), which follows.
    ! line_token-->i = ENDIT_TOKEN;
    ! line_ttype-->i = ELEMENTARY_TT;
    ! line_tdata-->i = ENDIT_TOKEN;
    ! }

    ! Unpack the syntax line from Inform format into three arrays; ensure that
    ! the sequence of tokens ends in an ENDIT_TOKEN.

    line_address = UnpackGrammarLine(line_address);

    #Ifdef DEBUG;
    if (parser_trace >= 1) {
    if (parser_trace >= 2) new_line;
    print "[line ", line; DebugGrammarLine(); ! say current grammar line;
    print "]^";
    }
    #Endif; ! DEBUG

    ! We aren't in "not holding" or inferring modes, and haven't entered
    ! any parameters on the line yet, or any special numbers; the multiple
    ! object is still empty.

    inferfrom = 0;
    parameters = 0;
    nsns = 0; special_word = 0;
    multiple_object-->0 = 0;
    multi_context = 0;
    etype = STUCK_PE;

    ! Put the word marker back to just after the verb

    wn = verb_wordnum+1;


    !Chapter - Parser Letter F

    ![Look ahead for advance warning for |multiexcept|/|multiinside|.

    !There are two special cases where parsing a token now has to be affected by
    !the result of parsing another token later, and these two cases (multiexcept
    !and multiinside tokens) are helped by a quick look ahead, to work out the
    !future token now. We can only carry this out in the simple (but by far the
    !most common) case:

    ! |multiexcept <one or more prepositions> noun|

    !and similarly for |multiinside|.]

    !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) & (+being a preposition+) ~= 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) ~= false) {
    ! 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) & (+being a preposition+) ~= 0));

    if (l && (l->#dict_par1) & (+being a preposition+)) 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;
    }
    }
    }

    ! 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;


    !Chapter - Parser Letter G

    ![Parse each token in turn (calling |ParseToken| to do most of the work).

    !The |pattern| gradually accumulates what has been recognised so far,
    !so that it may be reprinted by the parser later on.]

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

    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 {
    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;
    }
    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.


    !Chapter - Parser Letter H

    ![Cheaply parse otherwise unrecognised conversation and return.

    !(Errors are handled differently depending on who was talking. If the command
    !was addressed to somebody else (eg, DWARF, SFGH) then it is taken as
    !conversation which the parser has no business in disallowing.)

    !The parser used to return the fake action |##NotUnderstood| when a
    !command in the form PERSON, ARFLE BARFLE GLOOP is parsed, where a character
    !is addressed but with an instruction which the parser can't understand.
    !(If a command such as ARFLE BARFLE GLOOP is not an instruction to someone
    !else, the parser prints an error and requires the player to type another
    !command: thus |##NotUnderstood| was only returned when |actor| is not the
    !player.) And I6 had elaborate object-oriented ways to deal with this, but we
    !won't use any of that: we simply convert to a |##Answer| action, which
    !communicates a snippet of words to another character, just as if the
    !player had typed ANSWER ARFLE BARFLE GLOOP TO PERSON. For I7 purposes, the
    !fake action |##NotUnderstood| does not exist.]

    !Include (-
    .GiveError;

    etype = best_etype;
    if (actor ~= player) {
    if (usual_grammar_after ~= 0) {
    verb_wordnum = usual_grammar_after;
    jump AlmostReParse;
    }
    wn = verb_wordnum;
    special_word = NextWord();
    if (special_word == comma_word) {
    special_word = NextWord();
    verb_wordnum++;
    }
    parser_results-->ACTION_PRES = ##Answer;
    parser_results-->NO_INPS_PRES = 2;
    parser_results-->INP1_PRES = actor;
    parser_results-->INP2_PRES = 1; special_number1 = special_word;
    actor = player;
    consult_from = verb_wordnum; consult_words = num_words-consult_from+1;
    rtrue;
    }


    !Chapter - Parser Letter I

    ![Print best possible error message.]

    !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) 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 = (+ please report the noun not making sense in this context +);
    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);


    !Chapter - Parser Letter J

    ![Retry the whole lot.]

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

    jump ReType;

    ! ...being careful not to go all the way back, to avoid infinite repetition
    ! of a deferred command causing an error.


    !Chapter - Parser Letter K

    ![Last thing: check for THEN and further instructions(s), return.]

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


    ]