Example 2: Complex text substitutions backed by special-purpose to say phrases
This pattern pops up with some regularity as authors try to build reactive descriptions: The contents of a description or initial property are linked to a to say phrase with logic to vary the text produced.
Field of Flowers is a room. "A vast field of flowers, extending to the horizon in all directions."
A neuter animal called a butterfly is here. "[butterfly business]"
The butterfly has a number called business counter. The business counter of the butterfly is 1.
To say butterfly business:
if business counter of butterfly is:
-- 1: say "A butterfly dances in the breeze here.";
-- 2: say "The butterfly alights briefly on one of the flowers, slowing flexing its wings.";
-- 3: say "The butterfly leaps into the air again, fluttering about.";
unless expanding text for comparison purposes:
increment business counter of butterfly;
if business counter of butterfly is at least 4, now business counter of butterfly is one.
This yields output:
Field of Flowers
A vast field of flowers, extending to the horizon in all directions.
A butterfly dances in the breeze here.
>l
Field of Flowers
A vast field of flowers, extending to the horizon in all directions.
The butterfly alights briefly on one of the flowers, slowing flexing its wings.
>l
Field of Flowers
A vast field of flowers, extending to the horizon in all directions.
The butterfly leaps into the air again, fluttering about.
>l
Field of Flowers
A vast field of flowers, extending to the horizon in all directions.
A butterfly dances in the breeze here.
>
Aside from the fact that there are quite a few snares around this pattern due to nuances of the way Inform works under the hood, the example above basically works as naively expected except for the presence of extra line breaks before the command prompt.
There’s a hidden part of the puzzle in the Standard Rules:
For printing a locale paragraph about a thing (called the item)
(this is the use initial appearance in room descriptions rule):
if the item is not mentioned:
if the item provides the property initial appearance and the
item is not handled and the initial appearance of the item is not "":
increase the locale paragraph count by 1;
say "[initial appearance of the item]";
say "[paragraph break]";
if a locale-supportable thing is on the item:
repeat with possibility running through things on the item:
now the possibility is marked for listing;
if the possibility is mentioned:
now the possibility is not marked for listing;
say "On [the item] " (A);
list the contents of the item, as a sentence, including contents,
giving brief inventory information, tersely, not listing
concealed items, prefacing with is/are, listing marked items only;
say ".[paragraph break]";
now the item is mentioned;
continue the activity.
The reason that the extra line breaks occur are:
- The room description machinery tries to
say the initial appearance property of the butterfly. This generates a string with only one segment, the text substitution [butterfly business], which does not register as ending with sentence-ending punctuation.
- The
[butterfly business] text substitution invokes a routine which has its own internal say statements that generate single-segment strings, and these do end with sentence-ending punctuation. These underlying say phrases automatically generate single line breaks when the to say butterfly business phrase is executed, which are not generated when the initial appearance is a simple text string.
- The forced
[paragraph break] in the use initial appearance in room descriptions rule adds another two line breaks, which is one too many.
It’s not possible to unset say__p in a meaningful way, since the say statement requesting a paragraph break will reset it before deciding how many line breaks to generate. Instead, the problem must be addressed at the point where the initial appearance property is being printed.
When a plain text initial appearance is printed in the normal way, sentence-ending punctuation is ignored. (The code just prints the characters in the text property.) However, when a text substitution is invoked, a routine is run, and here the compiler has paid attention to sentence-ending punctuation, and has hardcoded some line breaks as a result:
! Request 46: phrase nothing -> nothing
! To say butterfly business:
[ PHR_799_r46 ;
! [2: if business counter of butterfly is]
switch (GProperty(10, I126_butterfly,p15_business_counter))
{1:
! [3: say ~A butterfly dances in the breeze here.~]
say__p=1;! [4: ~A butterfly dances in the breeze here.~]
ParaContent(); print "A butterfly dances in the breeze here."; new_line; .L_Say464; .L_SayX462;;
2:
! [5: say ~The butterfly alights briefly on one of the flowers, slowing flexing its wings.~]
say__p=1;! [6: ~The butterfly alights briefly on one of the flowers, slowing flexing its wings.~]
ParaContent(); print "The butterfly alights briefly on one of the flowers, slowing flexing its wings."; new_line; .L_Say465; .L_SayX463;;
3:
! [7: say ~The butterfly leaps into the air again, fluttering about.~]
say__p=1;! [8: ~The butterfly leaps into the air again, fluttering about.~]
ParaContent(); print "The butterfly leaps into the air again, fluttering about."; new_line; .L_Say466; .L_SayX464;}
! [9: unless expanding text for comparison purposes]
if (~~((say__comp)))
{! [10: increment business counter of butterfly]
WriteGProperty(10, I126_butterfly,p15_business_counter,GProperty(10, I126_butterfly,p15_business_counter) + 1);;
! [11: if business counter of butterfly is at least 4]
if (((GProperty(10, I126_butterfly,p15_business_counter) >= 4)))
{! [12: now business counter of butterfly is one]
WriteGProperty(10, I126_butterfly,p15_business_counter,1);
}
}
rfalse;
];
Assuming that the goal is output:
Field of Flowers
A vast field of flowers, extending to the horizon in all directions.
A butterfly dances in the breeze here.
>l
Field of Flowers
A vast field of flowers, extending to the horizon in all directions.
The butterfly alights briefly on one of the flowers, slowing flexing its wings.
>l
Field of Flowers
A vast field of flowers, extending to the horizon in all directions.
The butterfly leaps into the air again, fluttering about.
>
there are some options to make this happen.
Variant A: Suppress line breaks for text output within the special-purpose to say phrase
To say butterfly business:
if business counter of butterfly is:
-- 1: say "A butterfly dances in the breeze here.[no line break]";
-- 2: say "The butterfly alights briefly on one of the flowers, slowing flexing its wings.[run paragraph on]";
-- 3: say "The butterfly leaps into the air again, fluttering about. ";
unless expanding text for comparison purposes:
increment business counter of butterfly;
if business counter of butterfly is at least 4, now business counter of butterfly is one.
This works differently in the different possible say statements. The first and second variations generates strings that end in segments that are text substitutions, substitutions which tell the compiler not to automatically insert a hardcoded new_line; statement even if it normally would. The third variation works because the last character of the segment is a space, not a period, so the paragraph control logic will not insert a new_line; after the command to print the desired text.
You can see how these play out at the I6 level.
! To say butterfly business:
[ PHR_800_r46 ;
! [2: if business counter of butterfly is]
switch (GProperty(10, I126_butterfly,p15_business_counter))
{1:
! [3: say ~A butterfly dances in the breeze here.[no line break]~]
say__p=1;! [4: ~A butterfly dances in the breeze here.~]
ParaContent(); print "A butterfly dances in the breeze here.";! [5: no line break]
ParaContent(); (PHR_350_r2 ()); .L_Say464; .L_SayX462;;
2:
! [6: say ~The butterfly alights briefly on one of the flowers, slowing flexing its wings.[run paragraph on]~]
say__p=1;! [7: ~The butterfly alights briefly on one of the flowers, slowing flexing its wings.~]
ParaContent(); print "The butterfly alights briefly on one of the flowers, slowing flexing its wings.";! [8: run paragraph on]
ParaContent(); RunParagraphOn(); .L_Say465; .L_SayX463;;
3:
! [9: say ~The butterfly leaps into the air again, fluttering about. ~]
say__p=1;! [10: ~The butterfly leaps into the air again, fluttering about. ~]
ParaContent(); print "The butterfly leaps into the air again, fluttering about. "; .L_Say466; .L_SayX464;}
! [11: cancel pending paragraph break]
ClearParagraphing();
! [12: unless expanding text for comparison purposes]
if (~~((say__comp)))
{! [13: increment business counter of butterfly]
WriteGProperty(10, I126_butterfly,p15_business_counter,GProperty(10, I126_butterfly,p15_business_counter) + 1);;
! [14: if business counter of butterfly is at least 4]
if (((GProperty(10, I126_butterfly,p15_business_counter) >= 4)))
{! [15: now business counter of butterfly is one]
WriteGProperty(10, I126_butterfly,p15_business_counter,1);
}
}
rfalse;
];
Variant B: Create a say phrase variant
To simply say (T - text):
say "[T][no line break]".
To say butterfly business:
if business counter of butterfly is:
-- 1: simply say "A butterfly dances in the breeze here.";
-- 2: simply say "The butterfly alights briefly on one of the flowers, slowing flexing its wings.";
-- 3: simply say "The butterfly leaps into the air again, fluttering about.";
unless expanding text for comparison purposes:
increment business counter of butterfly;
if business counter of butterfly is at least 4, now business counter of butterfly is one.
This works by creating a phrase variant which indicates that text printed should not be followed by an automatic line break. The phrase variant automatically appends the [no line break] substitution to whichever text is printed.
The name of the phrase can be varied as the author likes, e.g. To sayx (T - text):. The important thing is to remember to use it inside your special-purpose to say phrases.
Variant C: Modify the Standard Rules to compensate for unexpected behavior
To clear pending paragraph break:
(- say__p = 0; -)
To surreptitiously line break:
(- new_line; -)
To decide whether (T - text) terminates in sentence-ending punctuation:
let N be (the number of characters in T) minus one;
if N is zero, decide no;
let terminating punctuation be {".", "?", "!"};
if character number N in T is "[quotation mark]", decrement N;
if character number N in T is "[apostrophe]", decrement N;
if character number N in T is listed in terminating punctuation, decide yes;
decide no.
For printing a locale paragraph about a thing (called the item)
(this is the alternate use initial appearance in room descriptions rule):
if the item is not mentioned:
if the item provides the property initial appearance and the
item is not handled and the initial appearance of the item is not "":
increase the locale paragraph count by 1;
let IA be the substituted form of "[initial appearance of the item]";
say IA;
if a paragraph break is pending and IA terminates in sentence-ending punctuation:
clear pending paragraph break;
surreptitiously line break;
otherwise:
say paragraph break;
if a locale-supportable thing is on the item:
repeat with possibility running through things on the item:
now the possibility is marked for listing;
if the possibility is mentioned:
now the possibility is not marked for listing;
say "On [the item] " (A);
list the contents of the item, as a sentence, including contents,
giving brief inventory information, tersely, not listing
concealed items, prefacing with is/are, listing marked items only;
say ".[paragraph break]";
now the item is mentioned;
continue the activity.
The alternate use initial appearance in room descriptions rule is listed instead of the use initial appearance in room descriptions rule in the for printing a locale paragraph about rules.
This works by detecting situations in which the undesired extra line break will have been generated by a special-purpose to say phrase embedded in an initial appearance routine, and to use some I6 hacks to get the desired line spacing in such cases. It’s pretty costly, from a computational perspective, but if you don’t care about that it may be your preferred solution.