cfad47cfa3/t3compiler/tads3/lib/adv3/lister.t

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