Readthrough for adv3Lite: the actual code

And here is the relevant code… I hashed this together very hastily so the code organization and formatting is helter-skelter, but I did a bit of testing and it seems to work.





readthru: PreinitObject
             // if this is nil, it is to be assumed that the author has 
             // manually typed the list of command strings in the [steps] property
	loadStepsFromFile = true
	stepFileName = 'rthSteps.txt'
	verticalSpaceAfterCmd = '<.p>'	//users may prefer '\n' or something custom
		//* any initial tokens not found in this list will cause the mode to suspend
                // savepoint(), and will revert the game to the state at the last readthrough 
                //step the next time there is an empty command or an explicit 'undo'
	excludeTokens = ['save','restore','restart','hint','script']
		//* optional text to display describing the mode the player is entering
	infoBlurb = ""
            //* this property could potentially be filled out as a list of single-quote strings,
            // but most likely an author will just play the game in RECORD mode
            // and the game will load this property from the resulting file
    steps = static new Vector(1200)
            //* this method should be overridden to determine how the game starts. You might
            // have a sort of welcome screen, or a single interactive prompt upon game start that
            // asks which mode to play in... at any rate, this method will be called after the player
            // enters 'readthrough', so you need to make sure the playerChar ends up in the
            // starting loc and gets a lookAround, situated ready to execute the first command
            // in the readthrough steps
	transitionToGame { }
            // this should be defined as a condition, based on the way you present the opening
            // of your game. Perhaps (gPlayerChar.isIn(welcomeScreen)) or !gRevealed('tag').
            // Or you could explicitly set this to nil in transitionToGame
	gameNotBegun = true
	execute() {	
		if(!loadStepsFromFile) return; 
        local f = File.openTextFile(stepFileName,FileAccessReadWriteKeep); 
        local line;
        while((line = f.readFile()) != nil) {
            if(!line.startsWith('\>') && !line.startsWith('function')) continue;
            steps.append(line);
            }
		f.closeFile();
    }
    launch() {
	   if(gameNotBegun) {
            libGlobal.readthruMode = true;
			infoBlurb;
                        // you may wish to call a pause here
			transitionToGame;
		}
	    else "<<if libGlobal.readthruMode>>Readthrough mode is currently running... 
			<<else>>Readthrough mode can only be launched at game startup... ";
    }
    stepCt = 1 //* the next step waiting to be executed
    needsUndo = nil
    needsDecr = nil
    lastCmd = ''
	shownSaveNotice = nil

    // infoBlurb = "\ \b\b\b<.p>Welcome to the readthrough! We wish to brief you on the fundamentals of experiencing the game in this mode. <.p>To advance, all you have to do is leave the command line blank and hit [Enter]. You could proceed through the whole game in this manner. However, if at any time you want to take the liberty of entering your own commands, to see what would happen if such-and-such, you can do so. In fact you can do so for as long as you please, but be aware that the next time you hit [Enter] with a blank command line, the game will undo all of your autonomous decisions, and continue from where it left in the auto-play sequence. As a further \"however\", if you use auto-play to reach a certain point in the game and then you decide you want to continue autonomously, you may enter READTHROUGH OFF, after which point you will not be returned to the auto-play sequence. You cannot reenter readthrough mode from the middle of a game, but you may save readthrough games where they are in order to branch off in normal play mode. <<inputManager.pauseForMore(true)>><.p>One final note is that the readthrough is not meant to be the same as a blazing-fast ultra-minimal walkthrough. It plays more so like a real player, who would have to examine things to get clues, come back to certain locations later after they\'ve figured out what they need to do there, and maybe do a few silly things for fun. <.p>With that, we proceed! <<inputManager.pauseForMore(true)>>"
;
DefineSystemAction(Readthrough) 
	execAction(cmd) 	{ readthru.launch; } ;
	VerbRule(Readthrough) ('start'|'begin'|) ('slideshow'|'slide''show'|'slides'|'auto''play'|'autoplay'|'auto-play'|'readthrough'|'readthru') : VerbProduction 
        action = Readthrough
;
DefineSystemAction(ReadthroughOff)
    execAction(cmd) { libGlobal.readthruMode = nil;
        "We have exited auto-play mode. You can continue the game from here with complete autonomy. You will not be able to reenter auto-play mode without restarting it from the beginning. If you wish to remain in auto-play mode, or wish to save the auto-play game before entering autonomous mode, enter UNDO now. "; } ;
    VerbRule(ReadthroughOff) (('slideshow'|'slide''show'|'slides'|'auto''play'|'autoplay'|'auto-play'|'readthrough') ('mode'|) 'off') | ('leave'|'end'|'quit'|'stop')('slideshow'|'slide''show'|'slides') ('mode'|) : VerbProduction 
        action = ReadthroughOff
