Since the rules around “paragraph control” are an enduring mystery and the subject of frequent posts on the forum, I spent some time analyzing the system. This post should be everything you need to know to get the line spacing behavior that you seek without unwanted surprises.
(Note that the following was derived via testing in 6M62, but 10.1 should behave largely the same.)
Reasons that line breaks are generated
There are three sources of line breaks in text output:
-
invocation of paragraph control text substitutions, per logic defined in the Standard Rules
-
automatic injection after text segments ending with ./!/? (or grammatical equivalent), per logic defined in I7 compiler code
-
automatic injection before evaluation of a rule (standalone or within a rulebook) after a
say
statement lacking certain substitutions has occurred, per logic defined in I7 template code
It is a complex system that is difficult to explain briefly. However, if an author is more interested in what the system does instead of why it does those things, then the rules can be laid out in a fairly compact way – which is what’s done in the table at the end of this post.
Core concepts
Text segments
The most important concept to understand is the idea of a text segment. Every say
statement is composed of one or more text segments. A new segment is generated for any contiguous run of one or more alphanumeric and/or white space characters within a text (called strings here), or for any single substitution within the text.
Automatic punctuation line breaks
When the I7 compiler is translating a say
statement into I6, it looks at the end of every text segment being created for a string. If the last character(s) of the string are sentence-ending punctuation (i.e. period, question mark or exclamation mark – or one of these followed by a close quote), then the I7 compiler adds a line break at the end of the segment… unless it is overridden by a paragraph control phrase, as described next.
Paragraph control substitutions
Some substitutions are designated as being for paragraph control. These are defined in “Section SR5/1/5 - Saying - Paragraph control” of the Standard Rules. They are: [line break]
(LB), [no line break]
(NLB), [run paragraph on]
(RPO), [paragraph break]
(PB), [conditional paragraph break]
(CPB) and [run paragraph on with special look spacing]
(RPOWSLS).
The to say...
phrases for these substitutions are all specified in such a way that they cancel an automatic punctuation line break for any text segment that precedes them within the same say
statement. They have no effect on a segment at the end of a previous say
statement.
Other substitutions
No other substitution is capable of canceling an automatic punctuation line break. This includes those related to conditions ([if]
, [otherwise]
, [else]
, [end if]
), those related to [one of]
constructions, etc.
Author-supplied substitutions can be set up to cancel automatic punctuation line breaks, but only if they do not accept parameters.
The say__p
and PARA_CONTENTEXPECTED
flags
The paragraph control system tracks many boolean flags. The most important of these is say__p
, which takes the form of an I6 global variable. Every say
statement at the I7 level sets the say__p
flag as its first effect – prior to any code related to text segments. This occurs even for say
statements that include no text at all (such as say no line break;
).
A significant secondary flag is called PARA_CONTENTEXPECTED
. At the start of each text segment (whether string or substitution), a routine is run that checks the state of PARA_CONTENTEXPECTED
. If it sees this flag set, the routine will set say__p
and clear PARA_CONTENTEXPECTED
.
Many paragraph control phrases clear say__p
. Some paragraph control phrases clear say__p
but also set PARA_CONTENTEXPECTED
. If one of these occurs as the last segment of a say
statement, say__p
will be clear at the end of that say
statement.
Pre-rule and inter-rule line breaks
Whenever a rule is about to be processed (either standalone or as part of a rulebook), then the state of say__p
is checked. If the flag is set, then a line break is printed and the flag is cleared. Line breaks generated in this manner are here called rulebook breaks.
Rulebook breaks do not run the routine that checks the state of PARA_CONTENTEXPECTED
. While processing rules, the first generated rulebook break will clear say__p
, and it cannot be set again unless one of the rules executes a say
statement.
EDIT: In trying to simplify this section, I went a bit too far. There is a significant distinction in default behavior for rulebooks that depends on whether or not the rulebook has a parameter, i.e. is “based” on something other than an action
(the implicit default) or explicitly nothing
. (See WWI 19.9 Basis of a rulebook for more.) For an <X> based rulebook
of any other kind other than these two, the default is for all rulebook breaks to be skipped.
Summary table
This table tries to condense all of the above into a visual quick reference:
TABLE 1: AUTHOR-VISIBLE EFFECTS OF PHRASES
LB NLB RPO PB CPB CCB RPOWSLS other
unconditional new_line? + - - + - + - -
conditional new_line? - - - +p +p - - -
overrides prev segment punct break? + + + + + + + -
suppresses rulebook breaks? - - + Lp Lp + + -
EFFECT KEY:
+ = always
- = never
L = only if occurring at end of most recent say statement
p = effect applies only when say__p is set at start of segment
Special credit to @neroden, who outlined the idea of text segments in Nathanael Nerdode’s Cookbook (https://raw.githubusercontent.com/i7/extensions/10.1/Nathanael%20Nerode/Nathanael’s%20Cookbook-v6.i7x), and to @drpeterbatesuk, who worked out the effect of the undocumented -- running on
designation for say
phrases (Trouble with paragraph breaks - #8 by drpeterbatesuk). Any errors in the above are mine.