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.)
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
saystatement 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.
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.
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.
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).
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
No other substitution is capable of canceling an automatic punctuation line break. This includes those related to conditions (
[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 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
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__p will be clear at the end of that
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
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.
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 + + -
+ = 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.