report.t

documentation
 #charset "us-ascii"
 
 /* 
  *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved. 
  *   
  *   TADS 3 Library: command reports
  *   
  *   This module defines the "command report" classes, which the command
  *   execution engine uses to keep track of the status of a command.  
  */
 
 #include "adv3.h"
 
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Command report objects.  The library uses these to control how the
  *   text from a command is displayed.  Game code can also use report
  *   objects to show and control command results, but this isn't usually
  *   necessary; game code can usually simply display messages directly.
  *   
  *   Reports are divided into two broad classes: "default" and "full"
  *   reports.
  *   
  *   A "default" report is one that simply confirms that an action was
  *   performed, and provides little additional information.  The library
  *   uses default reports for simple commands whose full implications
  *   should normally be obvious to a player typing such commands: take,
  *   drop, put in, and the like.  The library's default reports are
  *   usually quite terse: "Taken", "Dropped", "Done".
  *   
  *   A "full" report is one that gives the player more information than a
  *   simple confirmation.  These reports typically describe either the
  *   changes to the game state caused by a command or surprising side
  *   effects of a command.  For example, if the command is "push button,"
  *   and pushing the button opens the door next to the button, a full
  *   report would describe the door opening.
  *   
  *   Note that a full report is warranted any time a command describes
  *   anything beyond a simple confirmation.  In our door-opening button
  *   example, opening the door by pushing the button always warrants a
  *   full report, even if the player has already seen the effects of the
  *   button a hundred times before, and even if the button is labeled
  *   "push to open door."  It doesn't matter whether or not the
  *   consequences of the command ought to be obvious to the player; what
  *   matters is that the command warrants a report beyond a simple
  *   confirmation.  Any time a report is more than a simple confirmation,
  *   it is a full report, no matter how obvious to the player the effects
  *   of the action.
  *   
  *   Full reports are further divided into three subcategories by time
  *   ordering: "main," "before," and "after."  "Before" and "after"
  *   reports are ordered before and after (respectively) a main report.  
  */
 class CommandReport: object
     construct()
     {
         /* 
          *   remember the action with which we're associated, unless a
          *   subclass already specifically set the action 
          */
         if (action_ == nil)
             action_ = gAction;
     }
 
     /* get/set my action */
     getAction() { return action_; }
     setAction(action) { action_ = action; }
 
     /* check to see if my action is implicit */
     isActionImplicit() { return action_ != nil && action_.isImplicit; }
 
     /* check to see if my action is nested in the other report's action */
     isActionNestedIn(other)
     {
         return (action_ != nil
                 && other.getAction() != nil
                 && action_.isNestedIn(other.getAction()));
     }
 
     /*
      *   Flag: if this property is true, this report indicates a failure.
      *   By default, a report does not indicate failure.  
      */
     isFailure = nil
 
     /*
      *   Flag: if this property is true, this report indicates an
      *   interruption for interactive input. 
      */
     isQuestion = nil
 
     /* iteration number current when we were added to the transcript */
     iter_ = nil
 
     /* the action I'm associated with */
     action_ = nil
 
     /*
      *   Am I part of the same action as the given report?  Returns true if
      *   this action is part of the same iteration and part of the same
      *   action as the other report.  
      */
     isPartOf(report)
     {
         /* 
          *   if I don't have an action, or the other report doesn't have an
          *   action, we're not related 
          */
         if (action_ == nil || report.action_ == nil)
             return nil;
 
         /* if our iterations don't match, we're not related */
         if (iter_ != report.iter_)
             return nil;
 
         /* check if I'm part of the other report's action */
         return action_.isPartOf(report.action_);
     }
 ;
 
 /*
  *   Group separator.  This simply displays separation between groups of
  *   messages - that is, between one set of messages associated with a
  *   single action and a set of messages associated with a different
  *   action.  
  */
 class GroupSeparatorMessage: CommandReport
     construct(report)
     {
         /* use the same action and iteration as the given report */
         action_ = report.getAction();
         iter_ = report.iter_;
     }
 
     /* show the normal command results separator */
     showMessage() { say(gLibMessages.complexResultsSeparator); }
 ;
 
 /*
  *   Internal separator.  This displays separation within a group of
  *   messages for a command, to visually separate the results from an
  *   implied command from the results for the enclosing command. 
  */
 class InternalSeparatorMessage: CommandReport
     construct(report)
     {
         /* use the same action and iteration as the given report */
         action_ = report.getAction();
         iter_ = report.iter_;
     }
 
     /* show the normal command results separator */
     showMessage() { say(gLibMessages.internalResultsSeparator); }
 ;
 
 /*
  *   Report boundary marker.  This is a pseudo-report that doesn't display
  *   anything; its purpose is to allow a caller to identify a block of
  *   reports (the reports between two markers) for later removal or
  *   reordering.  
  */
 class MarkerReport: CommandReport
     showMessage() { }
 ;
 
 /*
  *   End-of-description marker.  This serves as a marker in the transcript
  *   stream to let us know where the descriptive reports for a given
  *   action end.  
  */
 class EndOfDescReport: MarkerReport
 ;
 
 /*
  *   Simple MessageResult-based command report 
  */
 class CommandReportMessage: CommandReport, MessageResult
     construct([params])
     {
         /* invoke our base class constructors */
         inherited CommandReport();
         inherited MessageResult(params...);
     }
 ;
 
 /* 
  *   default report 
  */
 class DefaultCommandReport: CommandReportMessage
 ;
 
 /*
  *   extra information report 
  */
 class ExtraCommandReport: CommandReportMessage
 ;
 
 /*
  *   default descriptive report 
  */
 class DefaultDescCommandReport: CommandReportMessage
 ;
 
 /*
  *   cosmetic spacing report 
  */
 class CosmeticSpacingCommandReport: CommandReportMessage
 ;
 
 /*
  *   base class for all "full" reports 
  */
 class FullCommandReport: CommandReportMessage
     /* 
      *   a full report has a sequence number that tells us where the
      *   report goes relative to the others - the higher this number, the
      *   later the report goes 
      */
     seqNum = nil
 ;
 
 /*
  *   "before" report - these come before the main report 
  */
 class BeforeCommandReport: FullCommandReport
     seqNum = 1
 ;
 
 /*
  *   main report 
  */
 class MainCommandReport: FullCommandReport
     seqNum = 2
 ;
 
 /*
  *   failure report 
  */
 class FailCommandReport: FullCommandReport
     seqNum = 2
     isFailure = true
 ;
 
 /*
  *   "after" report - these come after the main report 
  */
 class AfterCommandReport: FullCommandReport
     seqNum = 3
 ;
 
 /*
  *   An interruption for interactive input.  This is used to report a
  *   prompt for more information that's needed before the command can
  *   proceed, such as a prompt for a missing object, or a disambiguation
  *   prompt. 
  */
 class QuestionCommandReport: MainCommandReport
     isQuestion = true;
 ;
 
 /*
  *   A conversation begin/end report.  This is a special marker we insert
  *   into the transcript to flag the boundaries of an NPC's conversational
  *   message.  
  */
 class ConvBoundaryReport: CommandReport
     construct(id) { actorID = id; }
 
     /* the actor's ID number, as assigned by the ConversationManager */
     actorID = nil
 ;
 class ConvBeginReport: ConvBoundaryReport
     showMessage() { say('<.convbegin ' + actorID + '>'); }
 ;
 class ConvEndReport: ConvBoundaryReport
     construct(id, node)
     {
         inherited(id);
         defConvNode = node;
     }
     
     showMessage()
     {
         if (actorID != nil)
             say('<.convend ' + actorID + ' '
                 + (defConvNode == nil ? '' : defConvNode) + '>');
     }
 
     /* the default new ConvNode for the actor */
     defConvNode = nil
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Announcements.  We use these to track announcements to be made as
  *   part of an action's results. 
  */
 class CommandAnnouncement: CommandReport
     construct([params])
     {
         /* inherit default handling */
         inherited();
 
         /* remember our text */
         messageText_ = getMessageText(params...);
     }
 
     /* 
      *   Get our message text.  By default, we simply get the gLibMessages
      *   message given by the property. 
      */
     getMessageText([params])
     {
         /* get the library message */
         return gLibMessages.(messageProp_)(params...);
     }
 
     /* 
      *   Show our message.  Our default implementation shows the library
      *   message given by our messageProp_ property, using the parameters
      *   we stored in our constructor.  
      */
     showMessage()
     {
         /* call gLibMessages to show our message */
         say(messageText_);
     }
 
     /* our gLibMessages property */
     messageProp_ = nil
 
     /* our message text */
     messageText_ = ''
 ;
 
 /*
  *   Multiple-object announcement.  When the player applies a single
  *   command to a series of objects (as in "take the book and the folder"
  *   or "take all"), we'll show one of these announcements for each object,
  *   just before we execute the command for that object.  This announcement
  *   usually just shows the object's name plus suitable punctuation (in
  *   English, a colon), and helps the player see which results go with
  *   which objects.  
  */
 class MultiObjectAnnouncement: CommandAnnouncement
     /* show the announceMultiActionObject message */
     messageProp_ = &announceMultiActionObject
 ;
 
 /*
  *   Default object announcement.  We display this announcement whenever
  *   the player leaves out a required object from a command, but the parser
  *   is able to infer which object they must have meant.  The parser infers
  *   that an object was intended when a verb requires an object that the
  *   player didn't specify, and there's only one logical choice for the
  *   missing object.  We announce our assumption to put it out in the open,
  *   to ensure that the player is immediately alerted if they had something
  *   else in mind.
  *   
  *   In English, this type of announcement conventionally consists of
  *   simply the name of the assumed object, in parenthesis and on a line by
  *   itself.  In cases where the object role involves a prepositional
  *   phrase in the verb structure, we generally show the preposition before
  *   the object name.  This format usually reads intuitively, by combining
  *   with the text just above of the player's own command:
  *
  *.  >open
  *.  (the door>
  *.  You try opening the door, but it seems to be locked.
  *.
  *.  >unlock the door
  *   (with the key)
  */
 class DefaultObjectAnnouncement: CommandAnnouncement
     construct(obj, whichObj, action, allResolved)
     {
         /* remember our object */
         obj_ = obj;
 
         /* remember the message parameters */
         whichObj_ = whichObj;
         allResolved_ = allResolved;
 
         /* remember my action */
         action_ = action;
 
         /* inherit default handling */
         inherited();
     }
 
     /* get our message text */
     getMessageText()
     {
         /* get the announcement message from our object */
         return obj_.announceDefaultObject(whichObj_, action_, allResolved_);
     }
 
     /* our defaulted object */
     obj_ = nil
 
     /* our message parameters */
     whichObj_ = nil
     allResolved_ = nil
 ;
 
 /*
  *   Ambiguous object announcement.  We display this when the parser
  *   manages to resolve a noun phrase to an object (or objects) from an
  *   ambiguous set of possibilities, without having to ask the player for
  *   help but also without absolute certainty that the objects selected are
  *   the ones the player meant.  This happens when more than enough objects
  *   are logical possibilities for selection, but some objects are more
  *   logical choices than others.  The parser picks the most logical of the
  *   available options, but since other logical choices are present, the
  *   parser can't be certain that it chose the ones the player actually
  *   meant.  Because of this uncertainty, we generate one of these
  *   announcements each time this happens.  This report lets the player
  *   know exactly which object we chose, which will immediately alert the
  *   player when our selection is different from what they had in mind.
  *   
  *   In form, this type of announcement usually looks just like a default
  *   object announcement.  
  */
 class AmbigObjectAnnouncement: CommandAnnouncement
     /* show the announceAmbigObject announcement */
     messageProp_ = &announceAmbigActionObject
 ;
 
 /*
  *   Remapped action announcement.  This is used when we need to mention a
  *   defaulted or disambiguated object, but the player's original input was
  *   remapped to a different action that rearranges the object roles.  In
  *   these cases, rather than just announcing the defaulted object name, we
  *   announce the entire remapped action; we show the full action
  *   description because rearrangement of the object roles usually makes
  *   the standard object-only announcement confusing to read, since it
  *   doesn't naturally fit in as a continuation of what the user typed.
  *   
  *   In English, this message is usually shown with the entire verb phrase,
  *   in present participle form ("opening the door"), enclosed in
  *   parentheses and on a line by itself.  
  */
 class RemappedActionAnnouncement: CommandAnnouncement
     construct()
     {
         /* use the action as the message parameter */
         inherited(gAction);
     }
 
     messageProp_ = &announceRemappedAction
 ;
 
 /*
  *   Each language module must define a class called
  *   ImplicitAnnouncementContext, and three instances of the class, for use
  *   by the generic library.  The language module can define other
  *   instances of the context class as needed.  We minimally need the
  *   following instances to be defined by the language module:
  *   
  *   standardImpCtx: this is the standard context, which indicates that we
  *   want the default format for the implicit action announcement.
  *   
  *   tryingImpCtx: this is the "trying" context, which indicates that we
  *   want the announcement to phrase the action to indicate that we're only
  *   trying the action, not actually performing it.  We use this when the
  *   implicit action has failed, in which case we want our announcement to
  *   say that we're merely attempting the action; the announcement
  *   shouldn't imply that the action has actually been performed.
  *   
  *   askingImpCtx: this is the "asking" context, which indicates that the
  *   action was interrupted with an interactive question, such as a prompt
  *   for a missing direct object.
  *   
  *   We leave it up to the language module to define the class and these
  *   two instances.  This lets the language module represent the context
  *   types any way it likes.  
  */
 
 /*
  *   Implicit action announcement.  This is displayed when we perform a
  *   command implicitly, which we usually do to fulfill a precondition of
  *   an action.
  *   
  *   In English, we usually show an implied action as the verb participle
  *   phrase ("opening the door"), prefixed with "first", and enclosed in
  *   parentheses on a line by itself (hence, "(first opening the door)").  
  */
 class ImplicitActionAnnouncement: CommandAnnouncement
     construct(action, msg)
     {
         /* use the given message property */
         messageProp_ = msg;
 
         /* 
          *   Inherit default.  The first message parameter is the action;
          *   the second is our standard implicit action context object,
          *   indicating that we want the normal context.  
          */
         inherited(action, standardImpCtx);
     }
 
     /* 
      *   Make this announcement silent.  This eliminates any announcement
      *   for this action, but makes it otherwise behave like a normal
      *   implied action. 
      */
     makeSilent()
     {
         /* clear my message text */
         messageText_ = '';
 
         /* 
          *   use the silent announcement message if we have to regenerate
          *   our text for another context 
          */
         messageProp_ = &silentImplicitAction;
     }
 
     /*
      *   Note that the action we're attempting is merely an attempt that
      *   failed.  This will change our report to indicate that we're only
      *   trying the action, rather than suggesting that we actually carried
      *   it out.  
      */
     noteJustTrying()
     {
         /* note that we're just trying the action */
         justTrying = true;
 
         /* change our message to the "trying" form */
         messageText_ = getMessageText(getAction(), tryingImpCtx);
     }
 
     /* 
      *   Note that the action we're attempting is incomplete, as it was
      *   interupted for interactive input (such as asking for a missing
      *   object). 
      */
     noteQuestion()
     {
         /* note that the action was interrupted with a question */
         justAsking = true;
 
         /* change our message to the "asking" form */
         messageText_ = getMessageText(getAction(), askingImpCtx);
     }
 
     /* 
      *   Flag: we're just attempting the action; this is set when we
      *   determine that the implicit action has failed, in which case we
      *   want an announcement indicating that we're merely attempting the
      *   action, not actually performing it.  Presume that we're actually
      *   going to perform the action; the action can change this if
      *   necessary.  
      */
     justTrying = nil
 
     /* flag: the action was interrupted with an interactive question */
     justAsking = nil
 ;
 
 class CommandSepAnnouncement: CommandAnnouncement
     construct()
     {
         /* we're not associated with an iteration or action */
         action_ = nil;
         iter_ = 0;
     }
 
     showMessage()
     {
         /* show a command separator */
         "<.commandsep>";
     }
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Command Transcript.  This is a "semantic transcript" of the results of
  *   a command.  This provides a list of CommandReport objects describing
  *   the results of the command.  
  */
 class CommandTranscript: OutputFilter
     construct()
     {
         /* set up a vector to hold the reports */
         reports_ = new Vector(5);
     }
 
     /* 
      *   flag: the command has failed (i.e., at least one failure report
      *   has been generated) 
      */
     isFailure = nil
 
     /*
      *   Note that the current action has failed.  This is equivalent to
      *   adding a reportFailure() message to the transcript.  
      */
     noteFailure()
     {
         /* add an empty reportFailure message */
         reportFailure('');
     }
 
     /* 
      *   flag: I'm active; when this is nil, we'll pass text through our
      *   filter routine unchanged 
      */
     isActive = true
 
     /*
      *   Summarize the current action's reports.  This allows a caller to
      *   turn a series of iterated reports into a single report for the
      *   entire action.  For example, we could change something like this:
      *   
      *   gold coin: Bob accepts the gold coin.
      *.  gold coin: Bob accepts the gold coin.
      *.  gold coin: Bob accepts the gold coin.
      *   
      *   into this:
      *   
      *   Bob accepts the three gold coins.
      *   
      *   This function runs through the reports for the current action,
      *   submitting each one to the 'cond' callback to see if it's of
      *   interest to the summary.  For each consecutive run of two or more
      *   reports that can be summarized, we'll remove the reports that
      *   'cond' accepted, and we'll remove the multiple-object announcement
      *   reports associated with them, and we'll insert a new report with
      *   the message returned by the 'report' callback.
      *   
      *   'cond' is called as cond(x), where 'x' is a report object.  This
      *   callback returns true if the report can be summarized for the
      *   caller's purposes, nil if not.
      *   
      *   'report' is called as report(vec), where 'vec' is a Vector
      *   consisting of all of the consecutive report objects that we're now
      *   summarizing.  This function returns a string giving the message to
      *   use in place of the reports we're removing.  This should be a
      *   summary message, standing in for the set of individual reports
      *   we're removing.
      *   
      *   There's an important subtlety to note.  If the messages you're
      *   summarizing are conversational (that is, if they're generated by
      *   TopicEntry responses), you should take care to generate the full
      *   replacement text in the 'report' part, rather than doing so in
      *   separate code that you run after summarizeAction() returns.  This
      *   is important because it ensures that the Conversation Manager
      *   knows that your replacement message is part of the same
      *   conversation.  If you wait until after summarizeAction() returns
      *   to generate more response text, the conversation manager won't
      *   realize that the additional text is part of the same conversation.
      */
     summarizeAction(cond, report)
     {
         local vec = new Vector(8);
         local rpt = reports_;
         local cnt = rpt.length();
         local i;
 
         /* find the first report for the current action */
         for (i = 1 ; i <= cnt && rpt[i].getAction() != gAction ; ++i) ;
 
         /* iterate over the transcript for the current action */
         for ( ; ; ++i)
         {
             local ok;
 
             /* presume we won't find a summarizable item */
             ok = nil;
             
             /* if we're still in range, check what we have */
             if (i <= cnt)
             {
                 /* get the current item */
                 local cur = rpt[i];
 
                 /* if this one is of interest, note it in the vector */
                 if (cond(cur))
                 {
                     /* add it to our vector of summarizable reports */
                     vec.append(cur);
 
                     /* note that we're okay on this round */
                     ok = true;
                 }
                 else if (cur.ofKind(ImplicitActionAnnouncement)
                          || cur.ofKind(MultiObjectAnnouncement)
                          || cur.ofKind(DefaultCommandReport)
                          || cur.ofKind(ConvBoundaryReport))
                 {
                     /* we can keep these in summaries */
                     ok = true;
                 }
             }
 
             /* 
              *   If this item isn't summarizable, or we've reached the last
              *   item, generate a summary of any we have so far.  (We need
              *   to generate the summary on reaching the last item because
              *   there are no further items that could go in the summary.)
              */
             if (!ok || i == cnt)
             {
                 /* if we have two or more items, generate a summary */
                 if (vec.length() > 1)
                 {
                     local insIdx;
                     local txt;
                 
                     /* remove each item in the vector */
                     foreach (local cur in vec)
                     {
                         local idx;
                         
                         /* get the index of the current item */
                         idx = rpt.indexOf(cur);
 
                         /* 
                          *   we're summarizing this item, so remove the
                          *   individual item - subsume it into the summary 
                          */
                         rpt.removeElementAt(idx);
                         --i;
                         --cnt;
 
                         /* insert the summary here */
                         insIdx = idx;
 
                         /* 
                          *   skip any implicit action announcements,
                          *   default command announcements, and
                          *   conversational boundary markers 
                          */
                         for (--idx ;
                              idx > 0
                              && (rpt[idx].ofKind(ImplicitActionAnnouncement)
                                  || rpt[idx].ofKind(DefaultCommandReport)
                                  || rpt[idx].ofKind(ConvBoundaryReport)) ;
                              --idx) ;
 
                         /*
                          *   if the preceding element is a multi-object
                          *   announcement, remove it - let the summary
                          *   mention the individual objects if it wants to 
                          */
                         if (idx > 0
                             && rpt[idx].ofKind(MultiObjectAnnouncement))
                         {
                             /* remove this element and adjust the counters */
                             rpt.removeElementAt(idx);
                             --i;
                             --cnt;
                             --insIdx;
 
                             /*
                              *   If this leaves us with a <.convbegin>
                              *   preceded directly by a <.convend> for the
                              *   same actor, the two cancel each other out.
                              *   Simply remove both of them.  
                              */
                             if (idx <= rpt.length()
                                 && idx > 1
                                 && rpt[idx].ofKind(ConvBeginReport)
                                 && rpt[idx-1].ofKind(ConvEndReport)
                                 && rpt[idx].actorID == rpt[idx-1].actorID)
                             {
                                 /* 
                                  *   we have a canceling <.convend> +
                                  *   <.convbegin> pair - simply remove them
                                  *   both and adjust the counters
                                  *   accordingly 
                                  */
                                 rpt.removeRange(idx - 1, idx);
                                 i -= 2;
                                 cnt -= 2;
                                 insIdx -= 2;
                             }
                         }
                     }
 
                     /* ask the caller for the summary */
                     txt = report(vec);
 
                     /* insert it */
                     rpt.insertAt(insIdx, new MainCommandReport(txt));
                     ++cnt;
                     ++i;
                 }
 
                 /* clear the vector */
                 if (vec.length() > 0)
                     vec.removeRange(1, vec.length());
             }
 
             /* if we've reached the end of the list, we're done */
             if (i > cnt)
                 break;
         }
     }
 
     /* activate - set up to capture output */
     activate()
     {
         /* make myself active */
         isActive = true;
     }
 
     /* deactivate - stop capturing output */
     deactivate()
     {
         /* make myself inactive */
         isActive = nil;
     }
 
     /* 
      *   Count an iteration.  An Action should call this once per iteration
      *   if it's a top-level (non-nested) command.
      */
     newIter() { ++iter_; }
 
     /* 
      *   Flush the transcript in preparation for reading input.  This
      *   shows all pending reports, clears the backlog of reports (so that
      *   we don't show them again in the future), and deactivates the
      *   transcript's capture feature so that subsequent output goes
      *   directly to the output stream.  
      */
     flushForInput()
     {
         /* show our reports, and deactivate output capture */
         showReports(true);
 
         /* clear the reports, since we've now shown them all */
         clearReports();
     }
 
     /* show our reports */
     showReports(deact)
     {
         local wasActive;
         
         /* 
          *   remember whether we were active or not originally, then
          *   deactivate (maybe just temporarily) so that we can write out
          *   our reports without recursively intercepting them
          */
         wasActive = isActive;
         deactivate();
 
         /* first, apply all defined transformations to our transcript */
         applyTransforms();
         
         /*
          *   Temporarily cancel any sense context message blocking.  We
          *   have already taken into account for each report whether or
          *   not the report was visible when it was generated, so we can
          *   display each report that made it past that check without any
          *   further conditions.  
          */
         callWithSenseContext(nil, nil, new function()
         {
             /* show the reports */            
             foreach (local cur in reports_)
             {
                 /* if we're allowed to show this report, show it */
                 if (canShowReport(cur))
                     cur.showMessage();
             }
         });
             
         /* 
          *   if we were active and we're not being asked to deactivate,
          *   re-activate now that we're finished showing our reports 
          */
         if (wasActive && !deact)
             activate();
     }
 
     /*
      *   Add a report. 
      */
     addReport(report)
     {
         /* check for a failure report */
         if (report.isFailure)
         {
             /* set the failure flag for the entire command */
             isFailure = true;
 
             /* 
              *   If this is an implied command, and the actor is in "NPC
              *   mode", suppress the report.  When an implied action fails
              *   in NPC mode, we act as though we never attempted the
              *   action. 
              */
             if (gAction.isImplicit && gActor.impliedCommandMode() == ModeNPC)
                 return;
         }
 
         /* 
          *   Do not queue reports made while the sense context is blocking
          *   output because the player character cannot sense the locus of
          *   the action.  Note that this check comes before we queue the
          *   report, but after we've noted any effect on the status of the
          *   overall action; even if we're not going to show the report,
          *   its status effects are still valid.  
          */
         if (senseContext.isBlocking)
             return;
 
         /* 
          *   If the new report's iteration ID hasn't been set already, note
          *   the current iteration in the report.  Some types of reports
          *   will have already set a specific iteration before we get here,
          *   so set the iteration ID only if the report hasn't done so
          *   already.  
          */
         if (report.iter_ == nil)
             report.iter_ = iter_;
 
         /* append the report */
         reports_.append(report);
     }
 
     /* get the last report added */
     getLastReport()
     {
         local cnt = reports_.length();
         return (cnt == 0 ? nil : reports_[cnt]);
     }
 
     /* delete the last report added */
     deleteLastReport()
     {
         local cnt = reports_.length();
         if (cnt != 0)
             reports_.removeElementAt(cnt);
     }
 
     /*
      *   Add a marker report.  This adds a marker to the report stream,
      *   and returns the marker object.  The marker doesn't show any
      *   message in the final display, but callers can use a pair of
      *   markers to identify a range of reports for later reordering or
      *   removal. 
      */
     addMarker()
     {
         /* create the new report */
         local marker = new MarkerReport();
 
         /* add it to the stream */
         addReport(marker);
 
         /* return the new report */
         return marker;
     }
 
     /* delete the reports between two markers */
     deleteRange(marker1, marker2)
     {
         local idx1, idx2;
         
         /* find the indices of the two markers */
         idx1 = reports_.indexOf(marker1);
         idx2 = reports_.indexOf(marker2);
 
         /* if we found both, delete the range */
         if (idx1 != nil && idx2 != nil)
             reports_.removeRange(idx1, idx2);
     }
 
     /* 
      *   Pull out the reports between two markers, and reinsert them at
      *   the end of the transcript.  
      */
     moveRangeAppend(marker1, marker2)
     {
         local idx1, idx2;
         
         /* find the indices of the two markers */
         idx1 = reports_.indexOf(marker1);
         idx2 = reports_.indexOf(marker2);
 
         /* if we didn't find both, ignore the request */
         if (idx1 == nil || idx2 == nil)
             return;
 
         /* append each item in the range to the end of the report list */
         for (local i = idx1 ; i <= idx2 ; ++i)
             reports_.append(reports_[i]);
 
         /* delete the original copies */
         reports_.removeRange(idx1, idx2);
     }
 
     /* 
      *   Perform a callback on all of the reports in the transcript.
      *   We'll invoke the given callback function func(rpt) once for each
      *   report, with the report object as the parameter. 
      */
     forEachReport(func) { reports_.forEach(func); }
 
     /*
      *   End the description section of the report.  This adds a marker
      *   report that indicates that anything following (and part of the
      *   same action) is no longer part of the description; this can be
      *   important when we apply the default description suppression
      *   transformation, because it tells us not to consider the
      *   non-descriptive messages following this marker when, for example,
      *   suppressing default descriptive messages.  
      */
     endDescription()
     {
         /* add an end-of-description report */
         addReport(new EndOfDescReport());
     }
 
     /*
      *   Announce that the action is implicit
      */
     announceImplicit(action, msgProp)
     {
         /* 
          *   If the actor performing the command is not in "player" mode,
          *   save an implicit action announcement; for NPC mode, we treat
          *   implicit command results like any other results, so we don't
          *   want a separate announcement.  
          */
         if (gActor.impliedCommandMode() == ModePlayer)
         {
             /* create the new report */
             local report = new ImplicitActionAnnouncement(action, msgProp);
 
             /* add it to the transcript */
             addReport(report);
 
             /* return it */
             return report;
         }
         else
         {
             /* no need for a report */
             return nil;
         }
     }
 
     /*
      *   Announce a remapped action 
      */
     announceRemappedAction()
     {
         /* save a remapped-action announcement */
         addReport(new RemappedActionAnnouncement());
     }
 
     /*
      *   Announce one of a set of objects to a multi-object action.  We'll
      *   record this announcement for display with our report list.  
      */
     announceMultiActionObject(obj, whichObj)
     {
         /* save a multi-action object announcement */
         addReport(new MultiObjectAnnouncement(obj, whichObj, gAction));
     }
 
     /*
      *   Announce an object that was resolved with slight ambiguity. 
      */
     announceAmbigActionObject(obj, whichObj)
     {
         /* save an ambiguous object announcement */
         addReport(new AmbigObjectAnnouncement(obj, whichObj, gAction));
     }
 
     /*
      *   Announce a default object. 
      */
     announceDefaultObject(obj, whichObj, action, allResolved)
     {
         /* save the default object announcement */
         addReport(new DefaultObjectAnnouncement(
             obj, whichObj, action, allResolved));
     }
 
     /*
      *   Add a command separator. 
      */
     addCommandSep()
     {
         /* add a command separator announcement */
         addReport(new CommandSepAnnouncement());
     }
 
     /*
      *   clear our reports 
      */
     clearReports()
     {
         /* forget all of the reports in the main list */
         if (reports_.length() != 0)
             reports_.removeRange(1, reports_.length());
     }
 
     /*
      *   Can we show a given report?  By default, we always return true,
      *   but subclasses might want to override this to suppress certain
      *   types of reports.  
      */
     canShowReport(report) { return true; }
 
     /*
      *   Filter text.  If we're active, we'll turn the text into a command
      *   report and add it to our report list, blocking the text from
      *   reaching the underlying stream; otherwise, we'll pass it through
      *   unchanged.  
      */
     filterText(ostr, txt)
     {
         /* if we're inactive, pass text through unchanged */
         if (!isActive)
             return txt;
         
         /* 
          *   If the current sense context doesn't allow any messages to be
          *   generated, block the generated text entirely.  We want to
          *   block text or not according to the sense context in effect
          *   now; so we must note it now rather than wait until we
          *   actually display the report, since the context could be
          *   different by then.  
          */
         if (senseContext.isBlocking)
             return nil;
 
         /* add a main report to our list if the text is non-empty */
         if (txt != '')
             addReport(new MainCommandReport(txt));
 
         /* capture the text - send nothing to the underlying stream */
         return nil;
     }
 
     /* apply transformations */
     applyTransforms()
     {
         /* apply each defined transformation */
         foreach (local cur in transforms_)
             cur.applyTransform(self, reports_);
     }
 
     /* 
      *   check to see if the current action has a report matching the given
      *   criteria 
      */
     currentActionHasReport(func)
     {
         /* check to see if we can find a matching report */
         return (findCurrentActionReport(func) != nil);
     }
 
     /* find a report in the current action that matches the given criteria */
     findCurrentActionReport(func)
     {
         /* 
          *   Find an action that's part of the current iteration and which
          *   matches the given function's criteria.  Return the first match
          *   we find.  
          */
         return reports_.valWhich({x: x.iter_ == iter_ && (func)(x)});
     }
 
     /* 
      *   iteration number - for an iterated top-level command, this helps
      *   us keep the results for a particular iteration grouped together 
      */
     iter_ = 1
 
     /* our vector of reports */
     reports_ = nil
 
     /* our list of transformations */
     transforms_ = [defaultReportTransform, implicitGroupTransform,
                    reportOrderTransform, complexMultiTransform]
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Transcript Transform. 
  */
 class TranscriptTransform: object
     /*
      *   Apply our transform to the transcript vector.  By default, we do
      *   nothing; each subclass must override this to manipulate the vector
      *   to make the change it wants to make.  
      */
     applyTransform(trans, vec) { }
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Transcript Transform: set before/main/after report order.  We'll look
  *   for any before/after reports that are out of order with respect to
  *   their main reports, and move them into the appropriate positions. 
  */
 reportOrderTransform: TranscriptTransform
     applyTransform(trans, vec)
     {
         /* scan for before/after reports */
         for (local i = 1, local len = vec.length() ; i <= len ; ++i)
         {
             /* get this item */
             local cur = vec[i];
 
             /* if this is a before/after report, consider moving it */
             if (cur.ofKind(FullCommandReport) && cur.seqNum != nil)
             {
                 local idx;
                 
                 /* 
                  *   This item cares about its sequencing, so it could be
                  *   out of order with respect to other items from the same
                  *   sequence.  Find the first item with a higher sequence
                  *   number from the same group, and make sure this item is
                  *   before the first such item.  
                  */
                 for (idx = 1 ; idx < i ; ++idx)
                 {
                     local x;
                     
                     /* get this item */
                     x = vec[idx];
 
                     /* if x should come after cur, we need to move cur */
                     if (x.ofKind(FullCommandReport)
                         && x.seqNum > cur.seqNum
                         && x.isPartOf(cur))
                     {
                         /* remove cur and reinsert it before x */
                         vec.removeElementAt(i);
                         vec.insertAt(idx, cur);
 
                         /* adjust our scan index for the removal */
                         --i;
                         
                         /* no need to look any further */
                         break;
                     }
                 }
             }
         }
     }
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Transcript Transform: remove unnecessary default reports.  We'll scan
  *   the transcript for default reports for actions which also have
  *   implicit announcements or non-default reports, and remove those
  *   default reports.  We'll also remove default descriptive reports which
  *   also have non-default reports in the same action.  
  */
 defaultReportTransform: TranscriptTransform
     applyTransform(trans, vec)
     {
         /* scan for default reports */
         for (local i = 1, local len = vec.length() ; i <= len ; ++i)
         {
             local cur;
             
             /* get this item */
             cur = vec[i];
             
             /* 
              *   if this is a default report, check to see if we want to
              *   keep it 
              */
             if (cur.ofKind(DefaultCommandReport))
             {
                 /* 
                  *   check for a main report or an implicit announcement
                  *   associated with the same action; if we find anything,
                  *   we don't need to keep the default report 
                  */
                 if (vec.indexWhich(
                     {x: (x != cur
                          && cur.isPartOf(x)
                          && (x.ofKind(FullCommandReport)
                              || x.ofKind(ImplicitActionAnnouncement)))
                     }) != nil)
                 {
                     /* we don't need this default report */
                     vec.removeElementAt(i);
 
                     /* adjust our scan index for the removal */
                     --i;
                     --len;
                 }
             }
 
             /*
              *   if this is a default descriptive report, check to see if
              *   we want to keep it 
              */
             if (cur.ofKind(DefaultDescCommandReport))
             {
                 local fullIdx;
                 
                 /* 
                  *   check for a main report associated with the same
                  *   action
                  */
                 fullIdx = vec.indexWhich(
                     {x: (x != cur
                          && cur.isPartOf(x)
                          && x.ofKind(FullCommandReport))});
                 
                 /* 
                  *   if we found another report, check to see if it comes
                  *   before or after any 'end of description' for the same
                  *   action 
                  */
                 if (fullIdx != nil)
                 {
                     local endIdx;
 
                     /* find the 'end of description' report, if any */
                     endIdx = vec.indexWhich(
                         {x: (x != cur
                              && cur.isPartOf(x)
                              && x.ofKind(EndOfDescReport))});
 
                     /* 
                      *   if we found a full report before the
                      *   end-of-description report, then the full report is
                      *   part of the description and thus should suppress
                      *   the default report; otherwise, the description
                      *   portion includes only the default report and the
                      *   default report should thus remain 
                      */
                     if (endIdx == nil || fullIdx < endIdx)
                     {
                         /* don't keep the default descriptive report */
                         vec.removeElementAt(i);
 
                         /* adjust our indices for the removal */
                         --i;
                         --len;
                     }
                 }
             }
         }
     }
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Transcript Transform: group implicit announcements.  We'll find any
  *   runs of consecutive implicit command announcements, and group each run
  *   into a single announcement listing all of the implied actions.  For
  *   example, we'll turn this:
  *   
  *.  >go south
  *.  (first opening the door)
  *.  (first unlocking the door)
  *   
  *   this into:
  *   
  *.  >go south
  *.  (first opening the door and unlocking the door)
  *   
  *   In addition, if we find an implicit announcement in the middle of a
  *   set of regular command reports, and it's for an action nested within
  *   the action generating the regular reports, we'll start a new paragraph
  *   before the implicit announcement.  
  */
 implicitGroupTransform: TranscriptTransform
     applyTransform(trans, vec)
     {
         /* 
          *   Scan for implicit announcements whose actions failed, and mark
          *   the implicit actions as such.  This allows us to phrase the
          *   implicit announcements as attempts rather than as actual
          *   actions, which sounds a little better because it doesn't clash
          *   with the failure report that immediately follows.  
          */
         for (local i = 1, local len = vec.length() ; i <= len ; ++i)
         {
             local sub;
             
             /* get this item */
             local cur = vec[i];
 
             /* 
              *   If this is an implicit action announcement, and its
              *   corresponding action (or any nested action) failed, mark
              *   the implicit announcement as a mere attempt.  Likewise, if
              *   we're interrupting the action for interactive input, it's
              *   likewise just an incomplete attempt.  
              */
             if (cur.ofKind(ImplicitActionAnnouncement)
                 && (sub = vec.valWhich(
                     {x: ((x.isFailure || x.isQuestion)
                          && (x.isPartOf(cur)
                              || x.isActionNestedIn(cur)))})) != nil)
             {
                 /* 
                  *   it's either a failed attempt or an interruption for a
                  *   question - note which one 
                  */
                 if (sub.isFailure)
                     cur.noteJustTrying();
                 else
                     cur.noteQuestion();
             }
         }
 
         /* 
          *   Scan for implicit announcement groups.  Since we're only
          *   scanning for runs of two or more announcements, we can stop
          *   scanning one short of the end of the list - there's no need to
          *   check the last item because it can't possibly be followed by
          *   another item.  Thus, scan while i < len.  
          */
         for (local i = 1, local len = vec.length() ; i < len ; ++i)
         {
             local origI = i;
             
             /* get this item */
             local cur = vec[i];
             
             /* 
              *   If it's an implied action announcement, and the next one
              *   qualifies for group inclusion, build a group.  Note that
              *   because we only loop until we reach the second-to-last
              *   item, we know for sure there is indeed a next item to
              *   index here.  
              */
             if (cur.ofKind(ImplicitActionAnnouncement)
                 && canGroupWith(cur, vec[i+1]))
             {
                 local j;
                 local groupVec;
 
                 /* create a vector to hold the re-sorted group listing */
                 groupVec = new Vector(16);
 
                 /* 
                  *   Scan items for grouping.  This time, we want to scan
                  *   to the last (not second-to-last) item in the main
                  *   list, since we could conceivably group everything
                  *   remaining. 
                  */
                 for (j = i ; j <= len ; )
                 {
                     /* get this item */
                     cur = vec[j];
                     
                     /* unstack any recursive grouping */
                     j = unstackRecursiveGroup(groupVec, vec, j);
 
                     /* 
                      *   if we've used now everything in the list, or the
                      *   next item can't be grouped with the current item,
                      *   we're done 
                      */
                     if (j > len || !canGroupWith(cur, vec[j]))
                         break;
                 }
 
                 /* process default object announcements */
                 processDefaultAnnouncements(groupVec);
 
                 /* build the composite message for the entire group */
                 vec[i].messageText_ = implicitAnnouncementGrouper
                     .compositeMessage(groupVec);
 
                 /* 
                  *   Clear the messages in the second through last grouped
                  *   announcements.  Leave the report objects themselves
                  *   intact, so that our internal structural record of the
                  *   transcript remains as it was, but make them silent in
                  *   the displayed text, since these messages are now
                  *   subsumed into the combined first message.  
                  */
                 for (++i ; i < j ; ++i)
                     vec[i].messageText_ = '';
 
                 /* 
                  *   continue the main loop from the next element after the
                  *   last one we included in the group 
                  */
                 i = j - 1;
             }
 
             /*
              *   If this is an implied action or default object
              *   announcement that interrupts a set of regular command
              *   reports, and it's for an action nested within the action
              *   generating the reports, add a paragraph spacer before the
              *   implicit announcement.  
              */
             if ((cur.ofKind(ImplicitActionAnnouncement)
                  || cur.ofKind(DefaultObjectAnnouncement))
                 && cur.messageText_ != '')
             {
                 local j;
                 
                 /* scan back for the nearest announcement with text */
                 for (j = origI - 1 ; j >= 1 && vec[j].messageText_ == '' ;
                      --j) ;
 
                 /* 
                  *   if it's a regular command report, and our implied or
                  *   default announcement is nested within it, add a
                  *   paragraph spacer 
                  */
                 if (j >= 1
                     && vec[j].ofKind(FullCommandReport)
                     && cur.isActionNestedIn(vec[j]))
                 {
                     /* 
                      *   insert a paragraph spacer before the announcement
                      *   - this will make the implied action and its
                      *   results stand out as separate actions, rather than
                      *   running everything together without spacing 
                      */
                     vec.insertAt(origI, new GroupSeparatorMessage(cur));
 
                     /* adjust our indices for the insertion */
                     ++i;
                     ++len;
                 }
             }
         }
     }
 
     /*
      *   "Unstack" a recursive group of nested announcements.  Adds the
      *   recursive group to the output group vector in chronological order,
      *   and returns the index of the next item after the recursive group.
      *   
      *   A recursive group is a set of nested implicit commands, where one
      *   implicit command triggered another, which triggered another, and
      *   so on.  The innermost of the nested set is the one that's actually
      *   executed first chronologically, since an implied command must be
      *   carried out before its enclosing command can proceed.  For
      *   example:
      *   
      *.  >go south
      *.  (first opening the door)
      *.  (first unlocking the door)
      *.  (first taking the key out of the bag)
      *   
      *   Going south implies opening the door, but before we can open the
      *   door, we must unlock it, and before we can unlock it we must be
      *   holding the key.  In report order, the innermost command is listed
      *   last, since it's nested within the enclosing commands.
      *   Chronologically, though, the innermost command is actually
      *   executed first.  The purpose of this routine is to unstack these
      *   nested sets, rearranging them into chronological order.  
      */
     unstackRecursiveGroup(groupVec, vec, idx)
     {
         local cur;
 
         /* remember the item we're tasked to work on */
         cur = vec[idx];
 
         /* skip the current item */
         ++idx;
         
         /*
          *   Scan for items nested within vec[idx].  Process each child
          *   item first.  An item is nested within us if can be grouped
          *   with us, and its action is a child of our action.  
          */
         for (local len = vec.length() ; idx <= len ; )
         {
             /* if the next item is nested within 'cur', process it */
             if (canGroupWith(cur, vec[idx])
                 && vec[idx].getAction().isNestedIn(cur.getAction()))
             {
                 /* 
                  *   It's nested with us - process it recursively.  Since
                  *   our goal is to unstack these reports into
                  *   chronological order, we must process our children
                  *   first, so that they get added to the group vector
                  *   first, since children chronologically predede their
                  *   parents. 
                  */
                 idx = unstackRecursiveGroup(groupVec, vec, idx);
             }
             else
             {
                 /* it's not nested within us, so we're done */
                 break;
             }
         }
 
         /* add our item to the result vector */
         groupVec.append(cur);
 
         /* 
          *   return the index of the next item; this is simply the current
          *   'idx' value, since we've advanced it past each item we've
          *   processed 
          */
         return idx;
     }
 
     /*
      *   Process default object announcements in a grouped message vector.
      *   
      *   Default object announcements come in two flavors: with and without
      *   message text.  Those without message text are present purely to
      *   retain a structural record of the default object in the internal
      *   transcript; we can simply remove these, since the actions that
      *   created them didn't even want default messages.  For those that do
      *   include message text, remove them as well, but also use their
      *   actions to replace the corresponding parent actions, so that the
      *   parent actions reflect what actually happened with the final
      *   defaulted objects.  
      */
     processDefaultAnnouncements(vec)
     {
         /* scan the vector for default announcements */
         for (local i = 1, local len = vec.length() ; i <= len ; ++i)
         {
             local cur = vec[i];
             
             /* if this is a default announcement, process it */
             if (cur.ofKind(DefaultObjectAnnouncement))
             {
                 /* 
                  *   If it has a message, use its action to replace the
                  *   parent action.  The only way an implied command can
                  *   have a defaulted object is for the implied command to
                  *   have been stated with too few objects, so that an
                  *   askForIobj (for example) occurred.  In such cases, the
                  *   default announcement will be a child action of the
                  *   original underspecified action, so we can simply find
                  *   the original action and replace it with the defaulted
                  *   action.
                  */
                 if (cur.messageText_ != '')
                 {
                     /* 
                      *   Scan for the parent announcement.
                      *   
                      *   Note that the implicit announcement containing the
                      *   parent action will follow the default announcement
                      *   in the result list, since the default announcement
                      *   is a child of the parent.  
                      */
                     for (local j = i + 1 ; j <= len ; ++j)
                     {
                         /* if this is the parent action, replace it */
                         if (vec[j].getAction()
                             == cur.getAction().parentAction)
                         {
                             /* this is it - replace the action */
                             vec[j].setAction(cur.getAction());
                             
                             /* no need to look any further */
                             break;
                         }
                     }
                 }
 
                 /* remove the default announcement from the list */
                 vec.removeElementAt(i);
                 
                 /* adjust our list index and length for the deletion */
                 --i;
                 --len;
             }
         }
     }
 
     /* 
      *   Can we group the second item with the first?  Returns true if the
      *   second item is also an implicit action announcement, or it's a
      *   default object announcement whose parent action is the first
      *   item's action. 
      */
     canGroupWith(a, b)
     {
         /* if 'b' is also an implicit announcement, we can include it */
         if (b.ofKind(ImplicitActionAnnouncement))
             return true;
 
         /* 
          *   if 'b' is a default object announcement, and has the same
          *   parent action as 'a', then we can group it; otherwise we can't
          */
         return (b.ofKind(DefaultObjectAnnouncement)
                 && b.getAction().parentAction == a.getAction());
     }
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Transcript Transform: Complex Multi-object Separation.  If we have an
  *   action that's being applied to one of a bunch of iterated objects, and
  *   the action has any implied command announcements associated with it,
  *   we'll set off the result for this command from its preceding and
  *   following commands by a paragraph separator.  
  */
 complexMultiTransform: TranscriptTransform
     applyTransform(trans, vec)
     {
         /* scan the list for multi-object announcements */
         foreach (local cur in vec)
         {
             /* if it's a multi-object announcement, check it */
             if (cur.ofKind(MultiObjectAnnouncement))
             {
                 local idx;
                 local cnt;
                 local sep;
                     
                 /* 
                  *   We have a multi-object announcement.  If we find only
                  *   one other report within the group, and the report's
                  *   text is short, let this report run together with its
                  *   neighbors without any additional visual separation.
                  *   Otherwise, set this group apart from its neighbors by
                  *   adding a paragraph break before and after the group;
                  *   this will make the results easier to read by visually
                  *   separating each longish response as a separate
                  *   paragraph.
                  *   
                  *   First, find the current item's index.  
                  */
                 idx = vec.indexWhich({x: x == cur});
 
                 /* 
                  *   now scan subsequent items in the same command
                  *   iteration, and check to see if (1) we have more than
                  *   one item, or (2) the item has a longish message 
                  */
                 for (cnt = 0, ++idx, sep = nil ;
                      idx <= vec.length() && cnt < 2 ; ++idx, ++cnt)
                 {
                     local sub = vec[idx];
                     
                     /* if we've reached the end of the group, stop scanning */
                     if (sub.iter_ != cur.iter_)
                         break;
                     
                     /* 
                      *   If it has long text, add visual separation.  Note
                      *   that "long" is just a heuristic, because we can't
                      *   tell whether the text will wrap in any given
                      *   interpreter - that depends on the width of the
                      *   interpreter window and the font size, among other
                      *   things, and we have no way of knowing any of this
                      *   here.  
                      */
                     if (sub.ofKind(CommandReportMessage)
                         && sub.messageText_ != nil
                         && sub.messageText_.length() > 60)
                     {
                         /* it's long - add separation */
                         sep = true;
                         break;
                     }
                 }
 
                 /* if we need separation, add it now */
                 if (sep || cnt > 1)   
                 {
                     /* 
                      *   This is indeed a complex iterated item.  Set it
                      *   off by paragraph breaks before and after the
                      *   iteration.
                      *   
                      *   First, find the first item in this iteration.  If
                      *   it's not the first item in the whole transcript,
                      *   insert a separator before it.  
                      */
                     idx = vec.indexWhich({x: x.iter_ == cur.iter_});
                     if (idx != 1)
                         vec.insertAt(idx, new GroupSeparatorMessage(cur));
 
                     /* 
                      *   Next, find the last item in this iteration.  If
                      *   it's no the last item in the entire transcript,
                      *   add a separator after it. 
                      */
                     idx = vec.lastIndexWhich({x: x.iter_ == cur.iter_});
                     if (idx != vec.length())
                         vec.insertAt(idx + 1, new GroupSeparatorMessage(cur));
                 }
             }
 
             /* 
              *   if it's a command result from an implied command, and we
              *   have another command result following from the enclosing
              *   command, add a separator between this result and the next
              *   result 
              */
             if (cur.ofKind(CommandReportMessage) && cur.isActionImplicit)
             {
                 local idx;
 
                 /* get the index of this element */
                 idx = vec.indexOf(cur);
 
                 /* 
                  *   if there's another element following, check to see if
                  *   it's a command report for an enclosing action (i.e.,
                  *   an action that initiated this implied action) 
                  */
                 if (idx < vec.length())
                 {
                     local nxt;
 
                     /* get the next element */
                     nxt = vec[idx + 1];
 
                     /* 
                      *   if it's a command report for an action that
                      *   encloses this action, or it's another implicit
                      *   announcement, then put a separator before it 
                      */
                     if ((nxt.ofKind(CommandReportMessage)
                          && nxt.getAction() != cur.getAction()
                          && cur.isActionNestedIn(nxt))
                         || nxt.ofKind(ImplicitActionAnnouncement))
                          
                     {
                         /* add a separator */
                         vec.insertAt(idx + 1,
                                      new InternalSeparatorMessage(cur));
                     }
                 }
             }
         }
     }
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Invoke a callback function using a transcript of the given class.
  *   Returns the return value of the callback function.  
  */
 withCommandTranscript(transcriptClass, func)
 {
     local transcript;
     local oldTranscript;
 
     /* 
      *   if we already have an active transcript, just invoke the
      *   function, running everything through the existing active
      *   transcript 
      */
     if (gTranscript != nil && gTranscript.isActive)
     {
         /* invoke the callback and return the result */
         return (func)();
     }
 
     /* 
      *   Create a transcript of the given class.  Make the transcript
      *   transient, since it's effectively part of the output stream state
      *   and thus shouldn't be saved or undone. 
      */
     transcript = transcriptClass.createTransientInstance();
 
     /* make this transcript the current global transcript */
     oldTranscript = gTranscript;
     gTranscript = transcript;
 
     /* install the transcript as a filter on the main output stream */
     mainOutputStream.addOutputFilter(transcript);
 
     /* make sure we undo our global changes before we leave */
     try
     {
         /* invoke the callback and return the result */
         return (func)();
     }
     finally
     {
         /* uninstall the transcript output filter */
         mainOutputStream.removeOutputFilter(transcript);
 
         /* restore the previous global transcript */
         gTranscript = oldTranscript;
 
         /* show the transcript results */
         transcript.showReports(true);
     }
 }
 
TADS 3 Library Manual
Generated on 9/8/2006 from TADS version 3.0.11