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.

And welcome to the wide fields...

All the fields we've seen so far have been exactly as wide as their specifications. That's the whole point of having fields – they allow us to lay out formats "by eye".

But form also allows us to specify field widths in other ways. And better yet, to avoid specifying them at all and let form work out how big they should be.

The measure then of one is easily told.

When specific field widths are required (perhaps by some design document or data formatting protocol) laying out wide fields can be error-prone. For example, most people can't visually distinguish between a 52-column field and a 53-column field and are therefore forced to manually verify the width of the corresponding field specifier in some way.

When such fields are part of a larger format, errors like that can easily result in a call to form producing, say, 81-column lines. That would merely be messy if the extra characters wrapped, but could be disasterous if they happened to be chopped instead. Suppose, for example, that the last 4 columns of output contain nuclear reactor core temperatures and then consider the difference between an apparently normal reading of 567 Celsius and what might actually be happening if the reading were in fact a truncated 5678 Celsius.

To catch mistakes of this kind, fields can be specified with an embedded integer in parentheses (with optional whitespace inside the parens). For example:


    print form '{[[[( 15 )[[[[} {<<<<<(17)<<<<<<}  {]]](14)]]].[[}',
               *@data;

The integer in the parentheses acts like a checksum. Its value must be identical to the actual width of the field (including the delimiting braces and the embedded integer itself). Otherwise an exception is thrown. For instance, running the above example produces the error message:


    Inconsistent width for field 3.
    Specified as '{]]](14)]]].[[}' but actual width is 15
    in call to &form at demo.pl line 1

Numeric fields can be given a decimal checksum, which then also specifies their number of decimal places.


    print form
        '{[[[( 15 )[[[[} {<<<<<(17)<<<<<<}  {]](14.2)]].[}',
         *@data;

Note that the digits before the decimal still indicate the total width of the field. So the {]](14.2)]].[} field in the above example means must be 14 columns wide, including 2 decimal places, in exactly the same way as a "%14.2f" specifier would in a sprintf.

What you will command me will I do...

Of course, in some instances it would be much more convenient if we could simply tell form that we want a particular field to be a particular width, instead of having to explicitly show it.

So there's another type of integer field annotation that, instead of acting like a checksum, acts like an...err..."tellsum". That is, we can tell form to ignore a field's physical width and instead insist that it be magically expanded (or shrunk) to a nominated width. Such a field is said to have an imperative width. The integer specifying the imperative width is placed in curly braces instead of parens.

For example, the format in the previous example could be specified imperatively as:


    print form
        '{[{15}[} {<{17}<<}  {]]]]{14.2}]]]].[[}',
         *@data;

Note that the actual width of any field becomes irrelevant if it contains an imperative width. The field will be condensed or expanded to the specified width, with subsequent fields pushed left or right accordingly.

Imperative fields disrupt the WYSIWYG layout of a format, so they're generally only used when the format itself is being generated programmatically. For example, when we were counting down the top ten reasons not to do one's English Lit homework, we used a fixed-width {>} field to format each number:


    for @reasons.kv -> $n, $reason {
        my $n = @reasons - $index ~ '.';
        print form "   {>}  {[[[[[[[[[[[[[[[[[[[[[[[[[[[[}",
                       $n,  $reason,
                   "";
    }

But, of course, there's not reason (theoretically, at least) why we couldn't find more than 99 reasons not to do our homework, in which case we'd overflow the {>} field.

So instead of limiting ourselves that way, we could just tell form to make the first field wide enough to enumerate however many reasons we come up with, like so:


    my $width = length(+@reasons)+1;

    for @reasons.kv -> $n, $reason {
        my $n = @reasons - $index ~ '.';
        print form "   {>>{$width}>>}  {[[[[[[[[[[[[[[[[[[[[[[[[[[[[}",
                       $n,             $reason,
                   "";
    }

