The proper way to replace Parser__parse

I’m toying with some improvements to the parsing process that require adjusting the infamous Parser__parse routine. I’m doing that right now with:

Include (-
[ Parser__parse
! ...
];
-) replacing "Parser__parse".

However, Inform doesn’t like this:

Problem. Something went wrong late in compilation, when working through the ‘pipeline’ of code-generation steps.

Problem: unable to find definitions for the following name(s): TokeniseInput, VM_ProbablyDictionaryAddress, WordMarkedAsVerb, WordMarkedAsMeta, DICTPAR1_PREP

What am I doing wrong here?

Oh, oops. I was on the wrong branch of the Git repository and using code that was too new.

I’m now having a very different problem, though. If I include the entire vanilla Parser__parse routine as a replacement, it crashes on the line i = RunRoutines(actor, grammar); saying “Glulxe fatal error: Memory access out of range (92A4C)”.

Minimal example:

Include (-
[ Parser__parse
	syntax line num_lines line_address i j k token l m inferred_go;
	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;

! @h Parser Letter A.
! Get the input, do OOPS and AGAIN.
! 
! =
    if (held_back_mode) {
        held_back_mode = false; wn = hb_wn;
        if (verb_wordnum > 0) i = WordAddress(verb_wordnum); else i = WordAddress(1);
        j = WordAddress(wn);
        if (i<=j) 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);
        jump ReParse;
    }

  .ReType;

	cobj_flag = 0;
	actors_location = ScopeCeiling(player);
    BeginActivity(READING_A_COMMAND_ACT); if (ForActivity(READING_A_COMMAND_ACT)==false) {
		Keyboard(buffer,parse);
		num_words = WordCount(); players_command = 100 + num_words;
    } if (EndActivity(READING_A_COMMAND_ACT)) jump ReType;

  .ReParse;

    parser_inflection = name;

    ! Initially assume the command is aimed at the player, and the verb
    ! is the first word

    num_words = WordCount(); players_command = 100 + num_words;
    wn = 1; inferred_go = false;

    LanguageToInformese();
    ! Re-tokenise:
    VM_Tokenise(buffer,parse);

    num_words = WordCount(); players_command = 100 + num_words; token_filter = 0;
    allow_plurals = true; ResetDescriptors();


    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;
    }
	if (verb_word == comma_word) {
		best_etype = COMMABEGIN_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 GiveError;
        }
        #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 + num_words;
    	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;

! @h Parser Letter B.
! Is the command a direction name, and so an implicit GO? If so, go to (K).
! 
! =
    if (verb_word == 0) {
        i = wn; verb_word = LanguageIsVerb(buffer, parse, verb_wordnum);
        wn = i;
    }

    ! 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) & 1) == 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;
            inferred_go = true;
            jump LookForMore;
        }

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

! @h Parser Letter C.
! Is anyone being addressed?
! 
! =
	! 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 she 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;

	! 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, CREATURE_TOKEN);
	scope_reason = PARSING_REASON;
	if (l == REPARSE_CODE) jump ReParse;
	if (l == 0) {
		if (verb_word && ((verb_word->#dict_par1) & 1)) jump NotConversation;
		best_etype = MISSINGPERSON_PE; jump GiveError;
	}

	.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) {
 		best_etype = ANIMALISTEN_PE; noun = l; jump GiveError;
	}

	! 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) & 1)) jump NotConversation;
		best_etype = TOTALK_PE; jump GiveError;
	}

	! 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) {
			best_etype = ANIMAAGAIN_PE;
			jump GiveError;
		}
	}

	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;

! @h Parser Letter D.
! Get the verb: try all the syntax lines for that verb.
! 
! =
	.NotConversation;
	if (verb_word == 0 || ((verb_word->#dict_par1) & 1) == 0) {
		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) & 2)/2;

    ! 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, whose address is at word
    ! 7 in the Z-machine (in the header), so as to get the address of the syntax
    ! table for the given verb...

    #Ifdef TARGET_ZCODE;
    syntax = (HDR_STATICMEMORY-->0)-->i;
    #Ifnot; ! TARGET_GLULX
    syntax = (#grammar_table)-->(i+1);
    #Endif; ! TARGET_

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

! @h Parser Letter E.
! Break down a syntax line into analysed tokens.
! 
! =
    line_address = syntax + 1;

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

        ! 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();
            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; multi_had = 0;

        ! Put the word marker back to just after the verb

        wn = verb_wordnum+1;

! @h 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|.
! 
! =
        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 EmptyLine;
                        }

                        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;
                            }
                            if (wn <= num_words) l = NextWord();
                        } until (line_ttype-->pcount ~= PREPOSITION_TT);

                        .EmptyLine;
                        ! 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, true);
                            @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; multi_had = 1;
        wn = verb_wordnum+1;

