Exegesis 7
by Damian Conway
|
Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
Editor's note: this document is out of date and remains here for historic interest. See Synopsis 7 for the current design information.
Within that space you may have drawn together...
When a field is being filled in, whitespace is normally left as-is
(except for justification, and wrapping of lines in block fields).
However, this behaviour can be altered by specifying a whitespace
squeezing strategy. Squeezing replaces those substrings of the data
that match a specified pattern (for example: /\s+/), substituting
a single space character.
If we don't want the default (non-)squeezing strategy we can use
the :ws option specify the particular pattern that is to be
used for squeezing:
print form
:ws(/\h+/), # squeeze any horizontal whitespace
$format1, *@data1,
:ws(/<comment>|\s+/), # now squeeze comments or whitespace
$format2, *@data2;
For example, suppose we have a eulogy generator:
sub eulogize ($who, $to, $blaming) {...}
that (rather poorly) drops the appropriate names into a pre-formatted template, to produce strings like:
Friends, Romans , countrymen, lend me your ears;
I come to bury Caesar , not to praise him.
The evil that men do lives after them;
The good is oft interred with their bones;
So let it be with Caesar . The noble Brutus
Hath told you Caesar was ambitious:
If it were so, it was a grievous fault,
And grievously hath Caesar answer'd it.
If we interpolate that string, with its extra spaces and its embedded
newlines, into a form field:
print form
"| {[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[} |",
eulogize('Caesar', to=>'Romans', blaming=>'Brutus');
we'd get:
| Friends, Romans , countrymen, lend me |
| your ears; |
| I come to bury Caesar , not to praise |
| him. |
| The evil that men do lives after them; |
| The good is oft interred with their bones; |
| So let it be with Caesar . The noble |
| Brutus |
| Hath told you Caesar was |
| ambitious: |
| If it were so, it was a grievous fault, |
| And grievously hath Caesar answer'd |
| it. |
Note that the extra spaces and the embedded newlines are preserved in the resulting text.
But, if we told form to squeeze all whitespaces:
print form :ws(/\s+/),
"| {[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[} |",
eulogize('Caesar', to=>'Romans', blaming=>'Brutus');
we'd get:
| Friends, Romans , countrymen, lend me your |
| ears; I come to bury Caesar , not to |
| praise him. The evil that men do lives |
| after them; The good is oft interred with |
| their bones; So let it be with Caesar . |
| The noble Brutus Hath told you Caesar was |
| ambitious: If it were so, it was a |
| grievous fault, And grievously hath Caesar |
| answer'd it. |
with each sequence of characters that match /\s+/ being reduced
to a single space.
On the other hand, if we wanted to preserve the newlines and squeeze only horizontal whitespace, that would be:
print form :ws(/\h+/),
"| {[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[} |",
eulogize('Caesar', to=>'Romans', blaming=>'Brutus');
which produces:
| Friends, Romans , countrymen, lend me your |
| ears; |
| I come to bury Caesar , not to praise him. |
| The evil that men do lives after them; |
| The good is oft interred with their bones; |
| So let it be with Caesar . The noble |
| Brutus |
| Hath told you Caesar was ambitious: |
| If it were so, it was a grievous fault, |
| And grievously hath Caesar answer'd it. |
Of course, for this particular text, none of these solutions is entirely
satisfactory since squeezing the whitespaces to a single space still leaves a
single space in places like "Caesar ." and "Romans ,".
To remove those blemishes we need to take advantage of a more
sophisticated aspect of form's whitespace squeezing behaviour. Namely
that, when squeezing whitespace using a particular pattern, form
detects if that pattern captures anything and doesn't squeeze the
captured items.
More precisely, if the squeeze pattern matches but doesn't capture,
form simply replaces the entire match with a single space character.
But if the squeeze pattern does capture, form doesn't insert a
space character, but instead replaces the entire match with the
concatenation of the captured substrings.
That means we can completely eliminate any whitespace before a punctuation character with:
print form :ws(/\h+ (<punct>)?/),
"| {[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[} |",
eulogize('Caesar', to=>'Romans', blaming=>'Brutus');
which produces the desired:
| Friends, Romans, countrymen, lend me your |
| ears; |
| I come to bury Caesar, not to praise him. |
| The evil that men do lives after them; |
| The good is oft interred with their bones; |
| So let it be with Caesar. The noble Brutus |
| Hath told you Caesar was ambitious: |
| If it were so, it was a grievous fault, |
| And grievously hath Caesar answer'd it. |
This works because, in those instances where the pattern
/\h+ (<punct>)?/ matches some whitespace followed by
a punctuation character, the punctuation character is captured,
and the captured character is then used to replace the entire
whitespace-plus-punctuator. On the other hand, if the
pattern matches whitespace but no punctuator (and it's allowed to do that
because the punctuator is optional), then nothing is captured, so
form falls back to replacing the whitespace with a single space.
He doth fill fields with harness...
Fields are (almost) always of a fixed width. So, if there isn't
enough data to fill a particular field, the unused portions of that
field are filled in with spaces to preserve the vertical alignment of
other columns of formatted data. However, spaces are only the
default. The :hfill (horizontal fill) option can be used to change
fillers. For example:
print form
:hfill("=-"), # Fill next fields with "=-"
"{|{*}|}\n", # Full width field for title
"[ Table of Contents ]", # Title
:hfill(" ."), # Fill next fields with spaced dots
' {[[[[[{*}[[[[[}{]]]} ', # Two indented block fields
@contents, @page; # Data for those blocks
This fills the empty space either side of the centred title with a repeated
=-=-=- sequence. It then fills the gaps to the right of the left-justified
the contents field, and to left of the right-justified pages field,
with spaced dots. Which, rather prettily, produces something like:
=-=-=-=-=-=-=-[ Table of Contents ]-=-=-=-=-=-=-=
Foreword. . . . . . . . . . . . . . . . . .i
Preface . . . . . . . . . . . . . . . . .iii
Glossary. . . . . . . . . . . . . . . . . vi
Introduction. . . . . . . . . . . . . . . .1
The Tempest . . . . . . . . . . . . . . . .7
Two Gentlemen of Verona . . . . . . . . . 17
The Merry Wives of Winsor . . . . . . . . 27
Twelfh Night. . . . . . . . . . . . . . . 39
Measure for Measure . . . . . . . . . . . 50
Much Ado About Nothing. . . . . . . . . . 62
A Midsummer Night's Dream . . . . . . . . 73
Love's Labour's Lost. . . . . . . . . . . 82
The Merchant of Venice. . . . . . . . . . 94
As You Like It. . . . . . . . . . . . . .105
Note that the fill sequence doesn't have to be a single character and that the fill pattern is consistent across multiple fields and between adjacent lines. That is, it's as if every field is first filled with the same fill pattern, then the actual data written over the top. That's particularly handy in the above example, because it ensures that the fill pattern seamlessly bridges the boundary between the adjacent contents and pages fields.
It's also possible to specify separate fill sequences for the left-
and right-hand gaps in a particular field, using the :lfill and :rfill
options. This is particularly common for numerical fields. For example,
this call to form:
print form
'Name Bribe (per dastardry)',
'============= =====================',
'{[[[[[[[[[[[} {]],]]].[[[} ',
@names, @bribes;
would print something like:
Name Bribe (per dastardry)
============= =====================
Crookback 12.676
Iago 1.62
Borachio 45,615.0
Shylock 19.0003
with the numeric field padded with whitespace and only showing as many decimal places as there are in the data.
However, in order to prevent subsequent..err...creative calligraphy (they are, after all, villains and would presumably not hesitate to add a few digits to the front of each number), we might prefer to put stars before the numbers and show all decimal places. We could do that like so:
print form
'Name Bribe (per dastardry)',
'============= =====================',
'{[[[[[[[[[[[} {]],]]].[[[} ',
@names, :lfill('*'), :rfill('0'),
@bribes;
which would then print:
Name Bribe (per dastardry)
============= =====================
Crookback *****12.6760
Iago ******1.6200
Borachio *45,615.0000
Shylock *****19.0003
Note that the :lfill and :rfill options are specified after the
format string and, more particularly, before the data for the second
field. This means that those options only take effect for that
particular field and the previous fill behaviour is then reasserted
for subsequent fields. Many other form options – for example :ws,
:height, or :break – can be specified in this way, so as to
apply them only to a particular field.
There is also a general :fill option that sets the default
sequence for any filling that isn't otherwise specified.
But say thou nought...
Filling numeric fields with zeros is so common that form offers a
shorthand notation for it. If the first character inside a numeric field specification is a zero, then the left-fill string for that field is set to "0".
Likewise if the last character in the field is a zero, it is right-filled
with zeros. For example:
my @nums = (0, 1, -1.2345, 1234.56, -1234.56, 1234567.89);
print form
"{]]]].[[} {]]]].[0} {0]]].[[} {0]]].[0}",
@nums, @nums, @nums, @nums;
prints:
0.0 0.000 00000.0 00000.000
1.0 1.000 00001.0 00001.000
-1.234 -1.234 -0001.234 -0001.234
1234.56 1234.560 01234.56 01234.560
-1234.56 -1234.560 -1234.56 -1234.560
#####.### #####.### #####.### #####.###
Up and down, up and down, I will lead them up and down...
Formatted text blocks are also filled vertically. Empty lines at the end
of the block are normally filled with spaces (so as to preserve the
alignment of any other fields on the same line). However, this too can
be controlled, with the :vfill option. Alternatively – as with
horizontal filling – separate fill sequences can be specified for
above and below the text using the :tfill and :bfill ("top" and
"bottom" fill) options.
For example, if we had six elements in @task, but only four processors:
print form
:bfill('[unallocated]'),
'Task Processor',
'==== =========',
'{[[[[[[[[[[[[[[[[[[[[} {]]]]]][[[[[}',
@task, [1..4];
we'd get:
Task Processor
==== =========
Borrow story 1
Rename characters 2
Subdivide into scenes 3
Write dialogue 4
Check rhythm and meter [unallocated]
Insert puns and japes [unallocated]
I have got strength of limit...
It is possible to constrain the minimum and maximum number of lines
that a particular format or block field must cover, regardless of how much
data it contains. We do that using the :height option. For example:
print form
:height(3),
'{[[[[}{IIII}{]]]]}',
$l, $c, $r;
This will cause the call to form to generate exactly three output lines,
even if the contents of the data variables would normally fit in fewer lines
or would actually require more.
To specify a range of heights we can use the :min and :max suboptions:
print form
:height{ :min(3), :max(20) },
'{[[[[}{IIII}{]]]]}',
$l, $c, $r;
This specifies that, no matter how much data is available, the output will be no less than three lines and no more than 20.
Note, however, that the height option refers to the height of individual
fields, not of entire output pages. we'll see how to control the
latter anon.

