Nested 'say' rules get rid of bold text--any way around it?

I ran into this problem today with some weird code about bolded text. I have a big moving apparatus and it involves some nested ‘say’ conditions.

If I have this code:

Every turn:
	say "[bold type]hi[roman type][paragraph break]";
	let boldtext be "[bold type]hi[roman type][paragraph break]";
	say boldtext;
	say "[boldtext]";
	let nestedtext be "[boldtext]";
	say nestedtext;
	say "[nestedtext]";

Then the output is:

hi

hi

hi

hi

hi

>

(i.e., the first three are bold, the last two are not)

Does anyone know why the bold only appears on the first three 'hi’s and not the last two?

Edit: In my situation, I was trying to add some stuff to a list. I got around this problem by making a ‘list’ manually (coding in the commas and the ‘and’), so now I don’t really need an answer to this, but I do find it confusing and wonder what’s causing this. I’m in 6M62.

This is an example of code I wanted to work:

To say galexit:
	say "You can go ";
	let L be a list of text;
	repeat with way running through directions: 
		if way is inside:
			next;
		if way is outside:
			next;
		if way is down:
			next;
		let place be the room way from the location;
		let placename be "[place]";
		if place is a room:
			 add "[bold type][way][roman type] to the [placename in lower case]" to L;
	say L;

The bolds don’t show up for me. If I manually ‘say’ things instead of adding them to the list, the bold comes back. But it doesn’t matter if this whole thing is a ‘to say’ phrase like I have it or a straight up 'every turn: say" phrase.

This is what I ended up using. It’s tedious, but works, I only need this in one room, and I prefer to order the directions in this order because it’s a rotating room (I don’t know what order inform defaults to but it’s not this one):

To say galexit:
	say "You can go ";
	let L be a list of text;
	let place be the room north from the location;
	let placename be "[place]";
	if place is a room:
		say "[boldnorth] to the [placename in lower case], " ;
	let place be the room northeast from the location;
	let placename be "[place]";
	if place is a room:
		say "[boldnortheast] to the [placename in lower case], " ;
	let place be the room east from the location;
	let placename be "[place]";
	if place is a room:
		say "[boldeast] to the [placename in lower case], " ;
	let place be the room southeast from the location;
	let placename be "[place]";
	if place is a room:
		say "[boldsoutheast] to the [placename in lower case], " ;
	let place be the room south from the location;
	let placename be "[place]";
	if place is a room:
		say "[boldsouth] to the [placename in lower case], " ;
	let place be the room southwest from the location;
	let placename be "[place]";
	if place is a room:
		say "[boldsouthwest] to the [placename in lower case], " ;
	let place be the room west from the location;
	let placename be "[place]";
	if place is a room:
		say "[boldwest] to the [placename in lower case], " ;
	let place be the room northwest from the location;
	let placename be "[place]";
	if place is a room:
		say "and [boldnorthwest] to the [placename in lower case]" ;

(here [boldnorth] and so on are short for [bold type]north[roman type]).

This also works (for a different area):

To say energydesc:
	let L be a list of text;
	let place be the room north from the location;
	if place is in energy-region:
		add "[boldnorth]" to L;
	let place be the room northeast from the location;
	if place is in energy-region:
		add "[boldnortheast]" to L;
	let place be the room east from the location;
	if place is in energy-region:
		add "[boldeast]" to L;
	let place be the room southeast from the location;
	if place is in energy-region:
		add "[boldsoutheast]" to L;
	let place be the room south from the location;
	if place is in energy-region:
		add "[boldsouth]" to L;
	let place be the room southwest from the location;
	if place is in energy-region:
		add "[boldsouthwest]" to L;
	let place be the room west from the location;
	if place is in energy-region:
		add "[boldwest]" to L;
	let place be the room northwest from the location;
	if place is in energy-region:
		add "[boldnorthwest]" to L;
	say L;
	if the location is south-energy:
		say ". You can also return to the statue to the [boldsouth]"
2 Likes

There is an underlying difference in the way that the text values are constructed by the I7 compiler.

6M62 I6 details

Here’s are the important parts of the I6 generated for the every turn rule (in 6M62, reformatted for readability):

! [2: say ~[bold type]hi[roman type][paragraph break]~]
say__p=1;
! [3: bold type]
ParaContent(); style bold;
! [4: ~hi~]
ParaContent(); print "hi";
! [5: roman type]
ParaContent(); style roman;
! [6: paragraph break]
ParaContent(); DivideParagraphPoint(); new_line; .L_Say2; .L_SayX2;

! [7: let boldtext be ~[bold type]hi[roman type][paragraph break]~]
tmp_0 = I7SFRAME; 
BlkValueCopy(tmp_0, ((LocalParking-->0=tmp_0),TEXT_TY_ExpandIfPerishable((I7SFRAME+WORDSIZE*2),TX_S_136)));