;


modify Command
    // The only change to this method is the line where savepoint() is called,
    // marked with a block of slashes
exec()
    {
        try
        {
            action.reset();
            gAction = action;
            originalAction = action;
            lastAction = nil;
            gCommand = self;
            actions = [];
            
            if(verbProd != nil)
                local lastCommandStr = buildCommandString();
                           
            if(gameMain.autoSwitchAgain && action != Again)           
            {                
                gameMain.againRepeatsParse = action.againRepeatsParse;
            }
            
            if(action.isRepeatable)
            {
                libGlobal.lastCommand = self.createClone();
                libGlobal.lastCommandForAgain = lastCommandStr;                    
            }
            
            if(action.includeInUndo && verbProd != nil)
            {               
                libGlobal.lastCommandForUndo = lastCommandStr;
                if(!libGlobal.suspendSave) savepoint();         //////////
            }
    
            action.execGroup(self);
            local predRoles = npList.subset({ r: r.isPredicate });
            if (predRoles.length() == 0)
            {
                execIter([action]);
            }
            else
            {
                foreach (local role in predRoles)
                {
                    local matches = self.(role.objListProp);
                    local names = Distinguisher.getNames(
                        matches.mapAll({ m: m.obj }), nil);
                    matches.forEach(
                        { m: m.name = names.valWhich(
                            { n: n[2].indexOf(m.obj) != nil })[1] });
                }
                predRoles = predRoles.sort(SortAsc, {a, b: a.order - b.order});
                execCombos(predRoles, 1, [action]);
            }
            foreach(local a in actions)
            {
                action = a;
                action.reportAction();
            }
            action = (actions.indexOf(originalAction) || actions.length == 0) 
                ? originalAction : actions[1];
            afterReport();
            action.afterAction();
            action.turnSequence();
            action.advanceTime();
        }
        catch(AbortActionSignal aas)
        {
        }
    }
;

modify libGlobal
    readthruMode = nil
    suspendSave = nil
;
    //convenience functions
savepointOn() {	libGlobal.suspendSave = nil; }
savepointOff() { libGlobal.suspendSave = true; }


modify Undo
    execAction(cmd)
    {
			//* we need to store some values before we perform undo()
		local s = libGlobal.suspendSave;		
		local nd = readthru.needsDecr;
			//* Users: feel free to sophisticate this. I didn't want to go through all the rex parsing that would be necessary to separate individual commands out of something like '> north. east. south. enter cave' and keep the stepCt in sync
		if(readthru.lastCmd.find('.')) { "<.p>Apologies... we cannot perform an undo after a string of multiple commands. ";
			return nil; }
  		if (undo())        {
            PostUndoObject.classExec();
                if(!s && !libGlobal.readthruMode) 
                    DMsg(undo okay, 'One turn undone: {1}', libGlobal.lastCommandForUndo);
				else if(libGlobal.readthruMode) {
						//* A player entered 'undo' after some autonomous commands
					if(s) "<.p>We now return to the last auto-play step. <.p>";
						//* A player has been using the readthrough sequence but wants to back up one
					else "<.p>Undone. \b";
					if(nd) {
						--readthru.stepCt;
						readthru.needsDecr = nil;
					}
				}
			savepointOn();
            return true;        }
        else
        {
            DMsg(undo failed, 'Undo failed. ');
            return nil;
        }
    }
;