! @h 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.
! 
! =
		m = true;
        for (pcount=1 : : pcount++)
            if (line_token-->(pcount-1) == ENDIT_TOKEN) {
            	if (pcount >= 2) {
            		while ((((line_token-->(pcount-2))->0) & $10) ~= 0) pcount--;
            		AnalyseToken(line_token-->(pcount-2));
            		if (found_ttype == PREPOSITION_TT) {
            			l = -1;
            			while (true) {
            				m = NextWordStopped();
            				if (m == -1) break;
            				l = m;
            			}
            			if (PrepositionChain(l, pcount-2) == -1) {
            				m = false;
							#Ifdef DEBUG;
							if (parser_trace >= 2)
								print "[line rejected for not ending with correct preposition]^";
							#Endif; ! DEBUG
						} else m = true;
	           		}
            	}
            	break;
            }
		wn = verb_wordnum+1;

		if (m) 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 = true; 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) {
                	if (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) {
                	if (parser_results-->INP2_PRES == 0) {
            	        l = ReviseMulti(parser_results-->INP1_PRES);
                	    if (l ~= 0) { etype = l; break; }
                	} else {
                		k = parser_results-->INP1_PRES; l = parser_results-->INP2_PRES;
                		if (k && l) {
	                 		if ((multi_context==MULTIEXCEPT_TOKEN && k == l) ||
	                			((multi_context==MULTIINSIDE_TOKEN && k notin l && l notin k))) {
                				best_etype = NOTHING_PE;
                				parser_results-->ACTION_PRES = action_to_be; jump GiveError;
                			}
                		}
                	}
                }

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

				if (multi_had > 1) {
                    best_etype = TOOFEW_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(20);
                }

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

! @h 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.
! 
! In order to assist people who do want to parse that type of mistyped command
! in extensions, wn is left pointing at the first word not parsed as a command.
! 
! =
  .GiveError;

    etype = best_etype;
    if (actor ~= player) {
        if (usual_grammar_after ~= 0) {
            verb_wordnum = usual_grammar_after;
            jump AlmostReParse;
        }
        m = wn; ! Save wn so extension authors can parse command errors if they want to
        wn = 1;
        while ((wn <= num_words) && (NextWord() ~= comma_word)) ;
        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 = wn; consult_words = num_words-consult_from+1;
        wn = m; ! Restore wn so extension authors can parse command errors if they want to
        rtrue;
    }

! @h Parser Letter I.
! Print best possible error message.
! 
! =
    ! 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) {    PARSER_ERROR_INTERNAL_RM('A'); new_line; oops_from = 1; }
    if (etype == UPTO_PE) {
        for (m=0 : m<32 : m++) pattern-->m = pattern2-->m;
        pcount = pcount2;
    	if (inferred_go) {
    		PARSER_ERROR_INTERNAL_RM('C');
    		print (name) parser_results-->INP1_PRES;
    	} else {
    		PARSER_ERROR_INTERNAL_RM('B');
        	PrintCommand(0);
        }
        print ".^";
    }
    if (etype == NUMBER_PE) {   PARSER_ERROR_INTERNAL_RM('D'); new_line; }
    if (etype == CANTSEE_PE) {  PARSER_ERROR_INTERNAL_RM('E'); new_line; oops_from=saved_oops; }
    if (etype == TOOLIT_PE) {   PARSER_ERROR_INTERNAL_RM('F'); new_line; }
    if (etype == NOTHELD_PE) {  PARSER_ERROR_INTERNAL_RM('G'); new_line; oops_from=saved_oops; }
    if (etype == MULTI_PE) {    PARSER_ERROR_INTERNAL_RM('H'); new_line; }
    if (etype == MMULTI_PE) {   PARSER_ERROR_INTERNAL_RM('I'); new_line; }
    if (etype == VAGUE_PE) {    PARSER_ERROR_INTERNAL_RM('J'); new_line; }
    if (etype == ITGONE_PE) {
        if (pronoun_obj == NULL) { PARSER_ERROR_INTERNAL_RM('J'); new_line; }
        else { PARSER_ERROR_INTERNAL_RM('K', noun); new_line; }
    }
    if (etype == EXCEPT_PE) {   PARSER_ERROR_INTERNAL_RM('L'); new_line; }
    if (etype == ANIMA_PE) {    PARSER_ERROR_INTERNAL_RM('M'); new_line; }
    if (etype == VERB_PE) {     PARSER_ERROR_INTERNAL_RM('N'); new_line; }
    if (etype == SCENERY_PE) {  PARSER_ERROR_INTERNAL_RM('O'); new_line; }
    if (etype == JUNKAFTER_PE) {  PARSER_ERROR_INTERNAL_RM('P'); new_line; }
    if (etype == TOOFEW_PE) {  PARSER_ERROR_INTERNAL_RM('Q', multi_had); new_line; }
    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) { PARSER_N_ERROR_INTERNAL_RM('C', noun); new_line; }
            else if (noun hasnt container or supporter) { PARSER_N_ERROR_INTERNAL_RM('D', noun); new_line; }
            else if (noun has container && noun hasnt open)  { PARSER_N_ERROR_INTERNAL_RM('E', noun); new_line; }
            else if (children(noun)==0) { PARSER_N_ERROR_INTERNAL_RM('F', noun); new_line; }
            else parser_results-->ACTION_PRES = 0;
        }
        if (parser_results-->ACTION_PRES ~= ##Remove) {
            if (multi_wanted==100) { PARSER_N_ERROR_INTERNAL_RM('A'); new_line; }
            else                  {  PARSER_N_ERROR_INTERNAL_RM('B'); new_line; }
        }
    }
    if (etype == NOTINCONTEXT_PE) { PARSER_ERROR_INTERNAL_RM('R'); new_line; }
    if (etype == ANIMAAGAIN_PE) { PARSER_ERROR_INTERNAL_RM('S'); new_line; }
    if (etype == COMMABEGIN_PE) { PARSER_ERROR_INTERNAL_RM('T'); new_line; }
    if (etype == MISSINGPERSON_PE) { PARSER_ERROR_INTERNAL_RM('U'); new_line; }
    if (etype == ANIMALISTEN_PE) { PARSER_ERROR_INTERNAL_RM('V', noun); new_line; }
    if (etype == TOTALK_PE) { PARSER_ERROR_INTERNAL_RM('W'); new_line; }
    if (etype == ASKSCOPE_PE) {
        scope_stage = 3;
        if (scope_error() == -1) {
            best_etype = nextbest_etype;
            if (~~((etype ofclass Routine) || (etype ofclass String)))
            	EndActivity(PRINTING_A_PARSER_ERROR_ACT);
            jump GiveError;
        }
    }

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