! [8: say boldtext]
say__p=1;
![9: boldtext]
ParaContent(); print (TEXT_TY_Say) tmp_0; .L_Say3; .L_SayX3;

! [10: say ~[boldtext]~]
say__p=1;
! [11: boldtext]
ParaContent(); print (TEXT_TY_Say) tmp_0; .L_Say4; .L_SayX4;

! [12: let nestedtext be ~[boldtext]~]
tmp_1 = (I7SFRAME+WORDSIZE*4); 
BlkValueCopy(tmp_1, ((LocalParking-->0=tmp_0),(LocalParking-->1=tmp_1),TEXT_TY_ExpandIfPerishable((I7SFRAME+WORDSIZE*6),TX_S_137)));

! [13: say nestedtext]
say__p=1;
! [14: nestedtext]
ParaContent(); print (TEXT_TY_Say) tmp_1; .L_Say5; .L_SayX5;

! [15: say ~[nestedtext]~]
say__p=1;
! [16: nestedtext]
ParaContent(); print (TEXT_TY_Say) tmp_1; .L_Say6; .L_SayX6;
rfalse;

For the first say statement, the compiler translates the contents of the provided text literal directly into print and style statements.

For the second and third say statements, the compiler makes use of a true text object to store the provided literal, which is:

[ R_TX_S_136 
	tmp_0 ! Let/loop value, e.g., 'boldtext': text
	;
	#ifdef DEBUG; if (suppress_text_substitution) { print "~[bold type]hi[roman type][paragraph break]~"; rtrue; }
	#endif; ! DEBUG
	! [1: ~[bold type]hi[roman type][paragraph break]~]
	say__p=1;! [2: bold type]
	ParaContent(); style bold;
	! [3: ~hi~]
	ParaContent(); print "hi";
	! [4: roman type]
	ParaContent(); style roman;
	! [5: paragraph break]
	ParaContent(); DivideParagraphPoint(); new_line; .L_Say478; .L_SayX476;rtrue;
];
Array TX_S_136 --> CONSTANT_PACKED_TEXT_STORAGE R_TX_S_136;

For the fourth and fifth say statements, the compiler makes use of a different true text object, which is:

[ R_TX_S_137 
	tmp_0 ! Let/loop value, e.g., 'boldtext': text
	tmp_1 ! Let/loop value, e.g., 'nestedtext': text
	;
	tmp_0=LocalParking-->0;
	tmp_1=LocalParking-->1;
	#ifdef DEBUG; if (suppress_text_substitution) { print "~[boldtext]~"; rtrue; }
	#endif; ! DEBUG
	! [1: ~[boldtext]~]
	say__p=1;
	! [2: boldtext]
	ParaContent(); print (TEXT_TY_Say) tmp_0; .L_Say479; .L_SayX477;rtrue;
];
Array TX_S_137 --> CONSTANT_PERISHABLE_TEXT_STORAGE R_TX_S_137;

Note the difference between the internal text type for the two text objects. The first is type CONSTANT_PACKED_TEXT_STORAGE and the second is type CONSTANT_PERISHABLE_TEXT_STORAGE. This makes a difference when routine TEXT_TY_ExpandIfPerishable() is called:

[ TEXT_TY_ExpandIfPerishable to from;
	if ((from) && (from-->0 == CONSTANT_PERISHABLE_TEXT_STORAGE))
		return TEXT_TY_SubstitutedForm(to, from);	! <-- leaves behind formatting when substituting
	return from;
];

Translated into I7 speak, it’s as though your fourth and fifth statements were:

say the substituted form of nestedtext;
say the substituted form of "[nestedtext]";

Per the template comments about CONSTANT_PERISHABLE_TEXT_STORAGE:

This is a constant created by the I7 compiler which is marked as being tricky because its value is a text substitution containing references to local variables. Unlike other text substitutions, this can’t meaningfully be stored away to be expanded later: it must be expanded into unpacked text before it perishes.

There is also a discussion of this aspect in WWI 20.7 Making text with substitutions.

So, the short version is (I think) that substitution destroys formatting, and the compiler thinks that substitution is necessary here because your fourth and fifth say statements make use of locally-declared variable in the every turn rule (i.e. boldtext).

Wasn’t @Draconis working on something to allow texts to store formatting codes? That might be helpful.

On a more general note, this issue could be avoided in your use case by storing twin lists (one of rooms, one of directions) and then spooling those out with appropriate formatting at the end of the say phrase. Something along the lines of:

To say galexit:
	say "You can go ";
	let R be a list of rooms;
	let D be a list of directions;
	repeat with way running through directions: 
		if way is inside:
			next;
		if way is outside:
			next;
		if way is down:
			next;
		add way to D;
		add the room way from the location to R;
	let N be the number of entries in D;
	repeat with index running from 1 to N:
		say "[bold type][entry index of D][roman type] to the [entry index of R]";
		if index is N:
			say ".";
		otherwise if index is N minus 1:
			say ", or ";
		otherwise:
			say ", ".