modify processOptions(lst)
{
promptLoop:
    for (;;)
    {
        local resp;
        local rthr = libGlobal.readthruMode;
        if(!rthr || readthru.needsUndo /*|| READTHROUGH IS OVER COND*/) finishOptionsLister.show(lst, 0);
        statusLine.showStatusLine();
        ">";
        resp = inputManager.getInputLine();
        if(resp=='' && rthr /* && ///////////some condition that indicates that game end has not yet been reached*/ ) { 
              "<b>undo</b>\b"; 
              resp = 'undo';
            }
        foreach (local cur in lst)
        {
            if (cur.responseMatches(resp))
            {
                if (cur.doOption())
                {
                    continue promptLoop;
                }
                else
                {
                    return;
                }
            }
        }
        DMsg(invalid finish option, '<q>{1}</q> was not one of the
            options.<.p>', resp);
    }
}

PreSaveObject 
	execute {
		if(libGlobal.suspendSave) { 
			if(libGlobal.readthruMode && !readthru.shownSaveNotice) {
				say('<.p>You have entered autonomous commands since the last readthrough step. We regrettably cannot save autonomous changes if the game is to stay in readthrough mode. If you wish to save your autonomous changes, you can first enter READTHROUGH OFF, and then SAVE... just note that you will be unable to reenter readthrough mode from that point. If you wish to save the game in readthrough mode, enter SAVE again, but be aware that we will undo the game to the state it was in at the last readthrough step. <.p>');
				readthru.shownSaveNotice = true;
			}
			else if(libGlobal.readthruMode) "<.p> We return the game to the readthrough sequence before saving... <.p>";
			else "<.p>Undoing to the last savepoint before saving the game... <.p>"; 
			undo();  
			savepointOn();
		}
	}
;

modify Parser
        // in adv3Lite, we have to copy the whole code from this huge method even though 
        // we're only tampering with a few early blocks. Modified lines are marked with
        // slashes or preceded by comments
    parse(str,scriptedCmd?)     // added the optional parameter
        {
        local rth = libGlobal.readthruMode; //
        local toks;
        try
        {
            toks = cmdTokenizer.tokenize(str);
            while(toks.length > 0 && getTokType(toks[toks.length]) == tokPunct)
                toks = toks.removeElementAt(toks.length);
        }
        catch (TokErrorNoMatch err)
        {
            DMsg(token error, 'I don\'t understand the punctuation {1}',
                 err.curChar_);
            return;
        }
        if(question == nil)
            gActor = gPlayerChar;        
        local history = new transient SpellingHistory(self);
        local firstCmd = true;
        try
        {
            if (toks.length() == 0)
            {       //* There's an empty command line. If we're in readthrough mode, 
                    //  this is where all of the main processing occurs
                if(libGlobal.readthruMode) { 
                    if(readthru.needsUndo) undo();
                    readthru.needsUndo = nil;
                    savepointOn();
                    local str = readthru.steps[readthru.stepCt];
                        //* process any functions in the step list, and then get the next command
                    while(dataType(str)==TypeFuncPtr || dataType(str)==TypeSString && str.startsWith('function')) {
                        if(dataType(str)==TypeSString) str = Compiler.compile(str);
                        str();
                        str = readthru.steps[++readthru.stepCt];
                    }
                        //* if we used RECORD to make our step list, our string will be prefixed with '>' and suffixed with '\n'. Remove these characters before sending the command to the parser. 
                    if(readthru.loadStepsFromFile) str = str.substr(2,-1);
                        //* print comments without taking an action cycle
                    if(str.startsWith('*')) { 
                        "<b><<str>></b>"; 
                        ++readthru.stepCt;
                        return;
                        }
                    else {
                        str = StringPreParser.runAll(str, rmcCommand);
                        if(str==nil) return; 
                        readthru.lastCmd = str;
                        "<b><<str>></b><<readthru.verticalSpaceAfterCmd>>";
                        ++readthru.stepCt;
                        parse(str,true);
                        }
                }
                else {      //normal empty command processing
                    question = nil;
                    lastTokens = nil;
                    emptyCommand();
                    return;
                }
            }
            local lst = oopsCommand.parseTokens(toks, cmdDict);
            if (lst.length() != 0)
            {
                local ui;
                if (lastTokens == nil
                    || (ui = spellingCorrector.findUnknownWord(lastTokens))
                        == nil)
                {
                    throw new CantOopsError();
                }
                toks = OopsProduction.applyCorrection(lst[1], lastTokens, ui);
            }
            for (local root = firstCommandPhrase ; toks.length() != 0 ; )
            {
                    // this is the last block that we tamper with for readthrough mode
                if(rth && !scriptedCmd && !readthru.excludeTokens.indexOf(toks[1][3].toLower()) ) {
                    if(toks[1][3].toLower()=='undo') { 
                        if(libGlobal.suspendSave) ; //* just proceed: we'll jump back to the last game state in the auto-play sequence
                        else readthru.needsDecr = true; //* They want to back up the actual readthrough step: need extra measures so that the step counter doesn't go out of sync
                    }
                    else {  //* They entered a regular command during readthrough mode. Save the state now and then turn off savepoint so they can enter commands at will, and still be returned to the auto-play sequence
                        readthru.needsUndo = true;                 
                        if(!libGlobal.suspendSave) savepoint();
                        savepointOff();                        
                    }
                }

        // Beyond this point everything is copy/paste from the adv3Lite source
        // except for a single line where we put a condition on calling savepoint()

                local cmdLst = nil;
                local qErr = nil, defErr = nil;
                if (firstCmd && question != nil && question.priority)
                {
                    /* try parsing against the Question */
                    local l = question.parseAnswer(toks, cmdDict);

                    /* if it parsed and resolved, this is our command */
                    if (l != nil && l.cmd != nil)
                        cmdLst = l;

                    /* if it parsed but didn't resolved, note the error */
                    if (l != nil)
                        qErr = l.getResErr();
                }

                /* 
                 *   if the question didn't grab it, try parsing as a whole
                 *   new command against the ordinary command grammar
                 */
                if (cmdLst == nil || cmdLst.cmd == nil)
                {
                    cmdLst = new CommandList(
                        root, toks, cmdDict, { p: new Command(p) });
                }

                /* 
                 *   If we didn't find any resolvable commands, and this is
                 *   the first command, check to see if it's an answer to
                 *   an outstanding query.  We only check this if the
                 *   regular grammar parsing fails, because anything that
                 *   looks like a valid new command overrides a past query.
                 *   This is important because some of the short, common
                 *   commands sometimes can look like noun phrases, so we
                 *   explicitly give preference to interpreting these as
                 *   brand new commands.  
                 */
                if (cmdLst.cmd == nil
                    && firstCmd
                    && question != nil
                    && !question.priority)
                {
                    /* try parsing against the Question */
                    local l = question.parseAnswer(toks, cmdDict);

                    /* if it parsed and resolved, this is our command */
                    if (l != nil && l.cmd != nil)
                        cmdLst = l;

                    /* if it parsed but didn't resolved, note the error */
                    if (l != nil)
                        qErr = l.getResErr();
                }

                /*
                 *   If we don't have a command yet, and this is the first
                 *   command on the line, handle it as a conversational command
                 *   if conversation is in progress; otherwise if default
                 *   actions are enabled, check to see if the command looks like
                 *   a single noun phrase.  If so, handle it as the default
                 *   action on the noun.
                 */
                if (cmdLst.cmd == nil
                    && firstCmd)
                {
                    local l;                   
                    
                    
                    /* 
                     *   If a conversation is in progress parse the command line
                     *   as the single topic object phrase of a Say command,
                     *   provided that the first word on the command line
                     *   doesn't match a possible action.
                     */
                    
                    if(gPlayerChar.currentInterlocutor != nil
                       && cmdLst.length == 0 
                       && Q.canTalkTo(gPlayerChar,
                                      gPlayerChar.currentInterlocutor)
                       && str.find(',') == nil
                       && gPlayerChar.currentInterlocutor.allowImplicitSay())
                    {
                         l = new CommandList(
                            topicPhrase, toks, cmdDict,
                            { p: new Command(SayAction, p) });
                        
                        libGlobal.lastCommandForUndo = str;
                        if(!libGlobal.suspendSave) savepoint();     //
                    }
                    /* 
                     *   If the player char is not in conversation with anyone,
                     *   or the first word of the command matches a possible
                     *   command verb, then try parsing the command line as a
                     *   single direct object phrase for the DefaultAction verb,
                     *   provided defaultActions are enabled (which they aren't
                     *   by default).
                     */
                    else if(defaultActions)                                                
                        l = new CommandList(
                            defaultCommandPhrase, toks, cmdDict,
                            { p: new Command(p) });                       
                    
                    
                       
                    /* accept a curable reply */
                    if (l != nil && l.acceptCurable() != nil)
                    {
                        cmdLst = l;
                        
                        /* note any resolution error */
                        defErr = l.getResErr();
                    }
                }
                
                /*
                 *   If we've applied a spelling correction, and the
                 *   command match didn't consume the entire input, make
                 *   sure what's left of the input has a valid parsing as
                 *   another command.  This ensures that we don't get a
                 *   false positive by excessively shortening a command,
                 *   which we can sometimes do by substituting a word like
                 *   "then" for another word.  
                 */
                if (cmdLst.length() != nil
                    && history.hasCorrections())
                {
                    /* get the best available parsing */
                    local c = cmdLst.getBestCmd();

                    /* if it doesn't use all the tokens, check what's left */
                    if (c != nil && c.tokenLen < toks.length())
                    {
                        /* try parsing the next command */
                        local l = commandPhrase.parseTokens(
                            c.nextTokens, cmdDict);

                        /* 
                         *   if that didn't work, invalidate the command by
                         *   substituting an empty command list 
                         */
                        if (l.length() == 0)
                            cmdLst = new CommandList();
                    }
                }
                
                /* 
                 *   If we didn't find a parsing at all, it's a generic "I
                 *   don't understand" error.  If we found a parsing, but
                 *   not a resolution, reject it if it's a spelling
                 *   correction.  We only want completely clean spelling
                 *   corrections, without any errors.
                 */
                if (cmdLst.length() == 0
                    || (history.hasCorrections()
                        && cmdLst.getResErr() != nil
                        && !cmdLst.getResErr().allowOnRespell))
                {
                    /* 
                     *   If we were able to parse the input using one of
                     *   the non-command interpretations, use the
                     *   resolution error from that parsing.  Otherwise, we
                     *   simply can't make any sense of this input, so use
                     *   the generic "I don't understand" error. 
                     */
                    local err = (qErr != nil ? qErr :
                                 defErr != nil ? defErr :
                                 new NotUnderstoodError());
                    
                    /* look for a spelling correction */
                    local newToks = history.checkSpelling(toks, err);
                    if (newToks != nil)
                    {
                        /* parse again with the new tokens */
                        toks = newToks;
                        continue;
                    }

                    /* 
                     *   There's no spelling correction available.  If we've 
                     *   settled on an auto-examine or question error, skip 
                     *   that and go back to "I don't understand" after 
                     *   all.  We don't want to assume Auto-Examine unless we
                     *   actually have something to examine, since we can 
                     *   parse noun phrase grammar out of practically any 
                     *   input.  
                     */
                    if (err is in (defErr, qErr))
                    {
                        /* return to the not-understood error */
                        err = new NotUnderstoodError();
                        
                        /* check spelling again with this error */
                        newToks = history.checkSpelling(toks, err);
                        if (newToks != nil)
                        {
                            /* parse again with the new tokens */
                            toks = newToks;
                            continue;
                        }
                    
                        
                        /* 
                         *   We didn't find any spelling corrections this time
                         *   through.  Since we're rolling back to the
                         *   not-understood error, discard any spelling
                         *   corrections we attempted with other
                         *   interpretations.
                         */
                        history.clear();                   
                    }
                
                    /* fail with the error */
                    throw err;
                }

                /* if we found a resolvable command, execute it */
                if (cmdLst.cmd != nil)
                {
                    /* get the winning Command */
                    local cmd = cmdLst.cmd;
                    
                    /* 
                     *   We next have to ensure that the player hasn't entered
                     *   multiple nouns in a slot that only allows a single noun
                     *   in the grammar. If the player has entered two objects
                     *   like "the bat and the ball" in such a case, the
                     *   badMulti flag will be set on the command object, so we
                     *   first test for that and abort the command with a
                     *   suitable error message if badMulti is not nil (by
                     *   throwing a BadMultiError
                     *
                     *   Unfortunately the badMulti flag doesn't get set if the
                     *   player enters a multiple object as a plural (e.g.
                     *   "bats"), so we need to trap this case too. We do that
                     *   by checking whether there's multiple objects in the
                     *   direct, indirect and accessory object slots at the same
                     *   time as the grammar tag matching the slot in question
                     *   is 'normal', which it is only for a single noun match.
                     */
                     
                    if(cmd && cmd.verbProd != nil &&                        
                        (cmd.badMulti != nil 
                       || (cmd.verbProd.dobjMatch != nil &&
                           cmd.verbProd.dobjMatch.grammarTag == 'normal'
                           && cmd.dobjs.length > 1)
                       ||
                       (cmd.verbProd.iobjMatch != nil &&
                           cmd.verbProd.iobjMatch.grammarTag == 'normal'
                           && cmd.iobjs.length > 1)                          
                        ||
                       (cmd.verbProd.accMatch != nil &&
                           cmd.verbProd.accMatch.grammarTag == 'normal'
                           && cmd.accs.length > 1)
                           ))
                        cmd.cmdErr = new BadMultiError(cmd.np);
                    
                    /* if this command has a pending error, throw it */
                    if (cmd.cmdErr != nil)
                        throw cmd.cmdErr;

                    /* 
                     *   Forget any past question and typo information.
                     *   The new command is either an answer to this
                     *   question, or it's simply ignoring the question; in
                     *   either case, the question is no longer in play for
                     *   future input.  
                     */
                    question = nil;
                    lastTokens = nil;
                    
                    /* note any spelling changes */
                    history.noteSpelling(toks);
                    
                    /* execute the command */
                    cmd.exec();
                    
                    /* start over with a new spelling correction history */
                    history = new transient SpellingHistory(self);
                    
                    /* 
                     *   Set the root grammar production for the next
                     *   predicate.  If the previous command ended the
                     *   sentence, start a new sentence; otherwise, use the
                     *   additional clause syntax. 
                     */
                    root = cmd.endOfSentence
                        ? firstCommandPhrase : commandPhrase;
                    
                    /* we're no longer on the first command in the string */
                    firstCmd = nil;
                    
                    /* go back and parse the remainder of the command line */
                    toks = cmd.nextTokens;
                    continue;
                }

                /*
                 *   We weren't able to resolve any of the parse trees.  If
                 *   one of the errors is "curable", meaning that the
                 *   player can fix it by answering a question, pick the
                 *   first of those, in predicate priority order.
                 *   Otherwise, just pick the first command overall in
                 *   predicate priority order.  In either case, since we
                 *   didn't find any working alternatives, it's time to
                 *   actually show the error and fail the command.  
                 */
                local c = cmdLst.acceptAny();

                /* 
                 *   If the error isn't curable, check for spelling errors,
                 *   time permitting.  Don't bother doing this with a
                 *   curable error, since that will have its own way of
                 *   solving the problem that reflects a better
                 *   understanding of the input than considering it a
                 *   simple typo.  
                 */
                if (!c.cmdErr.curable)
                {
                    /*
                     *   For spelling correction purposes, if this is an
                     *   unmatched noun error, but the command has a misc
                     *   word list and an empty noun phrase, treat this as
                     *   a "not understood" error.  The combination of noun
                     *   phrase errors suggests that we took a word that
                     *   was meant to be part of the verb, and incorrectly
                     *   parsed it as part of a noun phrase, leaving the
                     *   verb structure and other noun phrase incomplete.
                     *   This is really a verb syntax error, not a noun
                     *   phrase error.  
                     */
                    local spellErr = c.cmdErr;
                    if (c.cmdErr.ofKind(UnmatchedNounError)
                        && c.miscWordLists.length() > 0
                        && c.missingNouns > 0)
                        spellErr = new NotUnderstoodError();

                    /* try spelling correction */
                    local newToks = history.checkSpelling(toks, spellErr);

                    /* if that worked, try the corrected command */
                    if (newToks != nil)
                    {
                        /* parse again with the new tokens */
                        toks = newToks;
                        continue;
                    }
                }

                /* re-throw the error that caused the resolution to fail */
                throw c.cmdErr;
            }
        }
        catch (ParseError err)
        {
            /* 
             *   roll back any spelling changes to the last one that
             *   improved matters 
             */
            local h = history.rollback(toks, err);
            toks = h.oldToks;
            err = h.parseError;

            /* 
             *   if this is a curable error, it poses a question, which the
             *   player can answer on the next input 
             */
            if (err.curable)
                question = new ParseErrorQuestion(err);
            
            /* 
             *   If the current error isn't curable, and unknown word
             *   disclosure is enabled, and there's a word in the command
             *   that's not in the dictionary, replace the parsing error
             *   with an unknown word error.  
             */
            local ui;
            if (!err.curable
                && showUnknownWords
                && (ui = spellingCorrector.findUnknownWord(toks)) != nil)
            {
                /* find the misspelled word in the original tokens */
                err = new UnknownWordError(getTokOrig(toks[ui]));
            }
            
            /* 
             *   If the new error isn't an error in an OOPS command, save
             *   the token list for an OOPS command next time out. 
             */
            if (!err.ofKind(OopsError))
                lastTokens = toks;
            
            /* log any spelling changes we kept */
            history.noteSpelling(toks);

            /* display the error we finally decided upon */
            err.display();
        }
        catch (CommandSignal sig)
        {
            /* 
             *   On any command signal we haven't caught so far, simply
             *   stop processing this command line.  
             */
        }
    }
;


3 Likes