1#charset "us-ascii"
2
3/* Copyright (c) 2000, 2002 Michael J. Roberts.  All Rights Reserved. */
4/*
5 *   TADS 3 Library - Lister class
6 *
7 *   This module defines the "Lister" class, which generates formatted
8 *   lists of objects, and several subclasses of Lister that generate
9 *   special kinds of lists.
10 */
11
12/* include the library header */
13#include "adv3.h"
14
15
16/* ------------------------------------------------------------------------ */
17/*
18 *   Lister.  This is the base class for formatting of lists of objects.
19 *
20 *   The external interface consists of the showList() method, which
21 *   displays a formatted list of objects according to the rules of the
22 *   lister subclass.
23 *
24 *   The rest of the methods are an internal interface which lister
25 *   subclasses can override to customize the way that a list is shown.
26 *   Certain of these methods are meant to be overridden by virtually all
27 *   listers, such as the methods that show the prefix and suffix
28 *   messages.  The remaining methods are designed to allow subclasses to
29 *   customize detailed aspects of the formatting, so they only need to be
30 *   overridden when something other than the default behavior is needed.
31 */
32class Lister: object
33    /*
34     *   Show a list, showing all items in the list as though they were
35     *   fully visible, regardless of their actual sense status.
36     */
37    showListAll(lst, options, indent)
38    {
39        local infoTab;
40
41        /* create a sense information table with each item in full view */
42        infoTab = new LookupTable(16, 32);
43        foreach (local cur in lst)
44        {
45            /* add a plain view sensory description to the info list */
46            infoTab[cur] = new SenseInfo(cur, transparent, nil, 3);
47        }
48
49        /* show the list from the current global point of view */
50        showList(getPOV(), nil, lst, options, indent, infoTab, nil);
51    }
52
53    /*
54     *   Display a list of items, grouping according to the 'listWith'
55     *   associations of the items.  We will only list items for which
56     *   isListed() returns true.
57     *
58     *   'pov' is the point of view of the listing, which is usually an
59     *   actor (and usually the player character actor).
60     *
61     *   'parent' is the parent (container) of the list being shown.  This
62     *   should be nil if the listed objects are not all within a single
63     *   object.
64     *
65     *   'lst' is the list of items to display.
66     *
67     *   'options' gives a set of ListXxx option flags.
68     *
69     *   'indent' gives the indentation level.  This is used only for
70     *   "tall" lists (specified by including ListTall in the options
71     *   flags).  An indentation level of zero indicates no indentation.
72     *
73     *   'infoTab' is a lookup table of SenseInfo objects for all of the
74     *   objects that can be sensed from the perspective of the actor
75     *   performing the action that's causing the listing.  This is
76     *   normally the table returned from Thing.senseInfoTable() for the
77     *   actor from whose point of view the list is being generated.  (We
78     *   take this as a parameter rather than generating ourselves for two
79     *   reasons.  First, it's often the case that the same information
80     *   table will be needed for a series of listings, so we can save the
81     *   compute time of recalculating the same table repeatedly by having
82     *   the caller obtain the table and pass it to each lister.  Second,
83     *   in some cases the caller will want to synthesize a special sense
84     *   table rather than using the actual sense information; taking this
85     *   as a parameter allows the caller to easily customize the table.)
86     *
87     *   'parentGroup' is the ListGroup object that is showing this list.
88     *   We will not group the objects we list into the parent group, or
89     *   into any group more general than the parent group.
90     *
91     *   This routine is not usually overridden in lister subclasses.
92     *   Instead, this method calls a number of other methods that
93     *   determine the listing style in more detail; usually those other,
94     *   simpler methods are customized in subclasses.
95     */
96    showList(pov, parent, lst, options, indent, infoTab, parentGroup)
97    {
98        local groups;
99        local groupTab;
100        local singles;
101        local origLst;
102        local itemCount;
103
104        /* remember the original list */
105        origLst = lst;
106
107        /* filter the list to get only the items we actually will list */
108        lst = getFilteredList(lst, infoTab);
109
110        /* create a lookup table to keep track of the groups we've seen */
111        groupTab = new LookupTable();
112        groups = new Vector(10);
113
114        /* set up a vector to keep track of the singles */
115        singles = new Vector(10);
116
117        /* figure the groupings */
118        itemCount = getListGrouping(groupTab, groups, singles,
119                                    lst, parentGroup);
120
121        /*
122         *   Now that we've figured out what's in the list and how it's
123         *   arranged into groups, show the list.
124         */
125        showArrangedList(pov, parent, lst, options, indent, infoTab,
126                         itemCount, singles, groups, groupTab, origLst);
127
128        /*
129         *   If the list is recursive, mention the contents of any items
130         *   that weren't listed in the main list, and of any contents
131         *   that are specifically to be listed out-of-line.  Don't do
132         *   this if we're already recursively showing such a listing,
133         *   since if we did so we could show items at recursive depths
134         *   more than once; if we're already doing a recursive listing,
135         *   our caller will itself recurse through all levels of the
136         *   tree, so we don't have to recurse any further ourselves.
137         */
138        if ((options & ListRecurse) != 0
139            && indent == 0
140            && (options & ListContents) == 0)
141        {
142            /* show the contents of each object we didn't list */
143            showSeparateContents(pov, origLst,
144                                 options | ListContents, infoTab);
145        }
146    }
147
148    /*
149     *   Filter a list to get only the elements we actually want to show.
150     *   Returns a new list consisting only of the items that (1) pass the
151     *   isListed() test, and (2) are represented in the sense information
152     *   table (infoTab).  If infoTab is nil, no sense filtering is
153     *   applied.
154     */
155    getFilteredList(lst, infoTab)
156    {
157        /* narrow the list down based on the isListed criteria */
158        lst = lst.subset({x: isListed(x)});
159
160        /*
161         *   If we have an infoTab, build a new list consisting only of
162         *   the items in 'lst' that have infoTab entries - we can't sense
163         *   anything that doesn't have an infoTab entry, so we don't want
164         *   to show any such objects.
165         */
166        if (infoTab != nil)
167        {
168            /* create a vector to contain the new filtered list */
169            local filteredList = new Vector(lst.length());
170
171            /*
172             *   run through our original list and confirm that each one
173             *   is in the infoTab
174             */
175            foreach (local cur in lst)
176            {
177                /*
178                 *   if this item has an infoTab entry, add this item to
179                 *   the filtered list
180                 */
181                if (infoTab[cur] != nil)
182                    filteredList.append(cur);
183            }
184
185            /* forget the original list, and use the filtered list instead */
186            lst = filteredList;
187        }
188
189        /* return the filtered list */
190        return lst;
191    }
192
193    /*
194     *   Get the groupings for a given listing.
195     *
196     *   'groupTab' is an empty LookupTable, and 'groups' is an empty
197     *   Vector; we'll populate these with the grouping information.
198     *   'singles' is an empty Vector that we'll populate with the single
199     *   items not part of any group.
200     */
201    getListGrouping(groupTab, groups, singles, lst, parentGroup)
202    {
203        local cur;
204        local i, cnt;
205
206        /*
207         *   First, scan the list to determine how we're going to group
208         *   the objects.
209         */
210        for (i = 1, cnt = lst.length() ; i <= cnt ; ++i)
211        {
212            local curGroups;
213            local parentIdx;
214
215            /* get this object into a local for easy reference */
216            cur = lst[i];
217
218            /* if the item isn't part of this listing, skip it */
219            if (!isListed(cur))
220                continue;
221
222            /* get the list of groups with which this object is listed */
223            curGroups = listWith(cur);
224
225            /* if there are no groups, we can move on to the next item */
226            if (curGroups == nil)
227                continue;
228
229            /*
230             *   If we have a parent group, and it appears in the list of
231             *   groups for this item, eliminate everything in the item's
232             *   group list up to and including the parent group.  If
233             *   we're showing this list as part of a group to begin with,
234             *   we obviously don't want to show this list grouped into
235             *   the same group, and we also don't want to group it into
236             *   anything broader than the parent group.  Groups are
237             *   listed from most general to most specific, so we can
238             *   eliminate anything up to and including the parent group.
239             */
240            if (parentGroup != nil
241                && (parentIdx = curGroups.indexOf(parentGroup)) != nil)
242            {
243                /* eliminate everything up to and including the parent */
244                curGroups = curGroups.sublist(parentIdx + 1);
245            }
246
247            /* if this item has no groups, skip it */
248            if (curGroups.length() == 0)
249                continue;
250
251            /*
252             *   This item has one or more group associations that we must
253             *   consider.
254             */
255            foreach (local g in curGroups)
256            {
257                local itemsInGroup;
258
259                /* find the group table entry for this group */
260                itemsInGroup = groupTab[g];
261
262                /* if there's no entry for this group, create a new one */
263                if (itemsInGroup == nil)
264                {
265                    /* create a new group table entry */
266                    itemsInGroup = groupTab[g] = new Vector(10);
267
268                    /* add it to the group vector */
269                    groups.append(g);
270                }
271
272                /*
273                 *   add this item to the list of items that want to be
274                 *   grouped with this group
275                 */
276                itemsInGroup.append(cur);
277            }
278        }
279
280        /*
281         *   We now have the set of all of the groups that could possibly
282         *   be involved in this list display.  We must now choose the
283         *   single group we'll use to display each grouped object.
284         *
285         *   First, eliminate any groups with insufficient membership.
286         *   (Most groups require at least two members, but this can vary
287         *   by group.)
288         */
289        for (i = 1, cnt = groups.length() ; i <= cnt ; ++i)
290        {
291            /* if this group has only one member, drop it */
292            if (groupTab[groups[i]].length() < groups[i].minGroupSize)
293            {
294                /* remove this group from the group list */
295                groups.removeElementAt(i);
296
297                /*
298                 *   adjust the list count, and back up to try the element
299                 *   newly at this index on the next iteration
300                 */
301                --cnt;
302                --i;
303            }
304        }
305
306        /*
307         *   Next, scan for groups with identical member lists, and for
308         *   groups with subset member lists.  For each pair of identical
309         *   elements we find, eliminate the more general of the two.
310         */
311        for (i = 1, cnt = groups.length() ; i <= cnt ; ++i)
312        {
313            local g1;
314            local mem1;
315
316            /* get the current group and its membership list */
317            g1 = groups[i];
318            mem1 = groupTab[g1];
319
320            /* look for matching items in the list after this one */
321            for (local j = i + 1 ; j <= cnt ; ++j)
322            {
323                local g2;
324                local mem2;
325
326                /* get the current item and its membership list */
327                g2 = groups[j];
328                mem2 = groupTab[g2];
329
330                /*
331                 *   Compare the membership lists for the two items.  Note
332                 *   that we built these membership lists all in the same
333                 *   order of objects, so if two membership lists have all
334                 *   the same members, those members will be in the same
335                 *   order in the two lists; hence, we can simply compare
336                 *   the two lists to determine the membership order.
337                 */
338                if (mem1 == mem2)
339                {
340                    local ordList;
341
342                    /*
343                     *   The groups have identical membership, so
344                     *   eliminate the more general group.  Groups are
345                     *   ordered from most general to least general, so
346                     *   keep the one with the higher index in the group
347                     *   list for an object in the membership list.  Note
348                     *   that we assume that each member has the same
349                     *   ordering for the common groups, so we can pick a
350                     *   member arbitrarily to find the way a member
351                     *   orders the groups.
352                     */
353                    ordList = listWith(mem1[1]);
354                    if (ordList.indexOf(g1) > ordList.indexOf(g2))
355                    {
356                        /*
357                         *   group g1 is more specific than group g2, so
358                         *   keep g1 and discard g2 - remove the 'j'
359                         *   element from the list, and back up in the
360                         *   inner loop so we reconsider the element newly
361                         *   at this index on the next iteration
362                         */
363                        groups.removeElementAt(j);
364                        --cnt;
365                        --j;
366                    }
367                    else
368                    {
369                        /*
370                         *   group g2 is more specific, so discard g1 -
371                         *   remove the 'i' element from the list, back up
372                         *   in the outer loop, and break out of the inner
373                         *   loop, since the outer loop element is no
374                         *   longer there for us to consider in comparing
375                         *   more elements in the inner loop
376                         */
377                        groups.removeElementAt(i);
378                        --cnt;
379                        --i;
380                        break;
381                    }
382                }
383            }
384        }
385
386        /*
387         *   Scan for subsets.  For each group whose membership list is a
388         *   subset of another group in our list, eliminate the subset,
389         *   keeping only the larger group.  The group lister will be able
390         *   to show the subgroup as grouped within its larger list.
391         */
392        for (local i = 1, cnt = groups.length() ; i <= cnt ; ++i)
393        {
394            local g1;
395            local mem1;
396
397            /* get the current group and its membership list */
398            g1 = groups[i];
399            mem1 = groupTab[g1];
400
401            /* look at the other elements to see if we have any subsets */
402            for (local j = 1 ; j <= cnt ; ++j)
403            {
404                local g2;
405                local mem2;
406
407                /* don't bother checking the same element */
408                if (j == i)
409                    continue;
410
411                /* get the current item and its membership list */
412                g2 = groups[j];
413                mem2 = groupTab[g2];
414
415                /*
416                 *   if g2's membership is a subset, eliminate g2 from the
417                 *   group list
418                 */
419                if (isListSubset(mem2, mem1))
420                {
421                    /* remove g2 from the list */
422                    groups.removeElementAt(j);
423
424                    /* adjust the loop counters for the removal */
425                    --cnt;
426                    --j;
427
428                    /*
429                     *   adjust the outer loop counter if it's affected -
430                     *   the outer loop is affected if it's already past
431                     *   this point in the list, which means that its
432                     *   index is higher than the inner loop index
433                     */
434                    if (i > j)
435                        --i;
436                }
437            }
438        }
439
440        /*
441         *   We now have a final accounting of the groups that we will
442         *   consider using.  Reset the membership list for each group in
443         *   the surviving list.
444         */
445        foreach (local g in groups)
446        {
447            local itemsInList;
448
449            /* get this group's membership list vector */
450            itemsInList = groupTab[g];
451
452            /* clear the vector */
453            itemsInList.removeRange(1, itemsInList.length());
454        }
455
456        /*
457         *   Now, run through our item list again, and assign each item to
458         *   the surviving group that comes earliest in the item's group
459         *   list.
460         */
461        for (i = 1, cnt = lst.length() ; i <= cnt ; ++i)
462        {
463            local curGroups;
464            local winningGroup;
465
466            /* get this object into a local for easy reference */
467            cur = lst[i];
468
469            /* if the item isn't part of this listing, skip it */
470            if (!isListed(cur))
471                continue;
472
473            /* get the list of groups with which this object is listed */
474            curGroups = listWith(cur);
475            if (curGroups == nil)
476                curGroups = [];
477
478            /*
479             *   find the first element in the group list that is in the
480             *   surviving group list
481             */
482            winningGroup = nil;
483            foreach (local g in curGroups)
484            {
485                /* if this group is in the surviving list, it's the one */
486                if (groups.indexOf(g) != nil)
487                {
488                    winningGroup = g;
489                    break;
490                }
491            }
492
493            /*
494             *   if we have a group, add this item to the group's
495             *   membership; otherwise, add it to the singles list
496             */
497            if (winningGroup != nil)
498                groupTab[winningGroup].append(cur);
499            else
500                singles.append(cur);
501        }
502
503        /* eliminate any surviving group with too few members */
504        for (i = 1, cnt = groups.length() ; i <= cnt ; ++i)
505        {
506            local mem;
507
508            /* get this group's membership list */
509            mem = groupTab[groups[i]];
510
511            /*
512             *   if this group's membership is too small, eliminate the
513             *   group and add the member into the singles pile
514             */
515            if (mem.length() < groups[i].minGroupSize)
516            {
517                /* put the item into the singles list */
518                if (mem.length() > 0)
519                    singles.append(mem[1]);
520
521                /* eliminate this item from the group list */
522                groups.removeElementAt(i);
523
524                /* adjust the loop counters */
525                --cnt;
526                --i;
527            }
528        }
529
530        /* return the cardinality of the arranged list */
531        return getArrangedListCardinality(singles, groups, groupTab);
532    }
533
534    /*
535     *   Show the list.  This is called after we've figured out which items
536     *   we intend to display, and after we've arranged the items into
537     *   groups.  In rare cases, listers might want to override this, to
538     *   customize the way the way the list is displayed based on the
539     *   internal arrangement of the list.
540     */
541    showArrangedList(pov, parent, lst, options, indent, infoTab, itemCount,
542                     singles, groups, groupTab, origLst)
543    {
544        /*
545         *   We now know how many items we're listing (grammatically
546         *   speaking), so we're ready to display the list prefix.  If
547         *   we're displaying nothing at all, just display the "empty"
548         *   message, and we're done.
549         */
550        if (itemCount == 0)
551        {
552            /* show the empty list */
553            showListEmpty(pov, parent);
554        }
555        else
556        {
557            local i;
558            local cnt;
559            local sublists;
560            local origOptions = options;
561            local itemOptions;
562            local groupOptions;
563            local listCount;
564            local dispCount;
565            local cur;
566
567            /*
568             *   Check to see if we have one or more group sublists - if
569             *   we do, we must use the "long" list format for our overall
570             *   list, otherwise we can use the normal "short" list
571             *   format.  The long list format uses semicolons to separate
572             *   items.
573             */
574            for (i = 1, cnt = groups.length(), sublists = nil ;
575                 i <= cnt ; ++i)
576            {
577                /*
578                 *   if this group's lister item displays a sublist, we
579                 *   must use the long format
580                 */
581                if (groups[i].groupDisplaysSublist)
582                {
583                    /* note that we are using the long format */
584                    sublists = true;
585
586                    /*
587                     *   one is enough to make us use the long format, so
588                     *   we need not look any further
589                     */
590                    break;
591                }
592            }
593
594            /* generate the prefix message if we're in a 'tall' listing */
595            if ((options & ListTall) != 0)
596            {
597                /* indent the prefix */
598                showListIndent(options, indent);
599
600                /*
601                 *   Show the prefix.  If this is a contents listing, and
602                 *   it's not at the top level, show the contents prefix;
603                 *   otherwise show the full list prefix.  Note that we can
604                 *   have a contents listing at the top level, since some
605                 *   lists are broken out for separate listing.
606                 */
607                if ((options & ListContents) != 0 && indent != 0)
608                    showListContentsPrefixTall(itemCount, pov, parent);
609                else
610                    showListPrefixTall(itemCount, pov, parent);
611
612                /* go to a new line for the list contents */
613                "\n";
614
615                /* indent the items one level now, since we showed a prefix */
616                ++indent;
617            }
618            else
619            {
620                /* show the prefix */
621                showListPrefixWide(itemCount, pov, parent);
622            }
623
624            /*
625             *   regardless of whether we're adding long formatting to the
626             *   main list, display the group sublists with whatever
627             *   formatting we were originally using
628             */
629            groupOptions = options;
630
631            /* show each item with our current set of options */
632            itemOptions = options;
633
634            /*
635             *   if we're using sublists, show "long list" separators in
636             *   the main list
637             */
638            if (sublists)
639                itemOptions |= ListLong;
640
641            /*
642             *   calculate the number of items we'll show in the list -
643             *   each group shows up as one list entry, so the total
644             *   number of list entries is the number of single items plus
645             *   the number of groups
646             */
647            listCount = singles.length() + groups.length();
648
649            /*
650             *   Show the items.  Run through the (filtered) original
651             *   list, so that we show everything in the original sorting
652             *   order.
653             */
654            dispCount = 0;
655            foreach (cur in lst)
656            {
657                local group;
658                local displayedCur;
659
660                /* presume we'll display this item */
661                displayedCur = true;
662
663                /*
664                 *   Figure out how to show this item: if it's in the
665                 *   singles list, show it as a single item; if it's in
666                 *   the group list, show its group; if it's in a group
667                 *   we've previously shown, show nothing, as we showed
668                 *   the item when we showed its group.
669                 */
670                if (singles.indexOf(cur) != nil)
671                {
672                    /*
673                     *   It's in the singles list, so show it as a single
674                     *   item.
675                     *
676                     *   If the item has contents that we'll display in
677                     *   'tall' mode, show the item with its contents - we
678                     *   don't need to show the item separately, since it
679                     *   will provide a 'tall' list prefix showing itself.
680                     *   Otherwise, show the item singly.
681                     */
682                    if ((options & ListTall) != 0
683                        && (options & ListRecurse) != 0
684                        && contentsListed(cur)
685                        && getListedContents(cur, infoTab) != [])
686                    {
687                        /* show the item with its contents */
688                        showContentsList(pov, cur, origOptions | ListContents,
689                                         indent, infoTab);
690                    }
691                    else
692                    {
693                        /* show the list indent if necessary */
694                        showListIndent(itemOptions, indent);
695
696                        /* show the item */
697                        showListItem(cur, itemOptions, pov, infoTab);
698
699                        /*
700                         *   if we're in wide recursive mode, show the
701                         *   item's contents as an in-line parenthetical
702                         */
703                        if ((options & ListTall) == 0
704                            && (options & ListRecurse) != 0
705                            && contentsListed(cur)
706                            && !contentsListedSeparately(cur))
707                        {
708                            /* show the item's in-line contents */
709                            showInlineContentsList(pov, cur,
710                                origOptions | ListContents,
711                                indent + 1, infoTab);
712                        }
713                    }
714                }
715                else if ((group = groups.valWhich(
716                    {g: groupTab[g].indexOf(cur) != nil})) != nil)
717                {
718                    /* show the list indent if necessary */
719                    showListIndent(itemOptions, indent);
720
721                    /* we found the item in a group, so show its group */
722                    group.showGroupList(pov, self, groupTab[group],
723                                        groupOptions, indent, infoTab);
724
725                    /*
726                     *   Forget this group - we only need to show each
727                     *   group once, since the group shows every item it
728                     *   contains.  Since we'll encounter the groups other
729                     *   members as we continue to scan the main list, we
730                     *   want to make sure we don't show the group again
731                     *   when we reach the other items.
732                     */
733                    groups.removeElement(group);
734                }
735                else
736                {
737                    /*
738                     *   We didn't find the item in the singles list or in
739                     *   a group - it must be part of a group that we
740                     *   already showed previously, so we don't need to
741                     *   show it again now.  Simply make a note that we
742                     *   didn't display it.
743                     */
744                    displayedCur = nil;
745                }
746
747                /* if we displayed the item, show a suitable separator */
748                if (displayedCur)
749                {
750                    /* count another list entry displayed */
751                    ++dispCount;
752
753                    /* show an appropriate separator */
754                    showListSeparator(itemOptions, dispCount, listCount);
755                }
756            }
757
758            /*
759             *   if we're in 'wide' mode, finish the listing (note that if
760             *   this is a 'tall' listing, we're already done, because a
761             *   tall listing format doesn't make provisions for anything
762             *   after the item list)
763             */
764            if ((options & ListTall) == 0)
765            {
766                /* show the wide-mode list suffix */
767                showListSuffixWide(itemCount, pov, parent);
768            }
769        }
770    }
771
772    /*
773     *   Get the cardinality of an arranged list.  Returns the number of
774     *   items that will appear in the list, for grammatical agreement.
775     */
776    getArrangedListCardinality(singles, groups, groupTab)
777    {
778        local cnt;
779
780        /* start with a count of zero; we'll add to it as we go */
781        cnt = 0;
782
783        /*
784         *   Add up the cardinality of the single items.  Some individual
785         *   items in the singles list might count as multiple items
786         *   grammatically - in particular, if an item has a plural name,
787         *   we need a plural verb to agree with it.
788         */
789        foreach (local s in singles)
790        {
791            /* add the grammatical cardinality of this single item */
792            cnt += listCardinality(s);
793        }
794
795        /* add in the cardinality of each group */
796        foreach (local g in groups)
797        {
798            /* add the grammatical cardinality of this group */
799            cnt += g.groupCardinality(self, groupTab[g]);
800        }
801
802        /* return the total */
803        return cnt;
804    }
805
806    /*
807     *   Get the number of noun phrase elements in a list.  This differs
808     *   from the cardinality in that we only count noun phrases, not the
809     *   cardinality of each noun phrase.  So, for example, "five coins"
810     *   has cardinality five, but has only one noun phrase.
811     */
812    getArrangedListNounPhraseCount(singles, groups, groupTab)
813    {
814        local cnt;
815
816        /* each single item counts as one noun phrase */
817        cnt = singles.length();
818
819        /* add in the noun phrases from each group */
820        foreach (local g in groups)
821            cnt += g.groupNounPhraseCount(self, groupTab[g]);
822
823        /* return the total */
824        return cnt;
825    }
826
827    /*
828     *   Service routine: show the separately-listed contents of the items
829     *   in the given list, and their separately-listed contents, and so
830     *   on.  This routine is not normally overridden in subclasses, and is
831     *   not usually called except from the Lister implementation.
832     *
833     *   For each item in the given list, we show the item's contents if
834     *   the item is either marked as unlisted, or it's marked as showing
835     *   its contents separately.  In the former case, we know that we
836     *   cannot have shown the item's contents in-line in the main list,
837     *   since we didn't show the item at all in the main list.  In the
838     *   latter case, we know that we didn't show the item's contents in
839     *   the main list because it's specifically marked as showing its
840     *   contents out-of-line.
841     */
842    showSeparateContents(pov, lst, options, infoTab)
843    {
844        /*
845         *   show the separate contents list for each item in the list
846         *   which isn't itself listable or which has its contents listed
847         *   separately despite its being listed
848         */
849        foreach (local cur in lst)
850        {
851            /* only show the contents if the contents are listed */
852            if (contentsListed(cur))
853            {
854                /*
855                 *   if we didn't list this item, or if it specifically
856                 *   wants its contents listed out-of-line, show its
857                 *   listable contents
858                 */
859                if (!isListed(cur) || contentsListedSeparately(cur))
860                {
861                    /*
862                     *   Show the item's contents.  Note that even though
863                     *   we're showing this list recursively, it's actually
864                     *   a new top-level list, so show it at indent level
865                     *   zero.
866                     */
867                    showContentsList(pov, cur, options, 0, infoTab);
868                }
869
870                /* recursively do the same thing with its contents */
871                showSeparateContents(pov, getContents(cur), options, infoTab);
872            }
873        }
874    }
875
876    /*
877     *   Service routine: determine if list a is a subset of list b.  a is
878     *   a subset of b if every element of a is in b.
879     */
880    isListSubset(a, b)
881    {
882        /* a can't be a subset if it has more elements than b */
883        if (a.length() > b.length())
884            return nil;
885
886        /* check each element of a to see if it's also in b */
887        foreach (local cur in a)
888        {
889            /* if this element of a is not in b, a is not a subset of b */
890            if (b.indexOf(cur) == nil)
891                return nil;
892        }
893
894        /*
895         *   we didn't find any elements of a not in b, so a is a subset
896         *   of b
897         */
898        return true;
899    }
900
901    /*
902     *   Show a list indent if necessary.  If ListTall is included in the
903     *   options, we'll indent to the given level; otherwise we'll do
904     *   nothing.
905     */
906    showListIndent(options, indent)
907    {
908        /* show the indent only if we're in "tall" mode */
909        if ((options & ListTall) != 0)
910        {
911            for (local i = 0 ; i < indent ; ++i)
912                "\t";
913        }
914    }
915
916    /*
917     *   Show a newline after a list item if we're in a tall list; does
918     *   nothing for a wide list.
919     */
920    showTallListNewline(options)
921    {
922        if ((options & ListTall) != 0)
923            "\n";
924    }
925
926    /*
927     *   Show a simple list, recursing into contents lists if necessary.
928     *   We pay no attention to grouping; we just show the items
929     *   individually.
930     *
931     *   'prevCnt' is the number of items already displayed, if anything
932     *   has already been displayed for this list.  This should be zero if
933     *   this will display the entire list.
934     */
935    showListSimple(pov, lst, options, indent, prevCnt, infoTab)
936    {
937        local i;
938        local cnt;
939        local dispCount;
940        local totalCount;
941
942        /* calculate the total number of items in the lis t*/
943        totalCount = prevCnt + lst.length();
944
945        /* display the items */
946        for (i = 1, cnt = lst.length(), dispCount = prevCnt ; i <= cnt ; ++i)
947        {
948            local cur;
949
950            /* get the item */
951            cur = lst[i];
952
953            /*
954             *   If the item has contents that we'll display in 'tall'
955             *   mode, show the item with its contents - we don't need to
956             *   show the item separately, since it will provide a 'tall'
957             *   list prefix showing itself.  Otherwise, show the item
958             *   singly.
959             */
960            if ((options & ListTall) != 0
961                && (options & ListRecurse) != 0
962                && contentsListed(cur)
963                && getListedContents(cur, infoTab) != [])
964            {
965                /* show the item with its contents */
966                showContentsList(pov, cur, options | ListContents,
967                                 indent + 1, infoTab);
968            }
969            else
970            {
971                /* show the list indent if necessary */
972                showListIndent(options, indent);
973
974                /* show the item */
975                showListItem(cur, options, pov, infoTab);
976
977                /*
978                 *   if we're in wide recursive mode, show the item's
979                 *   contents as an in-line parenthetical
980                 */
981                if ((options & ListTall) == 0
982                    && (options & ListRecurse) != 0
983                    && contentsListed(cur)
984                    && !contentsListedSeparately(cur))
985                {
986                    /* show the item's in-line contents */
987                    showInlineContentsList(pov, cur,
988                                           options | ListContents,
989                                           indent + 1, infoTab);
990                }
991            }
992
993            /* count the item displayed */
994            ++dispCount;
995
996            /* show the list separator */
997            showListSeparator(options, dispCount, totalCount);
998        }
999    }
1000
1001    /*
1002     *   List the contents of an item.
1003     *
1004     *   'pov' is the point of view, which is usually an actor (and
1005     *   usually the player character actor).
1006     *
1007     *   'obj' is the item whose contents we are to display.
1008     *
1009     *   'options' is the set of flags that we'll pass to showList(), and
1010     *   has the same meaning as for that function.
1011     *
1012     *   'infoTab' is a lookup table of SenseInfo objects giving the sense
1013     *   information for all of the objects that the actor to whom we're
1014     *   showing the contents listing can sense.
1015     */
1016    showContentsList(pov, obj, options, indent, infoTab)
1017    {
1018        /*
1019         *   List the item's contents.  By default, use the contentsLister
1020         *   property of the object whose contents we're showing to obtain
1021         *   the lister for the contents.
1022         */
1023        obj.showObjectContents(pov, obj.contentsLister, options,
1024                               indent, infoTab);
1025    }
1026
1027    /*
1028     *   Determine if an object's contents are listed separately from its
1029     *   own list entry for the purposes of our type of listing.  If this
1030     *   returns true, then we'll list the object's contents in a separate
1031     *   listing (a separate sentence following the main listing sentence,
1032     *   or a separate tree when in 'tall' mode).
1033     *
1034     *   Note that this only matters for objects listed in the top-level
1035     *   list.  We'll always show the contents separately for an object
1036     *   that isn't listed in the top-level list (i.e., an object for which
1037     *   isListed(obj) returns nil).
1038     */
1039    contentsListedSeparately(obj) { return obj.contentsListedSeparately; }
1040
1041    /*
1042     *   Show an "in-line" contents list.  This shows the item's contents
1043     *   list as a parenthetical, as part of a recursive listing.  This is
1044     *   pretty much the same as showContentsList(), but uses the object's
1045     *   in-line contents lister instead of its regular contents lister.
1046     */
1047    showInlineContentsList(pov, obj, options, indent, infoTab)
1048    {
1049        /* show the item's contents using its in-line contents lister */
1050        obj.showObjectContents(pov, obj.inlineContentsLister,
1051                               options, indent, infoTab);
1052    }
1053
1054    /*
1055     *   Show the prefix for a 'wide' listing - this is a message that
1056     *   appears just before we start listing the objects.  'itemCount' is
1057     *   the number of items to be listed; the items might be grouped in
1058     *   the listing, so a list that comes out as "three boxes and two
1059     *   books" will have an itemCount of 5.  (The purpose of itemCount is
1060     *   to allow the message to have grammatical agreement in number.)
1061     *
1062     *   This will never be called with an itemCount of zero, because we
1063     *   will instead use showListEmpty() to display an empty list.
1064     */
1065    showListPrefixWide(itemCount, pov, parent) { }
1066
1067    /*
1068     *   show the suffix for a 'wide' listing - this is a message that
1069     *   appears just after we finish listing the objects
1070     */
1071    showListSuffixWide(itemCount, pov, parent) { }
1072
1073    /*
1074     *   Show the list prefix for a 'tall' listing.  Note that there is no
1075     *   list suffix for a tall listing, because the format doesn't allow
1076     *   it.
1077     */
1078    showListPrefixTall(itemCount, pov, parent) { }
1079
1080    /*
1081     *   Show the list prefix for the contents of an object in a 'tall'
1082     *   listing.  By default, we just show our usual tall list prefix.
1083     */
1084    showListContentsPrefixTall(itemCount, pov, parent)
1085        { showListPrefixTall(itemCount, pov, parent); }
1086
1087    /*
1088     *   Show an empty list.  If the list to be displayed has no items at
1089     *   all, this is called instead of the prefix/suffix routines.  This
1090     *   can be left empty if no message is required for an empty list, or
1091     *   can display the complete message appropriate for an empty list
1092     *   (such as "You are empty-handed").
1093     */
1094    showListEmpty(pov, parent) { }
1095
1096    /*
1097     *   Is this item to be listed in room descriptions?  Returns true if
1098     *   so, nil if not.  By default, we'll use the object's isListed
1099     *   method to make this determination.  We virtualize this into the
1100     *   lister interface to allow for different inclusion rules for the
1101     *   same object depending on the type of list we're generating.
1102     */
1103    isListed(obj) { return obj.isListed(); }
1104
1105    /*
1106     *   Get the grammatical cardinality of this listing item.  This should
1107     *   return the number of items that this item appears to be
1108     *   grammatically, for noun-verb agreement purposes.
1109     */
1110    listCardinality(obj) { return obj.listCardinality(self); }
1111
1112    /*
1113     *   Are this item's contents listable?
1114     */
1115    contentsListed(obj) { return obj.contentsListed; }
1116
1117    /*
1118     *   Get all contents of this item.
1119     */
1120    getContents(obj) { return obj.contents; }
1121
1122    /*
1123     *   Get the listed contents of an object.  'infoTab' is the sense
1124     *   information table for the enclosing listing.  By default, we call
1125     *   the object's getListedContents() method, but this is virtualized
1126     *   in the lister interface to allow for listing other hierarchies
1127     *   besides ordinary contents.
1128     */
1129    getListedContents(obj, infoTab)
1130    {
1131        return obj.getListedContents(self, infoTab);
1132    }
1133
1134    /*
1135     *   Get the list of grouping objects for listing the item.  By
1136     *   default, we return the object's listWith result.  Subclasses can
1137     *   override this to specify different groupings for the same object
1138     *   depending on the type of list we're generating.
1139     *
1140     *   The group list returned is in order from most general to most
1141     *   specific.  For example, if an item is grouped with coins in
1142     *   general and silver coins in particular, the general coins group
1143     *   would come first, then the silver coin group, because the silver
1144     *   coin group is more specific.
1145     */
1146    listWith(obj) { return obj.listWith; }
1147
1148    /* show an item in a list */
1149    showListItem(obj, options, pov, infoTab)
1150    {
1151        obj.showListItem(options, pov, infoTab);
1152    }
1153
1154    /*
1155     *   Show a set of equivalent items as a counted item ("three coins").
1156     *   The listing mechanism itself never calls this directly; instead,
1157     *   this is provided so that ListGroupEquivalent can ask the lister
1158     *   how to describe its equivalent sets, so that different listers
1159     *   can customize the display of equivalent items.
1160     *
1161     *   'lst' is the full list of equivalent items.  By default, we pick
1162     *   one of these arbitrarily to show, since they're presumably all
1163     *   the same for the purposes of the list.
1164     */
1165    showListItemCounted(lst, options, pov, infoTab)
1166    {
1167        /*
1168         *   by defualt, show the counted name for one of the items
1169         *   (chosen arbitrarily, since they're all the same)
1170         */
1171        lst[1].showListItemCounted(lst, options, pov, infoTab);
1172    }
1173
1174    /*
1175     *   Show a list separator after displaying an item.  curItemNum is
1176     *   the number of the item just displayed (1 is the first item), and
1177     *   totalItems is the total number of items that will be displayed in
1178     *   the list.
1179     *
1180     *   This generic routine is further parameterized by properties for
1181     *   the individual types of separators.  This default implementation
1182     *   distinguishes the following separators: the separator between the
1183     *   two items in a list of exactly two items; the separator between
1184     *   adjacent items other than the last two in a list of more than two
1185     *   items; and the separator between the last two elements of a list
1186     *   of more than two items.
1187     */
1188    showListSeparator(options, curItemNum, totalItems)
1189    {
1190        local useLong = ((options & ListLong) != 0);
1191
1192        /* if this is a tall list, the separator is simply a newline */
1193        if ((options & ListTall) != 0)
1194        {
1195            "\n";
1196            return;
1197        }
1198
1199        /* if that was the last item, there are no separators */
1200        if (curItemNum == totalItems)
1201            return;
1202
1203        /* check to see if the next item is the last */
1204        if (curItemNum + 1 == totalItems)
1205        {
1206            /*
1207             *   We just displayed the penultimate item in the list, so we
1208             *   need to use the special last-item separator.  If we're
1209             *   only displaying two items total, we use an even more
1210             *   special separator.
1211             */
1212            if (totalItems == 2)
1213            {
1214                /* use the two-item separator */
1215                if (useLong)
1216                    longListSepTwo;
1217                else
1218                    listSepTwo;
1219            }
1220            else
1221            {
1222                /* use the normal last-item separator */
1223                if (useLong)
1224                    longListSepEnd;
1225                else
1226                    listSepEnd;
1227            }
1228        }
1229        else
1230        {
1231            /* in the middle of the list - display the normal separator */
1232            if (useLong)
1233                longListSepMiddle;
1234            else
1235                listSepMiddle;
1236        }
1237    }
1238
1239    /*
1240     *   Show the specific types of list separators for this list.  By
1241     *   default, these will use the generic separators defined in the
1242     *   library messages object (libMessages).  For English, these are
1243     *   commas and semicolons for short and long lists, respectively; the
1244     *   word "and" for a list with only two items; and a comma or
1245     *   semicolon and the word "and" between the last two items in a list
1246     *   with more than two items.
1247     */
1248
1249    /*
1250     *   normal and "long list" separator between the two items in a list
1251     *   with exactly two items
1252     */
1253    listSepTwo { libMessages.listSepTwo; }
1254    longListSepTwo { libMessages.longListSepTwo; }
1255
1256    /*
1257     *   normal and long list separator between items in list with more
1258     *   than two items
1259     */
1260    listSepMiddle { libMessages.listSepMiddle; }
1261    longListSepMiddle { libMessages.longListSepMiddle; }
1262
1263    /*
1264     *   normal and long list separator between second-to-last and last
1265     *   items in a list with more than two items
1266     */
1267    listSepEnd { libMessages.listSepEnd; }
1268    longListSepEnd { libMessages.longListSepEnd; }
1269
1270    /*
1271     *   Get my "top-level" lister.  For a sub-lister, this will return
1272     *   the parent lister's top-level lister.  The default lister is a
1273     *   top-level lister, so we just return ourself.
1274     */
1275    getTopLister() { return self; }
1276
1277    /*
1278     *   The last custom flag defined by this class.  Lister and each
1279     *   subclass are required to define this so that each subclass can
1280     *   allocate its own custom flags in a manner that adapts
1281     *   automatically to future additions of flags to base classes.  As
1282     *   the base class, we allocate our flags statically with #define's,
1283     *   so we simply use the fixed #define'd last flag value here.
1284     */
1285    nextCustomFlag = ListCustomFlag
1286;
1287
1288
1289/* ------------------------------------------------------------------------ */
1290/*
1291 *   Plain lister - this lister doesn't show anything for an empty list,
1292 *   and doesn't show a list suffix or prefix.
1293 */
1294plainLister: Lister
1295    /* show the prefix/suffix in wide mode */
1296    showListPrefixWide(itemCount, pov, parent) { }
1297    showListSuffixWide(itemCount, pov, parent) { }
1298
1299    /* show the tall prefix */
1300    showListPrefixTall(itemCount, pov, parent) { }
1301;
1302
1303/*
1304 *   Sub-lister for listing the contents of a group.  This lister shows a
1305 *   simple list with no prefix or suffix, and otherwise uses the
1306 *   characteristics of the parent lister.
1307 */
1308class GroupSublister: object
1309    construct(parentLister, parentGroup)
1310    {
1311        /* remember the parent lister and group objects */
1312        self.parentLister = parentLister;
1313        self.parentGroup = parentGroup;
1314    }
1315
1316    /* no prefix or suffix */
1317    showListPrefixWide(itemCount, pov, parent) { }
1318    showListSuffixWide(itemCount, pov, parent) { }
1319    showListPrefixTall(itemCount, pov, parent) { }
1320
1321    /* show nothing when empty */
1322    showListEmpty(pov, parent) { }
1323
1324    /*
1325     *   Show an item in the list.  Rather than going through the parent
1326     *   lister directly, we go through the parent group, so that it can
1327     *   customize the display of items in the group.
1328     */
1329    showListItem(obj, options, pov, infoTab)
1330    {
1331        /* ask the parent group to handle it */
1332        parentGroup.showGroupItem(parentLister, obj, options, pov, infoTab);
1333    }
1334
1335    /*
1336     *   Show a counted item in the group.  As with showListItem, we ask
1337     *   the parent group to do the work, so that it can customize the
1338     *   display if desired.
1339     */
1340    showListItemCounted(lst, options, pov, infoTab)
1341    {
1342        /* ask the parent group to handle it */
1343        parentGroup.showGroupItemCounted(
1344            parentLister, lst, options, pov, infoTab);
1345    }
1346
1347    /* delegate everything we don't explicitly handle to our parent lister */
1348    propNotDefined(prop, [args])
1349    {
1350        return delegated (getTopLister()).(prop)(args...);
1351    }
1352
1353    /* get the top-level lister - returns my parent's top-level lister */
1354    getTopLister() { return parentLister.getTopLister(); }
1355
1356    /* my parent lister */
1357    parentLister = nil
1358
1359    /* my parent list group */
1360    parentGroup = nil
1361;
1362
1363/*
1364 *   Paragraph lister: this shows its list items separated by paragraph
1365 *   breaks, with a paragraph break before the first item.
1366 */
1367class ParagraphLister: Lister
1368    /* start the list with a paragraph break */
1369    showListPrefixWide(itemCount, pov, parent) { "<.p>"; }
1370
1371    /* we show no list separators */
1372    showListSeparator(options, curItemNum, totalItems)
1373    {
1374        /* add a paragraph separator between items */
1375        if (curItemNum != totalItems)
1376            "<.p>";
1377    }
1378;
1379
1380/*
1381 *   Lister for objects in a room description with special descriptions.
1382 *   Each special description gets its own paragraph, so this is based on
1383 *   the paragraph lister.
1384 */
1385specialDescLister: ParagraphLister
1386    /* list everything */
1387    isListed(obj) { return true; }
1388
1389    /* show a list item */
1390    showListItem(obj, options, pov, infoTab)
1391    {
1392        /* show the object's special description */
1393        obj.showSpecialDescWithInfo(infoTab[obj], pov);
1394    }
1395
1396    /* use the object's special description grouper */
1397    listWith(obj) { return obj.specialDescListWith; }
1398;
1399
1400/*
1401 *   Special description lister for the contents of an item being examined.
1402 *   This is similar to the regular specialDescLister, but shows the
1403 *   special descriptions of the contents of an object being described with
1404 *   "examine" or "look in," rather than of the entire location.
1405 */
1406class SpecialDescContentsLister: ParagraphLister
1407    construct(cont)
1408    {
1409        /* remember the containing object being described */
1410        cont_ = cont;
1411    }
1412
1413    /* list everything */
1414    isListed(obj) { return true; }
1415
1416    /* show a list item */
1417    showListItem(obj, options, pov, infoTab)
1418    {
1419        /* show the object's special description in our container */
1420        obj.showSpecialDescInContentsWithInfo(infoTab[obj], pov, cont_);
1421    }
1422
1423    /* use the object's special description grouper */
1424    listWith(obj) { return obj.specialDescListWith; }
1425
1426    /* the containing object we're examining */
1427    cont_ = nil
1428;
1429
1430
1431/*
1432 *   Plain lister for actors.  This is the same as an ordinary
1433 *   plainLister, but ignores each object's isListed flag and lists it
1434 *   anyway.
1435 */
1436plainActorLister: plainLister
1437    isListed(obj) { return true; }
1438;
1439
1440/*
1441 *   Grouper for actors in a common posture and in a common location.  We
1442 *   create one of these per room per posture when we discover actors in
1443 *   the room during "look around" (or "examine" on a nested room).  This
1444 *   grouper lets us group actors like so: "Dan and Jane are sitting on
1445 *   the couch."
1446 */
1447class RoomActorGrouper: ListGroup
1448    construct(location, posture)
1449    {
1450        self.location = location;
1451        self.posture = posture;
1452    }
1453
1454    showGroupList(pov, lister, lst, options, indent, infoTab)
1455    {
1456        local cont;
1457        local outer;
1458
1459        /* if the location isn't in the sense table, skip the whole list */
1460        if (infoTab[location] == nil)
1461            return;
1462
1463        /* get the nominal posture container, if it's visible */
1464        cont = location.getNominalActorContainer(posture);
1465        if (cont != nil && !pov.canSee(cont))
1466            cont = nil;
1467
1468        /* get the outermost visible enclosing location */
1469        outer = location.getOutermostVisibleRoom(pov);
1470
1471        /*
1472         *   Only mention the outermost location if it's remote and it's
1473         *   not the same as the nominal container.  (If the remote outer
1474         *   room is the same as the nominal container, it would be
1475         *   redundant to mention it as both the nominal and remote
1476         *   container.)
1477         */
1478        if (outer == cont || pov.isIn(outer))
1479            outer = nil;
1480
1481        /* create a sub-lister for the group */
1482        lister = createGroupSublister(lister);
1483
1484        /*
1485         *   show the list prefix message - use the nominal container if
1486         *   we can see it, otherwise generate a generic message
1487         */
1488        if (cont != nil)
1489            cont.actorInGroupPrefix(pov, posture, outer, lst);
1490        else if (outer != nil)
1491            libMessages.actorThereGroupPrefix(pov, posture, outer, lst);
1492        else
1493            libMessages.actorHereGroupPrefix(posture, lst);
1494
1495        /* list the actors' names as a plain list */
1496        plainActorLister.showList(pov, location, lst, options,
1497                                  indent, infoTab, self);
1498
1499        /* add the suffix message */
1500        if (cont != nil)
1501            cont.actorInGroupSuffix(pov, posture, outer, lst);
1502        else if (outer != nil)
1503            libMessages.actorThereGroupSuffix(pov, posture, outer, lst);
1504        else
1505            libMessages.actorHereGroupSuffix(posture, lst);
1506    }
1507;
1508
1509/*
1510 *   Base class for inventory listers.  This lister uses a special listing
1511 *   method to show the items, so that items can be shown with special
1512 *   notations in an inventory list that might not appear in other types
1513 *   of listings.
1514 */
1515class InventoryLister: Lister
1516    /* list items in inventory according to their isListedInInventory */
1517    isListed(obj) { return obj.isListedInInventory; }
1518
1519    /*
1520     *   Show list items using the inventory name, which might differ from
1521     *   the regular nmae of the object.
1522     */
1523    showListItem(obj, options, pov, infoTab)
1524        { obj.showInventoryItem(options, pov, infoTab); }
1525
1526    showListItemCounted(lst, options, pov, infoTab)
1527        { lst[1].showInventoryItemCounted(lst, options, pov, infoTab); }
1528
1529    /*
1530     *   Show contents of the items in the inventory.  We customize this
1531     *   so that we can differentiate inventory contents lists from other
1532     *   contents lists.
1533     */
1534    showContentsList(pov, obj, options, indent, infoTab)
1535    {
1536        /* list the item's contents */
1537        obj.showInventoryContents(pov, obj.contentsLister, options,
1538                                  indent, infoTab);
1539    }
1540
1541    /*
1542     *   Show the contents in-line, for an inventory listing.
1543     */
1544    showInlineContentsList(pov, obj, options, indent, infoTab)
1545    {
1546        /* list the item's contents using its in-line lister */
1547        obj.showInventoryContents(pov, obj.inlineContentsLister,
1548                                  options, indent, infoTab);
1549    }
1550;
1551
1552/*
1553 *   Base class for worn-inventory listers.  This lister uses a special
1554 *   listing method to show the items, so that items being worn are shown
1555 *   *without* the special '(being worn)' notation that might otherwise
1556 *   appear in inventory listings.
1557 */
1558class WearingLister: InventoryLister
1559    /* show the list item using the "worn listing" name */
1560    showListItem(obj, options, pov, infoTab)
1561        { obj.showWornItem(options, pov, infoTab); }
1562    showListItemCounted(lst, options, pov, infoTab)
1563        { lst[1].showWornItemCounted(lst, options, pov, infoTab); }
1564;
1565
1566/*
1567 *   "Divided" inventory lister.  In 'wide' mode, this shows inventory in
1568 *   two parts: the items being carried, and the items being worn.  (We use
1569 *   the standard single tree-style listing in 'tall' mode.)
1570 */
1571class DividedInventoryLister: InventoryLister
1572    /*
1573     *   Show the list.  We completely override the main lister method so
1574     *   that we can show our two lists.
1575     */
1576    showList(pov, parent, lst, options, indent, infoTab, parentGroup)
1577    {
1578        /*
1579         *   If this is a 'tall' listing, use the normal listing style; for
1580         *   a 'wide' listing, use our special segregated style.  If we're
1581         *   being invoked recursively to show a contents listing, we
1582         *   similarly want to use the base handling.
1583         */
1584        if ((options & (ListTall | ListContents)) != 0)
1585        {
1586            /* inherit the standard behavior */
1587            inherited(pov, parent, lst, options, indent, infoTab,
1588                      parentGroup);
1589        }
1590        else
1591        {
1592            local carryingLst, wearingLst;
1593            local carryingStr, wearingStr;
1594
1595            /* divide the lists into 'carrying' and 'wearing' sublists */
1596            carryingLst = new Vector(32);
1597            wearingLst = new Vector(32);
1598            foreach (local cur in lst)
1599                (cur.isWornBy(parent) ? wearingLst : carryingLst).append(cur);
1600
1601            /* generate and capture the 'carried' listing */
1602            carryingStr = outputManager.curOutputStream.captureOutput({:
1603                carryingLister.showList(pov, parent, carryingLst, options,
1604                                        indent, infoTab, parentGroup)});
1605
1606            /* generate and capture the 'worn' listing */
1607            wearingStr = outputManager.curOutputStream.captureOutput({:
1608                wearingLister.showList(pov, parent, wearingLst, options,
1609                                       indent, infoTab, parentGroup)});
1610
1611            /* generate the combined listing */
1612            showCombinedInventoryList(parent, carryingStr, wearingStr);
1613
1614            /*
1615             *   Now show the out-of-line contents for the whole list, if
1616             *   appropriate.  We save this until after showing both parts
1617             *   of the list, to keep the direct inventory parts together
1618             *   at the beginning of the output.
1619             */
1620            if ((options & ListRecurse) != 0
1621                && indent == 0
1622                && (options & ListContents) == 0)
1623            {
1624                /* show the contents of each object we didn't list */
1625                showSeparateContents(pov, lst, options | ListContents,
1626                                     infoTab);
1627            }
1628        }
1629    }
1630
1631    /*
1632     *   Show the combined listing.  This must be provided by each
1633     *   language-specific subclass.  The inputs are the results (strings)
1634     *   of the captured output of the sublistings of the items being
1635     *   carried and the items being worn.  These will be "raw" listings,
1636     *   without any prefix or suffix text.  This routine's job is to
1637     *   display the final output, adding the framing text.
1638     */
1639    showCombinedInventoryList(parent, carrying, wearing) { }
1640
1641    /*
1642     *   The recommended maximum number of number of noun phrases to show
1643     *   in the single-sentence format.  This should be used by the
1644     *   showCombinedInventoryList() method to decide whether to display
1645     *   the combined listing as a single sentence or as two separate
1646     *   sentences.
1647     */
1648    singleSentenceMaxNouns = 7
1649
1650    /*
1651     *   Our associated sub-listers for items begin carried and worn,
1652     *   respectively.  We'll use these to list our sublist of items being
1653     *   worn.
1654     */
1655    carryingLister = actorCarryingSublister
1656    wearingLister = actorWearingSublister
1657;
1658
1659/*
1660 *   Base class for the inventory sub-lister for items being carried.  This
1661 *   is a minor specialization of the basic inventory lister; in this
1662 *   version, we omit any prefix, suffix, or empty messages, since we'll
1663 *   rely on the caller to combine our raw listing with the raw 'wearing'
1664 *   listing to form the full results.
1665 *
1666 *   This type of lister should normally only be used from within an
1667 *   inventory lister.  This type of lister assumes that it's part of a
1668 *   larger listing controlled externally; for example, we don't show
1669 *   out-of-line contents, since we assume the caller will be doing this.
1670 */
1671class InventorySublister: InventoryLister
1672    /* don't show any prefix, suffix, or 'empty' messages */
1673    showListPrefixWide(itemCount, pov, parent) { }
1674    showListSuffixWide(itemCount, pov, parent) { }
1675    showListEmpty(pov, parent) { }
1676
1677    /* don't show out-of-line contents */
1678    showSeparateContents(pov, lst, options, infoTab) { }
1679;
1680
1681/*
1682 *   Base class for the inventory sub-lister for items being worn.  We use
1683 *   a special listing method to show these items, so that items being
1684 *   shown explicitly in a worn list can be shown differently from the way
1685 *   they would in a normal inventory list.  (For example, a worn item in a
1686 *   normal inventory list might show a "(worn)" indication, whereas it
1687 *   would not want to show a similar indication in a list of objects
1688 *   explicitly being worn.)
1689 *
1690 *   This type of lister should normally only be used from within an
1691 *   inventory lister.  This type of lister assumes that it's part of a
1692 *   larger listing controlled externally; for example, we don't show
1693 *   out-of-line contents, since we assume the caller will be doing this.
1694 */
1695class WearingSublister: WearingLister
1696    /* don't show any prefix, suffix, or 'empty' messages */
1697    showListPrefixWide(itemCount, pov, parent) { }
1698    showListSuffixWide(itemCount, pov, parent) { }
1699    showListEmpty(pov, parent) { }
1700
1701    /* don't show out-of-line contents */
1702    showSeparateContents(pov, lst, options, infoTab) { }
1703;
1704
1705/*
1706 *   The standard inventory sublisters.
1707 */
1708actorCarryingSublister: InventorySublister;
1709actorWearingSublister: WearingSublister;
1710
1711/*
1712 *   Base class for contents listers.  This is used to list the contents
1713 *   of the objects that appear in top-level lists (a top-level list is
1714 *   the list of objects directly in a room that appears in a room
1715 *   description, or the list of items being carried in an INVENTORY
1716 *   command, or the direct contents of an object being examined).
1717 */
1718class ContentsLister: Lister
1719;
1720
1721/*
1722 *   Base class for description contents listers.  This is used to list
1723 *   the contents of an object when we examine the object, or when we
1724 *   explicitly LOOK IN the object.
1725 */
1726class DescContentsLister: Lister
1727    /*
1728     *   Use the explicit look-in flag for listing contents.  We might
1729     *   list items within an object on explicit examination of the item
1730     *   that we wouldn't list in a room or inventory list containing the
1731     *   item.
1732     */
1733    isListed(obj) { return obj.isListedInContents; }
1734;
1735
1736/*
1737 *   Base class for sense listers, which list the items that can be sensed
1738 *   for a command like "listen" or "smell".
1739 */
1740class SenseLister: ParagraphLister
1741    /* show everything we're asked to list */
1742    isListed(obj) { return true; }
1743
1744    /* show a counted list item */
1745    showListItemCounted(lst, options, pov, infoTab)
1746    {
1747        /*
1748         *   simply show one item, without the count - non-visual senses
1749         *   don't distinguish numbers of items that are equivalent
1750         */
1751        showListItem(lst[1], options, pov, infoTab);
1752    }
1753;
1754
1755/*
1756 *   Room contents lister for things that can be heard.
1757 */
1758roomListenLister: SenseLister
1759    /* list an item in a room if its isSoundListedInRoom is true */
1760    isListed(obj) { return obj.isSoundListedInRoom; }
1761
1762    /* list an item */
1763    showListItem(obj, options, pov, infoTab)
1764    {
1765        /* show the "listen" list name for the item */
1766        obj.soundHereDesc();
1767    }
1768;
1769
1770/*
1771 *   Lister for explicit "listen" action
1772 */
1773listenActionLister: roomListenLister
1774    /* list everything in response to an explicit general LISTEN command */
1775    isListed(obj) { return true; }
1776
1777    /* show an empty list */
1778    showListEmpty(pov, parent)
1779    {
1780        mainReport(&nothingToHearMsg);
1781    }
1782
1783;
1784
1785/*
1786 *   Room contents lister for things that can be smelled.
1787 */
1788roomSmellLister: SenseLister
1789    /* list an item in a room if its isSmellListedInRoom is true */
1790    isListed(obj) { return obj.isSmellListedInRoom; }
1791
1792    /* list an item */
1793    showListItem(obj, options, pov, infoTab)
1794    {
1795        /* show the "smell" list name for the item */
1796        obj.smellHereDesc();
1797    }
1798;
1799
1800/*
1801 *   Lister for explicit "smell" action
1802 */
1803smellActionLister: roomSmellLister
1804    /* list everything in response to an explicit general SMELL command */
1805    isListed(obj) { return true; }
1806
1807    /* show an empty list */
1808    showListEmpty(pov, parent)
1809    {
1810        mainReport(&nothingToSmellMsg);
1811    }
1812
1813;
1814
1815/*
1816 *   Inventory lister for things that can be heard.
1817 */
1818inventoryListenLister: SenseLister
1819    /* list an item */
1820    showListItem(obj, options, pov, infoTab)
1821    {
1822        /* show the "listen" list name for the item */
1823        obj.soundHereDesc();
1824    }
1825;
1826
1827/*
1828 *   Inventory lister for things that can be smelled.
1829 */
1830inventorySmellLister: SenseLister
1831    /* list an item */
1832    showListItem(obj, options, pov, infoTab)
1833    {
1834        /* show the "smell" list name for the item */
1835        obj.smellHereDesc();
1836    }
1837;
1838
1839
1840/* ------------------------------------------------------------------------ */
1841/*
1842 *   List Group Interface.  An instance of this object is created for each
1843 *   set of objects that are to be grouped together.
1844 */
1845class ListGroup: object
1846    /*
1847     *   Show a list of items from this group.  All of the items in the
1848     *   list will be members of this list group; we are to display a
1849     *   sentence fragment showing the items in the list, suitable for
1850     *   embedding in a larger list.
1851     *
1852     *   'options', 'indent', and 'infoTab' have the same meaning as they
1853     *   do for showList().
1854     *
1855     *   Note that we are not to display any separator before or after our
1856     *   list; the caller is responsible for that.
1857     */
1858    showGroupList(pov, lister, lst, options, indent, infoTab) { }
1859
1860    /*
1861     *   Show an item in the group's sublist.  The sublister calls this to
1862     *   display each item in the group when the group calls the sublister
1863     *   to display the group list.  By default, we simply let the
1864     *   sublister handle the request, which gives items in our group
1865     *   sublist the same appearance they would have had in the sublist to
1866     *   begin with.  We can customize this behavior to give our list
1867     *   items a different appearance special to the group sublist.
1868     *
1869     *   Note that the same customization could be accomplished by
1870     *   creating a specialized subclass of GroupSublister in
1871     *   createGroupSublister(), and overriding showListItem() in the
1872     *   specialized GroupSublister subclass.  We use this mechanism as a
1873     *   convenience, so that a separate group sublister class doesn't
1874     *   have to be created simply to customize the display of group
1875     *   items.
1876     */
1877    showGroupItem(sublister, obj, options, pov, infoTab)
1878    {
1879        /* by default, list using the regular sublister */
1880        sublister.showListItem(obj, options, pov, infoTab);
1881    }
1882
1883    /*
1884     *   Show a counted item in our group list.  This is the counted item
1885     *   equivalent of showGroupItem.
1886     */
1887    showGroupItemCounted(sublister, lst, options, pov, infoTab)
1888    {
1889        /* by default, list using the regular sublister */
1890        sublister.showListItemCounted(lst, options, pov, infoTab);
1891    }
1892
1893    /*
1894     *   Determine if showing the group list will introduce a sublist into
1895     *   an enclosing list.  This should return true if we will show a
1896     *   sublist without some kind of grouping, so that the caller knows
1897     *   to introduce some extra grouping into its enclosing list.  This
1898     *   should return nil if the sublist we display will be clearly set
1899     *   off in some way (for example, by being enclosed in parentheses).
1900     */
1901    groupDisplaysSublist = true
1902
1903    /*
1904     *   The minimum number of elements for which we should retain the
1905     *   group in a listing.  By default, we need two elements to display a
1906     *   group; any group with only one element is discarded, and the
1907     *   single element is moved into the 'singles' list.  This can be
1908     *   overridden to allow single-element groups to be retained.  In most
1909     *   cases, it's undesirable to retain single-element groups, but when
1910     *   grouping is used to partition a list into two or more fixed
1911     *   portions, single-element groups become desirable.
1912     */
1913    minGroupSize = 2
1914
1915    /*
1916     *   Determine the cardinality of the group listing, grammatically
1917     *   speaking.  This is the number of items that the group seems to be
1918     *   for the purposes of grammatical agreement.  For example, if the
1919     *   group is displayed as "$1.38 in change", it would be singular for
1920     *   grammatical agreement, hence would return 1 here; if it displays
1921     *   "five coins (two copper, three gold)," it would count as five
1922     *   items for grammatical agreement.
1923     *
1924     *   For languages (like English) that grammatically distinguish
1925     *   number only between singular and plural, it is sufficient for
1926     *   this to return 1 for singular and anything higher for plural.
1927     *   For the sake of languages that make more elaborate number
1928     *   distinctions for grammatical agreement, though, this should
1929     *   return as accurate a count as is possible.
1930     *
1931     *   By default, we return the number of items to be displayed in the
1932     *   list group.  This should be overridden when necessary, such as
1933     *   when the group message is singular in usage even if the list has
1934     *   multiple items (as in "$1.38 in change").
1935     */
1936    groupCardinality(lister, lst) { return lst.length(); }
1937
1938    /*
1939     *   Get the number of noun phrases this group will display.  This
1940     *   differs from the cardinality in that it doesn't matter how many
1941     *   *objects* the phrases represent; it only matters how many phrases
1942     *   are displayed.  For example, "five coins" has cardinality 5 but
1943     *   only displays one noun phrase.
1944     *
1945     *   By default, we simply return the number of items in the group,
1946     *   since most groups individually list their items.
1947     */
1948    groupNounPhraseCount(lister, lst) { return lst.length(); }
1949
1950    /*
1951     *   Create the group sublister.
1952     *
1953     *   In most cases, when a group displays a list of the items in the
1954     *   group as a sublist, it will not want to use the same lister that
1955     *   was used to show the enclosing group, because the enclosing lister
1956     *   will usually have different prefix/suffix styles than the sublist.
1957     *   However, the group list and the enclosing list might share many
1958     *   other attributes, such as the style of name to use when displaying
1959     *   items in the list.  The default sublister we create,
1960     *   GroupSublister, is a hybrid that uses the enclosing lister's
1961     *   attributes except for a few, such as the prefix and suffix, that
1962     *   usually need to be changed for the sublist.
1963     *
1964     *   This can be overridden to use a completely customized Lister
1965     *   object for the group list, if desired.
1966     */
1967    createGroupSublister(parentLister)
1968    {
1969        /* create the standard group sublister by default */
1970        return new GroupSublister(parentLister, self);
1971    }
1972;
1973
1974/*
1975 *   A "custom" List Group implementation.  This type of lister uses a
1976 *   completely custom message to show the group, without a need to
1977 *   recursively invoke a lister to list the individual elements.  The
1978 *   main difference between this and the base ListGroup is that the
1979 *   interface to the custom message generator is very simple - we can
1980 *   dispense with most of the numerous arguments that the base group
1981 *   message receives, since most of those arguments are there to allow
1982 *   recursive listing of the group list.
1983 */
1984class ListGroupCustom: ListGroup
1985    showGroupList(pov, lister, lst, options, indent, infoTab)
1986    {
1987        /* simply show the custom message for the list */
1988        showGroupMsg(lst);
1989    }
1990
1991    /* show the custom group message - subclasses should override */
1992    showGroupMsg(lst) { }
1993;
1994
1995/*
1996 *   Sorted group list.  This is a list that simply displays its members
1997 *   in a specific sorting order.
1998 */
1999class ListGroupSorted: ListGroup
2000    /*
2001     *   Show the group list
2002     */
2003    showGroupList(pov, lister, lst, options, indent, infoTab)
2004    {
2005        /* put the list in sorted order */
2006        lst = sortListGroup(lst);
2007
2008        /* create a sub-lister for the group */
2009        lister = createGroupSublister(lister);
2010
2011        /* show the list */
2012        lister.showList(pov, nil, lst, options & ~ListContents,
2013                        indent, infoTab, self);
2014    }
2015
2016    /*
2017     *   Sort the group list.  By default, if we have a
2018     *   compareGroupItems() method defined, we'll sort the list using
2019     *   that method; otherwise, we'll just return the list unchanged.
2020     */
2021    sortListGroup(lst)
2022    {
2023        /*
2024         *   if we have a compareGroupItems method, use it to sort the
2025         *   list; otherwise, just return the list in its current order
2026         */
2027        if (propDefined(&compareGroupItems, PropDefAny))
2028            return lst.sort(SortAsc, {a, b: compareGroupItems(a, b)});
2029        else
2030            return lst;
2031    }
2032
2033    /*
2034     *   Compare a pair of items from the group to determine their sorting
2035     *   order.  Returns 0 if the two items are at the same sorting order,
2036     *   1 if the first item sorts after the second item, -1 if the first
2037     *   item sorts before the second item.
2038     *
2039     *   We do not provide a default implementation of this method, so
2040     *   that sortListGroup will know not to bother sorting the list at
2041     *   all if the subclass doesn't provide a definition for this method.
2042     */
2043    // compareGroupItems(a, b) { return a > b ? 1 : a == b ? 0 : -1; }
2044;
2045
2046/*
2047 *   List Group implementation: parenthesized sublist.  Displays the
2048 *   number of items collectively, then displays the list of items in
2049 *   parentheses.
2050 *
2051 *   Note that this is a ListGroupSorted subclass.  If our subclass
2052 *   defines a compareGroupItems() method, we'll show the list in the
2053 *   order specified by compareGroupItems().
2054 */
2055class ListGroupParen: ListGroupSorted
2056    /*
2057     *   show the group list
2058     */
2059    showGroupList(pov, lister, lst, options, indent, infoTab)
2060    {
2061        /* sort the list group, if we have an ordering method defined */
2062        lst = sortListGroup(lst);
2063
2064        /* create a sub-lister for the group */
2065        lister = createGroupSublister(lister);
2066
2067        /* show the collective count of the object */
2068        showGroupCountName(lst);
2069
2070        /* show the tall or wide sublist */
2071        if ((options & ListTall) != 0)
2072        {
2073            /* tall list - show the items as a sublist */
2074            "\n";
2075            lister.showList(pov, nil, lst, options & ~ListContents,
2076                            indent, infoTab, self);
2077        }
2078        else
2079        {
2080            /* wide list - add a space and a paren for the sublist */
2081            " (";
2082
2083            /* show the sublist */
2084            lister.showList(pov, nil, lst, options & ~ListContents,
2085                            indent, infoTab, self);
2086
2087            /* end the sublist */
2088            ")";
2089        }
2090    }
2091
2092    /*
2093     *   Show the collective count for the list of objects.  By default,
2094     *   we'll simply display the countName of the first item in the list,
2095     *   on the assumption that each object has the same plural
2096     *   description.  However, in most cases this should be overridden to
2097     *   provide a more general collective name for the group.
2098     */
2099    showGroupCountName(lst)
2100    {
2101        /* show the first item's countName */
2102        say(lst[1].countName(lst.length()));
2103    }
2104
2105    /* we don't add a sublist, since we enclose our list in parentheses */
2106    groupDisplaysSublist = nil
2107;
2108
2109/*
2110 *   List Group implementation: simple prefix/suffix lister.  Shows a
2111 *   prefix message, then shows the list, then shows a suffix message.
2112 *
2113 *   Note that this is a ListGroupSorted subclass.  If our subclass
2114 *   defines a compareGroupItems() method, we'll show the list in the
2115 *   order specified by compareGroupItems().
2116 */
2117class ListGroupPrefixSuffix: ListGroupSorted
2118    showGroupList(pov, lister, lst, options, indent, infoTab)
2119    {
2120        /* sort the list group, if we have an ordering method defined */
2121        lst = sortListGroup(lst);
2122
2123        /* create a sub-lister for the group */
2124        lister = createGroupSublister(lister);
2125
2126        /* show the prefix */
2127        showGroupPrefix(pov, lst);
2128
2129        /* if we're in tall mode, start a new line */
2130        lister.showTallListNewline(options);
2131
2132        /* show the list */
2133        lister.showList(pov, nil, lst, options & ~ListContents,
2134                        indent + 1, infoTab, self);
2135
2136        /* show the suffix */
2137        showGroupSuffix(pov, lst);
2138    }
2139
2140    /* show the prefix - we just show the groupPrefix message by default */
2141    showGroupPrefix(pov, lst) { groupPrefix; }
2142
2143    /* show the suffix - we just show the groupSuffix message by default */
2144    showGroupSuffix(pov, lst) { groupSuffix; }
2145
2146    /*
2147     *   The prefix and suffix messages.  The showGroupPrefix and
2148     *   showGroupSuffix methods simply show these message properties.  We
2149     *   go through this two-step procedure for convenience: if the
2150     *   subclass doesn't need the POV and list parameters, it's less
2151     *   typing to just override these parameterless properties.  If the
2152     *   subclass needs to vary the message according to the POV or what's
2153     *   in the list, it can override the showGroupXxx methods instead.
2154     */
2155    groupPrefix = ""
2156    groupSuffix = ""
2157;
2158
2159/*
2160 *   Equivalent object list group.  This is the default listing group for
2161 *   equivalent items.  The Thing class creates an instance of this class
2162 *   during initialization for each set of equivalent items.
2163 */
2164class ListGroupEquivalent: ListGroup
2165    showGroupList(pov, lister, lst, options, indent, infoTab)
2166    {
2167        /* show a count of the items */
2168        lister.showListItemCounted(lst, options, pov, infoTab);
2169    }
2170
2171    /*
2172     *   An equivalence group displays only a single noun phrase to cover
2173     *   the entire group.
2174     */
2175    groupNounPhraseCount(lister, lst) { return 1; }
2176
2177    /* we display as a single item, so there's no sublist */
2178    groupDisplaysSublist = nil
2179;
2180
2181