lister.t
#charset "us-ascii"
/*
* Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved.
*
* TADS 3 Library - Lister class
*
* This module defines the "Lister" class, which generates formatted
* lists of objects, and several subclasses of Lister that generate
* special kinds of lists.
*/
/* include the library header */
#include "adv3.h"
/* ------------------------------------------------------------------------ */
/*
* Lister. This is the base class for formatting of lists of objects.
*
* The external interface consists of the showList() method, which
* displays a formatted list of objects according to the rules of the
* lister subclass.
*
* The rest of the methods are an internal interface which lister
* subclasses can override to customize the way that a list is shown.
* Certain of these methods are meant to be overridden by virtually all
* listers, such as the methods that show the prefix and suffix
* messages. The remaining methods are designed to allow subclasses to
* customize detailed aspects of the formatting, so they only need to be
* overridden when something other than the default behavior is needed.
*/
class Lister: object
/*
* Show a list, showing all items in the list as though they were
* fully visible, regardless of their actual sense status.
*/
showListAll(lst, options, indent)
{
/* create a sense information table with each item in full view */
local infoTab = new LookupTable(16, 32);
foreach (local cur in lst)
{
/* add a plain view sensory description to the info list */
infoTab[cur] = new SenseInfo(cur, transparent, nil, 3);
}
/* show the list from the current global point of view */
showList(getPOV(), nil, lst, options, indent, infoTab, nil);
}
/*
* Display a list of items, grouping according to the 'listWith'
* associations of the items. We will only list items for which
* isListed() returns true.
*
* 'pov' is the point of view of the listing, which is usually an
* actor (and usually the player character actor).
*
* 'parent' is the parent (container) of the list being shown. This
* should be nil if the listed objects are not all within a single
* object.
*
* 'lst' is the list of items to display.
*
* 'options' gives a set of ListXxx option flags.
*
* 'indent' gives the indentation level. This is used only for
* "tall" lists (specified by including ListTall in the options
* flags). An indentation level of zero indicates no indentation.
*
* 'infoTab' is a lookup table of SenseInfo objects for all of the
* objects that can be sensed from the perspective of the actor
* performing the action that's causing the listing. This is
* normally the table returned from Thing.senseInfoTable() for the
* actor from whose point of view the list is being generated. (We
* take this as a parameter rather than generating ourselves for two
* reasons. First, it's often the case that the same information
* table will be needed for a series of listings, so we can save the
* compute time of recalculating the same table repeatedly by having
* the caller obtain the table and pass it to each lister. Second,
* in some cases the caller will want to synthesize a special sense
* table rather than using the actual sense information; taking this
* as a parameter allows the caller to easily customize the table.)
*
* 'parentGroup' is the ListGroup object that is showing this list.
* We will not group the objects we list into the parent group, or
* into any group more general than the parent group.
*
* This routine is not usually overridden in lister subclasses.
* Instead, this method calls a number of other methods that
* determine the listing style in more detail; usually those other,
* simpler methods are customized in subclasses.
*/
showList(pov, parent, lst, options, indent, infoTab, parentGroup)
{
/* remember the original list */
local origLst = lst;
/* filter the list to get only the items we actually will list */
lst = getFilteredList(lst, infoTab);
/* create a lookup table to keep track of the groups we've seen */
local groupTab = new LookupTable();
local groups = new Vector(10);
/* set up a vector to keep track of the singles */
local singles = new Vector(10);
/* figure the groupings */
local itemCount = getListGrouping(groupTab, groups, singles,
lst, parentGroup);
/*
* Now that we've figured out what's in the list and how it's
* arranged into groups, show the list.
*/
showArrangedList(pov, parent, lst, options, indent, infoTab,
itemCount, singles, groups, groupTab, origLst);
/*
* If the list is recursive, mention the contents of any items
* that weren't listed in the main list, and of any contents
* that are specifically to be listed out-of-line. Don't do
* this if we're already recursively showing such a listing,
* since if we did so we could show items at recursive depths
* more than once; if we're already doing a recursive listing,
* our caller will itself recurse through all levels of the
* tree, so we don't have to recurse any further ourselves.
*/
if ((options & ListRecurse) != 0
&& indent == 0
&& (options & ListContents) == 0)
{
/* show the contents of each object we didn't list */
showSeparateContents(pov, origLst,
options | ListContents, infoTab);
}
}
/*
* Filter a list to get only the elements we actually want to show.
* Returns a new list consisting only of the items that (1) pass the
* isListed() test, and (2) are represented in the sense information
* table (infoTab). If infoTab is nil, no sense filtering is
* applied.
*/
getFilteredList(lst, infoTab)
{
/* narrow the list down based on the isListed criteria */
lst = lst.subset({x: isListed(x)});
/*
* If we have an infoTab, build a new list consisting only of
* the items in 'lst' that have infoTab entries - we can't sense
* anything that doesn't have an infoTab entry, so we don't want
* to show any such objects.
*/
if (infoTab != nil)
{
/* create a vector to contain the new filtered list */
local filteredList = new Vector(lst.length());
/*
* run through our original list and confirm that each one
* is in the infoTab
*/
foreach (local cur in lst)
{
/*
* if this item has an infoTab entry, add this item to
* the filtered list
*/
if (infoTab[cur] != nil)
filteredList.append(cur);
}
/* forget the original list, and use the filtered list instead */
lst = filteredList;
}
/* return the filtered list */
return lst;
}
/*
* Get the groupings for a given listing.
*
* 'groupTab' is an empty LookupTable, and 'groups' is an empty
* Vector; we'll populate these with the grouping information.
* 'singles' is an empty Vector that we'll populate with the single
* items not part of any group.
*/
getListGrouping(groupTab, groups, singles, lst, parentGroup)
{
local cur;
local i, cnt;
/*
* First, scan the list to determine how we're going to group
* the objects.
*/
for (i = 1, cnt = lst.length() ; i <= cnt ; ++i)
{
local curGroups;
local parentIdx;
/* get this object into a local for easy reference */
cur = lst[i];
/* if the item isn't part of this listing, skip it */
if (!isListed(cur))
continue;
/* get the list of groups with which this object is listed */
curGroups = listWith(cur);
/* if there are no groups, we can move on to the next item */
if (curGroups == nil)
continue;
/*
* If we have a parent group, and it appears in the list of
* groups for this item, eliminate everything in the item's
* group list up to and including the parent group. If
* we're showing this list as part of a group to begin with,
* we obviously don't want to show this list grouped into
* the same group, and we also don't want to group it into
* anything broader than the parent group. Groups are
* listed from most general to most specific, so we can
* eliminate anything up to and including the parent group.
*/
if (parentGroup != nil
&& (parentIdx = curGroups.indexOf(parentGroup)) != nil)
{
/* eliminate everything up to and including the parent */
curGroups = curGroups.sublist(parentIdx + 1);
}
/* if this item has no groups, skip it */
if (curGroups.length() == 0)
continue;
/*
* This item has one or more group associations that we must
* consider.
*/
foreach (local g in curGroups)
{
local itemsInGroup;
/* find the group table entry for this group */
itemsInGroup = groupTab[g];
/* if there's no entry for this group, create a new one */
if (itemsInGroup == nil)
{
/* create a new group table entry */
itemsInGroup = groupTab[g] = new Vector(10);
/* add it to the group vector */
groups.append(g);
}
/*
* add this item to the list of items that want to be
* grouped with this group
*/
itemsInGroup.append(cur);
}
}
/*
* We now have the set of all of the groups that could possibly
* be involved in this list display. We must now choose the
* single group we'll use to display each grouped object.
*
* First, eliminate any groups with insufficient membership.
* (Most groups require at least two members, but this can vary
* by group.)
*/
for (i = 1, cnt = groups.length() ; i <= cnt ; ++i)
{
/* if this group has only one member, drop it */
if (groupTab[groups[i]].length() < groups[i].minGroupSize)
{
/* remove this group from the group list */
groups.removeElementAt(i);
/*
* adjust the list count, and back up to try the element
* newly at this index on the next iteration
*/
--cnt;
--i;
}
}
/*
* Next, scan for groups with identical member lists, and for
* groups with subset member lists. For each pair of identical
* elements we find, eliminate the more general of the two.
*/
for (i = 1, cnt = groups.length() ; i <= cnt ; ++i)
{
local g1;
local mem1;
/* get the current group and its membership list */
g1 = groups[i];
mem1 = groupTab[g1];
/* look for matching items in the list after this one */
for (local j = i + 1 ; j <= cnt ; ++j)
{
local g2;
local mem2;
/* get the current item and its membership list */
g2 = groups[j];
mem2 = groupTab[g2];
/*
* Compare the membership lists for the two items. Note
* that we built these membership lists all in the same
* order of objects, so if two membership lists have all
* the same members, those members will be in the same
* order in the two lists; hence, we can simply compare
* the two lists to determine the membership order.
*/
if (mem1 == mem2)
{
local ordList;
/*
* The groups have identical membership, so
* eliminate the more general group. Groups are
* ordered from most general to least general, so
* keep the one with the higher index in the group
* list for an object in the membership list. Note
* that we assume that each member has the same
* ordering for the common groups, so we can pick a
* member arbitrarily to find the way a member
* orders the groups.
*/
ordList = listWith(mem1[1]);
if (ordList.indexOf(g1) > ordList.indexOf(g2))
{
/*
* group g1 is more specific than group g2, so
* keep g1 and discard g2 - remove the 'j'
* element from the list, and back up in the
* inner loop so we reconsider the element newly
* at this index on the next iteration
*/
groups.removeElementAt(j);
--cnt;
--j;
}
else
{
/*
* group g2 is more specific, so discard g1 -
* remove the 'i' element from the list, back up
* in the outer loop, and break out of the inner
* loop, since the outer loop element is no
* longer there for us to consider in comparing
* more elements in the inner loop
*/
groups.removeElementAt(i);
--cnt;
--i;
break;
}
}
}
}
/*
* Scan for subsets. For each group whose membership list is a
* subset of another group in our list, eliminate the subset,
* keeping only the larger group. The group lister will be able
* to show the subgroup as grouped within its larger list.
*/
for (local i = 1, cnt = groups.length() ; i <= cnt ; ++i)
{
local g1;
local mem1;
/* get the current group and its membership list */
g1 = groups[i];
mem1 = groupTab[g1];
/* look at the other elements to see if we have any subsets */
for (local j = 1 ; j <= cnt ; ++j)
{
local g2;
local mem2;
/* don't bother checking the same element */
if (j == i)
continue;
/* get the current item and its membership list */
g2 = groups[j];
mem2 = groupTab[g2];
/*
* if g2's membership is a subset, eliminate g2 from the
* group list
*/
if (isListSubset(mem2, mem1))
{
/* remove g2 from the list */
groups.removeElementAt(j);
/* adjust the loop counters for the removal */
--cnt;
--j;
/*
* adjust the outer loop counter if it's affected -
* the outer loop is affected if it's already past
* this point in the list, which means that its
* index is higher than the inner loop index
*/
if (i > j)
--i;
}
}
}
/*
* We now have a final accounting of the groups that we will
* consider using. Reset the membership list for each group in
* the surviving list.
*/
foreach (local g in groups)
{
local itemsInList;
/* get this group's membership list vector */
itemsInList = groupTab[g];
/* clear the vector */
itemsInList.removeRange(1, itemsInList.length());
}
/*
* Now, run through our item list again, and assign each item to
* the surviving group that comes earliest in the item's group
* list.
*/
for (i = 1, cnt = lst.length() ; i <= cnt ; ++i)
{
local curGroups;
local winningGroup;
/* get this object into a local for easy reference */
cur = lst[i];
/* if the item isn't part of this listing, skip it */
if (!isListed(cur))
continue;
/* get the list of groups with which this object is listed */
curGroups = listWith(cur);
if (curGroups == nil)
curGroups = [];
/*
* find the first element in the group list that is in the
* surviving group list
*/
winningGroup = nil;
foreach (local g in curGroups)
{
/* if this group is in the surviving list, it's the one */
if (groups.indexOf(g) != nil)
{
winningGroup = g;
break;
}
}
/*
* if we have a group, add this item to the group's
* membership; otherwise, add it to the singles list
*/
if (winningGroup != nil)
groupTab[winningGroup].append(cur);
else
singles.append(cur);
}
/* eliminate any surviving group with too few members */
for (i = 1, cnt = groups.length() ; i <= cnt ; ++i)
{
local mem;
/* get this group's membership list */
mem = groupTab[groups[i]];
/*
* if this group's membership is too small, eliminate the
* group and add the member into the singles pile
*/
if (mem.length() < groups[i].minGroupSize)
{
/* put the item(s) into the singles list */
singles.appendAll(mem);
/* eliminate this item from the group list */
groups.removeElementAt(i);
/* adjust the loop counters */
--cnt;
--i;
}
}
/* return the cardinality of the arranged list */
return getArrangedListCardinality(singles, groups, groupTab);
}
/*
* Show the list. This is called after we've figured out which items
* we intend to display, and after we've arranged the items into
* groups. In rare cases, listers might want to override this, to
* customize the way the way the list is displayed based on the
* internal arrangement of the list.
*/
showArrangedList(pov, parent, lst, options, indent, infoTab, itemCount,
singles, groups, groupTab, origLst)
{
/*
* We now know how many items we're listing (grammatically
* speaking), so we're ready to display the list prefix. If
* we're displaying nothing at all, just display the "empty"
* message, and we're done.
*/
if (itemCount == 0)
{
/* show the empty list */
showListEmpty(pov, parent);
}
else
{
local i;
local cnt;
local sublists;
local origOptions = options;
local itemOptions;
local groupOptions;
local listCount;
local dispCount;
local cur;
/*
* Check to see if we have one or more group sublists - if
* we do, we must use the "long" list format for our overall
* list, otherwise we can use the normal "short" list
* format. The long list format uses semicolons to separate
* items.
*/
for (i = 1, cnt = groups.length(), sublists = nil ;
i <= cnt ; ++i)
{
/*
* if this group's lister item displays a sublist, we
* must use the long format
*/
if (groups[i].groupDisplaysSublist)
{
/* note that we are using the long format */
sublists = true;
/*
* one is enough to make us use the long format, so
* we need not look any further
*/
break;
}
}
/* generate the prefix message if we're in a 'tall' listing */
if ((options & ListTall) != 0)
{
/* indent the prefix */
showListIndent(options, indent);
/*
* Show the prefix. If this is a contents listing, and
* it's not at the top level, show the contents prefix;
* otherwise show the full list prefix. Note that we can
* have a contents listing at the top level, since some
* lists are broken out for separate listing.
*/
if ((options & ListContents) != 0 && indent != 0)
showListContentsPrefixTall(itemCount, pov, parent);
else
showListPrefixTall(itemCount, pov, parent);
/* go to a new line for the list contents */
"\n";
/* indent the items one level now, since we showed a prefix */
++indent;
}
else
{
/* show the prefix */
showListPrefixWide(itemCount, pov, parent, lst: lst);
}
/*
* regardless of whether we're adding long formatting to the
* main list, display the group sublists with whatever
* formatting we were originally using
*/
groupOptions = options;
/* show each item with our current set of options */
itemOptions = options;
/*
* if we're using sublists, show "long list" separators in
* the main list
*/
if (sublists)
itemOptions |= ListLong;
/*
* calculate the number of items we'll show in the list -
* each group shows up as one list entry, so the total
* number of list entries is the number of single items plus
* the number of groups
*/
listCount = singles.length() + groups.length();
/*
* Show the items. Run through the (filtered) original
* list, so that we show everything in the original sorting
* order.
*/
dispCount = 0;
foreach (cur in lst)
{
local group;
local displayedCur;
/* presume we'll display this item */
displayedCur = true;
/*
* Figure out how to show this item: if it's in the
* singles list, show it as a single item; if it's in
* the group list, show its group; if it's in a group
* we've previously shown, show nothing, as we showed
* the item when we showed its group.
*/
if (singles.indexOf(cur) != nil)
{
/*
* It's in the singles list, so show it as a single
* item.
*
* If the item has contents that we'll display in
* 'tall' mode, show the item with its contents - we
* don't need to show the item separately, since it
* will provide a 'tall' list prefix showing itself.
* Otherwise, show the item singly.
*/
if ((options & ListTall) != 0
&& (options & ListRecurse) != 0
&& contentsListed(cur)
&& !contentsListedSeparately(cur)
&& getListedContents(cur, infoTab) != [])
{
/* show the item with its contents */
showContentsList(pov, cur, origOptions | ListContents,
indent, infoTab);
}
else
{
/* show the list indent if necessary */
showListIndent(itemOptions, indent);
/* show the item */
showListItem(cur, itemOptions, pov, infoTab);
/*
* if we're in wide recursive mode, show the
* item's contents as an in-line parenthetical
*/
if ((options & ListTall) == 0
&& (options & ListRecurse) != 0
&& contentsListed(cur)
&& !contentsListedSeparately(cur))
{
/* show the item's in-line contents */
showInlineContentsList(pov, cur,
origOptions | ListContents,
indent + 1, infoTab);
}
}
}
else if ((group = groups.valWhich(
{g: groupTab[g].indexOf(cur) != nil})) != nil)
{
/* show the list indent if necessary */
showListIndent(itemOptions, indent);
/* we found the item in a group, so show its group */
group.showGroupList(pov, self, groupTab[group],
groupOptions, indent, infoTab);
/*
* Forget this group - we only need to show each
* group once, since the group shows every item it
* contains. Since we'll encounter the groups other
* members as we continue to scan the main list, we
* want to make sure we don't show the group again
* when we reach the other items.
*/
groups.removeElement(group);
}
else
{
/*
* We didn't find the item in the singles list or in
* a group - it must be part of a group that we
* already showed previously, so we don't need to
* show it again now. Simply make a note that we
* didn't display it.
*/
displayedCur = nil;
}
/* if we displayed the item, show a suitable separator */
if (displayedCur)
{
/* count another list entry displayed */
++dispCount;
/* show an appropriate separator */
showListSeparator(itemOptions, dispCount, listCount);
}
}
/*
* if we're in 'wide' mode, finish the listing (note that if
* this is a 'tall' listing, we're already done, because a
* tall listing format doesn't make provisions for anything
* after the item list)
*/
if ((options & ListTall) == 0)
{
/* show the wide-mode list suffix */
showListSuffixWide(itemCount, pov, parent);
}
}
}
/*
* Get the cardinality of an arranged list. Returns the number of
* items that will appear in the list, for grammatical agreement.
*/
getArrangedListCardinality(singles, groups, groupTab)
{
local cnt;
/* start with a count of zero; we'll add to it as we go */
cnt = 0;
/*
* Add up the cardinality of the single items. Some individual
* items in the singles list might count as multiple items
* grammatically - in particular, if an item has a plural name,
* we need a plural verb to agree with it.
*/
foreach (local s in singles)
{
/* add the grammatical cardinality of this single item */
cnt += listCardinality(s);
}
/* add in the cardinality of each group */
foreach (local g in groups)
{
/* add the grammatical cardinality of this group */
cnt += g.groupCardinality(self, groupTab[g]);
}
/* return the total */
return cnt;
}
/*
* Get the number of noun phrase elements in a list. This differs
* from the cardinality in that we only count noun phrases, not the
* cardinality of each noun phrase. So, for example, "five coins"
* has cardinality five, but has only one noun phrase.
*/
getArrangedListNounPhraseCount(singles, groups, groupTab)
{
local cnt;
/* each single item counts as one noun phrase */
cnt = singles.length();
/* add in the noun phrases from each group */
foreach (local g in groups)
cnt += g.groupNounPhraseCount(self, groupTab[g]);
/* return the total */
return cnt;
}
/*
* Service routine: show the separately-listed contents of the items
* in the given list, and their separately-listed contents, and so
* on. This routine is not normally overridden in subclasses, and is
* not usually called except from the Lister implementation.
*
* For each item in the given list, we show the item's contents if
* the item is either marked as unlisted, or it's marked as showing
* its contents separately. In the former case, we know that we
* cannot have shown the item's contents in-line in the main list,
* since we didn't show the item at all in the main list. In the
* latter case, we know that we didn't show the item's contents in
* the main list because it's specifically marked as showing its
* contents out-of-line.
*/
showSeparateContents(pov, lst, options, infoTab)
{
/*
* show the separate contents list for each item in the list
* which isn't itself listable or which has its contents listed
* separately despite its being listed
*/
foreach (local cur in lst)
{
/* only show the contents if the contents are listed */
if (contentsListed(cur))
{
/*
* if we didn't list this item, or if it specifically
* wants its contents listed out-of-line, show its
* listable contents
*/
if (!isListed(cur) || contentsListedSeparately(cur))
{
/*
* Show the item's contents. Note that even though
* we're showing this list recursively, it's actually
* a new top-level list, so show it at indent level
* zero.
*/
showContentsList(pov, cur, options, 0, infoTab);
}
/* recursively do the same thing with its contents */
showSeparateContents(pov, getContents(cur), options, infoTab);
}
}
}
/*
* Show a list indent if necessary. If ListTall is included in the
* options, we'll indent to the given level; otherwise we'll do
* nothing.
*/
showListIndent(options, indent)
{
/* show the indent only if we're in "tall" mode */
if ((options & ListTall) != 0)
{
for (local i = 0 ; i < indent ; ++i)
"\t";
}
}
/*
* Show a newline after a list item if we're in a tall list; does
* nothing for a wide list.
*/
showTallListNewline(options)
{
if ((options & ListTall) != 0)
"\n";
}
/*
* Show a simple list, recursing into contents lists if necessary.
* We pay no attention to grouping; we just show the items
* individually.
*
* 'prevCnt' is the number of items already displayed, if anything
* has already been displayed for this list. This should be zero if
* this will display the entire list.
*/
showListSimple(pov, lst, options, indent, prevCnt, infoTab)
{
local i;
local cnt;
local dispCount;
local totalCount;
/* calculate the total number of items in the lis t*/
totalCount = prevCnt + lst.length();
/* display the items */
for (i = 1, cnt = lst.length(), dispCount = prevCnt ; i <= cnt ; ++i)
{
local cur;
/* get the item */
cur = lst[i];
/*
* If the item has contents that we'll display in 'tall'
* mode, show the item with its contents - we don't need to
* show the item separately, since it will provide a 'tall'
* list prefix showing itself. Otherwise, show the item
* singly.
*/
if ((options & ListTall) != 0
&& (options & ListRecurse) != 0
&& contentsListed(cur)
&& !contentsListedSeparately(cur)
&& getListedContents(cur, infoTab) != [])
{
/* show the item with its contents */
showContentsList(pov, cur, options | ListContents,
indent + 1, infoTab);
}
else
{
/* show the list indent if necessary */
showListIndent(options, indent);
/* show the item */
showListItem(cur, options, pov, infoTab);
/*
* if we're in wide recursive mode, show the item's
* contents as an in-line parenthetical
*/
if ((options & ListTall) == 0
&& (options & ListRecurse) != 0
&& contentsListed(cur)
&& !contentsListedSeparately(cur))
{
/* show the item's in-line contents */
showInlineContentsList(pov, cur,
options | ListContents,
indent + 1, infoTab);
}
}
/* count the item displayed */
++dispCount;
/* show the list separator */
showListSeparator(options, dispCount, totalCount);
}
}
/*
* List the contents of an item.
*
* 'pov' is the point of view, which is usually an actor (and
* usually the player character actor).
*
* 'obj' is the item whose contents we are to display.
*
* 'options' is the set of flags that we'll pass to showList(), and
* has the same meaning as for that function.
*
* 'infoTab' is a lookup table of SenseInfo objects giving the sense
* information for all of the objects that the actor to whom we're
* showing the contents listing can sense.
*/
showContentsList(pov, obj, options, indent, infoTab)
{
/*
* List the item's contents. By default, use the contentsLister
* property of the object whose contents we're showing to obtain
* the lister for the contents.
*/
obj.showObjectContents(pov, obj.contentsLister, options,
indent, infoTab);
}
/*
* Determine if an object's contents are listed separately from its
* own list entry for the purposes of our type of listing. If this
* returns true, then we'll list the object's contents in a separate
* listing (a separate sentence following the main listing sentence,
* or a separate tree when in 'tall' mode).
*
* Note that this only matters for objects listed in the top-level
* list. We'll always show the contents separately for an object
* that isn't listed in the top-level list (i.e., an object for which
* isListed(obj) returns nil).
*/
contentsListedSeparately(obj) { return obj.contentsListedSeparately; }
/*
* Show an "in-line" contents list. This shows the item's contents
* list as a parenthetical, as part of a recursive listing. This is
* pretty much the same as showContentsList(), but uses the object's
* in-line contents lister instead of its regular contents lister.
*/
showInlineContentsList(pov, obj, options, indent, infoTab)
{
/* show the item's contents using its in-line contents lister */
obj.showObjectContents(pov, obj.inlineContentsLister,
options, indent, infoTab);
}
/*
* Show the prefix for a 'wide' listing - this is a message that
* appears just before we start listing the objects. 'itemCount' is
* the number of items to be listed; the items might be grouped in
* the listing, so a list that comes out as "three boxes and two
* books" will have an itemCount of 5. (The purpose of itemCount is
* to allow the message to have grammatical agreement in number.)
*
* 'lst' is the entire list, which some languages need for
* grammatical agreement. This is passed as a named argument, so an
* overrider can omit it from the parameter list if it's not needed.
*
* This will never be called with an itemCount of zero, because we
* will instead use showListEmpty() to display an empty list.
*/
showListPrefixWide(itemCount, pov, parent, lst:) { }
/*
* show the suffix for a 'wide' listing - this is a message that
* appears just after we finish listing the objects
*/
showListSuffixWide(itemCount, pov, parent) { }
/*
* Show the list prefix for a 'tall' listing. Note that there is no
* list suffix for a tall listing, because the format doesn't allow
* it.
*/
showListPrefixTall(itemCount, pov, parent) { }
/*
* Show the list prefix for the contents of an object in a 'tall'
* listing. By default, we just show our usual tall list prefix.
*/
showListContentsPrefixTall(itemCount, pov, parent)
{ showListPrefixTall(itemCount, pov, parent); }
/*
* Show an empty list. If the list to be displayed has no items at
* all, this is called instead of the prefix/suffix routines. This
* can be left empty if no message is required for an empty list, or
* can display the complete message appropriate for an empty list
* (such as "You are empty-handed").
*/
showListEmpty(pov, parent) { }
/*
* Is this item to be listed in room descriptions? Returns true if
* so, nil if not. By default, we'll use the object's isListed
* method to make this determination. We virtualize this into the
* lister interface to allow for different inclusion rules for the
* same object depending on the type of list we're generating.
*/
isListed(obj) { return obj.isListed(); }
/*
* Get the grammatical cardinality of this listing item. This should
* return the number of items that this item appears to be
* grammatically, for noun-verb agreement purposes.
*/
listCardinality(obj) { return obj.listCardinality(self); }
/*
* Are this item's contents listable?
*/
contentsListed(obj) { return obj.contentsListed; }
/*
* Get all contents of this item.
*/
getContents(obj) { return obj.contents; }
/*
* Get the listed contents of an object. 'infoTab' is the sense
* information table for the enclosing listing. By default, we call
* the object's getListedContents() method, but this is virtualized
* in the lister interface to allow for listing other hierarchies
* besides ordinary contents.
*/
getListedContents(obj, infoTab)
{
return obj.getListedContents(self, infoTab);
}
/*
* Get the list of grouping objects for listing the item. By
* default, we return the object's listWith result. Subclasses can
* override this to specify different groupings for the same object
* depending on the type of list we're generating.
*
* The group list returned is in order from most general to most
* specific. For example, if an item is grouped with coins in
* general and silver coins in particular, the general coins group
* would come first, then the silver coin group, because the silver
* coin group is more specific.
*/
listWith(obj) { return obj.listWith; }
/* show an item in a list */
showListItem(obj, options, pov, infoTab)
{
obj.showListItem(options, pov, infoTab);
}
/*
* Show a set of equivalent items as a counted item ("three coins").
* The listing mechanism itself never calls this directly; instead,
* this is provided so that ListGroupEquivalent can ask the lister
* how to describe its equivalent sets, so that different listers
* can customize the display of equivalent items.
*
* 'lst' is the full list of equivalent items. By default, we pick
* one of these arbitrarily to show, since they're presumably all
* the same for the purposes of the list.
*/
showListItemCounted(lst, options, pov, infoTab)
{
/*
* by defualt, show the counted name for one of the items
* (chosen arbitrarily, since they're all the same)
*/
lst[1].showListItemCounted(lst, options, pov, infoTab);
}
/*
* Show a list separator after displaying an item. curItemNum is
* the number of the item just displayed (1 is the first item), and
* totalItems is the total number of items that will be displayed in
* the list.
*
* This generic routine is further parameterized by properties for
* the individual types of separators. This default implementation
* distinguishes the following separators: the separator between the
* two items in a list of exactly two items; the separator between
* adjacent items other than the last two in a list of more than two
* items; and the separator between the last two elements of a list
* of more than two items.
*/
showListSeparator(options, curItemNum, totalItems)
{
local useLong = ((options & ListLong) != 0);
/* if this is a tall list, the separator is simply a newline */
if ((options & ListTall) != 0)
{
"\n";
return;
}
/* if that was the last item, there are no separators */
if (curItemNum == totalItems)
return;
/* check to see if the next item is the last */
if (curItemNum + 1 == totalItems)
{
/*
* We just displayed the penultimate item in the list, so we
* need to use the special last-item separator. If we're
* only displaying two items total, we use an even more
* special separator.
*/
if (totalItems == 2)
{
/* use the two-item separator */
if (useLong)
longListSepTwo;
else
listSepTwo;
}
else
{
/* use the normal last-item separator */
if (useLong)
longListSepEnd;
else
listSepEnd;
}
}
else
{
/* in the middle of the list - display the normal separator */
if (useLong)
longListSepMiddle;
else
listSepMiddle;
}
}
/*
* Show the specific types of list separators for this list. By
* default, these will use the generic separators defined in the
* library messages object (gLibMessages). For English, these are
* commas and semicolons for short and long lists, respectively; the
* word "and" for a list with only two items; and a comma or
* semicolon and the word "and" between the last two items in a list
* with more than two items.
*/
/*
* normal and "long list" separator between the two items in a list
* with exactly two items
*/
listSepTwo { gLibMessages.listSepTwo; }
longListSepTwo { gLibMessages.longListSepTwo; }
/*
* normal and long list separator between items in list with more
* than two items
*/
listSepMiddle { gLibMessages.listSepMiddle; }
longListSepMiddle { gLibMessages.longListSepMiddle; }
/*
* normal and long list separator between second-to-last and last
* items in a list with more than two items
*/
listSepEnd { gLibMessages.listSepEnd; }
longListSepEnd { gLibMessages.longListSepEnd; }
/*
* Get my "top-level" lister. For a sub-lister, this will return
* the parent lister's top-level lister. The default lister is a
* top-level lister, so we just return ourself.
*/
getTopLister() { return self; }
/*
* The last custom flag defined by this class. Lister and each
* subclass are required to define this so that each subclass can
* allocate its own custom flags in a manner that adapts
* automatically to future additions of flags to base classes. As
* the base class, we allocate our flags statically with #define's,
* so we simply use the fixed #define'd last flag value here.
*/
nextCustomFlag = ListCustomFlag
;
/* ------------------------------------------------------------------------ */
/*
* A SimpleLister provides simplified interfaces for creating formatted
* lists.
*/
class SimpleLister: Lister
/*
* Show a formatted list given a list of items. This lets you create
* a formatted list from an item list without worrying about
* visibility or other factors that affect the full Lister
* interfaces.
*/
showSimpleList(lst)
{
showListAll(lst, 0, 0);
}
/* by default, everything given to a simple lister is listed */
isListed(obj) { return true; }
/*
* Format a simple list, but rather than displaying the result,
* return it as a string. This simply displays the list as normal,
* but captures the output as a string and returns it.
*/
makeSimpleList(lst)
{
return mainOutputStream.captureOutput({: showSimpleList(lst) });
}
;
/*
* objectLister is a concrete SimpleLister for listing simulation
* objects.
*/
objectLister: SimpleLister
;
/*
* stringLister is a concrete SimpleLister for formatting lists of
* strings. To use this lister, pass lists of single-quoted strings
* (instead of simulation objects) to showSimpleList(), etc.
*/
stringLister: SimpleLister
/* show a list item - list items are strings, so simply 'say' them */
showListItem(str, options, pov, infoTab) { say(str); }
/*
* get the cardinality of an arranged list (we need to override this
* because our items are strings, which don't have the normal object
* properties that would let us count cardinality the usual way)
*/
getArrangedListCardinality(singles, groups, groupTab)
{
return singles.length();
}
;
/* ------------------------------------------------------------------------ */
/*
* Plain lister - this lister doesn't show anything for an empty list,
* and doesn't show a list suffix or prefix.
*/
plainLister: Lister
/* show the prefix/suffix in wide mode */
showListPrefixWide(itemCount, pov, parent) { }
showListSuffixWide(itemCount, pov, parent) { }
/* show the tall prefix */
showListPrefixTall(itemCount, pov, parent) { }
;
/*
* Sub-lister for listing the contents of a group. This lister shows a
* simple list with no prefix or suffix, and otherwise uses the
* characteristics of the parent lister.
*/
class GroupSublister: object
construct(parentLister, parentGroup)
{
/* remember the parent lister and group objects */
self.parentLister = parentLister;
self.parentGroup = parentGroup;
}
/* no prefix or suffix */
showListPrefixWide(itemCount, pov, parent) { }
showListSuffixWide(itemCount, pov, parent) { }
showListPrefixTall(itemCount, pov, parent) { }
/* show nothing when empty */
showListEmpty(pov, parent) { }
/*
* Show an item in the list. Rather than going through the parent
* lister directly, we go through the parent group, so that it can
* customize the display of items in the group.
*/
showListItem(obj, options, pov, infoTab)
{
/* ask the parent group to handle it */
parentGroup.showGroupItem(parentLister, obj, options, pov, infoTab);
}
/*
* Show a counted item in the group. As with showListItem, we ask
* the parent group to do the work, so that it can customize the
* display if desired.
*/
showListItemCounted(lst, options, pov, infoTab)
{
/* ask the parent group to handle it */
parentGroup.showGroupItemCounted(
parentLister, lst, options, pov, infoTab);
}
/* delegate everything we don't explicitly handle to our parent lister */
propNotDefined(prop, [args])
{
return delegated (getTopLister()).(prop)(args...);
}
/* get the top-level lister - returns my parent's top-level lister */
getTopLister() { return parentLister.getTopLister(); }
/* my parent lister */
parentLister = nil
/* my parent list group */
parentGroup = nil
;
/*
* Paragraph lister: this shows its list items separated by paragraph
* breaks, with a paragraph break before the first item.
*/
class ParagraphLister: Lister
/* start the list with a paragraph break */
showListPrefixWide(itemCount, pov, parent) { "<.p>"; }
/* we show no list separators */
showListSeparator(options, curItemNum, totalItems)
{
/* add a paragraph separator between items */
if (curItemNum != totalItems)
"<.p>";
}
;
/*
* Lister for objects in a room description with special descriptions.
* Each special description gets its own paragraph, so this is based on
* the paragraph lister.
*/
specialDescLister: ParagraphLister
/* list everything */
isListed(obj) { return true; }
/* show a list item */
showListItem(obj, options, pov, infoTab)
{
/* show the object's special description */
obj.showSpecialDescWithInfo(infoTab[obj], pov);
}
/* use the object's special description grouper */
listWith(obj) { return obj.specialDescListWith; }
;
/*
* Special description lister for the contents of an item being examined.
* This is similar to the regular specialDescLister, but shows the
* special descriptions of the contents of an object being described with
* "examine" or "look in," rather than of the entire location.
*/
class SpecialDescContentsLister: ParagraphLister
construct(cont)
{
/* remember the containing object being described */
cont_ = cont;
}
/* list everything */
isListed(obj) { return true; }
/* show a list item */
showListItem(obj, options, pov, infoTab)
{
/* show the object's special description in our container */
obj.showSpecialDescInContentsWithInfo(infoTab[obj], pov, cont_);
}
/* use the object's special description grouper */
listWith(obj) { return obj.specialDescListWith; }
/* the containing object we're examining */
cont_ = nil
;
/*
* Plain lister for actors. This is the same as an ordinary
* plainLister, but ignores each object's isListed flag and lists it
* anyway.
*/
plainActorLister: plainLister
isListed(obj) { return true; }
;
/*
* Grouper for actors in a common posture and in a common location. We
* create one of these per room per posture when we discover actors in
* the room during "look around" (or "examine" on a nested room). This
* grouper lets us group actors like so: "Dan and Jane are sitting on
* the couch."
*/
class RoomActorGrouper: ListGroup
construct(location, posture)
{
self.location = location;
self.posture = posture;
}
showGroupList(pov, lister, lst, options, indent, infoTab)
{
local cont;
local outer;
/* if the location isn't in the sense table, skip the whole list */
if (infoTab[location] == nil)
return;
/* get the nominal posture container, if it's visible */
cont = location.getNominalActorContainer(posture);
if (cont != nil && !pov.canSee(cont))
cont = nil;
/* get the outermost visible enclosing location */
outer = location.getOutermostVisibleRoom(pov);
/*
* Only mention the outermost location if it's remote and it's
* not the same as the nominal container. (If the remote outer
* room is the same as the nominal container, it would be
* redundant to mention it as both the nominal and remote
* container.)
*/
if (outer == cont || pov.isIn(outer))
outer = nil;
/* create a sub-lister for the group */
lister = createGroupSublister(lister);
/*
* show the list prefix message - use the nominal container if
* we can see it, otherwise generate a generic message
*/
if (cont != nil)
cont.actorInGroupPrefix(pov, posture, outer, lst);
else if (outer != nil)
gLibMessages.actorThereGroupPrefix(pov, posture, outer, lst);
else
gLibMessages.actorHereGroupPrefix(posture, lst);
/* list the actors' names as a plain list */
plainActorLister.showList(pov, location, lst, options,
indent, infoTab, self);
/* add the suffix message */
if (cont != nil)
cont.actorInGroupSuffix(pov, posture, outer, lst);
else if (outer != nil)
gLibMessages.actorThereGroupSuffix(pov, posture, outer, lst);
else
gLibMessages.actorHereGroupSuffix(posture, lst);
}
;
/*
* Base class for inventory listers. This lister uses a special listing
* method to show the items, so that items can be shown with special
* notations in an inventory list that might not appear in other types
* of listings.
*/
class InventoryLister: Lister
/* list items in inventory according to their isListedInInventory */
isListed(obj) { return obj.isListedInInventory; }
/*
* Show list items using the inventory name, which might differ from
* the regular nmae of the object.
*/
showListItem(obj, options, pov, infoTab)
{ obj.showInventoryItem(options, pov, infoTab); }
showListItemCounted(lst, options, pov, infoTab)
{ lst[1].showInventoryItemCounted(lst, options, pov, infoTab); }
/*
* Show contents of the items in the inventory. We customize this
* so that we can differentiate inventory contents lists from other
* contents lists.
*/
showContentsList(pov, obj, options, indent, infoTab)
{
/* list the item's contents */
obj.showInventoryContents(pov, obj.contentsLister, options,
indent, infoTab);
}
/*
* Show the contents in-line, for an inventory listing.
*/
showInlineContentsList(pov, obj, options, indent, infoTab)
{
/* list the item's contents using its in-line lister */
obj.showInventoryContents(pov, obj.inlineContentsLister,
options, indent, infoTab);
}
;
/*
* Base class for worn-inventory listers. This lister uses a special
* listing method to show the items, so that items being worn are shown
* *without* the special '(being worn)' notation that might otherwise
* appear in inventory listings.
*/
class WearingLister: InventoryLister
/* show the list item using the "worn listing" name */
showListItem(obj, options, pov, infoTab)
{ obj.showWornItem(options, pov, infoTab); }
showListItemCounted(lst, options, pov, infoTab)
{ lst[1].showWornItemCounted(lst, options, pov, infoTab); }
;
/*
* "Divided" inventory lister. In 'wide' mode, this shows inventory in
* two parts: the items being carried, and the items being worn. (We use
* the standard single tree-style listing in 'tall' mode.)
*/
class DividedInventoryLister: InventoryLister
/*
* Show the list. We completely override the main lister method so
* that we can show our two lists.
*/
showList(pov, parent, lst, options, indent, infoTab, parentGroup)
{
/*
* If this is a 'tall' listing, use the normal listing style; for
* a 'wide' listing, use our special segregated style. If we're
* being invoked recursively to show a contents listing, we
* similarly want to use the base handling.
*/
if ((options & (ListTall | ListContents)) != 0)
{
/* inherit the standard behavior */
inherited(pov, parent, lst, options, indent, infoTab,
parentGroup);
}
else
{
local carryingLst, wearingLst;
local carryingStr, wearingStr;
/* divide the lists into 'carrying' and 'wearing' sublists */
carryingLst = new Vector(32);
wearingLst = new Vector(32);
foreach (local cur in lst)
(cur.isWornBy(parent) ? wearingLst : carryingLst).append(cur);
/* generate and capture the 'carried' listing */
carryingStr = outputManager.curOutputStream.captureOutput({:
carryingLister.showList(pov, parent, carryingLst, options,
indent, infoTab, parentGroup)});
/* generate and capture the 'worn' listing */
wearingStr = outputManager.curOutputStream.captureOutput({:
wearingLister.showList(pov, parent, wearingLst, options,
indent, infoTab, parentGroup)});
/* generate the combined listing */
showCombinedInventoryList(parent, carryingStr, wearingStr);
/*
* Now show the out-of-line contents for the whole list, if
* appropriate. We save this until after showing both parts
* of the list, to keep the direct inventory parts together
* at the beginning of the output.
*/
if ((options & ListRecurse) != 0
&& indent == 0
&& (options & ListContents) == 0)
{
/* show the contents of each object we didn't list */
showSeparateContents(pov, lst, options | ListContents,
infoTab);
}
}
}
/*
* Show the combined listing. This must be provided by each
* language-specific subclass. The inputs are the results (strings)
* of the captured output of the sublistings of the items being
* carried and the items being worn. These will be "raw" listings,
* without any prefix or suffix text. This routine's job is to
* display the final output, adding the framing text.
*/
showCombinedInventoryList(parent, carrying, wearing) { }
/*
* The recommended maximum number of number of noun phrases to show
* in the single-sentence format. This should be used by the
* showCombinedInventoryList() method to decide whether to display
* the combined listing as a single sentence or as two separate
* sentences.
*/
singleSentenceMaxNouns = 7
/*
* Our associated sub-listers for items begin carried and worn,
* respectively. We'll use these to list our sublist of items being
* worn.
*/
carryingLister = actorCarryingSublister
wearingLister = actorWearingSublister
;
/*
* Base class for the inventory sub-lister for items being carried. This
* is a minor specialization of the basic inventory lister; in this
* version, we omit any prefix, suffix, or empty messages, since we'll
* rely on the caller to combine our raw listing with the raw 'wearing'
* listing to form the full results.
*
* This type of lister should normally only be used from within an
* inventory lister. This type of lister assumes that it's part of a
* larger listing controlled externally; for example, we don't show
* out-of-line contents, since we assume the caller will be doing this.
*/
class InventorySublister: InventoryLister
/* don't show any prefix, suffix, or 'empty' messages */
showListPrefixWide(itemCount, pov, parent) { }
showListSuffixWide(itemCount, pov, parent) { }
showListEmpty(pov, parent) { }
/* don't show out-of-line contents */
showSeparateContents(pov, lst, options, infoTab) { }
;
/*
* Base class for the inventory sub-lister for items being worn. We use
* a special listing method to show these items, so that items being
* shown explicitly in a worn list can be shown differently from the way
* they would in a normal inventory list. (For example, a worn item in a
* normal inventory list might show a "(worn)" indication, whereas it
* would not want to show a similar indication in a list of objects
* explicitly being worn.)
*
* This type of lister should normally only be used from within an
* inventory lister. This type of lister assumes that it's part of a
* larger listing controlled externally; for example, we don't show
* out-of-line contents, since we assume the caller will be doing this.
*/
class WearingSublister: WearingLister
/* don't show any prefix, suffix, or 'empty' messages */
showListPrefixWide(itemCount, pov, parent) { }
showListSuffixWide(itemCount, pov, parent) { }
showListEmpty(pov, parent) { }
/* don't show out-of-line contents */
showSeparateContents(pov, lst, options, infoTab) { }
;
/*
* The standard inventory sublisters.
*/
actorCarryingSublister: InventorySublister;
actorWearingSublister: WearingSublister;
/*
* Base class for contents listers. This is used to list the contents
* of the objects that appear in top-level lists (a top-level list is
* the list of objects directly in a room that appears in a room
* description, or the list of items being carried in an INVENTORY
* command, or the direct contents of an object being examined).
*/
class ContentsLister: Lister
;
/*
* Base class for description contents listers. This is used to list
* the contents of an object when we examine the object, or when we
* explicitly LOOK IN the object.
*/
class DescContentsLister: Lister
/*
* Use the explicit look-in flag for listing contents. We might
* list items within an object on explicit examination of the item
* that we wouldn't list in a room or inventory list containing the
* item.
*/
isListed(obj) { return obj.isListedInContents; }
;
/*
* Base class for sense listers, which list the items that can be sensed
* for a command like "listen" or "smell".
*/
class SenseLister: ParagraphLister
/* show everything we're asked to list */
isListed(obj) { return true; }
/* show a counted list item */
showListItemCounted(lst, options, pov, infoTab)
{
/*
* simply show one item, without the count - non-visual senses
* don't distinguish numbers of items that are equivalent
*/
showListItem(lst[1], options, pov, infoTab);
}
;
/*
* Room contents lister for things that can be heard.
*/
roomListenLister: SenseLister
/* list an item in a room if its isSoundListedInRoom is true */
isListed(obj) { return obj.isSoundListedInRoom; }
/* list an item */
showListItem(obj, options, pov, infoTab)
{
/* show the "listen" list name for the item */
obj.soundHereDesc();
}
;
/*
* Lister for explicit "listen" action
*/
listenActionLister: roomListenLister
/* list everything in response to an explicit general LISTEN command */
isListed(obj) { return true; }
/* show an empty list */
showListEmpty(pov, parent)
{
mainReport(¬hingToHearMsg);
}
;
/*
* Room contents lister for things that can be smelled.
*/
roomSmellLister: SenseLister
/* list an item in a room if its isSmellListedInRoom is true */
isListed(obj) { return obj.isSmellListedInRoom; }
/* list an item */
showListItem(obj, options, pov, infoTab)
{
/* show the "smell" list name for the item */
obj.smellHereDesc();
}
;
/*
* Lister for explicit "smell" action
*/
smellActionLister: roomSmellLister
/* list everything in response to an explicit general SMELL command */
isListed(obj) { return true; }
/* show an empty list */
showListEmpty(pov, parent)
{
mainReport(¬hingToSmellMsg);
}
;
/*
* Inventory lister for things that can be heard.
*/
inventoryListenLister: SenseLister
/* list an item */
showListItem(obj, options, pov, infoTab)
{
/* show the "listen" list name for the item */
obj.soundHereDesc();
}
;
/*
* Inventory lister for things that can be smelled.
*/
inventorySmellLister: SenseLister
/* list an item */
showListItem(obj, options, pov, infoTab)
{
/* show the "smell" list name for the item */
obj.smellHereDesc();
}
;
/* ------------------------------------------------------------------------ */
/*
* List Group Interface. An instance of this object is created for each
* set of objects that are to be grouped together.
*/
class ListGroup: object
/*
* Show a list of items from this group. All of the items in the
* list will be members of this list group; we are to display a
* sentence fragment showing the items in the list, suitable for
* embedding in a larger list.
*
* 'options', 'indent', and 'infoTab' have the same meaning as they
* do for showList().
*
* Note that we are not to display any separator before or after our
* list; the caller is responsible for that.
*/
showGroupList(pov, lister, lst, options, indent, infoTab) { }
/*
* Show an item in the group's sublist. The sublister calls this to
* display each item in the group when the group calls the sublister
* to display the group list. By default, we simply let the
* sublister handle the request, which gives items in our group
* sublist the same appearance they would have had in the sublist to
* begin with. We can customize this behavior to give our list
* items a different appearance special to the group sublist.
*
* Note that the same customization could be accomplished by
* creating a specialized subclass of GroupSublister in
* createGroupSublister(), and overriding showListItem() in the
* specialized GroupSublister subclass. We use this mechanism as a
* convenience, so that a separate group sublister class doesn't
* have to be created simply to customize the display of group
* items.
*/
showGroupItem(sublister, obj, options, pov, infoTab)
{
/* by default, list using the regular sublister */
sublister.showListItem(obj, options, pov, infoTab);
}
/*
* Show a counted item in our group list. This is the counted item
* equivalent of showGroupItem.
*/
showGroupItemCounted(sublister, lst, options, pov, infoTab)
{
/* by default, list using the regular sublister */
sublister.showListItemCounted(lst, options, pov, infoTab);
}
/*
* Determine if showing the group list will introduce a sublist into
* an enclosing list. This should return true if we will show a
* sublist without some kind of grouping, so that the caller knows
* to introduce some extra grouping into its enclosing list. This
* should return nil if the sublist we display will be clearly set
* off in some way (for example, by being enclosed in parentheses).
*/
groupDisplaysSublist = true
/*
* The minimum number of elements for which we should retain the
* group in a listing. By default, we need two elements to display a
* group; any group with only one element is discarded, and the
* single element is moved into the 'singles' list. This can be
* overridden to allow single-element groups to be retained. In most
* cases, it's undesirable to retain single-element groups, but when
* grouping is used to partition a list into two or more fixed
* portions, single-element groups become desirable.
*/
minGroupSize = 2
/*
* Determine the cardinality of the group listing, grammatically
* speaking. This is the number of items that the group seems to be
* for the purposes of grammatical agreement. For example, if the
* group is displayed as "$1.38 in change", it would be singular for
* grammatical agreement, hence would return 1 here; if it displays
* "five coins (two copper, three gold)," it would count as five
* items for grammatical agreement.
*
* For languages (like English) that grammatically distinguish
* number only between singular and plural, it is sufficient for
* this to return 1 for singular and anything higher for plural.
* For the sake of languages that make more elaborate number
* distinctions for grammatical agreement, though, this should
* return as accurate a count as is possible.
*
* By default, we return the number of items to be displayed in the
* list group. This should be overridden when necessary, such as
* when the group message is singular in usage even if the list has
* multiple items (as in "$1.38 in change").
*/
groupCardinality(lister, lst) { return lst.length(); }
/*
* Get the number of noun phrases this group will display. This
* differs from the cardinality in that it doesn't matter how many
* *objects* the phrases represent; it only matters how many phrases
* are displayed. For example, "five coins" has cardinality 5 but
* only displays one noun phrase.
*
* By default, we simply return the number of items in the group,
* since most groups individually list their items.
*/
groupNounPhraseCount(lister, lst) { return lst.length(); }
/*
* Create the group sublister.
*
* In most cases, when a group displays a list of the items in the
* group as a sublist, it will not want to use the same lister that
* was used to show the enclosing group, because the enclosing lister
* will usually have different prefix/suffix styles than the sublist.
* However, the group list and the enclosing list might share many
* other attributes, such as the style of name to use when displaying
* items in the list. The default sublister we create,
* GroupSublister, is a hybrid that uses the enclosing lister's
* attributes except for a few, such as the prefix and suffix, that
* usually need to be changed for the sublist.
*
* This can be overridden to use a completely customized Lister
* object for the group list, if desired.
*/
createGroupSublister(parentLister)
{
/* create the standard group sublister by default */
return new GroupSublister(parentLister, self);
}
;
/*
* A "custom" List Group implementation. This type of lister uses a
* completely custom message to show the group, without a need to
* recursively invoke a lister to list the individual elements. The main
* difference between this and the base ListGroup is that the interface
* to the custom message generator is very simple - we can dispense with
* most of the numerous arguments that the base group message receives,
* since most of those arguments are there to allow recursive listing of
* the group list.
*
* This group type is intended mainly for cases where you want to display
* some sort of collective description of the group, rather than listing
* its members individually. The whole point of the simple interface is
* that we don't pass the normal big pile of parameters because we won't
* be invoking a full sublisting. Since we assume that this group won't
* itself look like a sublist, we set groupDisplaysSublist to nil by
* default. This means that our presence in the overall list won't
* trigger the "long list" format (usually, this uses semicolons instead
* of commas) in the enclosing list. If your custom group message does
* indeed look like a sublist (that is, it displays multiple items in a
* comma-separated list), you might want to change groupDisplaysSublist
* back to true so that the overall list is shown in the "long" format.
*/
class ListGroupCustom: ListGroup
showGroupList(pov, lister, lst, options, indent, infoTab)
{
/* simply show the custom message for the list */
showGroupMsg(lst);
}
/* show the custom group message - subclasses should override */
showGroupMsg(lst) { }
/* assume our listing message doesn't look like a sublist */
groupDisplaysSublist = nil
;
/*
* Sorted group list. This is a list that simply displays its members
* in a specific sorting order.
*/
class ListGroupSorted: ListGroup
/*
* Show the group list
*/
showGroupList(pov, lister, lst, options, indent, infoTab)
{
/* put the list in sorted order */
lst = sortListGroup(lst);
/* create a sub-lister for the group */
lister = createGroupSublister(lister);
/* show the list */
lister.showList(pov, nil, lst, options & ~ListContents,
indent, infoTab, self);
}
/*
* Sort the group list. By default, if we have a
* compareGroupItems() method defined, we'll sort the list using
* that method; otherwise, we'll just return the list unchanged.
*/
sortListGroup(lst)
{
/*
* if we have a compareGroupItems method, use it to sort the
* list; otherwise, just return the list in its current order
*/
if (propDefined(&compareGroupItems, PropDefAny))
return lst.sort(SortAsc, {a, b: compareGroupItems(a, b)});
else
return lst;
}
/*
* Compare a pair of items from the group to determine their relative
* sorting order. This should return 0 if the two items are at the
* same sorting order, a positive integer if the first item sorts
* after the second item, or a negative integer if the first item
* sorts before the second item.
*
* Note that we don't care about the return value beyond whether it's
* positive, negative, or zero. This makes it especially easy to
* implement this method if the sorting order is determined by a
* property on each object that has an integer value: in this case
* you simply return the difference of the two property values, as in
* a.prop - b.prop. This will have the effect of sorting the objects
* in ascending order of their 'prop' property values. To sort in
* descending order of the same property, simply reverse the
* subtraction: b.prop - a.prop.
*
* When no implementation of this method is defined in the group
* object, sortListGroup won't bother sorting the list at all.
*
* By default, we don't implement this method. Subclasses that want
* to impose a sorting order must implement the method.
*/
// compareGroupItems(a, b) { return a > b ? 1 : a == b ? 0 : -1; }
;
/*
* List Group implementation: parenthesized sublist. Displays the
* number of items collectively, then displays the list of items in
* parentheses.
*
* Note that this is a ListGroupSorted subclass. If our subclass
* defines a compareGroupItems() method, we'll show the list in the
* order specified by compareGroupItems().
*/
class ListGroupParen: ListGroupSorted
/*
* show the group list
*/
showGroupList(pov, lister, lst, options, indent, infoTab)
{
/* sort the list group, if we have an ordering method defined */
lst = sortListGroup(lst);
/* create a sub-lister for the group */
lister = createGroupSublister(lister);
/* show the collective count of the object */
showGroupCountName(lst);
/* show the tall or wide sublist */
if ((options & ListTall) != 0)
{
/* tall list - show the items as a sublist */
"\n";
lister.showList(pov, nil, lst, options & ~ListContents,
indent, infoTab, self);
}
else
{
/* wide list - add a space and a paren for the sublist */
" (";
/* show the sublist */
lister.showList(pov, nil, lst, options & ~ListContents,
indent, infoTab, self);
/* end the sublist */
")";
}
}
/*
* Show the collective count for the list of objects. By default,
* we'll simply display the countName of the first item in the list,
* on the assumption that each object has the same plural
* description. However, in most cases this should be overridden to
* provide a more general collective name for the group.
*/
showGroupCountName(lst)
{
/* show the first item's countName */
say(lst[1].countName(lst.length()));
}
/* we don't add a sublist, since we enclose our list in parentheses */
groupDisplaysSublist = nil
;
/*
* List Group implementation: simple prefix/suffix lister. Shows a
* prefix message, then shows the list, then shows a suffix message.
*
* Note that this is a ListGroupSorted subclass. If our subclass
* defines a compareGroupItems() method, we'll show the list in the
* order specified by compareGroupItems().
*/
class ListGroupPrefixSuffix: ListGroupSorted
showGroupList(pov, lister, lst, options, indent, infoTab)
{
/* sort the list group, if we have an ordering method defined */
lst = sortListGroup(lst);
/* create a sub-lister for the group */
lister = createGroupSublister(lister);
/* show the prefix */
showGroupPrefix(pov, lst);
/* if we're in tall mode, start a new line */
lister.showTallListNewline(options);
/* show the list */
lister.showList(pov, nil, lst, options & ~ListContents,
indent + 1, infoTab, self);
/* show the suffix */
showGroupSuffix(pov, lst);
}
/* show the prefix - we just show the groupPrefix message by default */
showGroupPrefix(pov, lst) { groupPrefix; }
/* show the suffix - we just show the groupSuffix message by default */
showGroupSuffix(pov, lst) { groupSuffix; }
/*
* The prefix and suffix messages. The showGroupPrefix and
* showGroupSuffix methods simply show these message properties. We
* go through this two-step procedure for convenience: if the
* subclass doesn't need the POV and list parameters, it's less
* typing to just override these parameterless properties. If the
* subclass needs to vary the message according to the POV or what's
* in the list, it can override the showGroupXxx methods instead.
*/
groupPrefix = ""
groupSuffix = ""
;
/*
* Equivalent object list group. This is the default listing group for
* equivalent items. The Thing class creates an instance of this class
* during initialization for each set of equivalent items.
*/
class ListGroupEquivalent: ListGroup
showGroupList(pov, lister, lst, options, indent, infoTab)
{
/* show a count of the items */
lister.showListItemCounted(lst, options, pov, infoTab);
}
/*
* An equivalence group displays only a single noun phrase to cover
* the entire group.
*/
groupNounPhraseCount(lister, lst) { return 1; }
/* we display as a single item, so there's no sublist */
groupDisplaysSublist = nil
;
TADS 3 Library Manual
Generated on 5/16/2013 from TADS version 3.1.3