Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

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.

Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13

Next Pagearrow