By evaluating @reasons in a numeric context (+@reasons) we determine the number of reasons we have, and hence the largest number that need ever fit into the first field. Taking the length of that number (length) gives us the number of digits in that largest number and hence the width of a field that can format that number. We add one extra column (for the dot we're appending to each number) and that's our required width. Then we just tell form to make the first field that wide ({>>{$width}>>}).

And every one shall share...

A special form of imperative width field is the starred field. A starred field is one that contains an imperative width specification in which the number is replaced by a single asterisk.

The width of a starred field is not fixed, but rather is computed during formatting. That width is whatever is required to cause the entire format to fill the current page width of the format (by default, 78 columns). Consider, for example:


    print form
        '{]]]]]]]]]]]]]]} {]]].[[}  {[[{*}[[}  ',
         @names,          @scores,  @comments;

The width of the starred comment field in this case is 49 columns – the default page width of 78 columns minus the 29 columns consumed by the fixed-width portions of the format (including the other two fields).

If a format contains two or more starred fields, the available space is shared equally between them. So, for example, to create two equal columns (say, to compare the contents of two files), we might use:


    print form 
         "{[[[[{*}[[[[}   {[[[[{*}[[[[}",
          slurp($file1),  slurp($file2);

And, yes, Perl 6 does have a built-in slurp function that takes a filename, opens the file, reads in the entire contents, and returns them as a single string. For more details see the Perl6::Slurp module (now on the CPAN).

There is one special case for starred fields: a starred verbatim field:


    {""""{*}""""}

It acts like any other starred field, growing according to the available space, except that it will never grow any wider than the widest line of the data it is formatting. For example, whereas a regular starred field:



    print form 
         '| {[[{*}[[} |',
            $monologue;

expands to the full page width:


    | Now is the winter of our discontent                           |
    | Made glorious summer by this sun of York;                     |
    | And all the clouds that lour'd upon our house                 |
    | In the deep bosom of the ocean buried.                        |
    | Now are our brows bound with victorious wreaths               |  
    | Our bruised arms hung up for monuments;                       |
    | Our stern alarums changed to merry meetings,                  |
    | Our dreadful marches to delightful measures.                  |
    | Grim-visaged war hath smooth'd his wrinkled front;            |
    | And now, instead of mounting barded steeds                    |  
    | To fright the souls of fearful adversaries,                   |  
    | He capers nimbly in a lady's chamber.                         |

a starred verbatim field:


    print form 
         '| {""{*}""} |',
            $monologue;

only expands as much as is strictly necessary to accommodate the data:


    | Now is the winter of our discontent                |
    | Made glorious summer by this sun of York;          |
    | And all the clouds that lour'd upon our house      |
    | In the deep bosom of the ocean buried.             |
    | Now are our brows bound with victorious wreaths;   |  
    | Our bruised arms hung up for monuments;            |
    | Our stern alarums changed to merry meetings,       |
    | Our dreadful marches to delightful measures.       |
    | Grim-visaged war hath smooth'd his wrinkled front; |
    | And now, instead of mounting barded steeds         |  
    | To fright the souls of fearful adversaries,        |  
    | He capers nimbly in a lady's chamber.              |

That we our largest bounty may extend...

By now you've probably noticed that there is quite a large overlap between the functionality of form and that of (s)printf. For example, the call:


    for @procs {
        print form
            "{>>>}  {<<<<<<<(20)<<<<<<<}  {>>>>>>}  {>>.}%",
            .{pid}, .{cmd},               .{time},  .{cpu};
    }

has approximately the same effect as the call:


    for @procs {
        printf "%5d  %-20s  %8s  %5.1f%%\n",
               .{pid}, .{cmd}, .{time}, .{cpu};
    }

One is more WYSIWYG, the other more concise, but (placed in a suitable loop), they would both print out lines like these:


     2461  vi -ii henry           0:55.83   11.6%
     2395  ex cathedra            0:06.59    3.5%
     2439  head anne.boleyn       0:00.18    0.1%
     2581  dig -short grave       0:01.04    0.0%

There is, however, a crucial difference between these two formatting facilities; one that only shows up when one of our processes runs over 99 hours. For example, suppose our browser has been running continuously for a few months (or, more precisely, for 1214:23.75 hours). Then the calls to printf would print:


     2461  vi -ii henry           0:55.83   11.6%
     2395  ex cathedra            0:06.59    3.5%
    27384  lynx www.divorce.com  1214:23.75    0.8%
     2439  head anne.boleyn       0:00.18    0.1%
     2581  dig -short grave       0:01.04    0.0%

whilst the calls to form would print:


     2461  vi -ii henry           0:55.83   11.6%
     2395  ex cathedra            0:06.59    3.5%
    27384  lynx www.divorce.com  1214:23-    0.8%
     2439  head anne.boleyn       0:00.18    0.1%
     2581  dig -short grave       0:01.04    0.0%

In other words, field widths in a printf represent minimal spacing (even if that throws off the overall layout), whereas field widths in a form represent guaranteed spacing (even if that truncates some of the data).

Of course, in a situation like this – where we knew that the data might not fit and we didn't want it truncated – we could use a block field instead:


    for @procs {
        print form
            "{>>>}  {<<<<<<<(19)<<<<<<}  {]]]]]]}  {>>.%}",
            .{pid}, .{cmd},              .{time},  .{cpu};
    }

in which case we'd get:


     2461  vi -ii henry           0:55.83   11.6%
     2395  ex cathedra            0:06.59    3.5%
    27384  lynx www.divorce.com  1214:23-    0.8%
                                      .75
     2439  head anne.boleyn       0:00.18    0.1%
     2581  dig -short grave       0:01.04    0.0%

That preserves the data, but the results are still ugly, and it also requires some fancy footwork – making the percentage sign part of the field specification, as if it were a currency marker – to make the last field work correctly. In other words: it's a kludge. The sad truth is that sometimes variable-width fields are a better solution.

So form provides them too. Any field specification may include a plus sign (+) anywhere between its braces, in which case it specifies an extensible field: a field whose width is minimal, rather than absolute. So, in the above example, our call to form should actually look like this:


    for @procs {
        print form
            "{>>>}  {<<<<<<<(20)<<<<<<<}  {>>>>>+}  {>>.}%",
            .{pid}, .{cmd},               .{time},  .{cpu};
    }

and would produce this:


     2461  vi -ii henry           0:55.83   11.6%
     2395  ex cathedra            0:06.59    3.5%
    27384  lynx www.divorce.com  1214:23.75    0.8%
     2439  head anne.boleyn       0:00.18    0.1%
     2581  dig -short grave       0:01.04    0.0%

just like printf does.

Likewise, if we thought the command names might exceed 20 columns we could let that field stretch too:


    for @procs {
        print form
            "{>>>}  {<<<<<<<(20+)<<<<<<}  {>>>>>+}  {>>.}%",
            .{pid}, .{cmd},               .{time},  .{cpu};
    }

Note that the field width specifier would still warn us if the field's "picture" was not exactly 20 columns wide, but the resulting field would nevertheless stretch as necessary to accommodate longer data.

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

Next Pagearrow