ListWriter contents
Specification.
The list-writer is called by one of the following function calls:
(1) WriteListOfMarkedObjects(style), where the set of objects listed is understood to be exactly those with the workflag2 attribute set, and (-a) the style is a sum of *_BIT constants as defined in Definitions.i6t.
(2) WriteListFrom(obj, style, depth, noactivity, iterator), where only the first two parameters are compulsory: (-a) the set of objects listed begins with obj; (-b) the style is a sum of *_BIT constants as defined in Definitions.i6t; (-c) the depth is the recursion depth within the list – ordinarily 0, but by supplying a higher value, we can simulate a sublist of another list; (-d) noactivity is a flag which forces the list-writer to ignore the "listing the contents of" activity (in cases where it would otherwise consult this): by default this is false; (-e) iterator is an iterator function which provides the objects in sequence.
WriteListOfMarkedObjects is simply a front-end which supplies suitable parameters for WriteListFrom.
The iterator function is by default ObjectTreeIterator. This defines the sequence of objects as being the children of the parent of obj, in object tree sequence (that is: child(parent(obj)) is first). Moreover, when using ObjectTreeIterator, the "listing the contents of" activity is carried out, unless noactivity is set.
We also provide the iterator function MarkedListIterator, which defines the sequence of objects as being the list in the word array MarkedObjectArray with length MarkedObjectLength. Here the "listing the contents of" activity is never used, since the objects are not necessarily the contents of any single thing. This of course is the iterator used by WriteListOfMarkedObjects(style): it works by filling this array with all the objects having workflag2 set.
The full specification for iterator functions is given below.
The list-writer automatically groups adjacent and indistinguishable terms in the sequence into plurals, and carries out the "printing the plural name" activity to handle these. Doing this alone would result in text such as "You can see a cake, three coins, an onion, and two coins here", where the five coins are mentioned in two clauses because they happen not to be adjacent in the list. The list-writer therefore carries out the activity "grouping together" to see if the user would like to tie certain objects together into a single entry: what this does is to set suitable list_together properties for the objects. NI has already given plural objects a similar list_together property. The net effect is that any entries in the list with a non-blank value of list_together must be adjacent to each other.
We could achieve that by sorting the list in order of list_together value, but that would result in drastic movements, whereas we want to upset the original ordering as little as possible. So instead we use a process called coalescing the list. This is such that for every value L ≠ 0 of list_together, every entry with that value is moved back in the list to follow the first entry with that value. Thus if the original order is x1, x2, ..., xN then xj still precedes xk in coalesced order unless there exists i<j<k such that L(i) = L(k) ≠ 0 and L(j) ≠ L(i). This is as stable as it can be while achieving the "interval" property that non-zero L values occur in contiguous runs.
We therefore obtain text such as "You can see a cake, five coins, the tiles W, X, Y and Z from a Scrabble set, and an onion here." where the coins and the Scrabble tiles have been coalesced together in the list.
It's important to note that the default ObjectTreeIterator has the side-effect of actually reordering the object tree: it rearranges the children being listed so that they appear in the tree in coalesced order. The MarkedListIterator used by WriteListOfMarkedObjects has the same side-effect if the marked objects all happen to share a common parent. It might seem odd for a list-writer to have side effects at all, but the idea is that occasional coalescing improves the quality of play in many small ways – for instance, the sequence of matches to TAKE ALL is tidier – and that coalescing has a small speed cost, so we want to do it as little as possible. (The latter was more of a consideration for I6: interpreters are faster nowadays.)
This specification is somewhat stronger than that of the I6 library's traditional list-writer, because (i) it supports arbitrary lists of objects, not just children of specific parents, while still allowing coalesced and grouped lists, (ii) it can be used recursively in all cases, (iii) it uses its own memory, rather than borrowing memory from the parser, so that it can safely be used while the parser is working, and (iv) it manages this memory more flexibly and without silently failing by buffer overruns on unexpectedly large lists.
The I7 version of WriteListFrom, when using ObjectTreeIterator, differs from the I6 version in that the object o is required to be the child of its parent, that is, to be the eldest child. (So in effect it's a function of parents, not children, but we retain the form for familiarity's sake.) In practice this was invariably the way WriteListFrom was used even in I6 days.
Finally, the ISARE_BIT is differently interpreted in I7: instead of printing something like " are ducks and drakes", as it would have done in I6, the initial space is now suppressed and we instead print "are ducks and drakes".
Memory.
The list-writer needs to dynamically allocate temporary array space of a known size, in such a way that the array is effectively on the local stack frame: if only either the Z-machine or Glulx supported a stack in memory, this would be no problem, but they do not and we must therefore use the following.
The size of the stack is such that it can support a list which includes every object and recurses in turn to most other objects: in practice, this never happens. It would be nice to allocate more just in case, but the Z-machine is desperately short of array memory.
117Constant REQUISITION_STACK_SIZE = 3*{-value:Instances::count(K_object)};
118Array requisition_stack --> REQUISITION_STACK_SIZE;
119Global requisition_stack_pointer = 0;
120
121[ RequisitionStack len top addr;
122 top = requisition_stack_pointer + len;
123 if (top > REQUISITION_STACK_SIZE) return false;
124 addr = requisition_stack + requisition_stack_pointer*WORDSIZE;
125
126 requisition_stack_pointer = top;
127 return addr;
128];
129
130[ FreeStack addr;
131 if (addr == 0) return;
132 requisition_stack_pointer = (addr - requisition_stack)/WORDSIZE;
133];
WriteListOfMarkedObjects.
This routine will use the MarkedListIterator. That means it has to create an array containing the object numbers of every object with the workflag2 attribute set, placing the address of this array in MarkedObjectArray and the length in MarkedObjectLength. Note that we preserve any existing marked list on the stack (using the assembly-language instructions @push and @pull) for the duration of our use.
While the final order of this list will depend on what it looks like after coalescing, the initial order is also important. If all of the objects have a common parent in the object tree, then we coalesce those objects and place the list in object tree order. But if not, we place the list in object number order (which is essentially the order of traversal of the initial state of the object tree: thus objects in Room A will all appear before objects in Room B if A was created before B in the source text).
152Global MarkedObjectArray = 0;
153Global MarkedObjectLength = 0;
154
155[ WriteListOfMarkedObjects style
156 obj common_parent first mixed_parentage length g gc;
157
158 gc = -2;
159 objectloop (obj ofclass Object && obj has workflag2) {
160 length++;
161 if (first == nothing) { first = obj; common_parent = parent(obj); }
162 else { if (parent(obj) ~= common_parent) mixed_parentage = true; }
163 g = GetGNAOfObject(obj); g = g%3;
164 if (gc == -2) gc = g;
165 else if (gc ~= g) gc = -1;
166 }
167 if (mixed_parentage) common_parent = nothing;
168
169 if (length == 0) {
170 if (style & ISARE_BIT ~= 0) LIST_WRITER_INTERNAL_RM('W');
171 else if (style & CFIRSTART_BIT ~= 0) LIST_WRITER_INTERNAL_RM('X');
172 else LIST_WRITER_INTERNAL_RM('Y');
173 } else {
174 @push MarkedObjectArray; @push MarkedObjectLength;
175 MarkedObjectArray = RequisitionStack(length);
176 MarkedObjectLength = length;
177 if (MarkedObjectArray == 0) return RunTimeProblem(RTP_LISTWRITERMEMORY);
178
179 if (common_parent) {
180 ObjectTreeCoalesce(child(common_parent));
181 length = 0;
182 objectloop (obj in common_parent)
183 if (obj has workflag2) MarkedObjectArray-->length++ = obj;
184 } else {
185 length = 0;
186 objectloop (obj ofclass Object)
187 if (obj has workflag2) MarkedObjectArray-->length++ = obj;
188 }
189
190 WriteListFrom(first, style, 0, false, MarkedListIterator);
191
192 FreeStack(MarkedObjectArray);
193 @pull MarkedObjectLength; @pull MarkedObjectArray;
194 }
195 prior_named_list = length;
196 prior_named_list_gender = gc;
197 return;
198];
List Number and Gender.
As a brief parenthesis, the same algorithm can be used to work out the prior named list number and gender for everything matching a description.
205[ RegardingMarkedObjects
206 obj length g gc;
207 gc = -2;
208 objectloop (obj ofclass Object && obj has workflag2) {
209 length++;
210 g = GetGNAOfObject(obj); g = g%3;
211 if (gc == -2) {
212 gc = g;
213 prior_named_noun = obj;
214 } else if (gc ~= g) gc = -1;
215 }
216 prior_named_list = length;
217 prior_named_list_gender = gc;
218 if (length == 0) { prior_named_noun = nothing; prior_named_list_gender = -1; }
219 return;
220];
221
222[ RegardingSingleObject obj;
223 prior_named_list = 1;
224 prior_named_list_gender = -1;
225 prior_named_noun = obj;
226];
227
228[ RegardingNumber n;
229 prior_named_list = n;
230 prior_named_list_gender = -1;
231 prior_named_noun = nothing;
232];
233
234[ PNToVP gna;
235 if (prior_named_noun == player) return story_viewpoint;
236 if (prior_named_noun) gna = GetGNAOfObject(prior_named_noun);
237 if (((gna%6)/3 == 1) || (prior_named_list >= 2)) return 6;
238 return 3;
239];
240
241[ PrintVerbAsValue vb;
242 if (vb == 0) print "(no verb)";
243 else { print "verb "; vb(1); }
244];
245
246[ VerbIsMeaningful vb;
247 if ((vb) && (BlkValueCompare(vb(CV_MEANING), Rel_Record_0) ~= 0)) rtrue;
248 rfalse;
249];
250
251[ VerbIsModal vb;
252 if ((vb) && (vb(CV_MODAL))) rtrue;
253 rfalse;
254];
List Writer Regard Storage.
This is somewhat hacky, but enables "[regarding list writer internals]".
260Array LWI_Storage --> 1 (-1) nothing;
261[ SetLWI a b c;
262 LWI_Storage-->0 = a;
263 LWI_Storage-->1 = b;
264 LWI_Storage-->2 = c;
265];
266[ RegardingLWI;
267 prior_named_list = LWI_Storage-->0;
268 prior_named_list_gender = LWI_Storage-->1;
269 prior_named_noun = LWI_Storage-->2;
270];
Response Printing.
From which:
276[ ResponseViaActivity R;
277 @push prior_named_noun; @push prior_named_list; @push prior_named_list_gender;
278 RegardingSingleObject(nothing);
279 CarryOutActivity(PRINTING_RESPONSE_ACT, R);
280 @pull prior_named_list_gender; @pull prior_named_list; @pull prior_named_noun;
281];
About Iterator Functions.
Suppose Iter is an iterator function and that we have a "raw list" x1, x2, ..., xn of objects. Of these, the iterator function will choose a sublist of "qualifying" objects. It is called with arguments
Iter(obj, depth, L, function)
where the obj is required to be xj for some j and the function is one of the *_ITF constants defined below:
(a) On START_ITF, we return x1, or nothing if the list is empty.
(b) On SEEK_ITF, we return the smallest k ≥ j such that xk qualifies, or nothing if none of xj, xj+1, ..., xn qualify.
(c) On ADVANCE_ITF, we return the smallest k > j such that xk qualifies, or nothing if none of xj+1, xj+2 ..., xn qualify.
(d) On COALESCE_ITF, we coalesce the entire list (not merely the qualifying entries) and return the new x1, or nothing if the list is empty.
Thus, given any xi, we can produce the sublist of qualifying entries by performing START_ITF on xi, then SEEK_ITF, to produce q1; then ADVANCE_ITF to produce q2, and so on until nothing is returned, when there are no more qualifying entries. SEEK_ITF and ADVANCE_ITF always return qualifying objects, or nothing; but START_ITF and COALESCE_ITF may return a non-qualifying object, since there's no reason why x1 should qualify in any ordering.
The iterator can make its own choice of which entries qualify, and of what the raw list is; depth is supplied to help in that decision. But if L is non-zero then the iterator is required to disqualify any entry whose list_together value is not L.
318Constant SEEK_ITF = 0;
319Constant ADVANCE_ITF = 1;
320Constant COALESCE_ITF = 2;
321Constant START_ITF = 3;
322
323
Marked List Iterator.
Here the raw list is provided by the MarkedObjectArray, which is convenient for coalescing, but not so helpful for translating the obj parameter into the i such that it is xi. We simply search from the beginning to do this, which combined with other code using the iterator makes for some unnecessary O(n2) calculations. But it saves memory and nuisance, and the iterator is used only with printing to the screen, which never needs to be very rapid anyway (because the player can only read very slowly).
335[ MarkedListIterator obj depth required_lt function i;
336 if (obj == nothing) return nothing;
337 if (required_lt == 0) required_lt = EMPTY_TEXT_VALUE;
338 switch(function) {
339 START_ITF: return MarkedObjectArray-->0;
340 COALESCE_ITF: return MarkedListCoalesce();
341 SEEK_ITF, ADVANCE_ITF:
342 for (i=0: i<MarkedObjectLength: i++)
343 if (MarkedObjectArray-->i == obj) {
344 if (function == ADVANCE_ITF) i++;
345 for (:i<MarkedObjectLength: i++) {
346 obj = MarkedObjectArray-->i;
347 if ((LT_Compare(required_lt, EMPTY_TEXT_VALUE) ~= 0) &&
348 (LT_Compare(obj.list_together, required_lt) ~= 0)) continue;
349 if ((c_style & WORKFLAG_BIT) && (depth==0) && (obj hasnt workflag))
350 continue;
351 if ((c_style & CONCEAL_BIT) && (ConcealedFromLists(obj))) continue;
352 return obj;
353 }
354 return nothing;
355 }
356 }
357 return nothing;
358];
Concealment.
This is the definition of concealment used by the list-writer, and means that, for example, the "deciding the concealed possessions" activity is taken into account.
366[ ConcealedFromLists obj c;
367 if ((obj has concealed) || (obj has scenery)) rtrue;
368 c = parent(obj);
369 if ((c) && (c ofclass K5_container or K6_supporter) && (TestConcealment(c, obj))) rtrue;
370 rfalse;
371];
Coalesce Marked List.
The return value is the new first entry in the raw list.
377[ MarkedListCoalesce o i lt l swap m;
378 for (i=0: i<MarkedObjectLength: i++) {
379 lt = (MarkedObjectArray-->i).list_together;
380 if (LT_Compare(lt, EMPTY_TEXT_VALUE) ~= 0) {
381
382 for (i++: (i<MarkedObjectLength) &&
383 (LT_Compare((MarkedObjectArray-->i).list_together, lt) == 0): i++) ;
384
385 if (i == MarkedObjectLength) return MarkedObjectArray-->0;
386
387 for (l=i+1: l<MarkedObjectLength: l++)
388 if (LT_Compare((MarkedObjectArray-->l).list_together, lt) == 0) {
389
390 swap = MarkedObjectArray-->l;
391 for (m=l: m>i: m--) MarkedObjectArray-->m = MarkedObjectArray-->(m-1);
392 MarkedObjectArray-->i = swap;
393
394 i++;
395 if (i == MarkedObjectLength) return MarkedObjectArray-->0;
396 }
397 i--;
398 }
399 }
400 return MarkedObjectArray-->0;
401];
Object Tree Iterator.
Here the raw list is the list of all children of a given parent: since the argument obj is required to be a member of the raw list, we can use parent to find it. Now seeking and advancing are fast, but coalescing is slower.
410Global list_filter_routine;
411
412[ ObjectTreeIterator obj depth required_lt function;
413 if ((obj == nothing) || (parent(obj) == nothing)) return nothing;
414 if (function == START_ITF) obj = child(parent(obj));
415 if (function == COALESCE_ITF) return ObjectTreeCoalesce(obj);
416 if (function == ADVANCE_ITF) obj = sibling(obj);
417 if (required_lt == 0) required_lt = EMPTY_TEXT_VALUE;
418 for (:: obj = sibling(obj)) {
419 if (obj == nothing) return nothing;
420
421 if ((LT_Compare(required_lt, EMPTY_TEXT_VALUE) ~= 0) &&
422 (LT_Compare(obj.list_together, required_lt) ~= 0)) continue;
423 if ((c_style & WORKFLAG_BIT) && (depth==0) && (obj hasnt workflag)) continue;
424 if (obj hasnt list_filter_permits) continue;
425 if ((c_style & CONCEAL_BIT) && (ConcealedFromLists(obj))) continue;
426 return obj;
427 }
428];
Coalesce Object Tree.
Again, the return value is the new first entry in the raw list.
434[ ObjectTreeCoalesce obj memb lt later;
435 #Ifdef DBLW; print "^^Sorting out: "; DiagnoseSortList(obj); #Endif;
436 .StartAgain;
437 for (memb=obj: memb~=nothing: memb=sibling(memb)) {
438 lt = memb.list_together;
439 if (LT_Compare(lt, EMPTY_TEXT_VALUE) ~= 0) {
440
441 for (memb=sibling(memb):
442 (memb) && (LT_Compare(memb.list_together, lt) == 0): memb = sibling(memb)) ;
443
444 if (memb == 0) return obj;
445
446 for (later=sibling(memb): later: later=sibling(later))
447 if (LT_Compare(later.list_together, lt) == 0) {
448
449 obj = GroupChildren(parent(obj), lt);
450 #Ifdef DBLW; print "^^Sorted to: "; DiagnoseSortList(obj); #Endif;
451 jump StartAgain;
452 }
453 }
454 }
455 return obj;
456];
457#Ifdef DBLW;
458[ DiagnoseSortList obj memb;
459 for (memb=child(obj): memb~=nothing: memb=sibling(memb)) print memb, " --> "; new_line;
460];
461#Endif;
WriteListFrom.
And here we go at last. Or at any rate we initialise the quartet of global variables detailing the current list-writing process, and begin.
468[ WriteListFrom first style depth noactivity iter a ol;
469 @push c_iterator; @push c_style; @push c_depth; @push c_margin;
470 if (iter) c_iterator = iter; else c_iterator = ObjectTreeIterator;
471 c_style = style; c_depth = depth;
472 c_margin = 0; if (style & EXTRAINDENT_BIT) c_margin = 1;
473
474 objectloop (a ofclass Object) {
475 give a list_filter_permits;
476 if ((list_filter_routine) && (list_filter_routine(a) == false))
477 give a ~list_filter_permits;
478 }
479
480 first = c_iterator(first, depth, 0, START_ITF);
481 if (first == nothing) {
482 if (style & ISARE_BIT ~= 0) LIST_WRITER_INTERNAL_RM('W');
483 else LIST_WRITER_INTERNAL_RM('Y');
484 if (style & NEWLINE_BIT ~= 0) new_line;
485 } else {
486 if ((noactivity) || (iter)) {
487 WriteListR(first, c_depth, true);
488 say__p = 1;
489 } else {
490 objectloop (ol provides list_together)
491 BlkValueCopy(ol.list_together, EMPTY_TEXT_VALUE);
492 CarryOutActivity(LISTING_CONTENTS_ACT, parent(first));
493 }
494 }
495
496 @pull c_margin; @pull c_depth; @pull c_style; @pull c_iterator;
497];
Standard Contents Listing Rule.
The default for the listing contents activity is to call this rule in its "for" stage: note that this suppresses the use of the activity, to avoid an infinite regress. The activity is used only for the default ObjectTreeIterator, so there is no need to specify which is used.
506[ STANDARD_CONTENTS_LISTING_R;
507 WriteListFrom(child(parameter_value), c_style, c_depth, true);
508];
Partitioning.
Given qualifying objects x1, ..., xj, we partition them into classes of the equivalence relation xi ~ xj if and only
(i) they both have a plural property (not necessarily the same), and (ii) neither will cause the list-maker to recurse downwards to show the objects inside or on top of them, and (iii) if they cause the list-maker to add information about whether they are worn, lighted or open, then it will add the same information about both, and (iv) they are considered identical by the parser, in that they respond to the same syntaxes of name: so that it is impossible to find a TAKE X command such that X matches one and not the other.
The equivalence classes are numbered consecutively upwards from 1 to n, in order of first appearance in the list. For each object xi, partition_classes-> is the number of its equivalence class. For each equivalence class number c, partition_class_sizes->c is the number of objects in this class.
530#Ifdef DBLW;
531Global DBLW_no_classes; Global DBLW_no_objs;
532[ DebugPartition partition_class_sizes partition_classes first depth i k o;
533 print "[Length of list is ", DBLW_no_objs, " with ", k, " plural.]^";
534 print "[Partitioned into ", DBLW_no_classes, " equivalence classes.]^";
535 for (i=1: i<=DBLW_no_classes : i++) {
536 print "Class ", i, " has size ", partition_class_sizes->i, "^";
537 }
538 for (k=0, o=first: k<DBLW_no_objs : k++, o = c_iterator(o, depth, lt_value, ADVANCE_ITF)) {
539 print "Entry ", k, " has class ", partition_classes->k,
540 " represented by ", o, " with L=", o.list_together, "^";
541 }
542];
543#Endif;
Partition List.
The following creates the partition_classes and partition_class_sizes accordingly. We return n, the number of classes.
550[ PartitionList first no_objs depth partition_classes partition_class_sizes
551 i k l n m;
552 for (i=0: i<no_objs: i++) partition_classes->i = 0;
553 n = 1;
554 for (i=first, k=0: k<no_objs: i=c_iterator(i, depth, lt_value, ADVANCE_ITF), k++)
555 if (partition_classes->k == 0) {
556 partition_classes->k = n; partition_class_sizes->n = 1;
557 for (l=c_iterator(i, depth, lt_value, ADVANCE_ITF), m=k+1:
558 (l~=0) && (m<no_objs):
559 l=c_iterator(l, depth, lt_value, ADVANCE_ITF), m++) {
560 if ((partition_classes->m == 0) && (ListEqual(i, l))) {
561 if (partition_class_sizes->n < 255) (partition_class_sizes->n)++;
562 partition_classes->m = n;
563 }
564 }
565 if (n < 255) n++;
566 }
567 n--;
568 #Ifdef DBLW;
569 DBLW_no_classes = n; DBLW_no_objs = no_objs;
570 DebugPartition(partition_class_sizes, partition_classes, first, depth);
571 #Endif;
572 return n;
573];
Equivalence Relation.
The above algorithm will fail unless ListEqual is indeed reflexive, symmetric and transitive, which ultimately depends on the care with which Identical is implemented, which in turn hangs on the parse_noun properties compiled by NI. But this seems to be sound.
582[ ListEqual o1 o2;
583 if ((o1.plural == 0) || (o2.plural == 0)) rfalse;
584 if (child(o1) ~= 0 && WillRecurs(o1) ~= 0) rfalse;
585 if (child(o2) ~= 0 && WillRecurs(o2) ~= 0) rfalse;
586 if (c_style & (FULLINV_BIT + PARTINV_BIT) ~= 0) {
587 if ((o1 hasnt worn && o2 has worn) || (o2 hasnt worn && o1 has worn)) rfalse;
588 if ((o1 hasnt light && o2 has light) || (o2 hasnt light && o1 has light)) rfalse;
589 if (o1 has container) {
590 if (o2 hasnt container) rfalse;
591 if ((o1 has open && o2 hasnt open) || (o2 has open && o1 hasnt open))
592 rfalse;
593 }
594 else if (o2 has container)
595 rfalse;
596 }
597 return Identical(o1, o2);
598];
599
600[ WillRecurs o;
601 if (c_style & ALWAYS_BIT ~= 0) rtrue;
602 if (c_style & RECURSE_BIT == 0) rfalse;
603 if ((o has supporter) || ((o has container) && (o has open or transparent))) rtrue;
604 rfalse;
605];
Grouping.
A "group" is a maximally-sized run of one or more adjacent partition classes in the list whose first members have a common value of list_together which is a routine or string, and which is not equal to lt_value, the current grouping value. (As we see below, it's by setting lt_value that we restrict attention to a particular group: if we reacted to that as a list_together value here, then we would simply go around in circles, and never be able to see the individual objects in the group.)
For instance, suppose we have objects with list_together values as follows, where R1 and R2 are addresses of routines:
coin (R1), coin (R1), box (R2), statuette (0), coin (R1), box (R2)
Then the partition is 1, 1, 2, 3, 1, 2, so that the final output will be something like "three coins, two boxes and a statuette" – with three grouped terms. Here each partition class is the only member in its group, so the number of groups is the same as the number of classes. (The above list is not coalesced, so the R1 values, for instance, are not contiguous: we need to work in general with non-coalesced lists because not every iterator function will be able to coalesce fully.)
But if we have something like this:
coin (R1), Q (R2), W (R2), coin (R1), statuette (0), E (R2), R (R2)
then the partition is 1, 2, 3, 1, 4, 5, 6 and we have six classes in all. But classes 2 and 3 are grouped together, as are classes 5 and 6, so we end up with a list having just four groups: "two coins, the letters Q and W from a Scrabble set, a statuette and the letters E and R from a Scrabble set". (Again, this list has not been fully coalesced: if it had been, it would be reordered coin, coin, Q, W, E, R, statuette, with partition 1, 1, 2, 3, 4, 5, 6, and three groups: "two coins, the letters Q, W, E and R from a Scrabble set and a statuette".)
The reason we do not group together classes with a common non-zero list_together which is not a routine or string is that low values of list_together are used in coalescing the list into a pleasing order (say, moving all of the key-like items together) but not in grouping: see list_together in the Inform Designer's Manual, 4th edition.
We calculate the number of groups by starting with the number of classes and then subtracting one each time two adjacent classes share list_together in the above sense.
653[ NumberOfGroupsInList o no_classes depth partition_classes partition_class_sizes
654 no_groups cl memb k current_lt lt;
655 current_lt = EMPTY_TEXT_VALUE;
656 lt = EMPTY_TEXT_VALUE;
657 no_groups = no_classes;
658 for (cl=1, memb=o, k=0: cl<=no_classes: cl++) {
659
660 while (partition_classes->k ~= cl) {
661 k++; memb = c_iterator(memb, depth, lt_value, ADVANCE_ITF);
662 }
663 if (memb) {
664 lt = memb.list_together;
665 if ((LT_Compare(lt, lt_value) ~= 0) &&
666 (LT_Compare(lt, EMPTY_TEXT_VALUE) ~= 0) &&
667 (LT_Compare(lt, current_lt) == 0)) {
668 no_groups--;
669 }
670 current_lt = lt;
671 }
672 }
673 #Ifdef DBLW; print "[There are ", no_groups, " groups.]^"; #Endif;
674 return no_groups;
675];
676
677[ LT_Compare lt1 lt2;
678 if (lt1 == lt2) return 0;
679 if (lt1 == 0) lt1 = EMPTY_TEXT_VALUE;
680 if (lt2 == 0) lt2 = EMPTY_TEXT_VALUE;
681 if (TEXT_TY_IsSubstituted(lt1) == false) {
682 if (TEXT_TY_IsSubstituted(lt2) == false) return (lt1-->1)-(lt2-->1);
683 return -1;
684 }
685 if (TEXT_TY_IsSubstituted(lt2) == false) {
686 return -1;
687 }
688 return BlkValueCompare(lt1, lt2);
689];
Write List Recursively.
The big one: WriteListR is the heart of the list-writer.
695[ WriteListR o depth from_start
696 partition_classes partition_class_sizes
697 cl memb index k2 l m no_classes q groups_to_do current_lt;
698 if (o == nothing) return;
699
700 if (from_start) {
701 o = c_iterator(o, depth, 0, COALESCE_ITF);
702 }
703 o = c_iterator(o, depth, 0, SEEK_ITF);
704 if (o == nothing) return;
705
706
707 for (memb=o, index=0: memb: memb=c_iterator(memb, depth, lt_value, ADVANCE_ITF)) index++;
708
709 if (c_style & ISARE_BIT ~= 0) {
710 SetLWI(index, -1, o);
711 LIST_WRITER_INTERNAL_RM('V', o);
712 if (c_style & NEWLINE_BIT ~= 0) print ":^";
713 else print (char) ' ';
714 c_style = c_style - ISARE_BIT;
715 }
716
717 partition_classes = RequisitionStack(index/WORDSIZE + 2);
718 partition_class_sizes = RequisitionStack(index/WORDSIZE + 2);
719 if ((partition_classes == 0) || (partition_class_sizes == 0))
720 return RunTimeProblem(RTP_LISTWRITERMEMORY);
721
722 no_classes =
723 PartitionList(o, index, depth, partition_classes, partition_class_sizes);
724
725 groups_to_do =
726 NumberOfGroupsInList(o, no_classes, depth, partition_classes, partition_class_sizes);
727
728 for (cl=1, memb=o, index=0, current_lt=EMPTY_TEXT_VALUE: groups_to_do>0: cl++) {
729
730 while (partition_classes->index ~= cl) {
731 index++; memb=c_iterator(memb, depth, lt_value, ADVANCE_ITF);
732 if (memb==0) { print "*** Error in list-writer ***^"; return; }
733 }
734
735 #Ifdef DBLW;
736
737 print "^[Class ", cl, " of ", no_classes, ": first object ", memb,
738 " (", memb.list_together, "); groups_to_do ", groups_to_do, ",
739 current_lt=", current_lt, " listing_size=", listing_size,
740 " lt_value=", lt_value, " memb.list_together=", memb.list_together, "]^";
741 #Endif;
742
743 if ((LT_Compare(memb.list_together, lt_value) == 0) ||
744 (LT_Compare(memb.list_together, EMPTY_TEXT_VALUE) == 0)) current_lt = EMPTY_TEXT_VALUE;
745 else {
746 if (LT_Compare(memb.list_together, current_lt) == 0) continue;
747
748
749 @push listing_size;
750 q = memb; listing_size = 1; l = index; m = cl;
751 while (m < no_classes &&
752 (LT_Compare(q.list_together, memb.list_together) == 0)) {
753 m++;
754 while (partition_classes->l ~= m) {
755 l++; q = c_iterator(q, depth, lt_value, ADVANCE_ITF);
756 }
757 if (LT_Compare(q.list_together, memb.list_together) == 0)
758 listing_size++;
759 }
760
761 if (listing_size > 1) {
762
763 WriteMultiClassGroup(cl, memb, depth, partition_class_sizes);
764 current_lt = memb.list_together;
765 jump GroupComplete;
766 }
767 current_lt = EMPTY_TEXT_VALUE;
768 @pull listing_size;
769 }
770
771 WriteSingleClassGroup(cl, memb, depth, partition_class_sizes->cl);
772
773 .GroupComplete;
774 groups_to_do--;
775 if (c_style & ENGLISH_BIT ~= 0) {
776 if (groups_to_do == 1) {
777 #ifdef SERIAL_COMMA; if (cl > 1) print ","; #endif;
778 LIST_WRITER_INTERNAL_RM('C');
779 }
780 if (groups_to_do > 1) print ", ";
781 }
782 }
783
784 FreeStack(partition_class_sizes);
785 FreeStack(partition_classes);
786];
Write Multiple Class Group.
The text of a single group which contains more than one partition class. We carry out the "grouping together" activity, so that the user can add text fore and aft – this is how groups of objects such as "X, Y and Z" can be fluffed up to "the letters X, Y and Z from a Scrabble set" – but the default is simple: we print the grouped items by calling WriteListR once again, but this time starting from X, and where lt_value is set to the common list_together value of X, Y and Z. (That restricts the list to just the objects in this group.) Because lt_value is set to this value, the grouping code won't then group X, Y and Z again, and they will instead be individual classes in the new list – so each will end up being sent, in turn, to WriteSingleClassGroup below, and that is where they are printed.
803[ WriteMultiClassGroup cl memb depth partition_class_sizes pv q k2 l;
804
805 q = c_style;
806 if (c_style & INDENT_BIT ~= 0) PrintSpaces(2*(depth+c_margin));
807
808 BeginActivity(GROUPING_TOGETHER_ACT, memb);
809
810 if (ForActivity(GROUPING_TOGETHER_ACT, memb)) {
811 c_style = c_style &~ NEWLINE_BIT;
812 } else {
813 pv = memb.list_together;
814 if (TEXT_TY_IsSubstituted(pv) == false) {
815 inventory_stage = 1;
816 parser_one = memb; parser_two = depth + c_margin;
817 if ((pv-->1)() == 1) jump Omit__Sublist2;
818 } else if (pv) {
819
820 k2 = 0;
821 for (l=0 : l<listing_size : l++) k2 = k2 + partition_class_sizes->(l+cl);
822 EnglishNumber(k2); print " ";
823 print (TEXT_TY_Say) pv;
824 if (c_style & ENGLISH_BIT ~= 0) print " (";
825 if (c_style & INDENT_BIT ~= 0) print ":^";
826 }
827
828 c_margin++;
829 @push lt_value; @push listing_together; @push listing_size;
830
831 lt_value = memb.list_together; listing_together = memb;
832 #Ifdef DBLW; print "^^DOWN lt_value = ", lt_value, " listing_together = ", memb, "^^";
833 @push DBLW_no_classes; @push DBLW_no_objs; #Endif;
834 WriteListR(memb, depth, false);
835 #Ifdef DBLW; print "^^UP^^"; @pull DBLW_no_objs; @pull DBLW_no_classes; #Endif;
836
837 @pull listing_size; @pull listing_together; @pull lt_value;
838 c_margin--;
839
840 pv = memb.list_together;
841 if (TEXT_TY_IsSubstituted(pv) == false) {
842 inventory_stage = 2;
843 parser_one = memb; parser_two = depth+c_margin;
844 (pv-->1)();
845 } else if (LT_Compare(pv, EMPTY_TEXT_VALUE) ~= 0) {
846 if (q & ENGLISH_BIT ~= 0) print ")";
847 }
848 .Omit__Sublist2;
849 }
850
851 EndActivity(GROUPING_TOGETHER_ACT, memb);
852
853
854
855 if (q & NEWLINE_BIT ~= 0 && c_style & NEWLINE_BIT == 0) new_line;
856
857
858 c_style = q;
859];
Write Single Class Group.
The text of a single group which contains exactly one partition class. Because of the way the multiple-class case recurses, every class ends up in this routine sooner or later; this is the place where the actual name of an object is printed, at long last.
868[ WriteSingleClassGroup cl memb depth size q;
869 q = c_style;
870 if (c_style & INDENT_BIT) PrintSpaces(2*(depth+c_margin));
871 if (size == 1) {
872 if (c_style & NOARTICLE_BIT ~= 0) print (name) memb;
873 else {
874 if (c_style & DEFART_BIT) {
875 if ((cl == 1) && (c_style & CFIRSTART_BIT)) print (The) memb;
876 else print (the) memb;
877 } else {
878 if ((cl == 1) && (c_style & CFIRSTART_BIT)) print (CIndefArt) memb;
879 else print (a) memb;
880 }
881 }
882 } else {
883 if (c_style & DEFART_BIT) {
884 if ((cl == 1) && (c_style & CFIRSTART_BIT)) PrefaceByArticle(memb, 0, size);
885 else PrefaceByArticle(memb, 1, size);
886 }
887 @push listing_size; listing_size = size;
888 CarryOutActivity(PRINTING_A_NUMBER_OF_ACT, memb);
889 @pull listing_size;
890 }
891 if ((size > 1) && (memb hasnt pluralname)) {
892 give memb pluralname;
893 WriteAfterEntry(memb, depth);
894 give memb ~pluralname;
895 } else WriteAfterEntry(memb, depth);
896 c_style = q;
897];
Write After Entry.
Each entry can be followed by supplementary, usually parenthetical, information: exactly what, depends on the style. The extreme case is when the style, and the object, call for recursion to list the object-tree contents: this is achieved by calling WriteListR, using the ObjectTreeIterator (whatever the iterator used at the top level) and increasing the depth by 1.
907[ WriteAfterEntry o depth
908 p recurse_flag parenth_flag eldest_child child_count combo;
909
910 inventory_stage = 2;
911 if (c_style & PARTINV_BIT) {
912 BeginActivity(PRINTING_ROOM_DESC_DETAILS_ACT, o);
913 if (ForActivity(PRINTING_ROOM_DESC_DETAILS_ACT, o) == false) {
914 combo = 0;
915 if (o has light && location hasnt light) combo=combo+1;
916 if (o has container && o hasnt open) combo=combo+2;
917 if ((o has container && (o has open || o has transparent))
918 && (child(o)==0)) combo=combo+4;
919 if (combo) LIST_WRITER_INTERNAL_RM('A');
920 switch (combo) {
921 1: LIST_WRITER_INTERNAL_RM('D', o);
922 2: LIST_WRITER_INTERNAL_RM('E', o);
923 3: LIST_WRITER_INTERNAL_RM('H', o);
924 4: LIST_WRITER_INTERNAL_RM('F', o);
925 5: LIST_WRITER_INTERNAL_RM('I', o);
926 6: LIST_WRITER_INTERNAL_RM('G', o);
927 7: LIST_WRITER_INTERNAL_RM('J', o);
928 }
929 if (combo) LIST_WRITER_INTERNAL_RM('B');
930 }
931 EndActivity(PRINTING_ROOM_DESC_DETAILS_ACT, o);
932 }
933
934 if (c_style & FULLINV_BIT) {
935 BeginActivity(PRINTING_INVENTORY_DETAILS_ACT, o);
936 if (ForActivity(PRINTING_INVENTORY_DETAILS_ACT, o) == false) {
937 if (o has light && o has worn) { LIST_WRITER_INTERNAL_RM('A'); LIST_WRITER_INTERNAL_RM('K', o); parenth_flag = true; }
938 else {
939 if (o has light) { LIST_WRITER_INTERNAL_RM('A'); LIST_WRITER_INTERNAL_RM('D', o); parenth_flag = true; }
940 if (o has worn) { LIST_WRITER_INTERNAL_RM('A'); LIST_WRITER_INTERNAL_RM('L', o); parenth_flag = true; }
941 }
942
943 if (o has container)
944 if (o has openable) {
945 if (parenth_flag) {
946 #Ifdef SERIAL_COMMA; print ","; #Endif;
947 LIST_WRITER_INTERNAL_RM('C');
948 } else LIST_WRITER_INTERNAL_RM('A', o);
949 if (o has open)
950 if (child(o)) LIST_WRITER_INTERNAL_RM('M', o);
951 else LIST_WRITER_INTERNAL_RM('N', o);
952 else
953 if (o has lockable && o has locked) LIST_WRITER_INTERNAL_RM('P', o);
954 else LIST_WRITER_INTERNAL_RM('O', o);
955 parenth_flag = true;
956 }
957 else
958 if (child(o)==0 && o has transparent)
959 if (parenth_flag) { LIST_WRITER_INTERNAL_RM('C'); LIST_WRITER_INTERNAL_RM('F'); }
960 else { LIST_WRITER_INTERNAL_RM('A'); LIST_WRITER_INTERNAL_RM('F'); LIST_WRITER_INTERNAL_RM('B'); }
961
962 if (parenth_flag) LIST_WRITER_INTERNAL_RM('B');
963 }
964 EndActivity(PRINTING_INVENTORY_DETAILS_ACT, o);
965 }
966
967 child_count = 0;
968 eldest_child = nothing;
969 objectloop (p in o)
970 if ((c_style & CONCEAL_BIT == 0) || (ConcealedFromLists(p) == false))
971 if (p has list_filter_permits) {
972 child_count++;
973 if (eldest_child == nothing) eldest_child = p;
974 }
975
976 if (child_count && (c_style & ALWAYS_BIT)) {
977 if (c_style & ENGLISH_BIT) { print " "; LIST_WRITER_INTERNAL_RM('Q', o); print " "; }
978 recurse_flag = true;
979 }
980
981 if (child_count && (c_style & RECURSE_BIT)) {
982 if (o has supporter) {
983 if (c_style & ENGLISH_BIT) {
984 if (c_style & TERSE_BIT) {
985 LIST_WRITER_INTERNAL_RM('A', o);
986 LIST_WRITER_INTERNAL_RM('R', o);
987 } else LIST_WRITER_INTERNAL_RM('S', o);
988 }
989 recurse_flag = true;
990 }
991 if (o has container && (o has open || o has transparent)) {
992 if (c_style & ENGLISH_BIT) {
993 if (c_style & TERSE_BIT) {
994 LIST_WRITER_INTERNAL_RM('A', o);
995 LIST_WRITER_INTERNAL_RM('T', o);
996 } else LIST_WRITER_INTERNAL_RM('U', o);
997 }
998 recurse_flag = true;
999 }
1000 }
1001
1002 if (recurse_flag && (c_style & ENGLISH_BIT)) {
1003 SetLWI(child_count, -1, eldest_child);
1004 LIST_WRITER_INTERNAL_RM('V', o); print " ";
1005 }
1006
1007 if (c_style & NEWLINE_BIT) new_line;
1008
1009 if (recurse_flag) {
1010 o = child(o);
1011 @push lt_value; @push listing_together; @push listing_size;
1012 @push c_iterator;
1013 c_iterator = ObjectTreeIterator;
1014 lt_value = EMPTY_TEXT_VALUE; listing_together = 0; listing_size = 0;
1015 WriteListR(o, depth+1, true);
1016 @pull c_iterator;
1017 @pull listing_size; @pull listing_together; @pull lt_value;
1018 if (c_style & TERSE_BIT) LIST_WRITER_INTERNAL_RM('B');
1019 }
1020];
Internal Rule.
This rule does nothing in itself; it exists as a placeholder for the response texts used by the list-writer.
1027[ LIST_WRITER_INTERNAL_R;
1028];