lister.t | documentation |
#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
Generated on 5/16/2013 from TADS version 3.1.3