! @h Parser Letter J.
! Retry the whole lot.
! 
! =
    ! 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.

! @h Parser Letter K.
! Last thing: check for THEN and further instructions(s), return.
! 
! =
    ! 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;
        }
        hb_wn = wn;
        held_back_mode = true;
       return;
    }
    best_etype = UPTO_PE;
    jump GiveError;

! @h End of Parser Proper.
! 
! =
]; ! end of Parser__parse
-) replacing "Parser__parse".

Lab is a room. Test me with "look".

This only happens on Glulx; on Z-machine, it works okay.

The only change I’ve made from the code in Parser.i6t is to comment out the literate-programming annotations. What’s going wrong?

Your version has two lines different.

***************
*** 77303,77310 ****
      }
      if ((usual_grammar_after == 0)) {
          (j = verb_wordnum);
!         (i = RunRoutines(actor, A_grammar));
!         if ((((parser_trace >= 2)) && (((actor.grammar) ~= 0 or NULL)))) {
              print " [Grammar property returned ";
              print i;
              print "]^";
--- 77305,77312 ----
      }
      if ((usual_grammar_after == 0)) {
          (j = verb_wordnum);
!         (i = RunRoutines(actor, grammar));
!         if ((((parser_trace >= 2)) && ((_final_propertyvalue(OBJECT_TY, actor, grammar) ~= 0 or NULL)))) {
              print " [Grammar property returned ";
              print i;
              print "]^";

Hrm. So this is how it appears in Parser.i6t (line 689):

if (parser_trace >= 2 && actor.grammar ~= 0 or NULL)

And that’s what I copied into my version. But it looks like the . operator is translated differently into auto.inf if it’s found in a kit or in an extension. And it looks like the “grammar” property is translated differently too…

Fortunately, it seems the actor.grammar property is vestigial in I7, so maybe I can just cut it out entirely.

And that seems to fix it! Perfect. I do wonder what the proper way to fix this is, though, for cases when a non-vestigial property is being checked.