File Coverage

blib/lib/Lisp/Fmt.pm
Criterion Covered Total %
statement 516 626 82.4
branch 349 486 71.8
condition 62 89 69.6
subroutine 23 26 88.4
pod 0 22 0.0
total 950 1249 76.0


line stmt bran cond sub pod time code
1             # -*- perl -*-
2              
3             # Copyright (c) 1998 by Jeff Weisberg
4             # Author: Jeff Weisberg
5             # Function: Lisp format
6             #
7             # $Id: Fmt.pm,v 1.1 1998/08/31 23:50:12 jaw Exp jaw $
8             #
9             # LICENSE: at end
10             # DOCUMENTATION: at end
11             #
12              
13             package Lisp::Fmt;
14             require Exporter;
15             @ISA = qw(Exporter);
16             @EXPORT = qw(fmt pfmt);
17             $VERSION = "0.01";
18              
19 1     1   1994 use vars qw($accum $argno @arglist $VERSION);
  1         1  
  1         71  
20 1     1   4 use strict;
  1         1  
  1         8340  
21              
22             ### config options
23              
24             # lisp's notion of false differs from that of perl
25             # you may or may not want 0 be considered false...
26             # this effects ~:[ and ~@[
27             my($config_zero_is_false) = 0;
28              
29             # X3J13 is ambiguous on how to align the middle columns of <>
30             # set this to 'r', 'l', or 'c'
31             my($config_align_middle) = 'l';
32              
33             # turn on verbose debugging output, most run 0 through 2
34             my($verbose_tok) = 0;
35             my($verbose_parse) = 0;
36             my($verbose_reduce) = 0;
37             my($verbose_run) = 0;
38             my($verbose_fmt) = 0;
39              
40             ### end of config options
41              
42             ################################################################
43             ### No user servicable parts below this point
44             ################################################################
45              
46             ### tokenize the format spec
47             sub tok {
48 213     213 0 249 my( $s ) = @_;
49 213         258 my( $fulls ) = $s;
50 213         361 my( @s );
51             my( @t );
52 213         240 my( $len ) = length($s);
53 213         196 my( $soff, $eoff );
54              
55 213         393 while( $s ){
56 896 50       1472 print STDERR "T: $s\n" if $verbose_tok;
57            
58 896 100 66     7217 if( $s =~ /^([^~]+)/ && $1 ){
    100 66        
    50 33        
59 292 50       511 print STDERR "T-> $1 <$'>\n" if $verbose_tok > 1;
60 292         342 $soff = $len - length($s) - 1;
61 292         483 $s = $';
62 292         306 $eoff = $len - length($s) - 1;
63            
64             # add literal
65 292         1429 push @t, {
66             directive => 'literal',
67             text => $1,
68             fullfmt => $fulls,
69             fmtoffset => $eoff,
70             fmtstart => $soff,
71             };
72             }
73              
74             elsif( $s =~ /^~(<{2,}|>{2,}|\|{2,})/ && $1 ){
75             # perl-esque format spec ~<<<<<< ~>>>>>>> ~||||||
76             # NB: must be at least 3 long (eg ~<<) to avoid confusion with ~|, ~< and ~>
77 6         10 my( $p ) = $1;
78 6 50       14 print STDERR "T-> $1 {$'}\n" if $verbose_tok > 1;
79 6         9 $soff = $len - length($s) - 1;
80 6         13 $s = $';
81 6         7 $eoff = $len - length($s) - 1;
82              
83 6 100       63 push @t, {
    100          
84             directive => 'A',
85             numbers => [ length($1)+1 ],
86             gravity => ( $p =~ // ) ? 'r' : 'c'),
87             fullfmt => $fulls,
88             fmtoffset => $eoff,
89             fmtstart => $soff,
90             };
91             }
92              
93             elsif( $s =~ /^~(((-?\d*|v|\#|\'.)(,(-?\d*|v|\#|\'.))*)? # comma sep. list of params
94             ([\@:!]*) # optional @ ! and :
95             ([^=\@:!\'v\#]|\n\s*|=.))/x # directive
96             && $1 ){
97 598         1189 my($ns, $sp, $f) = ($2, $6, $7);
98 598 50       1002 print STDERR "T-> $f; <$ns>; $sp <$'>\n" if $verbose_tok > 1;
99 598         757 $soff = $len - length($s) - 1;
100 598         930 $s = $';
101 598         599 $eoff = $len - length($s) - 1;
102 598         1020 my(@n) = split ',', $ns;
103 598         894 foreach (@n){
104 244 100       560 if( /^\'(.*)$/ ){
105 27         81 $_ = $1;
106             }
107             }
108              
109             # add it
110 598 100       4805 push @t, {
    100          
111             numbers => [@n],
112             directive => uc($f),
113             atsign => ($sp =~ /@|!/) ? 1 : 0, # because we have to \@, we allow ! as a synonym
114             colon => ($sp =~ /:/) ? 1 : 0,
115             fullfmt => $fulls,
116             fmtoffset => $eoff,
117             fmtstart => $soff,
118             };
119             }
120             }
121              
122 213         664 @t;
123             }
124              
125              
126             ### parse the tokenized format string
127             sub parse {
128 213     213 0 336 my( @t ) = @_;
129 213         239 my( $t );
130 213         231 my( $i ) = 0;
131 213         177 my( $tnext );
132            
133             $tnext = sub {
134 1109 100   1109   2024 return undef if( $i >= @t );
135 896         1377 return $t[ $i ++ ];
136 213         672 };
137              
138 213         1476 parser('', 1, $tnext);
139             }
140              
141             sub parser {
142 293     293 0 367 my( $term, $n, $tnext ) = @_;
143 293         260 my( $t, @tt );
144            
145 293         269 while( 1 ){
146 1109         1648 $t = &$tnext();
147            
148 1109 50 66     3267 if( $term && ! $t ){
149             # error ESARAHCONNOR - no terminator
150 0         0 $t = $tt[0];
151 0         0 formaterror("I see no matching $term here", $t);
152 0         0 return ;
153             }
154            
155 1109 100       8184 return \@tt unless $t;
156              
157 896 50       1486 print STDERR "PP", "<"x$n, ": $t->{directive}\n" if $verbose_parse;
158            
159 896 100       1946 return \@tt if( $t->{'directive'} eq $term );
160            
161 816 100       1525 if( $t->{'directive'} eq '[' ){
162 37 50       86 print STDERR "PP->[\n" if $verbose_parse > 1;
163 37         94 my( $l ) = parser(']', $n+1, $tnext);
164 37         51 my( @l ) = @{$l};
  37         112  
165 37         39 my( @nn, @ll );
166 37         43 my( $n ) = 0;
167              
168 37         66 while( @l ){
169 203         216 $l = shift @l;
170 203 100       346 if( $l->{'directive'} eq ";" ){
171 58         104 push @nn, [@ll];
172 58 100       128 $t->{'default_item'} = ($n+1) if $l->{'colon'};
173 58         65 @ll = ();
174 58         102 $n++;
175             }else{
176 145         299 push @ll, $l;
177             }
178             }
179 37         66 push @nn, [@ll];
180            
181 37         86 $t->{'subparts'} = \@nn;
182 37 50       101 print STDERR "PP->]\n" if $verbose_parse > 1;
183             }
184            
185 816 100       1524 if( $t->{'directive'} eq '<' ){
186 11 50       18 print STDERR "PP-><\n" if $verbose_parse > 1;
187 11         26 my( $l ) = parser('>', $n+1, $tnext);
188 11         12 my( @l ) = @{$l};
  11         20  
189 11         18 my( @nn, @ll );
190 11         12 my( $n ) = 0;
191              
192 11         20 while( @l ){
193 35         36 $l = shift @l;
194 35 100       59 if( $l->{'directive'} eq ";" ){
195 12         20 push @nn, [@ll];
196 12         14 @ll = ();
197 12         22 $n++;
198             }else{
199 23         44 push @ll, $l;
200             }
201             }
202 11         21 push @nn, [@ll];
203            
204 11         22 $t->{'subparts'} = \@nn;
205 11 50       27 print STDERR "PP->>\n" if $verbose_parse > 1;
206             }
207              
208 816 100       1313 if( $t->{'directive'} eq '{' ){
209 24 50       48 print STDERR "PP->{\n" if $verbose_parse > 1;
210 24         55 my( $b ) = parser('}', $n+1, $tnext);
211              
212 24 100       29 if( @{$b} != 0 ){
  24         53  
213 23         57 $t->{'body'} = $b;
214             }
215 24 50       50 print STDERR "PP->}\n" if $verbose_parse > 1;
216             }
217              
218 816 100       1463 if( $t->{'directive'} eq '(' ){
219 7 50       14 print STDERR "PP->(\n" if $verbose_parse > 1;
220 7         17 $t->{'body'} = parser(')', $n+1, $tnext);
221 7 50       12 print STDERR "PP->)\n" if $verbose_parse > 1;
222             }
223            
224 816 50       1346 if( $t->{'directive'} eq '/' ){
225 0         0 my( $tt );
226 0         0 $tt = &$tnext();
227 0 0       0 $tt->{'directive'} eq 'literal' || return formaterror("I was expecting a function name. Pity.", $tt);
228 0         0 $t->{'funcname'} = $tt->{'text'};
229 0         0 $tt = &$tnext();
230 0 0       0 $tt->{'directive'} eq '/' || return formaterror("I see no matching / here", $tt);
231             }
232              
233 816 100       1390 if( $t->{'directive'} eq '=(' ){
234 1 50       4 print STDERR "PP->=(\n" if $verbose_parse > 1;
235 1         5 $t->{'body'} = parser('=)', $n+1, $tnext);
236 1 50       4 print STDERR "PP->=)\n" if $verbose_parse > 1;
237             }
238            
239 816         1392 push @tt, reduce($t);
240            
241             }
242             }
243              
244             ### the optimizer - simplify parse tree
245             sub reduce {
246 816     816 0 797 my( $t ) = @_;
247 816         702 my( @n );
248             my( $d );
249              
250 816         1044 $d = $t->{'directive'};
251 816 50       1269 print STDERR "R: $d\n" if $verbose_reduce;
252            
253             # collapse all numbers together
254 816 100       2129 if( $d =~ /^[DOXBR]$/ ){
255 47         64 $t->{'directive'} = 'number';
256 47 100       113 $t->{'radix'} = 10 if $d eq "D";
257 47 100       280 $t->{'radix'} = 8 if $d eq "O";
258 47 100       77 $t->{'radix'} = 16 if $d eq "X";
259 47 100       80 $t->{'radix'} = 2 if $d eq "B";
260            
261             # NB: radix could end up 'v' or '#'
262            
263 47 100       84 if( $d eq "R" ){
264 21         21 @n = @{$t->{'numbers'}};
  21         44  
265 21         59 $t->{'radix'} = shift @n;
266 21         41 $t->{'numbers'} = [ @n ];
267 21 100       52 if( ! $t->{'radix'} ){
268 16 100       441 if( $t->{'atsign'} ){
269 11         20 $t->{'directive'} = 'roman';
270             }else{
271 5         9 $t->{'directive'} = 'english';
272             }
273             }
274             }
275 47 50       86 print STDERR "R-> number, $t->{'radix'}\n" if $verbose_reduce > 1;
276             }
277              
278 816 100       1695 if( $d =~ /^[ASW]$/ ){
279 213         329 $t->{'directive'} = 'A';
280 213 50       669 $t->{'how'} = $d eq 'A' ? 0 : 1;
281             }
282            
283             # convert to literal or repeated text
284 816 100       1661 if( $d =~ /^[%_|~]$/ ){
285 3         2 my( $c, $n );
286            
287 3         4 @n = @{$t->{'numbers'}};
  3         6  
288 3   100     10 $n = shift @n || "";
289 3 100       6 $n = 1 if $n eq "";
290              
291 3         3 $c = "";
292 3 50       6 $c = " " if $d eq "_";
293 3 50       7 $c = "~" if $d eq "~";
294 3 50       5 $c = "\n" if $d eq "%";
295 3 50       6 $c = "\f" if $d eq "|";
296            
297 3 50       8 if( $n !~ /\d/ ){
298 0         0 $t->{'directive'} = "repeat";
299 0         0 $t->{'text'} = $c;
300             }else{
301 3         7 $t->{'text'} = $c x $n;
302 3         3 $t->{'directive'} = "literal";
303             }
304 3 50       8 print STDERR "R-> $d rewrite $t->{'directive'}\n" if $verbose_reduce > 1;
305             }
306            
307             # convert to literal text
308 816 50       1561 if( $d =~ /^\n/ ){
309 0         0 my( $sp ) = $d;
310            
311 0         0 $sp =~ s/\n//;
312 0 0       0 $t->{'text'} = ($t->{'atsign'} ? "\n" : "") . ($t->{'colon'} ? $sp : "");
    0          
313 0         0 $t->{'directive'} = "literal";
314            
315 0 0       0 print STDERR "R-> whitespace literal\n" if $verbose_reduce > 1;
316             }
317            
318 816         1792 $t;
319             }
320              
321             sub formaterror {
322 0     0 0 0 my( $msg, $t ) = @_;
323 0         0 my( $fmt );
324              
325 0         0 $fmt = $t->{'fullfmt'};
326 0         0 $fmt =~ s/\n/\$/g;
327 0         0 print STDERR "\n## FORMAT ERROR: $msg\n";
328 0         0 print STDERR "## \t\"", $fmt, "\"\n";
329 0         0 print STDERR "## \t ", " " x $t->{'fmtoffset'}, "^\n";
330            
331 0         0 "error";
332             }
333              
334             my($_fmtobja, $_fmtobjs) = ("","");
335             sub objecttostring {
336 291     291 0 314 my( $how, $obj ) = @_;
337              
338 291 100       1115 if( ref($obj) ){
    100          
    50          
339 1 50       14 if( ref($obj) eq "SCALAR"){
    50          
    50          
340 0 0       0 if( $how ){
341 0         0 fmt( "\\~s", $$obj );
342             }else{
343 0         0 fmt( "\\~a", $$obj );
344             }
345             }elsif( ref($obj) eq "CODE"){
346             # is it possible to do anything more useful?
347 0         0 "CODE";
348             }elsif( ref($obj) eq "GLOB"){
349 0         0 "GLOB";
350             }else{
351 1         2 my( $fmta, $fmtb );
352             # recursively call fmt, to nicely render lists (refs)
353            
354 1 50       3 if( $how ){
355 0 0       0 $_fmtobjs = compile( "~#[~;~s~:;~!{~#[~;~s~:;~s, ~]~}~]" ) unless $_fmtobjs;
356 0         0 $fmtb = $_fmtobjs;
357             }else{
358 1 50       4 $_fmtobja = compile( "~#[~;~a~:;~!{~#[~;~a~:;~a, ~]~}~]" ) unless $_fmtobja;
359 1         9 $fmtb = $_fmtobja;
360             }
361              
362             # should I do anything with the package name?
363 1         2 $fmta = "<~?>";
364 1 50       5 $fmta = "[~?]" if ref($obj) eq "ARRAY";
365 1 50       5 $fmta = "{~?}" if ref($obj) eq "HASH";
366 1         12 fmt($fmta, $fmtb, $obj);
367             }
368             }elsif( $obj =~ /^[+-]?\d+$/ ){
369 111         230 "$obj";
370             }elsif( $obj =~ /^[+-]\d*\.\d*$/ ){
371 0         0 "$obj";
372             }else{
373 179 50       265 if( $how ){
374 0         0 qq("$obj");
375             }else{
376 179         467 "$obj";
377             }
378             }
379             }
380              
381             sub formatstring {
382 291     291 0 520 my( $how, $mincol, $colinc, $minpad, $padchar, $ovchar, $gravity, $obj ) = @_;
383 291         462 my( $str ) = objecttostring($how, $obj);
384 291         325 my( $l, $w, $padamt, $maxcolp );
385              
386 291 100       668 $padchar = chr($padchar) if( $padchar =~ /^\d+$/ );
387 291 50       504 $ovchar = chr($ovchar) if( $ovchar =~ /^\d+$/ );
388            
389 291 50       447 print STDERR "F: $how, MC:$mincol, C:$colinc, MP:$minpad, P:$padchar, OV:$ovchar, G:$gravity, O:$obj\n"
390             if $verbose_fmt;
391              
392 291         486 $l = length($str) + $minpad;
393 291 100       471 $w = abs($mincol?$mincol:0);
394 291         551 $padamt = $colinc * int((($w - $l) + $colinc - 1) / $colinc);
395 291   66     669 $maxcolp = ($mincol ne "") && ($mincol < 0);
396              
397 291 50       439 print STDERR "F: $l, $w, $padamt, $maxcolp, $str\n" if $verbose_fmt;
398              
399             # and minimum padding
400 291 100       618 if( $gravity eq "r" ){
    100          
401 63         131 $str = $padchar x $minpad . $str;
402             }elsif( $gravity eq "l" ){
403 225         321 $str .= $padchar x $minpad;
404             }
405            
406 291 100       576 if( $l == $w ){
    100          
407             # Happy Happy, Joy Joy!
408             }elsif( $l > $w ){
409             # You can't fit this five-foot clam through that little passage!
410 232 50       398 if( $maxcolp ){
411 0         0 my( $fl ) = $w - 1;
412 0 0       0 if( $gravity eq "r" ){
413 0         0 $str =~ s/^(.{$fl}).*$/$1$ovchar/;
414             }else{
415 0         0 $str =~ s/^(.{$fl}).*$/$ovchar$1/;
416             }
417             }
418             }else{
419             # too short - pad
420 57 100       113 if( $gravity eq "r" ){
    100          
421 20         39 $str = $padchar x $padamt . $str;
422             }elsif( $gravity eq "l" ){
423 34         60 $str .= $padchar x $padamt;
424             }else{
425 3         5 my($rp, $lp);
426 3         7 $rp = int( ($w - $l) / 2 );
427 3         4 $lp = $w - $l - $rp;
428 3         8 $str = $padchar x $lp . $str . $padchar x $rp;
429             }
430             }
431 291         1221 $str;
432             }
433              
434             sub formatnumber {
435 31     31 0 58 my( $radix, $mincol, $padchar, $commachar,
436             $commawidth, $ovchar, $withsign, $withcommas, $val ) = @_;
437 31         30 my( $str, $sign, @cs );
438              
439 31         31 $str = "";
440 31         26 $val = int($val);
441 31 100       61 if( $val < 0 ){
    100          
442 7         7 $val = - $val;
443 7         9 $sign = "-";
444             }elsif( $withsign ){
445 5         8 $sign = "+";
446             }else{
447 19         21 $sign = "";
448             }
449              
450             # convert to desired radix
451 31 50       39 if( $radix == 1 ){
452             # special case
453 0         0 $str = "1" x $val;
454             }else{
455 31         283 @cs = split //,"0123456789abcdefghijklmnopqrstuvwxyz";
456 31         86 while( $val ){
457 140         177 $str = $cs[ $val % $radix ] . $str;
458 140         257 $val = int( $val / $radix );
459             }
460             }
461              
462             # add commas
463 31 100       52 if( $withcommas ){
464 15         290 1 while $str =~ s/^(-?\d+)(\d{$commawidth})/$1$commachar$2/;
465             }
466              
467 31         80 formatstring(0, $mincol, 1, 0, $padchar, $ovchar, "r", "$sign$str");
468             }
469              
470             sub roman {
471 11     11 0 17 my( $oldway, $val ) = @_;
472 11         15 my( $rc, $rv, $i, $str );
473 0         0 my( @rc, @rv );
474            
475 11         37 @rc = qw(/M /D /C /L /X /V M D C L X V I);
476 11         33 @rv = qw(1000000 500000 100000 50000 10000 5000 1000 500 100 50 10 5 1 0 0);
477 11         10 $i = 0;
478 11         15 $str = '';
479            
480 11         20 while($val){
481            
482 133 100 100     428 if( $val >= $rv[$i] ){
    100 66        
    100 66        
    100 100        
483 16         21 $str .= $rc[$i];
484 16         26 $val -= $rv[$i];
485              
486             }elsif( $val <= $rv[$i+1] ){
487 105         174 $i++;
488              
489             }elsif( !$oldway && ($i&1)==0 && ($val >= ($rv[$i] - $rv[$i+2])) ){
490 5         8 $str .= $rc[$i+2];
491 5         8 $str .= $rc[$i];
492 5         12 $val -= $rv[$i] - $rv[$i+2];
493              
494             }elsif( !$oldway && ($i&1)!=0 && ($val >= ($rv[$i] - $rv[$i+1])) ){
495 2         22 $str .= $rc[$i+1];
496 2         4 $str .= $rc[$i];
497 2         7 $val -= $rv[$i] - $rv[$i+1];
498              
499             }else{
500 5         12 $i ++;
501             }
502             }
503 11         131 $str;
504             }
505              
506             sub englishsmall {
507 7     7 0 9 my($ordinal, $val) = @_;
508 7         7 my($n, $str);
509 0         0 my(@units, @ounits, @tens, @otens);
510              
511 7         31 @units = qw(zero one two three four five six seven eight nine
512             ten eleven twelve thirteen fourteen fifteen sixteen
513             seventeen eighteen nineteen);
514 7         112 @ounits = qw(zeroth first second third fourth fifth sixth seventh
515             eighth ninth tenth eleventh twelfth);
516 7         31 @tens = qw(twenty thirty forty fifty sixty seventy eighty ninety);
517 7         16 @otens = qw(twentieth thirtieth fortieth fiftieth sixtieth seventieth eightieth ninetieth);
518 7         13 unshift @tens, ""; unshift @tens, "";
  7         11  
519 7         10 unshift @otens, ""; unshift @otens, "";
  7         9  
520              
521 7         8 $str = "";
522 7 100       16 if( $val >= 100 ){
523 2         8 $str .= " " . $units[ $val / 100 ] . " hundred";
524 2         3 $val %= 100;
525 2 50 66     10 $str .= "th" if $ordinal && !$val;
526             }
527              
528 7 100       13 if( $val >= 20 ){
529 2         3 $n = $val % 10;
530 2 50 66     24 if( $ordinal && !$n ){
531 0         0 $str .= " " . $otens[ $val / 10 ];
532             }else{
533 2         7 $str .= " " . $tens[ $val / 10 ];
534             }
535 2         4 $val = $n;
536             }
537              
538 7 50       13 if( $val ){
539 7 100       11 if( $ordinal ){
540 1 50       9 if( $val < @ounits ){
541 1         3 $str .= " " . $ounits[$val];
542             }else{
543 0         0 $str .= " " . $units[$val] . "th";
544             }
545             }else{
546 6         54 $str .= " " . $units[$val];
547             }
548             }
549              
550 7         30 $str =~ s/^ //;
551 7         36 $str;
552             }
553              
554             sub english {
555 6     6 0 10 my($ordinal, $val) = @_;
556 6         86 my(@illions);
557 6         9 my($ordd, $f);
558            
559 6         25 @illions = qw(thousand million billion trillion quadrillion quintillion
560             sextillion septillion octillion nonillion decillion undecillion
561             duodecillion tredecillion quattuordecillion quindecillion
562             sexdecillion septdecillion octodecillion novemdecillion vigintillion);
563 6         13 unshift @illions, "";
564            
565             $f = sub {
566 7     7   9 my($val, $k) = @_;
567 7         8 my($n, $r, $str);
568              
569 7         7 $str = "";
570 7         10 $n = $val % 1000;
571 7         13 $r = int($val / 1000);
572              
573 7 100       21 if( $r ){
574 2         11 $str .= &$f($r, $k + 1);
575 2 50       6 if( $n ){
576 2 50 33     19 if( !$k && ($n < 100) ){
577 0         0 $str .= " and ";
578             }else{
579 2         4 $str .= ", ";
580             }
581             }
582             }
583            
584 7 50       15 if( $n ){
585 7         6 my($o);
586 7   100     30 $o = $ordinal && ($k==0);
587 7 100       15 $ordd = 1 if($o);
588 7         17 $str .= englishsmall($o , $n);
589             }
590            
591 7 100 66     27 if( $k && $n ){
592 2         2 $str .= " ";
593 2 50       6 if( $k > @illions ){
594 0         0 $str .= "times ten to the " . english(1, $k * 3);
595             }else{
596 2         5 $str .= $illions[ $k ];
597             }
598             }
599 7         39 $str;
600 6         28 };
601              
602 6 50       19 if( ! $val ){
    100          
603 0 0       0 if( $ordinal ){
604 0         0 return "zeroth";
605             }else{
606 0         0 return "zero";
607             }
608             }elsif( $val < 0 ){
609 1         6 return "minus " . english($ordinal, - $val);
610             }else{
611 5 50 66     10 &$f($val, 0) . (($ordinal && !$ordd) ? "th" : "");
612             }
613             }
614            
615             sub falsep {
616 18     18 0 21 my($val) = @_;
617              
618 18 50       29 if( $config_zero_is_false ){
619 0 0       0 $val ? 0 : 1;
620             }else{
621 18         54 $val eq "";
622             }
623             }
624              
625             sub mkarray {
626 20     20 0 24 my( $a ) = @_;
627 20         21 my( @a );
628              
629 20 50       65 if( $a =~ /ARRAY/ ){
    0          
630 20         19 @a = @{$a};
  20         54  
631             }elsif( $a =~ /HASH/ ){
632 0         0 @a = %{$a};
  0         0  
633             }else{
634 0         0 @a = ( $a );
635             }
636 20         55 @a;
637             }
638              
639             sub capitalize {
640 2     2 0 3 my( $s ) = @_;
641              
642 2         7 $s = ucfirst(lc($s));
643 2         16 $s =~ s/\b(\w)/\U$1/g;
644 2         6 $s;
645             }
646            
647             sub nextarg {
648 335 50   335 0 633 return "" if( $argno >= @arglist );
649 335         886 return $arglist[ $argno ++ ];
650             }
651              
652             sub pound {
653 20 100   20 0 44 return 0 if( $argno >= @arglist );
654 18         49 return @arglist - $argno;
655             }
656              
657             sub param {
658 1543     1543 0 1854 my( $t, $nth, $dfl ) = @_;
659 1543         2039 my( $n, @n );
660              
661 1543         1375 @n = @{$t->{'numbers'}};
  1543         2691  
662 1543 100       9332 return $dfl if( $nth >= @n );
663 252         853 $n = $n[ $nth ];
664              
665 252 100       419 return nextarg() if( $n eq "v" );
666 249 100       427 return pound() if( $n eq "#" );
667              
668 229 100       591 return $dfl if $n eq ""; # no n, dfl
669              
670 201         576 $n;
671             }
672              
673             ### run the compiled format
674             sub run {
675 325     325 0 937 my( $t ) = @_;
676 325         291 my( $d, @t );
677              
678 325         292 @t = @{$t};
  325         596  
679            
680 325         643 while( @t ){
681 756         1111 $t = shift @t;
682 756         1030 $d = $t->{'directive'};
683              
684 756 50       1145 print STDERR "U: $d\n" if $verbose_run;
685              
686 756 100       3148 if( $d eq 'literal' ){
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
687 297         696 $accum .= $t->{'text'};
688            
689             }elsif( $d eq 'repeat' ){
690 0         0 $accum .= $t->{'text'} x param($t, 0, 1);
691              
692             }elsif( $d eq "&" ){
693 20         29 my($n) = param($t, 0, 1);
694              
695 20 50       59 next unless $n;
696 0 0       0 $accum .= "\n" unless( $accum =~ /\n$/ );
697 0 0       0 if( $n > 1 ){
698 0         0 $accum .= "\n" x ($n - 1);
699             }
700            
701             }elsif( $d eq "T" ){
702 24         24 my($colnum, $colinc, $tabchar);
703 24         27 my($l) = $accum;
704 24         23 my($cp, $mp);
705              
706 24         38 $colnum = param($t,0,1);
707 24         44 $colinc = param($t,1,1);
708 24         38 $tabchar = param($t,2," ");
709            
710 24         43 $l =~ s/.*\n$//;
711 24         21 $cp = length $l;
712 24         25 $mp = 0;
713            
714 24 100       36 if( $t->{'atsign'} ){
715 4 50       7 if( $colinc ){
716 4         9 $mp = $colnum + $colinc - (($cp + $colnum) % $colinc);
717             }
718             }else{
719 20         25 $mp = $colnum - $cp;
720 20 100       57 if( $mp < 0 ){
721 15 50       23 if( $colinc ){
722 15         23 $mp = $colnum + $colinc - ($cp % $colinc);
723 15 100       31 $mp = $mp - $colinc if $mp >= $colinc;
724             }
725             }
726             }
727              
728 24         62 $accum .= $tabchar x $mp;
729            
730             }elsif( $d eq 'A' ){
731             # mincol, colinc, minpad, padchr, ovchr, gravity
732 221   66     1029 $accum .=
733             formatstring( $t->{'how'}, param($t,0,""), param($t,1,1),
734             param($t,2,0), param($t,3," "),
735             param($t,4,"*"),
736             $t->{'gravity'} || ($t->{'atsign'} ? "r" : "l"),
737             nextarg() );
738              
739             }elsif( $d eq '*' ){
740 6 100       16 my( $n ) = param($t, 0, $t->{'atsign'} ? 0 : 1);
741              
742 6 100       15 $n = -$n if( $t->{'colon'} );
743              
744 6 100       9 if( $t->{'atsign'} ){
745 2         3 $argno = $n;
746             }else{
747 4         5 $argno += $n;
748             }
749 6 50       17 $argno = 0 if( $argno < 0 );
750              
751             }elsif( $d eq '?' ){
752 3         6 my( $fmt ) = nextarg();
753 3         5 my( $rv );
754              
755 3 100       17 if( $t->{'atsign'} ){
756             # use current arglist
757 1 50       4 $rv = run( ref($fmt) ? $fmt : compile( $fmt ));
758             }else{
759             # nextarg is list of args
760 2         5 my( $a ) = nextarg();
761 2         11 local( $argno ) = 0;
762 2         3 local( @arglist );
763              
764 2         10 @arglist = mkarray( $a );
765            
766 2 100       9 $rv = run( ref($fmt) ? $fmt : compile( $fmt ));
767             }
768 3 50       20 return $rv if $rv;
769            
770             }elsif( $d eq 'P' ){
771 15         16 my( $n );
772              
773 15 100       30 if( $t->{'colon'} ){
774 8         15 $n = $arglist[ $argno - 1 ];
775             }else{
776 7         12 $n = nextarg();
777             }
778              
779 15 100       26 if( $n == 1 ){
780 5 100       18 $accum .= "y" if $t->{'atsign'};
781             }else{
782 10 100       19 if( $t->{'atsign'} ){
783 2         7 $accum .= "ies";
784             }else{
785 8         25 $accum .= "s";
786             }
787             }
788            
789             }elsif( $d eq 'C' ){
790 17         80 my( @cv ) = qw(NUL SOH STX ETX EOT ENQ ACK BEL BS HT NL
791             VT NP CR SO SI DLE DC1 DC2 DC3 DC4 NAK
792             SYN ETB CAN EM SUB ESC FS GS RS US SP);
793 17         29 my( $n ) = param($t, 0, "");
794 17         22 my( $c, $str );
795 17 100       42 $n = nextarg() if $n eq "";
796 17 100       63 $n = ord($n) unless $n =~ /^\d+$/;
797 17         23 $c = chr($n);
798              
799 17 100       29 if( $t->{'colon'} ){
800 7 50 33     30 if( $t->{'atsign'} && $n && $n < 27 ){
    100 33        
    100          
801 0         0 $str = "Control-" . chr($n+ord('A'));
802             }elsif( $n < @cv ){
803 3         5 $str = $cv[$n];
804             }elsif( $n >= 127 ){
805 3 50       7 if( $t->{'atsign'} ){
806             # ...
807 0         0 $str = "Meta-" . fmt("~:!C", $n & 127);
808             }else{
809 3         33 $str = sprintf "\\0%o", $n;
810             }
811             }else{
812 1         2 $str = $c;
813             }
814             }else{
815 10 100       20 if( $t->{'atsign'} ){
816 8 100 100     35 if( ($n >= 127) || ($n < @cv) ){
817 7         23 $str = sprintf "\"\\0%o\"", $n;
818             }else{
819 1         2 $str = "\"$c\"";
820             }
821             }else{
822 2         3 $str = $c;
823             }
824             }
825 17         72 $accum .= $str;
826            
827             }elsif( $d eq '(' ){
828 7         7 my( $str, $rv );
829 7         7 do {
830 7         9 local( $accum ) = ("");
831 7         11 $rv = run( $t->{'body'} );
832 7         8 $str = $accum;
833              
834 7 100       13 if( $t->{'colon'} ){
835 4 100       8 if( $t->{'atsign'} ){
836 2         4 $str = uc($str);
837             }else{
838 2         6 $str = capitalize($str);
839             }
840             }else{
841 3 100       6 if( $t->{'atsign'} ){
842 2         7 $str = ucfirst(lc($str));
843             }else{
844 1         3 $str = lc($str);
845             }
846             }
847             };
848 7         9 $accum .= $str;
849 7 50       19 return $rv if $rv;
850              
851             }elsif( $d eq '/' ){
852             # this is implemented as ~/funcname~/ and not as ~/funcname/
853             # so sue me...
854 0         0 my( $func, $str, $p, @p );
855              
856 0         0 $func = $t->{'funcname'};
857 0         0 foreach $p ( @{$t->{'numbers'}} ){
  0         0  
858 0 0       0 $p = pound() if( $p eq "#" );
859 0 0       0 $p = nextarg() if( $p eq "v" );
860 0         0 push @p, $p;
861             }
862 0         0 $str = nextarg();
863 0         0 $str = "$func($str, $t->{'colon'}, t->{'atsign'}";
864 0 0       0 $str .= ", " . join(", ", @p) if( @p );
865 0         0 $str .= ")";
866              
867 0         0 $accum .= eval( $str );
868            
869             }elsif( $d eq '<' ){
870             # XXX
871 11         12 my( $str, $rv, $n, $s );
872 0         0 my( @str );
873 11         21 my( $mincol, $colinc, $minpad, $padchar ) =
874             (param($t,0,""), param($t,1,1),
875             param($t,2,0), param($t,3," "));
876            
877            
878 11         15 do {
879 11         12 local( $accum );
880             # $rv = run( $t->{'subparts'}[0] );
881             # $str = $accum;
882            
883 11         10 foreach $s ( @{$t->{'subparts'}} ){
  11         22  
884 23         25 $accum = "";
885 23         41 $rv = run( $s );
886 23 50       43 last if $rv =~ /hat/;
887 23         30 push @str, $accum;
888 23         42 $n ++;
889             }
890             };
891              
892 11 100       40 if( $n == 1 ){
    50          
893 3 100       20 $str = formatstring( 0, $mincol, $colinc, $minpad, $padchar, "*",
    100          
894             $t->{'atsign'} ? ($t->{'colon'} ? 'c' : 'l') : 'r',
895             $str[0] );
896              
897             }elsif( $n >= 2 ){
898 8         8 my( $rspace, $lspace, $space, $m );
899              
900 8         11 $rspace = $mincol;
901 8         10 $m = 0;
902 8         13 $str = '';
903 8         35 foreach $s (@str){
904 20         26 $space = $rspace / ( $n - $m );
905 20         22 $rspace -= $space;
906            
907 20 100       87 $str .= formatstring( 0, $space, $colinc, $m?$minpad:0, $padchar, "*",
    100          
    100          
    100          
    100          
908             $m==0 ? ($t->{'colon'} ? 'r' : 'l') :
909             ($m==$n-1 ? ($t->{'atsign'} ? 'l' : 'r') :
910             $config_align_middle),
911             $str[$m]);
912 20         37 $m ++;
913             }
914             }
915            
916            
917 11         40 $accum .= $str;
918            
919             }elsif( $d eq '{' ){
920 22         42 my( $maxiter ) = param($t, 0, "");
921 22 100       50 my( $maxiterp ) = $maxiter ne "" ? 1 : 0;
922 22         23 my( $retv );
923 22         34 my( $body ) = $t->{'body'};
924              
925 22 100       38 if( !$body ){
926 1         3 $body = nextarg();
927 1 50       4 return formaterror("An empty {} may be less filling, but it won't work", $t) unless $body;
928 1         2 $body = compile($body);
929             }
930              
931 22 100 100     98 if( $t->{'colon'} && $t->{'atsign'} ){
    100          
    100          
932             # use remaining args, which are sublists
933              
934 2   100     11 while( !$maxiterp || $maxiter-- ){
935 6         6 my( $a, @a );
936 6 100       12 last if $argno >= @arglist;
937            
938 5         7 $a = nextarg();
939 5         11 @a = mkarray($a);
940 5         5 do {
941 5         6 local($argno) = 0;
942 5         10 local(@arglist) = @a;
943              
944 5         7 $retv = run( $body );
945 5 50       29 last if( $retv =~ /colon/ ); # otherwise just this iter
946             };
947             }
948            
949             }elsif( $t->{'colon'} ){
950             # next arg is list of sublists
951 2         3 my( $a, @a );
952            
953 2         5 $a = nextarg();
954 2         2 @a = @{$a};
  2         5  
955              
956 2   100     12 while( !$maxiterp || $maxiter-- ){
957 6 100       15 last unless @a;
958 5         6 do {
959 5         6 local($argno) = 0;
960 5         5 local(@arglist) = @{shift @a};
  5         16  
961              
962 5         10 $retv = run( $body );
963 5 50       29 last if( $retv =~ /colon/ ); # otherwise just this iter
964             };
965             }
966              
967             }elsif( $t->{'atsign'} ){
968             # use remaining args
969              
970 5   100     21 while( !$maxiterp || $maxiter-- ){
971 16 100       40 last if $argno >= @arglist;
972 14         58 $retv = run( $body );
973 14 100       60 last if( $retv =~ /hat/ );
974             }
975              
976             }else{
977             # next arg is list of args
978 13         13 my( $a, @a );
979              
980 13         21 $a = nextarg();
981 13         25 @a = mkarray($a);
982 13         17 do {
983 13         17 local($argno) = 0;
984 13         30 local(@arglist) = @a;
985              
986 13   100     45 while( !$maxiterp || $maxiter-- ){
987 37 100       109 last if $argno >= @arglist;
988 28         49 $retv = run( $body );
989 28 100       101 last if( $retv =~ /hat/ );
990             }
991             };
992             }
993            
994             }elsif( $d eq '[' ){
995 38         45 my( @ch ) = @{$t->{'subparts'}};
  38         73  
996 38         52 my( $ni ) = scalar @ch;
997 38         60 my( $n ) = param($t, 0, "");
998 38         49 my( $rv );
999 38 100       79 $n = nextarg() if $n eq "";
1000              
1001 38 100       89 if( $t->{'atsign'} ){
    100          
1002 8 100       13 $argno -- if( !falsep($n) );
1003 8 100       18 $n = !falsep($n) ? 0 : 1;
1004             }elsif( $t->{'colon'} ){
1005 2 100       7 $n = falsep($n) ? 0 : 1;
1006             }
1007              
1008 38 100 100     103 if( $n >= $ni && (defined $t->{'default_item'} ) ){
1009 2         2 $n = $t->{'default_item'};
1010             }
1011              
1012 38 100       70 if( $n < $ni ){
1013 30         25 my( $str );
1014 30         28 do {
1015 30         40 local( $accum ) = ("");
1016 30         175 $rv = run( $t->{'subparts'}[$n] );
1017 30         52 $str = $accum;
1018             };
1019 30         44 $accum .= $str;
1020             }
1021 38 50       118 return $rv if $rv;
1022            
1023             }elsif( $d eq '^' ){
1024 27         29 my( $np, $out );
1025            
1026 27         22 $np = @{$t->{'numbers'}};
  27         48  
1027              
1028 27 100       71 if( $np == 1 ){
    100          
    100          
1029 2 100       7 $out = 1 if param($t, 0, "") == 0;
1030             }elsif( $np == 2 ){
1031 2 100       7 $out = 1 if param($t, 0, "") == param($t, 1, "");
1032             }elsif( $np == 3 ){
1033 3 100 100     7 $out = 1 if (param($t, 0, "") <= param($t, 1, ""))
1034             && (param($t, 1, "") <= param($t, 2, ""));
1035             }else{
1036 20 100       45 $out = 1 if $argno >= @arglist;
1037             }
1038              
1039 27 100       65 if( $out ){
1040 9 50       20 return "hat/colon" if( $t->{'colon'} );
1041 9         19 return "hat";
1042             }
1043            
1044             }elsif( $d eq 'number' ){
1045 31         34 my( $r );
1046             # $radix, $mincol, $padchar, $commachar, $commawidth, $ovchar, $withsign, $withcommas, $val
1047              
1048 31         36 $r = $t->{'radix'};
1049 31 50       54 $r = pound() if( $r eq "#" );
1050 31 50       44 $r = nextarg() if( $r eq "v" );
1051 31 50 33     652 return formaterror("In base $r? I'm game. Would you care to explain how?", $t) if( $r<1 || $r>36 );
1052            
1053 31 100       228 $accum .=
    100          
1054             formatnumber( $r, param($t,0,""), param($t,1," "),
1055             param($t,2,","), param($t,3,3), param($t,4,"*"),
1056             ($t->{'atsign'}?1:0), ($t->{'colon'}?1:0),
1057             nextarg());
1058            
1059             }elsif( $d eq 'roman' ){
1060 11         25 $accum .= formatstring(0, param($t,0,""), 1, 0, param($t,1, " "),
1061             param($t,4, "*"), "r",
1062             roman($t->{'colon'}, nextarg()));
1063            
1064             }elsif( $d eq 'english' ){
1065 5         13 $accum .= formatstring(0, param($t,0,""), 1, 0, param($t,1, " "),
1066             param($t,4, "*"), "r",
1067             english($t->{'colon'}, nextarg()));
1068              
1069             ### Non-standard 2 character (=X) directives
1070             }elsif( $d eq '=V' ){
1071             # Version info
1072             # ~=:V - long form
1073             # ~=:@V - longer form
1074              
1075 0 0       0 $accum .= "Good morning Dr. Chandra, I am " if $t->{'atsign'};
1076 0 0       0 $accum .= "Fmt Version " if $t->{'colon'};
1077 0         0 $accum .= "$VERSION";
1078            
1079             }elsif( $d eq '=(' ){
1080             # "eval" - use results of formatting as a format spec
1081             # ~=(...~=)
1082            
1083 1         2 my( $str, $rv, $fmt );
1084 1         3 do {
1085 1         23 local( $accum ) = ("");
1086 1         4 $rv = run( $t->{'body'} );
1087 1         10 $str = $accum;
1088              
1089 1         1 $accum = '';
1090 1         4 $fmt = compile($str);
1091 1         5 $rv = run( $fmt );
1092 1         3 $str = $accum;
1093            
1094             };
1095 1         2 $accum .= $str;
1096 1 50       7 return $rv if $rv;
1097              
1098             }elsif( $d eq '=F' ){
1099             # suck in line/lines from a file
1100             # ~=F - entire file
1101             # ~N=F - just line N
1102             # ~N,M=F lines N through M
1103              
1104 0         0 my( $null, $i, $n, $m, $f );
1105              
1106 0         0 $n = param($t,0,'');
1107 0         0 $m = param($t,1,'');
1108 0         0 $f = nextarg();
1109 0         0 $i = 1;
1110 0 0       0 open(FMT_FILE, $f) || return formaterror( "'$f' is stubborn and refuses to open: $!", $t);
1111              
1112 0 0       0 if( $n ne '' ){
1113 0   0     0 while( $n != $i && !eof(FMT_FILE) ){
1114 0         0 $null = ;
1115 0         0 $i++;
1116             }
1117 0 0       0 if( $m ne '' ){
1118 0   0     0 while( $m + 1 != $i++ && !eof(FMT_FILE) ){
1119 0         0 $accum .= ;
1120             }
1121             }else{
1122 0         0 $accum .= ;
1123             }
1124             }else{
1125 0         0 $accum .= $_ while( );
1126             }
1127 0         0 close(FMT_FILE);
1128            
1129              
1130             ### directives that should not happen (errors)
1131             }elsif( $d eq ')' ){
1132             # error - no start
1133 0         0 return formaterror( "I see no matching ( here", $t );
1134              
1135             }elsif( $d eq '}' ){
1136             # error - no start
1137 0         0 return formaterror( "I see no matching { here", $t );
1138            
1139             }elsif( $d eq ']' ){
1140             # error - no start
1141 0         0 return formaterror( "I see no matching [ here", $t );
1142              
1143             }elsif( $d eq ';' ){
1144             # no enclosing [] or <>
1145 0         0 return formaterror( "I see no enclosing [] or <> here", $t );
1146            
1147             }else{
1148             # error - unknown
1149 0         0 return formaterror( "I don't know how to apply that word ($d) here.", $t);
1150             }
1151             }
1152 316         515 "";
1153             }
1154              
1155             ### for debugging
1156             sub tree {
1157 0     0 0 0 my( $n, $t ) = @_;
1158 0         0 my( $nn, @t, @nn );
1159              
1160 0         0 @t = @{$t};
  0         0  
1161 0         0 while( @t ){
1162 0         0 $t = shift @t;
1163            
1164 0         0 print " " x $n, "$t->{'directive'}\n";
1165 0 0       0 tree($n+1, $t->{'body'} ) if( $t->{'body'} );
1166              
1167 0 0       0 if( $t->{'subparts'} ){
1168 0         0 @nn = @{$t->{'subparts'}};
  0         0  
1169 0         0 while( @nn ){
1170 0         0 $nn = shift @nn;
1171 0         0 tree($n+1, $nn);
1172 0         0 print " " x $n, " ;\n";
1173             }
1174             }
1175             }
1176             }
1177            
1178             ### compile the format spec
1179             sub compile {
1180 213     213 0 244 my( $fmt ) = @_;
1181              
1182 213         333 parse(tok( $fmt ));
1183             }
1184              
1185             ### the main entry point
1186             sub fmt {
1187 208     208 0 3458 my( $fmt ) = shift;
1188 208         443 local( @arglist ) = @_; # our arglist
1189 208         261 local( $argno ) = 0; # index into above
1190 208         244 local( $accum ) = ""; # accumulator for output string
1191            
1192 208 50       487 run( ref($fmt) ? $fmt : compile($fmt));
1193              
1194 208         5496 $accum;
1195             }
1196              
1197             ### format, print to stdout
1198             sub pfmt {
1199 0     0 0   print fmt(@_);
1200             }
1201              
1202             ################################################################
1203             ################################################################
1204             ################################################################
1205              
1206             =head1 NAME
1207              
1208             Lisp::Fmt - Perl module for Common Lisp like formatting
1209              
1210             =head1 SYNOPSIS
1211              
1212             use Lisp::Fmt;
1213             $str = fmt("~{~a ~5,,,'*a~}", $a,$b,$c,$d); # store result in $str
1214             pfmt("~{ ~a~5,,,'*a~}", $a,$b,$c,$d); # print to stdout
1215              
1216             =head1 DESCRIPTION
1217              
1218             The Common Lisp "format" function provides an extremely rich set of formatting
1219             directives. This module brings this to Perl.
1220              
1221             The formatting directives all begin with a C<~> and take the form:
1222             C<~[N]{,N}[@][:]X>
1223              
1224             where C is a number, C is a formatting directive, and C<@> and C<:> are
1225             optional modifiers. Recognized directives are: A, S, W, D, O, B, X, R, C, P,
1226             T, ~, %, |, _, ?, *, \n, {, }, (, ), [, ], <, >, ^
1227              
1228             examples:
1229            
1230             C<~A> - simplest format spec, prints the arg
1231             C<~D> - prints a number in base 10
1232             C<~X> - prints a number in base 16
1233             C<~12R> - prints a number in base 12
1234             C<~@R> - prints a number in roman numerals
1235             C<~#[ none~; ~a~; ~a and ~a~:;~!{~#[~; and~] ~a~^,~}~].">
1236             - prints a list in nice readable english
1237              
1238             =head1 FORMAT SPEC
1239              
1240             as a param, a v will read the param from the arglist
1241             a # will interpolate to the number of remaining args
1242              
1243             the directive can be one of:
1244              
1245             A print the arg
1246             S print the arg in a readable form (strings are quotes,...)
1247             @ will pad on left
1248             params are: mincols (maxcols if <0), colinc, minpad, padchar, overflowchar
1249              
1250             ~ print a ~ [N ~s]
1251             % print a newline [N newlines]
1252             | print a formfeed [N formfeeds]
1253             _ print a space [N spaces]
1254             & print a newline unless already at the beginning of a line
1255             T tabulate
1256             @ relative
1257             params are: colnum, colinc
1258            
1259             n ignore the newline and any following whitespace
1260             : newline is ignored, whitespace is left
1261             @ newline is printed, following whitespce is ignored
1262             * next arg is ignored, with param, next N args are ignored
1263             : back up in arg list, with param, backup N args
1264             @ goto 0th arg, or with a param, Nth arg
1265             ? indirect - 2 args are a format string and list of args
1266             @ - 1 arg - is a format string, use args from current arglist
1267            
1268             P pluralize
1269             @ use y/ies
1270             : use previous arg
1271            
1272             D a number in base 10
1273             O a number in base 8
1274             X a number in base 16
1275             B a number in base 2
1276             R a number in specified radix (ie. ~7R)
1277             @ print leading sign
1278             : print with commas
1279             params are: mincol, padchar, commachar, commawidth, overflowchar
1280            
1281             without a radix specifier:
1282             in english "four"
1283             : in english, ordinal "fourth"
1284             @ in roman "IV"
1285             :@ in old roman "IIII"
1286              
1287              
1288             C a character
1289             @ as with write
1290             : spell out control chars
1291            
1292             ( downcase until ~) - hello world
1293             @ capitalize the first word - Hello world
1294             : capitalize - Hello World
1295             :@ uppercase - HELLO WORLD
1296            
1297             { iteration spec until ~}
1298             @ use remaining args
1299             : arg is list of sublists
1300             :@ use remaining args, which are sublists
1301            
1302             [ conditional spec, separated with ~; ending with ~]
1303             choose item specified by arg ~:; is default item
1304             with a param, chhose with it instead of arg
1305             @ choose if not false
1306             : use first item if false, second otherwise
1307            
1308             ^ abort ? {} or <> if no args remain,
1309             or if a param is given, it is 0
1310             or if 2 params are given, they are equal
1311             or if 3 params are given, the 1st is <= 2nd <= 3rd
1312             : terminate an entire :{ or :@{, not just this iteration
1313              
1314              
1315             For a more complete description of the various formatting directives, parameters, etc.
1316             see your favorite lisp reference, such as
1317             http://www.harlequin.com/education/books/HyperSpec/Body/sec_22-3.html.
1318              
1319             =head1 NOTES
1320              
1321             ! is a synonym for @
1322              
1323             Often used format strings can be pre-compiled:
1324             C<$f = Fmt::compile("~{ ~a ~5,,,'*a~}");>
1325             C<$str = fmt( $f, ...);>
1326              
1327             when lisp says an arg is a "list", we translate that as a reference to a list (or hash)
1328              
1329             lisp: (format () "~{ ~A~}\n" '(a b c d e))
1330             perl: fmt( "~{ ~A~}\n", ["a", "b", "c", "d"])
1331             fmt( "~{ key ~A value ~A\n~}", {foo=>1, bar=>2, baz=>3})
1332              
1333             =head1 BUGS
1334              
1335             Floating-point output is not yet supported.
1336              
1337             the <> formatting support is incomplete.
1338              
1339             the radix for ~R is restricted to the range 1-36
1340              
1341             no test is performed to detect circular data structures
1342              
1343             many other bugs not listed here
1344              
1345             =head1 CHANGES
1346              
1347             none.
1348              
1349             =head1 TO DO
1350              
1351             see BUGS.
1352              
1353             =head1 SEE ALSO
1354              
1355             Common Lisp - The Language 2nd. ed.
1356             L
1357             Yellowstone National Park.
1358              
1359             =head1 AUTHOR
1360              
1361             Jeff Weisberg - http://www.tcp4me.com/code/
1362              
1363             =head1 COPYRIGHT
1364              
1365             This software is Copyright (c) 1998 Jeff Weisberg
1366             Permission is granted to use, copy and distribute this software
1367             under the following conditions:
1368             - This license covers the original software, as well as
1369             modified or derived works.
1370             - All modified or derived works must contain this notice
1371             unmodified and in its entirety.
1372             - This software is not to be used for any purpose which
1373             may be considered illegal, immoral, or unethical.
1374             - This software is provided as is and without warranty.
1375              
1376             =cut
1377             ;
1378              
1379             1;