1 Like

The easiest way to think of this is that a text with substitutions (stuff in square brackets) is not really a text, it’s a function that returns a text. So long as it remains stored as a function rather than as a plain text, saying it will invoke the [bold] etc. substitutions as it is printed. But plain text can only store plain characters, not formatted characters, so as soon as you store the result of your expanded function as a plain text, all formatting is lost.

In your example, let nestedtext be "[boldtext]" expands the boldtext function straightaway and stores the result as plain text in nestedtext- losing all character formatting. Inform does this because boldtext is a local variable. When allocating “[boldtext]” to another variable, Inform has to decide whether to make the new variable reference the unexpanded boldtext function itself, or its expanded text. Because there is the possibility that a local variable may go out of scope before it gets the chance to be expanded, and therefore no longer be valid when the time for expansion comes, Inform errs on the side of caution and does the expansion immediately, allocating the expanded text to nestedtext as plain text, and in doing so losing all character formatting.

When play begins:
	say "[bold type]hi[roman type][paragraph break]";
	let boldtext be "[bold type]hi[roman type][paragraph break]";
	say boldtext;
	say "[boldtext]";
	let nestedtext be "[boldtext]"; [the boldtext function is immediately expanded and the resulting text stored in nestedtext as a plain text, losing character formatting]
	say nestedtext;
	say "[nestedtext]";

If you make boldtext a global variable, Inform is confident that it could expand the boldtext function anytime and nestedtext therefore is made to be a function referencing that unexpanded function, not a plain text. When nestedtext is then later said the boldtext function is only expanded at that point and character formatting is preserved:

boldtext is a text that varies.

When play begins:
	say "[bold type]hi[roman type][paragraph break]";
	let boldtext be "[bold type]hi[roman type][paragraph break]";
	say boldtext;
	say "[boldtext]";
	let nestedtext be "[boldtext]"; [nestedtext will be a function referencing the boldtext function, preserving character formatting]
	say nestedtext;
	say "[nestedtext]";

gives five bold 'hi’s.

Or in this simple case you can take the decision about whether to expand boldtext away from Inform by the following:


When play begins:
	say "[bold type]hi[roman type][paragraph break]";
	let boldtext be "[bold type]hi[roman type][paragraph break]";
	say boldtext;
	say "[boldtext]";
	let nestedtext be boldtext; [nestedtext simply references the same function as boldtext, preserving character formatting]
	say nestedtext;
	say "[nestedtext]";

EDIT: Like Otis just said :slight_smile:

3 Likes

There hasn’t been much mad science posted lately, so if you want to live dangerously, you can also try:

Suppress perishable expansion is a truth state that varies. The suppress perishable expansion variable translates into I6 as "suppress_perishable_expansion". 

Include (-

Global suppress_perishable_expansion;

-) after "Definitions.i6t".

Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! Text.i6t: Perishability
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

[ TEXT_TY_ExpandIfPerishable to from;
	if (suppress_perishable_expansion) return from;
	if ((from) && (from-->0 == CONSTANT_PERISHABLE_TEXT_STORAGE))
		return TEXT_TY_SubstitutedForm(to, from);
	return from;
];

-) instead of "Perishability" in "Text.i6t".

which will get you five bolded “hi” lines with:

Every turn:
	say "[bold type]hi[roman type][paragraph break]";
	let boldtext be "[bold type]hi[roman type][paragraph break]";
	say boldtext;
	say "[boldtext]";
	now suppress perishable expansion is true;
	let nestedtext be "[boldtext]";
	say nestedtext;
	say "[nestedtext]";
	now suppress perishable expansion is false;

I wouldn’t recommend leaving that flag set to true any longer than absolutely necessary.

(…and it would be smarter to do what the good doctor just said!)

4 Likes

lol

1 Like

Depends whether your affinity is with Barbie or Oppenheimer, I suspect

1 Like

This is great! I may not understand half of this (the Inform 6 half) but I appreciate the answers you both gave.

1 Like

This line inserted into the TEXT_TY_ExpandIfPerishable() function:

if (suppress_perishable_expansion) return from;

basically says ‘If this flag is set, don’t worry that we’re allocating an unexpanded local text function variable- just go ahead and allocate as an unexpanded function, (not as expanded plain text)- same as if we were allocating an unexpanded global text function variable’

1 Like

Yes, though currently anything with formatting codes captured needs to be printed with a special routine, which makes it a bit clunkier to use.

But you can in fact handle this with Formatting Capture. (Huh, apparently I never put it in the 10.1 branch on Github? I should figure out why I didn’t do that.) When you start capturing text, it saves everything printed—including formatting—into a global buffer, until you tell it to stop. When you say the captured text, it uses the special routine to reproduce any formatting that happened during the printing.

1 Like