File Coverage

blib/lib/Perl6/Form.pm
Criterion Covered Total %
statement 453 938 48.2
branch 159 542 29.3
condition 83 322 25.7
subroutine 52 96 54.1
pod 0 57 0.0
total 747 1955 38.2


line stmt bran cond sub pod time code
1             package Perl6::Form;
2 2     2   34486 use 5.008;
  2         5  
3              
4             our $VERSION = '0.06';
5              
6 2     2   1520 use Perl6::Export;
  2         39975  
  2         12  
7 2 0 50 2   13596 use Scalar::Util qw( readonly );
  2 50 0 2   9  
  2   0 2   659  
  2   0 2   63  
  2         12  
  2         3  
  2         198  
  2         19  
  2         23  
  0         0  
  0         0  
  0         0  
  0         0  
  2         10  
  2         265  
  2         5  
8 2     2   9 use List::Util qw( max min first );
  2         3  
  2         149  
9 2     2   9 use Carp;
  2         2  
  2         83  
10 2     2   1323 use charnames ':full';
  2         50125  
  2         733  
11              
12             my %caller_opts;
13              
14             sub fatal {
15 0     0 0 0 croak @_, "\nin call to &form";
16             }
17              
18             sub defined_or_space {
19 0 0 0 0 0 0 return " " if !defined $_[0] || length $_[0] == 0;
20 0         0 return $_[0];
21             }
22              
23             sub boolean {
24 0 0   0 0 0 return $_[0] ? 1 : 0;
25             }
26              
27             sub pattern {
28 0 0   0 0 0 return $_[0] if ref $_[0] eq 'Regexp';
29 0 0       0 return $_[0] ? qr/(?=)/ : qr/(?!)/;
30             }
31              
32             sub code {
33 0     0 0 0 my ($newval, $name ) = @_;
34 0   0     0 my $type = ref($newval) || "'$newval'";
35 0 0       0 fatal "Value for '$name' option must be code reference (not $type)"
36             unless $type eq 'CODE';
37 0         0 return $newval;
38             }
39              
40             my %std_one = (
41             '=' => '{=[{1}[=}',
42             '_' => '{_[{1}[_}',
43             );
44              
45             sub one_char {
46 0     0 0 0 my ($newval, undef, $opts ) = @_;
47 0 0       0 $newval = [ $newval ] unless ref $newval eq 'ARRAY';
48 0         0 for (@$newval) {
49 0 0       0 fatal "Value for 'single' option must be single character (not '$_')"
50             if length() != 1;
51             $opts->{field} =
52 0   0     0 user_def([qr/\Q$_\E/, $std_one{$_}||'{[{1}[}'], 'single', $opts);
53             }
54 0         0 return;
55             }
56              
57             sub layout_word {
58 0 0   0 0 0 fatal "Value for layout option must be 'across', 'down', 'balanced', ",
59             "or 'tabular\n(not '$_[0]')"
60             unless $_[0] =~ /^(across|down|balanced|tabular)$/;
61 0         0 return $_[0];
62             }
63              
64             sub pos_integer {
65 0 0 0 0 0 0 fatal "Value for '$_[1]' option must be positive integer (not $_[0])"
66             unless int($_[0]) eq $_[0] && $_[0] > 0;
67 0         0 return $_[0];
68             }
69              
70             sub strings_or_undef {
71 0     0 0 0 my ($val, $name) = @_;
72 0         0 my $type = ref $val;
73 0 0       0 if (!defined $val) { $val = [] }
  0 0       0  
74 0         0 elsif (!$type) { $val = [ "$val" ] }
75 0 0       0 fatal "Value for '$name' option must be string, array of strings, or undef (not \L$type\E)"
76             unless ref $val eq 'ARRAY';
77 0         0 return $val;
78             }
79              
80             my $unlimited = ~0>>1; # Ersatz infinity
81              
82             sub height_vals {
83 0     0 0 0 my ($vals) = @_;
84 0         0 my $type = ref $vals;
85 0 0 0     0 if (!defined $vals) { $vals = {min=>0, max=>$unlimited} }
  0 0       0  
    0          
    0          
86             elsif (!$type && $vals eq 'minimal')
87 0         0 { $vals = {min=>0, max=>$unlimited, minimal=>1} }
88 0         0 elsif (!$type) { $vals = {min=>$vals, max=>$vals} }
89 0   0     0 elsif ($type eq 'HASH') { $vals->{min}||=0;
90 0 0       0 defined $vals->{max} or $vals->{max}=$unlimited;
91             }
92             fatal "Values for height option must be positive integers (not $_[0])"
93             unless ref $vals eq 'HASH'
94 0 0 0     0 && !grep {int($vals->{$_}) ne $vals->{$_}} qw(min max);
  0         0  
95 0         0 return $vals;
96             }
97              
98             my %nothing = map {$_=>sub{""}} qw(first even odd other);
99              
100             sub std_body {
101 22     22 0 23 my ($rows, $fill, $opt) = @_;
102 22         41 join("", @$rows, @$fill);
103             }
104             my %std_body = (other =>\&std_body);
105              
106             my %def_page = (
107             length => $unlimited,
108             width => 78,
109             header => \%nothing, # Args: ($opts)
110             footer => \%nothing, # Args: ($opts)
111             body => \%std_body, # Args: ($body_rows, $body_len, $opts)
112             feed => \%nothing, # Args: ($opts)
113             number => undef,
114             );
115              
116             sub form_body {
117 0     0 0 0 my ($format) = @_;
118 0 0       0 $format = '{[{*}[}' unless defined $format;
119             return sub {
120 0     0   0 my ($rows, $fill, $opt) = @_;
121 0         0 my %form_opts = ( page=>{width => $opt->{page}{width}} );
122 0         0 @{$form_opts{height}}{qw(min max)} = (@$rows+@$fill) x 2
123 0 0       0 unless $opt->{page}{length} == $unlimited;
124 0         0 return form(\%form_opts, $format, $rows);
125             }
126 0         0 }
127              
128             sub hashify {
129 0     0 0 0 my ($what, $val, $default_undef, $default_val) = @_;
130 0 0       0 if (!defined $val) {
131 0         0 return { other => $default_undef};
132             }
133 0 0       0 if (!ref $val) {
134 0         0 return { other => $default_val->($val) };
135             }
136 0 0       0 if (ref $val eq 'CODE') {
137 0         0 return { other => $val };
138             }
139 0 0       0 if (ref $val eq 'HASH') {
140             fatal "Invalid key for $what: '$_'"
141 0         0 for grep { !/^(first|last|even|odd|other)$/ } keys %$val;
  0         0  
142 0         0 my %hash;
143 0         0 for (keys %$val) {
144 0 0       0 if (!ref $val->{$_}) {
    0          
145 0         0 $hash{$_} = $default_val->($val->{$_})
146             }
147             elsif (ref $val->{$_} ne 'CODE') {
148 0         0 fatal "Value for $what '$_' must be string or subroutine";
149             }
150             else {
151 0         0 $hash{$_} = $val->{$_};
152             }
153             }
154 0         0 return \%hash;
155             }
156 0         0 fatal "Value for $what must be string, subroutine, or hash";
157             }
158              
159             sub page_hash {
160 0     0 0 0 my ($h, undef, $opts) = @_;
161             fatal "Value for 'page' option must be hash reference (not $_)"
162 0         0 for grep $_ ne 'HASH', ref $h;
163 0         0 $h = { %{$opts->{page}}, %$h };
  0         0  
164             fatal "Unknown page sub-option ('$_')"
165 0         0 for grep {!exists $def_page{$_}} keys %$h;
  0         0  
166             fatal "Page $_ must be greater than zero"
167 0         0 for grep $h->{$_} <= 0, qw(length width);
168             $h->{body} =
169 0         0 hashify("body preprocessor", $h->{body}, \&std_body, \&form_body);
170 0         0 for (qw( header footer feed )) {
171 0     0   0 $h->{$_} = hashify($_, $h->{$_}, sub{""}, sub{my($str)=@_; sub{$str}});
  0         0  
  0         0  
  0         0  
  0         0  
172             }
173 0         0 return $h;
174             }
175              
176             sub filehandle {
177             fatal "Value for 'out' option must be filehandle (not '$_')"
178 0     0 0 0 for grep {$_ ne 'GLOB' } ref $_[0];
  0         0  
179 0         0 return $_[0];
180             }
181              
182             sub user_def {
183 0     0 0 0 my ($spec, $name, $opts) = @_;
184 0         0 my $type = ref $spec;
185 0 0 0     0 fatal "Value of 'field' option must be an array of pairs or a hash (not ",
186             $type||"'$spec'", ")"
187             unless $type =~ /^(ARRAY|HASH)$/;
188 0 0       0 if ($type eq 'ARRAY') {
189 0 0       0 fatal "Missing value for last user-defined field ('$spec->[-1]')"
190             if @$spec % 2;
191             }
192             else {
193 0         0 $spec = [%$spec];
194             }
195 0   0     0 my @from = @{$opts->{field}{from}||=[]};
  0         0  
196 0   0     0 my @to = @{$opts->{field}{to}||=[]};
  0         0  
197 0         0 my $count = @from;
198 0         0 for (my $i=0; $i<@$spec; $i+=2, $count++) {
199 0         0 my ($pat, $fld) = @{$spec}[$i,$i+1];
  0         0  
200 0         0 push @from, "$pat(?{$count})";
201 0 0   0   0 push @to, (ref $fld eq 'CODE' ? $fld : sub{$fld});
  0         0  
202             }
203 0         0 return {from=>\@from, to=>\@to};
204             }
205              
206             my %std_opt = (
207             out => { set => \&filehandle, def => \*STDOUT, },
208             ws => { set => \&pattern, def => undef, },
209             fill => { set => \&defined_or_space, def => " ", },
210             lfill => { set => \&defined_or_space, def => undef, },
211             rfill => { set => \&defined_or_space, def => undef, },
212             hfill => { set => \&defined_or_space, def => undef, },
213             tfill => { set => \&defined_or_space, def => undef, },
214             bfill => { set => \&defined_or_space, def => undef, },
215             vfill => { set => \&defined_or_space, def => undef, },
216             single => { set => \&one_char, def => undef, },
217             field => { set => \&user_def, def => {from=>[],to=>[]} },
218             bullet => { set => \&strings_or_undef, def => [] },
219             height => { set => \&height_vals, def => {min=>0, max=>$unlimited} },
220             layout => { set => \&layout_word, def => 'balanced', },
221             break => { set => \&code, def => break_at('-'), },
222             page => { set => \&page_hash, def => {%def_page}, },
223             under => { set => sub {"$_[0]"}, def => undef },
224             interleave => { set => \&boolean, def => 0 },
225             untrimmed => { set => \&boolean, def => 0, },
226             locale => { set => \&boolean, def => 0, },
227             );
228              
229             my %def_opts = map {$_=>$std_opt{$_}{def}} keys %std_opt;
230              
231             sub get_locale_vals { # args: $dec_mark, $thou_sep, $thou_group
232 2     2   6584 use POSIX;
  2         10737  
  2         10  
233 0     0 0 0 $lconv = POSIX::localeconv();
234 0 0       0 $_[0] = exists $lconv->{decimal_point} ? $lconv->{decimal_point} : "?";
235 0 0       0 $_[1] = exists $lconv->{thousands_sep} ? $lconv->{thousands_sep} : "";
236 0 0       0 $_[2] = exists $lconv->{grouping} ? [unpack "c*", $lconv->{grouping}] : [0];
237             }
238              
239             my %std_literal = (
240             break => \&break_lit,
241             literal => 1,
242             vjust => \&jverlit,
243             hjust => \&jhorlit,
244             );
245              
246             sub update(\%\%;$) {
247 0     0 0 0 my ($old, $new, $croak) = @_;
248 0         0 my @bad;
249 0         0 for my $opt (keys %$new) {
250 0         0 my $std = $std_opt{$opt};
251 0 0 0     0 push @bad, "Unknown option: $opt=>'$new->{$opt}" and next unless $std;
252 0         0 $old->{$opt} = $std->{set}->($new->{$opt}, $opt, $old);
253             }
254 0 0 0     0 if (@bad && $croak) { croak join "\n", @bad }
  0 0       0  
255 0         0 elsif (@bad) { fatal join "\n", @bad }
256             }
257              
258              
259              
260             # Horizontal justifiers
261              
262             sub fillpat {
263 8     8 0 8 my ($pos, $fill, $len) = @_;
264 8 50       16 return "" if $len < 0;
265 8         25 return substr($fill x (($pos+$len)/length($fill)+1), $pos, $len);
266             }
267              
268       44 0   sub jhorlit {} # literals don't need any justification
269              
270             sub jverbatim {
271 0     0 0 0 jleft(@_, precropped=>1);
272             }
273              
274             sub jleft {
275 4     4 0 15 my (undef, %val) = @_;
276 4 50       11 $_[0] =~ s/^\s+// unless $val{precropped};
277 4         5 my $len = length $_[0];
278 4         11 $_[0] .= fillpat($val{pos}+$len, $val{post}, $val{width}-$len);
279 4 50       19 substr($_[0],$val{width}) = "" unless $val{stretch};
280             }
281              
282             sub jright {
283 4     4 0 9 my (undef, %val) = @_;
284 4 50       9 $_[0] =~ s/\s+$// unless $val{precropped};
285 4         9 $_[0] = fillpat($val{pos}, $val{pre}, $val{width}-length($_[0])) . $_[0];
286 4 50       18 substr($_[0],0,-$val{width}) = "" unless $val{stretch};
287             }
288              
289             sub jcentre {
290 0     0 0 0 my (undef, %val) = @_;
291 0         0 $_[0] =~ s/^\s+|\s+$//g;
292 0         0 $val{precropped} = 1;
293 0         0 my $indent = int( ($val{width}-length $_[0])/2 );
294 0         0 jleft($_[0], %val, stretch=>0, pos=>$val{pos}+$indent, width=>$val{width}-$indent);
295 0         0 jright($_[0], %val);
296             }
297              
298             sub jfull {
299 0     0 0 0 my ($str, %val) = @_;
300 0         0 my $rem = $val{width};
301 0         0 $str =~ s/^\s+|\s+$//g;
302 0 0       0 unless ($val{last}) {
303 0         0 my $rem = $val{width}-length($str);
304 0         0 $str = reverse $str;
305 0 0 0     0 1 while $rem>0 && $str =~ s/( +)/($rem-->0?" ":"").$1/ge;
  0         0  
306 0         0 $_[0] = reverse $str;
307             }
308 0         0 &jleft;
309             }
310              
311             sub jsingle {
312 0     0 0 0 my (undef, %val) = @_;
313 0 0       0 $_[0] = length $_[0] ? substr($_[0],0,1) : fillpat($val{pos}, $val{pre},1);
314             }
315              
316             sub jfatal {
317 0     0 0 0 die "Internal error in &form."
318             }
319              
320             sub joverflow (\%\%) {
321 0     0 0 0 $_[0]{overflow} = 1;
322 0         0 %{$_[1]} = ();
  0         0  
323 0         0 return \&jfatal;
324             }
325              
326             sub jbullet {
327 0     0 0 0 return ($_[0],1);
328             }
329              
330             sub jnum {
331 22     22 0 40 my ($fld,$precurr,$incurr,$postcurr,$width,$opts,$setplaces,$checkplaces)
332             = @_;
333 22         17 my $orig = $fld;
334 22   33     91 $incurr ||= qr/(?!)/;
335              
336 22         46 my $comma_count = $fld=~tr/,//;
337 22         19 my $period_count = $fld=~tr/.//;
338 22         17 my $apost_count = $fld=~tr/ '//;
339 22   33     125 my $integral = $comma_count > 1 && !($period_count || $apost_count)
340             || $period_count > 1 && !($comma_count || $apost_count)
341             || $apost_count > 1 && !($comma_count || $period_count);
342 22 100       90 my ($whole, $point, $places) =
343             $integral ? ($fld, "", "")
344             : ($fld =~ /^([]{>,.' 0]*)([.,]|\Q$incurr\E)([[}<0]*)/g);
345              
346 22         25 my $missing = $width-length($fld);
347 22 100       31 if ($missing>0) { $fld = substr($fld,0,1) x $missing . $fld }
  2         5  
348              
349 22 50       41 $opts->{lfill} = '0' if $whole =~ m/^0+/;
350 22 50       33 $opts->{rfill} = '0' if $places =~ m/0+$/;
351 22 50       57 my $comma = $whole =~ /([,.' ])/ ? $1 : '';
352 22         15 my $grouping;
353 22 50       54 if ($comma) {
354 22 50       131 $grouping = $whole =~ /,(?:\]{2},\]{3}|>{2},>{3})\z/ ? [3,2] # Subcont
    50          
355             : $whole =~ /[,.' ](\]+|>+)\z/ ? [length($1)]
356             : undef;
357             }
358 22 100       29 if (defined $setplaces) {
359 8         10 $places = $setplaces;
360 8         11 $whole = $width - length($point) - $setplaces;
361             }
362             else {
363 14         32 $_ = length for $whole, $places;
364             }
365 22 50 33     47 fatal "Inconsistent number of decimal places in numeric field.\n",
366             "Specified as $checkplaces but found $places"
367             if $checkplaces && $places != $checkplaces;
368 22         66 my $huh = substr( ('?'x$whole).$point.('?'x$places), 0, $width);
369 22         31 my $duh = substr( ('#'x$whole).$point.('#'x$places), 0, $width);
370              
371 22         20 $places -= length($postcurr);
372              
373 22 50       39 get_locale_vals($point, $comma, $grouping) if $opts->{locale};
374              
375             return sub {
376 22     22   76 my ($orig, %val) = @_;
377 22 50 0     62 $_[0] = " "x$val{width} and return if $orig =~ /^\s*$/;
378 22 100       104 $orig =~ s/,|\Q$incurr\E/./ if $point ne '.';
379 22         24 my ($pre,$post) = ($precurr,$postcurr);
380 22 50 33     143 if ($orig !~ /^\s*-/ || $orig == -$orig) {
381 0         0 $pre =~ s/^[(-]|[(-]$/ /g;
382 0         0 $post =~ s/^[)-]|[)-]$/ /g;
383             }
384 22         17 my ($fail, $str);
385 22 100       29 if ($integral) {
386 15         67 local $SIG{__WARN__} = sub { $fail = 1 };
  0         0  
387 15         85 $str = sprintf('%*d',$val{width},int($orig));
388             }
389             else {
390 7         31 local $SIG{__WARN__} = sub { $fail = 1 };
  0         0  
391 7         73 $str = sprintf('%*.*f',$val{width},$places,$orig);
392             }
393 22 50       61 if ($fail) {
394 0         0 $_[0] = $huh;
395             }
396             else {
397 22         48 my ($w,$p) = ($str =~ /^\s*(.*)\.(.*)$/g); # floating point
398 22 100       64 ($w,$p) = ($str =~ /^\s*(.*)$/,"") if !$w; # integer
399 22 50       35 if ($grouping) {
400 22         26 my @groups = @$grouping;
401 22         20 my $group = shift @groups;
402 22 50       28 if ($group) {
403 22         154 $w =~ s/(\d)(\d{$group})\z/$1$comma$2/;
404 22   33     19 do {
405 22 50       184 $group = shift @groups if @groups;
406             } while $group && $w =~ s/(?
407             }
408             }
409 22 50 66     106 if (length($w) > $width || !$val{stretch} && ($w ? length($w) : 0)+length($pre) > $whole) {
    100 66        
410 4         17 $_[0] = $duh;
411             }
412             else {
413 18 100       29 $str = $integral ? $w : $w.q(.).$p;
414 18 50       91 $str =~ s/(\.\d+?)(0+)$/$1/
415             unless $orig =~ /\.\d\{$places,\}[1-9]/;
416 18 100 66     62 if ($integral && $str < 0) {
417 14 100 66     41 if ($pre =~ /[(]/ || $post =~ /[)]/) {
418 2         5 $str =~ s/-//;
419             }
420             else {
421 12         30 s/-/ / for $pre, $post;
422             }
423             }
424 18         83 $str =~ s/^(?:\Q$pre\E)?/$pre/;
425 18 50       42 if ($val{pre} =~ /^0+$/) {
426 0         0 $str =~ s{^((\D*)(\d.*))\.}
427 0         0 {$2 . ("0" x ($whole-length $1)) . "$3."}e;
428 0         0 $val{pre} = " ";
429             }
430 18         17 my $postlen = length($post);
431 18 100       21 if (!$integral) {
432 4         16 $str =~ s/^(.*)\./$1$point/;
433 4         6 my $width = $val{width}-$whole+length($1);
434 4         14 jleft($str, %val, width=>$width, precropped=>1);
435 4         12 jright($str, %val, precropped=>1);
436             }
437 18 100       27 if ($integral) {
    50          
438 14         36 $str = substr((q{ } x $width) . $str . $post, -$width);
439 14         118 $str =~ s/(?:[ ]{$postlen}([ ]*))$/$post$+/;
440             }
441             elsif ($postlen) {
442 0         0 $str =~ s/(?:[ ]{$postlen}([ ]*)|.{$postlen}())$/$post$+/;
443             }
444 18         74 $_[0] = $str;
445             }
446             }
447             }
448 22         158 }
449              
450              
451             # Vertical justifiers
452              
453             sub jverlit {
454 44     44 0 45 my ($height, $above, $below, $column) = @_;
455 44   50     162 push @$column, ($column->[0]||"") while @$column < $height;
456             }
457              
458             sub jmiddle {
459 0     0 0 0 my ($height, $above, $below, $column) = @_;
460 0         0 my $add = int(($height-@$column)/2);
461 0         0 splice @$column, 0, 0, ($above)x$add;
462 0         0 $add = $height-@$column;
463 0         0 push @$column, ($below)x$add;
464             }
465              
466             sub jbottom {
467 0     0 0 0 my ($height, $above, $below, $column) = @_;
468 0         0 my $pre = $height-@$column;
469 0         0 splice @$column, 0, 0, ($above)x$pre;
470             }
471              
472             sub jtop {
473 22     22 0 24 my ($height, $above, $below, $column) = @_;
474 22         23 my $post = $height-@$column;
475 22         30 push @$column, ($below)x$post;
476             }
477              
478              
479             my $precurrpat = qr/^(\{) ([^]0>[<,']+?) ([]>,'0])/x;
480             my $incurrpat = qr/([]>0]) ([^]0>[<,'. ]+?) ([[<0]) /x;
481             my $postcurrpat = qr/([[<>0]) ([^]0>[<]+) (\}$) /x;
482              
483             sub perl6_match {
484 0     0 0 0 my ($str, $pat) = @_;
485 2     2   9261 use re 'eval';
  2         4  
  2         840  
486 0 0       0 if (my @vals = $str =~ /$pat/) {
487 0         0 unshift @vals, $&;
488 0         0 bless \@vals, 'Perl6::Form::Rule::Okay';
489             }
490             else {
491 0         0 bless [], 'Perl6::Form::Rule::Fail';
492             }
493             }
494              
495             my $litval;
496             sub litval {
497 66 100   66 0 157 ($litval) = @_ if @_;
498 66         191 return $litval;
499             }
500              
501             my ($fld, $udnum);
502             sub fldvals {
503 44 100   44 0 87 ($fld, $udnum) = @_ if @_;
504 44         965 return ($fld, $udnum);
505             }
506              
507             our $nestedbraces = qr/ \{ (?: (?> ((?!\{|\}).)+ ) | (??{ $nestedbraces }) )* \} /sx;
508              
509             sub segment ($\@\%$\%) {
510 22     22 0 25 my ($format, $args, $opts, $fldcnt, $argcache) = @_;
511             my $width =
512 22 50       49 defined $opts->{page}{width} ? $opts->{page}{width} : length($format);
513 22   33     19 my $userdef = join("|", @{$opts->{field}{from}}) || qr/(?!)/;
514 22   33     25 my $bullet = join("|", map quotemeta, @{$opts->{bullet}}) || qr/(?!)/;
515 2     2   11 use re 'eval';
  2         2  
  2         13227  
516 22         15 my @format;
517 22         1125 while ($format =~ /\G ((?>(?:\\.|(?!$userdef|$bullet|\{).)*))
518 44         55 (?{litval($^N)})
519 0         0 (?: ($userdef) (?{fldvals($^N,$^R)})
520 0         0 | ($bullet) (?{fldvals($^N,-1)})
521 22         27 | ($nestedbraces) (?{fldvals($^N,undef)})
522             )
523             /gcsx) {
524 22         48 push @format, litval(), fldvals();
525             }
526 22   50     96 push @format, substr ($format, pos($format)||0);
527 22         49 my $args_req = int(@format/3);
528 22         20 my (@formatters,@starred,@vstarred);
529 22         36 for my $i (0..$args_req) {
530 44         175 my ($literal,$field,$userdef) = @format[3*$i..3*$i+2];
531 44         57 $literal =~ s/\\\{/{/g;
532 44         140 push @formatters, { %std_literal,
533             width => length($literal),
534             src => \$literal,
535             };
536 44         48 $width -= length($literal);
537 44 100       73 if (defined $field) {
538 22         18 my %form;
539 22         153 my %fldopts = %$opts;
540 22         28 $fldcnt++;
541 22         18 my ($setwidth, $setplaces, $checkwidth, $checkplaces);
542 22 50       29 if (defined $userdef) {
543 0 0       0 if ($userdef < 0) {
544 0         0 $form{isbullet} = \"$field";
545             }
546             else {
547             my ($from,$to) =
548 0         0 map $_->[$userdef], @{$opts->{field}}{'from','to'};
  0         0  
549 0         0 $field = $to->(perl6_match($field,$from),\%fldopts);
550             }
551             }
552 22         18 my $fld = $field;
553 22         39 my ($precurr, $incurr, $postcurr) = ("")x3;
554 22         30 $form{width} = length $field;
555 22 50       29 if ($form{isbullet}) {
556 0         0 $form{vjust} = \&jtop;
557 0         0 $form{hjust} = \&jbullet;
558 0         0 $form{break} = \&break_bullet;
559 0         0 $form{src} = [];
560 0         0 ($form{bullethole} = $field) =~ s/./ /gs;
561             }
562             else {
563 22   33     88 $form{stretch} = !$form{isbullet} && $fld =~ s/[+]//;
564 22 50 33     52 @form{qw(verbatim break hjust)}
565             = (1, \&break_verbatim, \&jverbatim)
566             if $fld =~ /["']/ && $fld !~ /[][><]/;
567             # was: if $fld =~ /["']/ && $fld !~ /[][]/;
568 22         94 $form{trackpos} = $fld =~ s/(\{):|:(\})/$+/g;
569 22 50       63 $form{vjust} = $fld =~ s/=//g ? \&jmiddle
    50          
570             : $fld =~ s/_//g ? \&jbottom
571             : \&jtop
572             ;
573              
574 22         33 ($checkwidth, $extras) = $fld =~ m/\(\s*(\d+[.,]?\d*)\s*\)/g;
575 22 50       32 fatal "Too many width specifications in $field" if $extras;
576 22 50       26 if ($checkwidth) {
577 0   0     0 $checkplaces = $checkwidth =~ s/[.,](\d+)// && $1;
578 0         0 for ($fld) {
579 0 0       0 s{([][>
580 0         0 { $1 . ($1 x length $2) }xe and last;
581 0 0       0 s{(\(\s*\d+[.,]?\d*\s*\)) ([][>
582 0         0 { ($2 x length $1) . $2 }xe and last;
583 0 0       0 s{(> [.,]) (\(\s*\d+[.,]?\d*\s*\))}
584 0         0 { $1 . ('<' x length $2) }xe and last;
585 0 0       0 s{(\(\s*\d+[.,]?\d*\s*\)) ([.,] <)}
586 0         0 { ('>' x length $1) . $2 }xe and last;
587 0 0       0 s{(\(\s*\d+[.,]?\d*\s*\)) ([.,] \[)}
588 0         0 { (']' x length $1) . $2 }xe and last;
589 0 0       0 s{(\(\s*\d+[.,]?\d*\s*\))}
590 0         0 { '[' x length $1 }xe and last;
591             }
592             }
593              
594 22 100       141 ($setwidth, $extras) = $fld =~ m/\{\s*(\d+[.,]?\d*|\*)\s*\}/g
595             and $fld =~ s/\{\s*(\d+[.,]?\d*|\*)\s*\}//;
596 22 50 66     96 fatal "Too many width specifications in $field"
      33        
597             if $extras || $setwidth && $checkwidth;
598 22 100 100     67 if ($setwidth && $setwidth =~ s/[.,](\d+)//) {
599 8   100     25 $setplaces = $1 || 0;
600             }
601              
602 22         54 for ([$checkwidth, $checkplaces], [$setwidth, $setplaces]) {
603 44 0 100     141 fatal "Can't fit $_->[1] decimal place",($_->[1]!=1?'s':''),
    50 66        
      66        
604             " in a $_->[0]-character field"
605             if defined($_->[0]) && defined($_->[1])
606             && $_->[0] ne '*'
607             && $_->[0] <= $_->[1];
608             }
609              
610             $precurr =
611 22 100       112 $fld =~ s/$precurrpat/$1.($3 x length $2).$3/e ? "$2" : "";
  5         29  
612 22 50       89 $incurr =
613             $fld =~ m/$incurrpat/ ? "$2" : "";
614 22 100       88 $postcurr =
615 2         9 $fld =~ s/$postcurrpat/$1.($1 x length $2).$3/e ? "$2" : "";
616              
617 22 50       66 if ($form{width} == 2) {
    50          
    50          
618 0         0 $fld = '[[';
619             }
620             elsif ($form{width} == 3) {
621 0         0 $fld =~ s/^ \{ ([.,]) \} $/].[/x;
622 0         0 $fld =~ s/^ \{ (.) \} $/$+$+$+/x;
623             }
624             elsif ($form{width} > 3) {
625 22         93 $fld =~ s/^ \{ ([]>,]+ ([]>])) \} $/$2$2$1/x; # Integral comma'd field
626 22         25 $fld =~ s/^ \{ ([.,] \[) /]$1/x;
627 22         29 $fld =~ s/^ \{ ([.,] \<) />$1/x;
628 22         22 $fld =~ s/(\] .* [.,]) \} $/$1\[/x;
629 22         44 $fld =~ s/(\> .* [.,]) \} $/$1
630 22         91 $fld =~ s/^ \{ (.) | (.) \} $/$+$+/gx;
631             }
632              
633 22 100 66     74 $form{width} = $setwidth
634             if defined $setwidth && $setwidth ne '*';
635              
636 22 50       64 if ($form{width} == 2) {
    50          
    50          
637 0         0 $fld = substr($fld,0,1) x 2;
638             }
639             elsif ($form{width} == 3) {
640 0         0 $fld =~ s/^ \{ ([.,]) \} $/].[/x;
641 0         0 $fld =~ s/^ \{ (.) \} $/$+$+$+/x;
642             }
643             elsif ($form{width} > 3) {
644 22         21 $fld =~ s/^ \{ ([.,] \[) /]$1/x;
645 22         16 $fld =~ s/^ \{ ([.,] \<) />$1/x;
646 22         45 $fld =~ s/(\] .* [.,]) \} $/$1\[/x;
647 22         19 $fld =~ s/(\> .* [.,]) \} $/$1
648 22         54 $fld =~ s/^ \{ (.) | (.) \} $/$+$+/gx;
649             }
650              
651 22 100 66     65 $form{width} = $setwidth
652             if defined $setwidth && $setwidth ne '*';
653             }
654              
655 22 50 66     50 if ($setwidth && $setwidth eq '*') {
656 0 0       0 push @{$form{verbatim} ? \@vstarred : \@starred}, \%form;
  0         0  
657             }
658             else {
659             $width -= $form{width}
660 22         23 }
661              
662 22 50 33     93 $form{line} = 1 unless $form{isbullet} || $fld =~ /[][IV"]/;
663              
664             $form{hjust} ||= $form{width} == 1 ? \&jsingle
665             : ($fld =~ /^(?:<+|\[+)$/) ? \&jleft
666             : ($fld =~ /^(?:>+|\]+)$/) ? \&jright
667             : ($fld =~ /^(?:I+|\|+|>+<+|\]+\[+)$/)? \&jcentre
668             : ($fld =~ /^(?:<+>+|\[+\]+)$/) ? \&jfull
669             : ($fld =~ /^(?:V+)$/) ? joverflow(%form, %fldopts)
670             : ($fld =~ /^(?: [>,' 0]* \. [<0]*
671             | [],' 0]* \. [[0]*
672             | [>.' 0]* \, [<0]*
673             | [].' 0]* \, [[0]*
674             | [>.,' 0]* \Q$incurr\E [<0]*
675             | [].,' 0]* \Q$incurr\E [[0]*
676             | [].' 0]* \, [[0]*
677 22 50 33     325 )$/x) ? do {
    50          
    50          
    50          
    50          
    50          
    50          
678 22         30 $form{break}=\&break_nl;
679             jnum($fld,$precurr,$incurr,$postcurr,
680 22         48 $form{width},\%fldopts,
681             $setplaces, $checkplaces)
682             }
683             : fatal "Field $fldcnt is of unknown type: $field"
684             ;
685              
686 22 50       43 $form{break}=\&break_nl if $form{stretch};
687              
688             fatal "Inconsistent width for field $fldcnt.\n",
689             "Specified as '$field' but actual width is $form{width}"
690 22 50 33     38 if defined $checkwidth && $form{width} != $checkwidth;
691              
692 22 50       30 splice @$args, $i, 0, "" if $form{isbullet}; # BEFORE ANY OPTIONS
693              
694 22         51 while (ref $args->[$i] eq 'HASH') {
695 0         0 update %fldopts, %{splice @$args, $i, 1};
  0         0  
696             }
697 22         36 $form{opts} = \%fldopts;
698              
699 22 50       38 splice @$args, $i, 0, "" if $form{overflow}; # AFTER ANY OPTIONS
700              
701 22 50       33 fatal "Missing data value for field ", $i, " ($field)"
702             unless defined $args->[$i];
703              
704 22         28 for ($args->[$i]) {
705 22 50       31 next if $form{isbullet};
706             $form{src} ||=
707             ref eq 'ARRAY' ? do {
708 0         0 my $s = join "", map { my $val = $_; $val =~ s/\n(?!\z)/\r/g; $val }
  0         0  
  0         0  
709 0 0       0 map {!defined() ? "\n"
  0 0       0  
710             : /\n\z/ ? $_
711             : "$_\n"} @$_;
712 0 0 0     0 $form{trackpos} ? ($argcache->{$_} ||= \$s) : \$s;
713             }
714 22 50 33     153 : (readonly $_ || !$form{trackpos}) ? \(my$s=$_)
    50 33        
715             : \$_;
716             }
717              
718 22   0     35 $form{break} ||= $fldopts{break} || $opts->{break};
      33        
719              
720 22         57 push @formatters, \%form;
721             }
722             }
723 22         29 splice @$args, 0, $args_req;
724 22         24 $_[-1] = $fldcnt; # remember field count
725             # Distribute {*} widths...
726 22         26 for my $f (@vstarred) {
727 0         0 $f->{maxwidth} = max 0, map {length} split "\n", ${$f->{src}};
  0         0  
  0         0  
728             }
729 22 50 33     73 if (@starred||@vstarred) {
730 0         0 my $fldwidth = int($width/(@starred+@vstarred));
731 0         0 for my $f (@vstarred) {
732             $f->{width} = @starred ? $f->{maxwidth}
733 0 0       0 : min $fldwidth, $f->{maxwidth};
734 0         0 $width += $fldwidth - $f->{width};
735             }
736 0 0       0 $fldwidth = int($width/(@starred+@vstarred)) if @starred;
737 0         0 $_->{width} = $fldwidth for @starred;
738             }
739              
740             # Attach bullets to neighbouring fields,
741             # and compute offsets from left margin...
742 22         19 my $offset = 0;
743 22         15 my $lastbullet;
744 22         22 for my $f (@formatters) {
745 66         49 $f->{pos} = $offset;
746 66         54 $offset += $f->{width};
747 66 50       68 if ($lastbullet) {
748 0 0       0 if ($f->{literal}) { # IGNORE IT
    0          
749             }
750             elsif ($f->{isbullet}) {
751 0         0 my $literal = ${$lastbullet->{isbullet}};
  0         0  
752 0         0 %$lastbullet = (%std_literal, width=>length($literal), src=>\$literal);
753 0         0 $lastbullet = undef;
754             }
755             else {
756 0         0 $f->{hasbullet} = $lastbullet;
757 0         0 $lastbullet = undef;
758             }
759             }
760 66 50       89 $lastbullet = $f if $f->{isbullet};
761             }
762 22 50       29 if ($lastbullet) {
763 0         0 my $literal = ${$lastbullet->{isbullet}};
  0         0  
764 0         0 %$lastbullet = (%std_literal, width=>length($literal), src=>\$literal);
765             }
766              
767 22         75 return \@formatters;
768             }
769              
770             sub layout_groups {
771 44     44 0 33 my @groups;
772 44         31 my $i = 0;
773 44         45 FORMATTER: for my $f (@_) {
774 66         60 $f->{index} = $i++;
775 66         52 for my $group (@groups) {
776 66 50       133 if ($f->{src} == $group->[0]{src}) {
777 0         0 push @$group, $f;
778 0         0 next FORMATTER;
779             }
780             }
781 66         74 push @groups, [$f];
782             }
783 44         65 return @groups;
784             }
785              
786             sub make_col {
787 66     66 0 47 my ($f, $opts, $maxheight, $tabular) = @_;
788             $maxheight = min $unlimited,
789 66         155 grep defined(), $maxheight, $f->{opts}{height}{max};
790 66         43 my ($str_ref, $width) = @{$f}{qw(src width)};
  66         76  
791 66         47 my @col;
792 66         57 my ($more, $text) = (1,"");
793 66         54 my $bullet = $f->{hasbullet};
794 66 50       74 $bullet->{bullets} = [] if $bullet;
795 66         41 my $bulleted = 1;
796 66         87 until ($f->{done}) {
797 66         42 my $skipped = 0;
798 66 50 66     220 unless ($f->{isbullet} || $f->{width} == 1 || $f->{verbatim}) {
      66        
799 44         297 ($skipped) = ($$str_ref =~ /\G(\s*)/gc);
800 44 50 50     138 if ($skipped||=0) {
801 0         0 $bulleted = ($skipped =~ /\n/);
802 0         0 $skipped=~s/\r\Z//;
803 0         0 $skipped = ($skipped=~tr/\r//);
804 0         0 push @col, ("") x $skipped;
805 0 0 0     0 last if $tabular && $bulleted && @col;
      0        
806             }
807             }
808 66         121 ($text,$more,$eol) = $f->{break}->($str_ref,$width,$f->{opts}{ws});
809 66 50       120 if ($f->{opts}{ws}) {
810 0         0 $text =~ s{($f->{opts}{ws})}
811 0         0 { @caps = grep { defined $$_ } 2..$#+;
  0         0  
812 0 0       0 @caps = length($1) ? " " : "" unless @caps;
    0          
813 0         0 join "", @caps;
814             }ge;
815             }
816 66 100       104 $text .= "\r" if $eol;
817 66         66 push @col, $text;
818 66 50 33     94 if ($bullet && $text =~ /\S/) {
819 0         0 push @{$bullet->{bullets}}, ($bullet->{bullethole}) x $skipped;
  0         0  
820 0         0 push @{$bullet->{bullets}}, $bulleted ? ${$bullet->{isbullet}}
  0         0  
821 0 0       0 : $bullet->{bullethole};
822             }
823             $f->{done} = 1
824 66 50 66     134 if defined $f->{opts}{height}{max} && @col==$f->{opts}{height}{max};
825 66 50 33     122 last if !$more || @col==$maxheight;
826 0 0       0 $f->{done} = 1 if $f->{line};
827 0         0 $bulleted = 0;
828             }
829 66 100 66     188 @col = () if @col == 1 && $col[0] eq "";
830 66 50 0     88 $_[3] = $more && !$f->{done} if @_>3;
831 66         102 return \@col;
832             }
833              
834             my $count = 0;
835              
836             sub balance_cols {
837 66     66 0 49 my ($group, $opts, $maxheight) = @_;
838 66         68 my ($first, $src) = ($group->[0], $group->[0]{src});
839 66 50       101 if (@$group<=1) {
840 66         72 $first->{formcol} = make_col($first,$opts,$maxheight);
841 66         77 return;
842             }
843 0   0     0 my $pos = pos($$src) || 0;
844 0         0 my $minheight = 0;
845 0         0 while (1) {
846 0         0 my @cols;
847 0         0 pos($$src) = $pos;
848 0         0 my $medheight = int(($maxheight+$minheight+1)/2);
849 0         0 for my $f (@$group) {
850 0         0 $f->{done} = 0;
851 0         0 push @cols, make_col($f,$opts,$medheight)
852             }
853 0 0       0 if ($maxheight <= $minheight+1) {
854 0         0 for (0..$#cols) {
855 0         0 $group->[$_]{formcol} = $cols[$_];
856             }
857 0         0 return;
858             }
859 0 0       0 (substr($$src,pos$$src) =~ /\S/) ? $minheight : $maxheight = $medheight;
860             }
861             }
862              
863             sub delineate_overflows {
864 22     22 0 22 for my $formats (@_) {
865             # Is there a block field on the line?
866 22 50       24 next if grep { !( $_->{line}
867             || $_->{literal}
868             || $_->{notlastoverflow}
869             )
870 66   33     179 } @$formats;
871 22         24 for (@$formats) {
872 66 50       95 next unless $_->{overflow};
873 0 0       0 if ($_->{notlastoverflow}) {
874 0         0 $_->{line} = 1;
875             }
876             }
877             }
878 22         24 for my $formats (@_) {
879 22         22 for (@$formats) {
880 66 50 33     113 next if !$_->{overflow} || $_->{notlastoverflow};
881 0         0 $_->{opts}{height}{max} = $unlimited;
882 0         0 $_->{opts}{height}{minimal} = 0;
883             }
884             }
885             }
886              
887             sub resolve_overflows {
888 22     22 0 19 my ($formatters,$prevformatters) = @_;
889 22         26 FORMATTER: for my $fld (@$formatters) {
890 66 50       99 next unless $fld->{overflow};
891 0         0 my $left = $fld->{pos};
892 0         0 my $right = $left + $fld->{width} - 1;
893 0         0 my $overflowed;
894 0         0 for my $prev (@$prevformatters) {
895 0 0       0 next if $prev->{literal};
896 0         0 my $prevleft = $prev->{pos};
897 0         0 my $prevright = $prevleft + $prev->{width} - 1;
898 0 0 0     0 if ($right >= $prevleft && $left <= $prevright) { # overlap
899 0 0       0 if ($overflowed) {
900             $prev->{notlastoverflow} = 1
901 0 0 0     0 if $prev->{overflow} && $prev->{src} == $fld->{src};
902 0         0 next;
903             }
904 0         0 my %newfld = ( %$prev, opts=>{}, overflow=>1 );
905 0         0 my @keep = qw( width pos complete done line );
906 0         0 @newfld{@keep} = @{$fld}{@keep};
  0         0  
907 0         0 update %{$newfld{opts}}, %{$fld->{opts}};
  0         0  
  0         0  
908 0         0 $newfld{opts}{height} = {min=>0, max=>undef, minimal=>1};
909 0         0 $fld = \%newfld;
910 0 0       0 $prev->{notlastoverflow} = 1 if $prev->{overflow};
911 0         0 $overflowed = 1;
912             }
913             }
914 0 0       0 croak "Useless overflow field (no field above it)"
915             unless $overflowed;
916             }
917             }
918              
919             sub make_cols($$\@\%$) {
920 22     22 0 24 my ($formatters,$prevformatters,$parts, $opts, $maxheight) = @_;
921 22         15 my (@bullets, @max, @min);
922 22         21 for my $f (@$formatters) {
923 66 50       149 if ($f->{isbullet}) { push @bullets, $f }
  0 50       0  
924 0         0 elsif ($f->{opts}{height}{minimal}) { push @min, $f }
925 66         64 else { push @max, $f }
926             }
927 22         33 my @maxgroups = layout_groups(@max);
928 22         25 my @mingroups = layout_groups(@min);
929 22   66     22 my $has_nonminimal = grep {!$_->{literal} && !$_->{line}} @max;
  66         151  
930 22 50       31 if ($opts->{layout} eq 'balanced') { # balanced column-by-column
    0          
    0          
931 22         22 for my $g (@maxgroups) {
932 66         72 balance_cols($g,$opts, $maxheight);
933             }
934 66 50       109 $maxheight = map 0+@{$_->{formcol}||[]}, @$formatters
935 22 50 66     27 if grep {!$_->{literal} && !$_->{opts}{height}{minimal}} @$formatters;
  66         162  
936 22         23 for my $g (@mingroups) {
937 0         0 balance_cols($g, $opts, $maxheight);
938             }
939 22         22 for my $f (@$formatters) {
940 66   0     104 push @$parts, $f->{formcol}||$f->{bullets}||[];
941             }
942             }
943             elsif ($opts->{layout} eq 'down') { # column-by-column
944 0         0 for my $col (0..$#$formatters) {
945 0         0 my $f = $formatters->[$col];
946 0 0 0     0 next if $f->{isbullet} || $f->{opts}{height}{minimal};
947 0         0 $parts->[$col] = make_col($f,$opts, $maxheight);
948             }
949             $maxheight = min $maxheight,
950 0 0       0 max map { defined() ? scalar @$_ : 0 } @$parts
  0 0       0  
951             if $has_nonminimal;
952 0         0 for my $col (0..$#$formatters) {
953 0         0 my $f = $formatters->[$col];
954 0 0 0     0 next if $f->{isbullet} || !$f->{opts}{height}{minimal};
955 0         0 $parts->[$col] = make_col($f,$opts, $maxheight);
956             }
957 0         0 for my $col (0..$#$formatters) {
958 0         0 my $f = $formatters->[$col];
959 0 0       0 next unless $f->{isbullet};
960 0   0     0 $parts->[$col] = $f->{bullets}||[];
961             }
962             }
963             elsif ($opts->{layout} eq 'across') { # across row-by-row
964 0         0 my %incomplete = (first=>1);
965 0   0     0 for (my $row=0;$row<$maxheight && grep {$_} values %incomplete;$row++) {
  0         0  
966 0         0 %incomplete = ();
967 0         0 for my $col (0..$#$formatters) {
968 0   0     0 $parts->[$col] ||= [];
969             }
970 0         0 for my $col (0..$#$formatters) {
971 0         0 my $f = $formatters->[$col];
972 0 0 0     0 next if $f->{isbullet} || $f->{opts}{height}{minimal};
973 0 0 0     0 next if $f->{line} && $row>0 || $f->{done};
      0        
974 0         0 my ($str_ref, $width) = @{$f}{qw(src width)};
  0         0  
975 0 0       0 $$str_ref =~ /\G\s+/gc unless $f->{verbatim};
976             ($parts->[$col][$row], my $more) =
977 0         0 $f->{break}->($str_ref,$width,$f->{opts}{ws});
978 0 0       0 $parts->[$col][$row] =~ s/$f->{opts}{ws}/ /g if $f->{opts}{ws};
979             $f->{done} = 1 if !$f->{literal}
980 0 0 0     0 && $row+1 >= ($f->{opts}{height}{max}||$maxheight);
      0        
981             $incomplete{$str_ref} = $more
982 0 0 0     0 unless $f->{literal} || $f->{line} || $f->{done};
      0        
983             }
984 0         0 for my $col (0..$#$formatters) {
985 0         0 my $f = $formatters->[$col];
986 0 0 0     0 next if $f->{isbullet} || !$f->{opts}{height}{minimal};
987 0 0 0     0 next if $f->{line} && $row>0 || $f->{done};
      0        
988 0         0 my ($str_ref, $width) = @{$f}{qw(src width)};
  0         0  
989 0 0       0 $$str_ref =~ /\G\s+/gc unless $f->{verbatim};
990             ($parts->[$col][$row], my $more) =
991 0         0 $f->{break}->($str_ref,$width,$f->{opts}{ws});
992 0 0       0 $parts->[$col][$row] =~ s/$f->{opts}{ws}/ /g if $f->{opts}{ws};
993             $f->{done} = 1 if !$f->{literal}
994 0 0 0     0 && $row+1 >= ($f->{opts}{height}{max}||$maxheight);
      0        
995             $incomplete{$str_ref} = $more
996 0 0 0     0 unless $has_nonminimal || $f->{done};
997             }
998 0         0 for my $col (0..$#$formatters) {
999 0         0 my $f = $formatters->[$col];
1000 0 0       0 next unless $f->{isbullet};
1001 0         0 $parts->[$col][$row] = shift @{$f->{bullets}};
  0         0  
1002             }
1003             }
1004             }
1005             else { # tabular layout: down to the first \n, then across, then fill
1006 0         0 my $finished = 0;
1007 0         0 for my $col (0..$#$formatters) { $parts->[$col] = []; }
  0         0  
1008 0         0 while (!$finished) {
1009 0         0 $finished = 1;
1010 0         0 for my $col (0..$#$formatters) {
1011 0         0 my $tabular_more = 1;
1012 0         0 my $f = $formatters->[$col];
1013 0 0 0     0 next if $f->{isbullet} || $f->{opts}{height}{minimal};
1014 0         0 push @{$parts->[$col]},
1015 0         0 @{make_col($f,$opts, $maxheight, $tabular_more)};
  0         0  
1016 0   0     0 $finished &&= !$tabular_more;
1017             }
1018             my $minimaxheight = min $maxheight,
1019 0 0       0 max map { defined() ? scalar @$_ : 0 } @$parts
  0 0       0  
1020             if $has_nonminimal;
1021 0         0 for my $col (0..$#$formatters) {
1022 0         0 my $tabular = 1;
1023 0         0 my $f = $formatters->[$col];
1024 0 0 0     0 next if $f->{isbullet} || !$f->{opts}{height}{minimal};
1025 0         0 push @{$parts->[$col]},
1026 0         0 @{make_col($f,$opts, $maxheight, $tabular)};
  0         0  
1027             }
1028 0         0 for my $col (0..$#$formatters-1) {
1029 0         0 my $f = $formatters->[$col];
1030 0 0       0 if ($f->{isbullet}) {
    0          
1031 0 0       0 push @{$parts->[$col]}, @{$f->{bullets}||[]};
  0         0  
  0         0  
1032 0         0 push @{$parts->[$col]},
1033 0         0 ($f->{bullethole})x($minimaxheight-@{$parts->[$col]});
  0         0  
1034             }
1035             elsif ($f->{literal}) {
1036 0         0 push @{$parts->[$col]},
1037 0         0 (${$f->{src}})x($minimaxheight-@{$parts->[$col]});
  0         0  
  0         0  
1038             }
1039             else {
1040 0         0 push @{$parts->[$col]},
1041 0         0 ("")x($minimaxheight-@{$parts->[$col]});
  0         0  
1042             }
1043             }
1044 0   0     0 $maxheight -= $minimaxheight||0;
1045             }
1046             }
1047 22         24 for my $g (@maxgroups, @mingroups) {
1048 66         51 my $text = $g->[-1]{src};
1049 66 50 100     250 next if substr($$text,pos($$text)||0) =~ /\S/;
1050 66         83 for (1..@$g) {
1051 66 100       32 next unless @{$parts->[$g->[-$_]{index}]};
  66         143  
1052 44         43 $g->[-$_]{final} = 1;
1053 44         45 last;
1054             }
1055             }
1056 22         26 for my $i (1..@$parts) {
1057 66         60 $formatters->[-$i]{complete} = 0
1058             }
1059 22   66     20 for my $f (grep {!($_->{literal}||$_->{line})} @$formatters) {
  66         133  
1060 0 0 0     0 next if $f->{done} || $f->{isbullet} || $f->{opts}{height}{minimal};
      0        
1061 0 0 0     0 return 1 if substr(${$f->{src}},pos(${$f->{src}})||0) =~ /\S/;
  0         0  
1062             }
1063 22         52 return 0;
1064             }
1065              
1066             sub make_underline {
1067 0     0 0 0 my ($under, $prevline, $nextline) = @_;
1068 0         0 $under =~ s/(\n*)\z//;
1069 0         0 my $trail = "$1"|"\n";
1070 0         0 for my $l ($nextline, $prevline) {
1071 0 0       0 $l = join "", map {$_->{literal} ? ${$_->{src}} : '*'x$_->{width} } @$l;
  0         0  
  0         0  
1072 0 0       0 $l =~ s{(.)}{$1 =~ /\s/ ? "\0" : "\1"}ges;
  0         0  
1073             }
1074 0         0 $nextline |= $prevline;
1075 0         0 $nextline =~ s{\0}{ }g;
1076 0         0 $nextline =~ s{(\cA+)}{my $len=length($1); substr($under x $len,0,$len)}ge;
  0         0  
  0         0  
1077 0         0 $nextline .= $trail;
1078 0         0 return [{ %std_literal, width => length($nextline), src => \$nextline }];
1079             }
1080              
1081             sub linecount($) {
1082 88 50   88 0 247 return tr/\n// + (/[^\n]\z/?1:0) for @_;
1083             }
1084              
1085 2     2   19 use warnings::register;
  2         2  
  2         273  
1086              
1087 2     2   11 sub form is export(:MANDATORY) {
  2     2   5  
  2         180  
  2         6  
  2         5  
  2         18  
  4         3716  
1088 22 50   22 0 502 croak "Useless call to &form in void context" unless defined wantarray;
1089              
1090             # Handle formatting calls...
1091 22         68 my ($package, $file, $line) = caller;
1092 22   50     82 my $caller_opts = $caller_opts{$package,$file} ||= {};
1093 22 50       45 if (keys %$caller_opts) {
1094 22     22   127 $line = first { $_ < $line } sort {$b<=>$a} keys %$caller_opts;
  22         43  
  0         0  
1095 22 50 50     90 $caller_opts = $caller_opts->{$line} || {}
1096             if defined $line;
1097             }
1098 22         161 my %opts = (%def_opts, %$caller_opts);
1099 22         26 my $fldcnt = 0;
1100 22         160 my @section = {opts=>{%opts}, text=>[]};
1101 22         37 my $formats = \@_;
1102 22         15 my $first = 1;
1103 22         17 my %argcache;
1104 22         16 my ($prevformat,$currformat,@allformats);
1105 22         34 while (@$formats) {
1106 22         56 my $format = shift @$formats;
1107 22 50       34 if (ref $format eq 'HASH') {
1108 0         0 update %opts, %$format;
1109 0 0       0 $opts{page}{number} = undef unless defined $format->{page}{number};
1110 0         0 push @section, {opts=>{%opts}};
1111 0         0 redo;
1112             }
1113 22 50       35 if ($first) { # Change format lists if data first or last
1114 22 50       32 if ($opts{interleave}) {
1115 0         0 $formats = [$format =~ /.*(?:\n|\z)/g];
1116 0         0 $format = shift @$formats;
1117             }
1118 22         20 $first = 0;
1119             }
1120 22         88 $format =~ s/\n?\z/\n/;
1121 22         21 $prevformat = $currformat;
1122 22         46 $currformat = segment($format, @_, %opts, $fldcnt, %argcache);
1123 22         41 resolve_overflows($currformat, $prevformat);
1124 22 50       31 if (defined $opts{under}) {
1125 0         0 push @{$section[-1]{formatters}},
1126 0         0 make_underline($opts{under}, $prevformat, $currformat);
1127 0         0 $opts{under} = undef;
1128             }
1129 22         19 push @{$section[-1]{formatters}}, $currformat;
  22         50  
1130 22         42 push @allformats, $currformat;
1131             }
1132 22 50       30 croak scalar(@_), " too many data values after last format" if @_;
1133 22         27 delineate_overflows(@allformats);
1134              
1135 22         18 my $text = "";
1136 22         16 my $pagetype = 'first';
1137 22         18 my $pagenum = 1;
1138 22         20 for my $section (@section) {
1139 22 50       32 next unless $section->{formatters};
1140 22         20 my $sect_opts = $section->{opts};
1141 22         18 my $page = $sect_opts->{page};
1142 22 100       30 $page->{number} = $pagenum unless defined $page->{number};
1143 22         21 my $pagelen = $page->{length};
1144 22         10 while (1) {
1145 22 100       38 my $parity = $page->{number}%2 ? 'odd' : 'even';
1146             my $header =
1147             $page->{header}{$pagetype} ? $page->{header}{$pagetype}($sect_opts)
1148             : $page->{header}{$parity} ? $page->{header}{$parity}($sect_opts)
1149 22 0       56 : $page->{header}{other} ? $page->{header}{other}($sect_opts)
    0          
    50          
1150             : "";
1151             my $footer =
1152             $page->{footer}{$pagetype} ? $page->{footer}{$pagetype}($sect_opts)
1153             : $page->{footer}{$parity} ? $page->{footer}{$parity}($sect_opts)
1154 22 0       45 : $page->{footer}{other} ? $page->{footer}{other}($sect_opts)
    0          
    50          
1155             : "";
1156             my $feed =
1157             $page->{feed}{$pagetype} ? $page->{feed}{$pagetype}($sect_opts)
1158             : $page->{feed}{$parity} ? $page->{feed}{$parity}($sect_opts)
1159 22 0       42 : $page->{feed}{other} ? $page->{feed}{other}($sect_opts)
    0          
    50          
1160             : "";
1161 22   33     58 length and s/\n?\z/\n/ for $header, $footer; # NOT for $feed
1162             my $bodyfn = $page->{body}{$pagetype}
1163             || $page->{body}{$parity}
1164             || $page->{body}{other}
1165 22   50     79 || \&std_body;
1166 22         32 my $bodylen = max 1, $pagelen - linecount($header) - linecount($footer);
1167 22         38 my ($pagetext, $more) = make_page($section, $sect_opts, $bodylen);
1168 22 50 33     70 if (!$more && $section == $section[-1]) {
1169             my $lastheader =
1170 22 50       42 $page->{header}{last} ? $page->{header}{last}($sect_opts)
1171             : $header;
1172             my $lastfooter =
1173 22 50       26 $page->{footer}{last} ? $page->{footer}{last}($sect_opts)
1174             : $footer;
1175 22   33     112 length and s/\n?\z/\n/ for $lastheader, $lastfooter;
1176 22         31 my $lastlen = max 1, $pagelen-linecount($lastheader)-linecount($lastfooter);
1177 22 50       39 if (@$pagetext <= $lastlen) {
1178 22         19 $pagetype = 'last';
1179 22         24 ($header, $footer, $bodylen)
1180             = ($lastheader, $lastfooter, $lastlen);
1181             $feed = $page->{feed}{last}($sect_opts)
1182 22 50       34 if $page->{feed}{last};
1183             $bodyfn = $page->{body}{last}
1184 22 50       34 if $page->{body}{last};
1185             }
1186             }
1187 22 50       37 my $fill = $pagelen < $unlimited ? [("\n") x ($bodylen-@$pagetext)]
1188             : [];
1189              
1190 22         38 my $body = $bodyfn->($pagetext, $fill, \%opts);
1191              
1192 22         46 $text .= $header . $body . $footer . $feed;
1193 22         23 $page->{number}++;
1194              
1195             # Handle special case of empty last page...
1196 22 50 33     118 last unless $more || $section == $section[-1] && $pagetype ne 'last';
      33        
1197 0 0       0 $pagetype = $page->{number}%2 ? 'odd' : 'even';
1198             }
1199 22         31 $pagenum = $page->{number};
1200             }
1201              
1202 22 50       75 $text =~ s/[^\S\n]+\n/\n/g unless $opts{untrimmed};
1203 22         639 return $text;
1204             }
1205              
1206             sub make_page {
1207 22     22 0 21 my ($section, $sect_opts, $bodylen) = @_;
1208 22         17 my (@text, $more);
1209 0         0 my ($prevformatters, $formatters);
1210 22   66     37 while (@text < $bodylen && @{$section->{formatters}}) {
  44         115  
1211 22         12 $prevformatters = $formatters;
1212 22         22 $formatters = $section->{formatters}[0];
1213 22         45 $more = make_cols($formatters,$prevformatters,my @parts, %$sect_opts, $bodylen-@text);
1214 22 50       36 shift @{$section->{formatters}} unless $more;
  22         27  
1215 22         18 my $maxheight = 0;
1216 22         11 my $maxwidth = 0;
1217 22         34 for my $col (0..$#parts) {
1218 66         59 local $_ = $parts[$col];
1219 66   66     151 pop @$_ while @$_ && ! length($_->[-1]);
1220 66   50     204 $maxheight = max($maxheight, scalar(@$_), $formatters->[$col]{opts}{height}{min}||0);
1221             # $formatters->[$col]{pos} = $maxwidth;
1222             # $maxwidth += $formatters->[$col]{width};
1223             }
1224 22         34 for my $col (0..$#parts) {
1225 66         58 my $f = $formatters->[$col];
1226 66   50     41 push @{$parts[$col]}, ("") x (($f->{height}{min}||0)-@{$parts[$col]});
  66         194  
  66         83  
1227 66         52 my $fopts = $f->{opts};
1228 66     242   184 my $tfill = first {defined $_} @{$fopts}{qw(tfill vfill fill)}, " ";
  242         197  
  66         128  
1229 66     242   146 my $bfill = first {defined $_} @{$fopts}{qw(bfill vfill fill)}, " ";
  242         144  
  66         97  
1230 66     242   113 my $lfill = first {defined $_} @{$fopts}{qw(lfill hfill fill)}, " ";
  242         157  
  66         91  
1231 66     242   105 my $rfill = first {defined $_} @{$fopts}{qw(rfill hfill fill)}, " ";
  242         147  
  66         122  
1232 66         124 $f->{vjust}->($maxheight,$tfill,$bfill,$parts[$col]);
1233 66         50 for my $row (0..$#{$parts[$col]}) {
  66         94  
1234 66         105 my $last = $parts[$col][$row] =~ s/\r//;
1235             $f->{hjust}->($parts[$col][$row], pre=>$lfill, post=>$rfill,
1236             last=>$last, pos=>$f->{pos},
1237             stretch=>$f->{stretch}, width=>$f->{width},
1238 66         151 );
1239             }
1240             }
1241 22         27 for my $row (0..$maxheight) {
1242             push @text, join "",
1243 44         98 grep { defined } map $parts[$_][$row],0..$#parts;
  132         189  
1244             }
1245             }
1246 22         40 return (\@text, $more);
1247             }
1248              
1249             # Extract perpendicular cross-sections from an AoA, AoH, HoA, HoH, AoHoA, etc.
1250              
1251             sub section {
1252 0     0 0 0 my ($structure, @index) = @_;
1253 0 0       0 $structure = [ values %$structure ] if ref $structure eq 'HASH';
1254 0         0 my @section;
1255 0         0 for my $row ( @$structure ) {
1256 0         0 local $,=",";
1257 0 0       0 my $type = ref $row or croak "Too many indices (starting with [@index])";
1258 0 0       0 if ($type eq 'HASH') {
    0          
1259 0 0       0 @index = keys %$row unless @index;
1260 0         0 push @{$section[$_]}, $row->{$index[$_]} for 0..$#index;
  0         0  
1261             }
1262             elsif ($type eq 'ARRAY') {
1263 0 0       0 @index = (0..$#$row) unless @index;
1264 0         0 push @{$section[$_]}, $row->[$index[$_]] for 0..$#index;
  0         0  
1265             }
1266             else {
1267 0         0 my $what = ref $structure;
1268 0 0       0 croak "Can't drill ", ($what ? lc $what : $structure) , " of $type";
1269             }
1270             }
1271 0         0 return @section;
1272             }
1273              
1274             sub slice {
1275 0     0 0 0 my ($structure, @indices) = @_;
1276 0 0       0 return ref eq 'HASH' ? @{$_}{@indices} : @{$_}[@indices] for $structure;
  0         0  
  0         0  
1277             }
1278              
1279 0 0   0 0 0 sub vals { return ref eq 'HASH' ? values %$_ : @$_ for $_[0] }
1280              
1281 2     2   11 sub drill (\[@%];@) is export {
  2     2   2  
  2         117  
  2         4  
  2         4  
  2         3  
  2         1061  
1282 0     0 0 0 my ($structure, @indices) = @_;
1283 0 0       0 return $structure unless @indices;
1284 0         0 my $index = shift @indices;
1285 0 0       0 my @section = [ @$index ? slice($structure,@$index) : vals($structure) ];
1286 0 0       0 return @section unless @indices;
1287 0         0 for my $index (@indices) {
1288 0         0 @section = map {section $_, @$index} @section;
  0         0  
1289             }
1290 0         0 return @section;
1291             }
1292              
1293             sub break_lit {
1294 44     44 0 30 return (${$_[0]},0,0);
  44         93  
1295             }
1296              
1297             sub break_bullet {
1298 0     0 0 0 my ($src) = @_;
1299 0   0     0 my $next = pop @$src || "";
1300 0         0 return ($next,@$src>0,0);
1301             }
1302              
1303             sub break_verbatim {
1304 0     0 0 0 my ($str,$rem) = @_;
1305 0 0       0 $$str =~ m/ \G ([^\n\r]*) (?:\r|\n|\z) /gcx or return ("",0);
1306 0 0       0 return (substr("$1",0,$rem), $$str =~ m/ \G (?=.) /sgcx ? 1 : 0,0);
1307             }
1308              
1309             sub break_nl {
1310 22     22 0 21 my ($str) = @_;
1311 22 50       135 if ($$str =~ m/\G [^\S\n\r]* ([^\n\r]*?) [^\S\r\n]* (?:\r|$)/gcxm) {
1312 22 50       119 return ("$1", $$str =~ /\G(?=.*\S)/sgc?1:0, 1);
1313             }
1314             else {
1315 0         0 return ("",0,0);
1316             }
1317             }
1318              
1319             my $wsnzw = q{ (??{length($^N)?'(?=)':'(?!)'}) };
1320              
1321 2     2   10 sub break_at is export {
  2     2   3  
  2         112  
  2         3  
  2         3  
  2         4  
  2         188  
1322 2     2 0 3 my ($hyphen) = @_;
1323 2         25 my ($lit_hy) = qr/\Q$hyphen\E/;
1324 2         3 my $hylen = length($hyphen);
1325 2         2 my @ret;
1326             return sub {
1327 0     0   0 my ($str,$rem,$ws) = @_;
1328 0         0 my ($last_breakable, $res) = ($rem+1,"", 0);
1329 0         0 for ($$str) {
1330 2     2   8 use re 'eval';
  2         3  
  2         1540  
1331 0   0     0 while ($rem > 0 && (pos()||0) < length()) {
      0        
1332 0 0 0     0 if ($ws && /\G ($ws) $wsnzw/gcx) {
    0 0        
    0          
1333 0         0 my $captured;
1334 0 0       0 if ($#+ > 1) { # may be extra captures...
1335 0         0 for (2..$#+) {
1336 0 0       0 next unless defined $$_;
1337 0         0 $captured++;
1338 0         0 $res .= $$_;
1339 0         0 $rem -= length $$_;
1340             }
1341             }
1342 0 0       0 unless ($captured) {
1343 0         0 $res .= $1;
1344 0         0 $rem--;
1345             }
1346 0         0 $last_breakable = length $res;
1347             }
1348             elsif ($rem>=$hylen && /\G $lit_hy /gcx) {
1349 0         0 $res .= $hyphen;
1350 0         0 $rem -= $hylen;
1351 0         0 $last_breakable = length $res;
1352             }
1353             elsif (/\G ((?!$lit_hy)[^\n\r]) /gcx) {
1354 0         0 $res .= $1;
1355 0         0 $rem--;
1356 0 0       0 $last_breakable = length $res if $res =~ /\s$/;
1357             }
1358 0         0 else { last }
1359             }
1360 0         0 my $reslen = length $res;
1361 0   0     0 $ws ||= qr/\s/;
1362 0 0       0 unless (/\G (?=$lit_hy|($ws)$wsnzw|\z|\n|\r) /gcx) {
1363 0 0       0 if ($last_breakable <= $reslen) {
    0          
1364 0         0 pos() -= $reslen-$last_breakable;
1365 0         0 substr($res,$last_breakable) = "";
1366             }
1367             elsif ($reslen > $hylen) {
1368 0 0       0 if ($res =~ /\S\S\S{$hylen}$/) {
    0          
1369 0         0 pos() -= $hylen;
1370 0         0 substr($res,-$hylen) = $hyphen;
1371             }
1372             elsif ($res =~ s/(\S+)$//) {
1373 0         0 pos() -= length($1);
1374             }
1375             }
1376             }
1377 0         0 my $rem = substr($$str, pos $$str);
1378 0 0       0 return ($res, $rem=~/\S/?1:0, $rem =~ /^\s*(?:\z|\n|\r)/);
1379             }
1380 2         42 };
1381             }
1382              
1383             sub import {
1384 2     2   4 my $class = shift;
1385 2         3 my ($package, $file, $line) = caller;
1386 2         3 my %opts;
1387 2         4 for (@_) {
1388 0 0       0 croak "Options for $class must be specified in a hash"
1389             unless ref eq 'HASH';
1390 0         0 update(%opts, %$_, 'croak');
1391             }
1392 2         1590 $caller_opts{$package,$file}{$line} = \%opts;
1393             }
1394              
1395             package Perl6::Form::Rule::Fail;
1396             use overload
1397 0     0   0 '""' => sub{ undef },
1398 0     0   0 '0+' => sub{ undef },
1399 0     0   0 'bool' => sub{ 0 },
1400 2     2   10 ;
  2         3  
  2         29  
1401              
1402             package Perl6::Form::Rule::Okay;
1403             use overload
1404 0     0   0 '""' => sub{ $_[0][0] },
1405 0     0   0 '0+' => sub{ $_[0][0] },
1406 0     0   0 'bool' => sub{ 1 },
1407 2     2   264 ;
  2         2  
  2         11  
1408              
1409             1;
1410             __END__