Segmented substitutions- error in Documentation 27.29 and example- and a question?

The code for invoking new segmented substitutions from I7 appears to be one of the more arcane and unstable features of the remaining non-deprecated interfaces between I6 and I7 (I’m going from the dire warnings in the documentation against attempting to use undocumented syntaxes evident in the Standard Rules).

However, there seems to be an outdated ‘supported’ usage still extant in Documentation 27.29, which states:


create (if one does not already exist) a counter called NAME. This is initially zero, and can be reset back to zero using “{-zero-counter:NAME}”, which expands into no text. The token “{-counter:NAME}” expands into the current value of the counter, as a literal decimal number. The token “{-counter-up:NAME}” does the same, but then also increases it by one. Finally, the token “{-counter-makes-array:NAME}” expands to nothing, but tells Inform to create an “–>” array called “I7_ST_NAME” which includes entries from 0 up to the final value of the NAME counter.

This allows each instance in the source text of a given phrase to have both (i) a unique ID number for that invocation, and (ii) its own word of run-time storage, which can allow it to have a state preserved in between times when it is executed. For example:

To say once only -- beginning say_once_only: 
    (- {-counter-makes-array:say_once_only}if (I7_ST_say_once_only-->{-counter:say_once_only} == false) {-open-brace} I7_ST_say_once_only-->{-counter-up:say_once_only} = true; -). 
To say end once only -- ending say_once_only: 
    (- {-close-brace} -).

The text and the example imply that {-counter-up:NAME} expands to the literal numeral held in the compiler’s internal counter NAME as well as incrementing the compiler’s counter NAME by 1. However, attempting to compile the example (which fails with a numeral missing from the I6 code where {-counter-up:say_once_only} occurs) and examining Standard Rules and the examples given in 27.30 indicates that this is no longer the case- {-counter-up:NAME} increments the compiler’s internal counter for NAME but no longer expands to any text in the I6 code. The correct version of the above example is therefore now:

To say once only -- beginning say_once_only: 
    (- {-counter-makes-array:say_once_only}if (I7_ST_say_once_only-->{-counter:say_once_only} == false) {-open-brace} I7_ST_say_once_only-->{-counter:say_once_only}{-counter-up:say_once_only} = true; -). 
To say end once only -- ending say_once_only: 
    (- {-close-brace} -).

As an aside, for anyone interested in this subject, there is also not allowed to be any whitespace before or after the colon in {-counter-up:say_once_only}. {-counter-up :say_once_only} doesn’t compile, but {-counter-up: say_once_only} does compile but doesn’t increment the counter, leading to incorrect I6 code.

Finally, in the more complex example in 27.30 can anyone explain the arcane reason for the insertion of @nop op-codes into the I6 output of the switch statement, and the invocation of a second (flag) array and reference to a say__comp global not mentioned in the documentation. The I7 code given (and the Standard Rules) definitively do NOT compile to the pattern of I6 code suggested in the Documentation:

I7_ST_say_one_of-->2 = I7_SOO_PAR(I7_ST_say_one_of-->2, 4); 
switch((I7_ST_say_one_of-->2)%5 - 1) { 
    0: ... first text ... 
    1: ... second text ... 
    2: ... third text ... 
    3: ... fourth text ... 

but rather (in the case of e.g. ‘say “[one of]one pig[or]two pigs[or]three pigs[or]four pigs[purely at random]!”;’) to the following (cleaned up of confusing comments and layout):

    	if (I7_ST_say_one_flag-->2 == false) { 
    		I7_ST_say_one_of-->2 = I7_SOO_PAR(I7_ST_say_one_of-->2, 4); 
    		I7_ST_say_one_flag-->2 = true; 

    	if (say__comp == false) I7_ST_say_one_flag-->2 = false; 
    	switch ((I7_ST_say_part_of-->2)%(4+1)-1) 
    	0: ParaContent(); print "one pig"; ParaContent(); @nop;
    	1: ParaContent(); print "two pigs"; ParaContent(); @nop;
    	2: ParaContent(); print "three pigs"; ParaContent(); @nop;
    	3: ParaContent(); print "four pigs"; ParaContent();
ParaContent(); print "!"; new_line;
1 Like

The say__comp global is the “if expanding text for comparison purposes” flag. The idea is that if we’re evaluating a text for “if X is Y” reasons, we should not advance the state for “cycling”, “once only”, “sticky”, etc.

The I6 code in the documentation is out of date; it’s from an older version of I7 that had bugs in the say__comp handling. The current compiled code ensures that if we compare the text several times in a row, it will select a random value the first time and stick with it for subsequent comparisons. That selected value will stick until the next time the text is actually printed (i.e., when not expanding text for comparison purposes). Then it will be wiped (I7_ST_say_one_flag-->2 = false) so that later use of the text will be re-randomized.


I have no idea about the @nop though.

I thought it might be to avoid generating an empty switch case. But empty switch cases are legal in I6.

Thanks, that makes sense now. So, essentially the say_one_flag array exists to indicate whether the last text expansion was for the sake of comparison (true) -in which case generate the same expansion again this time- or not (false) in which case generate a new expansion.

The @nop injection remains mysterious, then. Perhaps an inadvertent relic of older code? The present code seems to compile and work fine if @nop is removed.

I think Zarf’s right and it’s an unnecessary way to avoid generating empty switch-cases. Here’s where it comes from:

To say or -- continuing say_one_of (documented at phs_or):
	(- @nop; {-segment-count}: -).

it’s an unnecessary way to avoid generating empty switch-cases.

I agree, it does sort of look that way from the code.

In which case it’s perhaps unnecessary on two counts, since in the case of omitted text between [or] expansions, otherwise ‘empty’ switch statements now still have a ParaContent(); inserted even in the absence of any printed text or @nop; …

	0: ParaContent(); print "one pig"; ParaContent();
	1: ParaContent();
	2: ParaContent();
	3: ParaContent(); print "four pigs"; ParaContent();
    ParaContent(); print "!"; new_line;

For a “cycling” statement, that array tracks where we are in the cycle. For an “at random” statement, it tracks the last selection to prevent an immediate repeat. And so on.

I think here you’re referring to the say_one_of array, which holds a reference to the most recent expansion, rather than the say_one_flag array, which holds the context of the most recent expansion?

Ah, you’re right, I wasn’t distinguishing them as I read the code. Sorry.

You are, naturally, correct about this. It seems not to be documented in the DM4.

Similarly that duplicate or overlapping switch cases are permitted, in which case only the code block after the first match in the presented sequence will be executed.