File Coverage

blib/lib/Text/Template/Compact.pm
Criterion Covered Total %
statement 901 964 93.4
branch 277 388 71.3
condition 106 151 70.2
subroutine 155 155 100.0
pod 0 33 0.0
total 1439 1691 85.1


line stmt bran cond sub pod time code
1             #!perl --
2             package Text::Template::Compact;
3 4     4   47014 use utf8;
  4         22  
  4         84  
4 4     4   92 use strict;
  4         12  
  4         106  
5 4     4   34 use warnings;
  4         14  
  4         141  
6 4     4   1453 use Encode;
  4         26136  
  4         255  
7 4     4   32 use Carp;
  4         8  
  4         2331  
8             our $VERSION = "0.1.12";
9            
10             sub getDataType($){
11             # return empty string if not reference type.
12 1446 100   1446 0 3841 return '' if not ref $_[0];
13             # use scalar to avoid bless.
14 1444         5385 $_[0]=~/(\w+)\(/;
15 1444         4263 return $1;
16             }
17            
18             # decode "quoted" string to plain.
19             sub decodeQuote{
20 686 50 33 686 0 3044 return if not @_ or not defined $_[0];
21 686 100       2803 return $_[0] if not $_[0] =~ /^"/;
22 226         655 my $r = substr($_[0],1,length($_[0])-2);
23 226         519 $r =~ s/""/"/g;
24 226         940 return $r;
25             }
26            
27             # defined or ...
28             sub dor{
29 866 100   866 0 1774 for(@_){ defined($_) and return $_;}
  1260         3755  
30 1         4 return;
31             }
32            
33             # filter for variable expansion
34             our %filter_map =(
35             raw => sub{
36             return $_[0];
37             },
38             html => sub{
39             my $a = $_[0];
40             $a =~ s/&/&/g;
41             $a =~ s/
42             $a =~ s/>/>/g;
43             $a =~ s/"/"/g;
44             $a =~ s/'/'/g;
45             $a =~ s/\n/
\n/g;
46             return $a;
47             },
48             nobr => sub{
49             my $a = $_[0];
50             $a =~ s/&/&/g;
51             $a =~ s/
52             $a =~ s/>/>/g;
53             $a =~ s/"/"/g;
54             $a =~ s/'/'/g;
55             return $a;
56             },
57             uri => sub{
58             my $a = Encode::encode('utf8',$_[0]);
59             $a =~ s/([^a-zA-Z0-9_.!~*'()-])/'%' . unpack('H2', $1)/eg;
60             return $a;
61             },
62             );
63            
64             {
65             package Text::Template::Compact::ExprParser;
66 4     4   29 use Carp;
  4         9  
  4         838  
67             our @ExprOperator;
68             our %ExprOperator;
69             our $token_re;
70             our $dataset;
71             our $list_op_prio;
72             {
73             my $prio=0;
74             for(
75             # 優先順序が同じ場合の結合順序: L=左結合((a+b)+c) R=右結合(a=(b=c)) _=非結合
76             # 演算子の見た目:
77             # b (braket) (a) のような外見,
78             # l (left) 左側の単項演算子。 +num
79             # a (array) a[b] のような外見,
80             # r (right) 右側の単項演算子。 num++
81             # m (middle) 二項演算子 a ** b
82             # t (tri) 三項演算子 a?b:c
83             # k (keyword) 二項演算子 a.keyword 演算子の右側に式ではなくキーワードが入る
84             # c (const) 定数
85             # K (const) キーワード
86             # 優先順序の区切り: []
87             ['Lb','(',1,sub{ return $_[0];},')'],
88             ['La','(',1,sub{
89             # find list operator
90             my $key = $dataset->getV($_[0]);
91             my $op = findOp($key,qr/l/);
92             $op or return $dataset->encode("[Error: $op() is not found]");
93             return $op->{_eval}->($_[1]);
94             },')'],
95             [],
96             ['Lk','.',2,sub{ return $dataset->child($_[0],$_[1] ) } ],
97             ['La','[',2,sub{ return $dataset->child($_[0],$dataset->encode($dataset->getV($_[1]))) },']' ],
98             [],
99 4     4   27 ['_l','++',1,sub{ no warnings; my $v= $dataset->getV($_[0]); $dataset->setV($_[0],++$v); return $dataset->encode( $v);} ],
  4         7  
  4         366  
100 4     4   30 ['_l','--',1,sub{ no warnings; my $v= $dataset->getV($_[0]); $dataset->setV($_[0],--$v); return $dataset->encode( $v);} ],
  4         14  
  4         242  
101 4     4   22 ['_r','++',1,sub{ no warnings; my $v= $dataset->getV($_[0]); $dataset->setV($_[0],++$v); return $dataset->encode(--$v);} ],
  4         14  
  4         228  
102 4     4   36 ['_r','--',1,sub{ no warnings; my $v= $dataset->getV($_[0]); $dataset->setV($_[0],--$v); return $dataset->encode(++$v);} ],
  4         9  
  4         270  
103             [],
104 4     4   31 ['Rm','**',2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0])**$dataset->getV($_[1])); } ],
  4         16  
  4         221  
105             [],
106 4     4   25 ['Rl','!',1,sub{ no warnings; return $dataset->encode(!$dataset->getV($_[0]))} ],
  4         12  
  4         189  
107 4     4   24 ['Rl','~',1,sub{ no warnings; return $dataset->encode(~(0+$dataset->getV($_[0])))} ],
  4         9  
  4         185  
108 4     4   29 ['Rl','+',1,sub{ no warnings; return $dataset->encode(+$dataset->getV($_[0]))} ],
  4         9  
  4         171  
109 4     4   21 ['Rl','-',1,sub{ no warnings; return $dataset->encode(-$dataset->getV($_[0]))} ],
  4         8  
  4         182  
110             [],
111 4     4   24 ['Lm','*',2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) * $dataset->getV($_[1])) }],
  4         9  
  4         208  
112 4     4   21 ['Lm','/',2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) / $dataset->getV($_[1])) }],
  4         10  
  4         227  
113 4     4   22 ['Lm','%',2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) % $dataset->getV($_[1])) }],
  4         8  
  4         198  
114 4     4   22 ['Lm','repeat',2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) x $dataset->getV($_[1])) }],
  4         11  
  4         214  
115             [],
116 4     4   22 ['Lm','+',2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) + $dataset->getV($_[1])) } ],
  4         7  
  4         220  
117 4     4   21 ['Lm','-',2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) - $dataset->getV($_[1])) } ],
  4         16  
  4         234  
118 4     4   27 ['Lm','cat',2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) . $dataset->getV($_[1])) } ],
  4         10  
  4         1144  
119             [],
120 4     4   27 ['Lm','<<',2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) << $dataset->getV($_[1])) } ],
  4         10  
  4         259  
121 4     4   24 ['Lm','>>',2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) >> $dataset->getV($_[1])) } ],
  4         10  
  4         401  
122             [],
123             ['_l','defined' ,1,sub{ return $dataset->encode( defined($dataset->getV($_[0])) ?1:0); }],
124             ['_l','bool' ,1,sub{ return $dataset->encode($dataset->getV($_[0]) ?1:0); }],
125 4     4   21 ['_l','nz' ,1,sub{ no warnings; return $dataset->encode($dataset->getV($_[0])!=0 ?1:0); }],
  4         9  
  4         203  
126 4     4   22 ['_l','int' ,1,sub{ no warnings; return $dataset->encode(int $dataset->getV($_[0]) ); }],
  4         8  
  4         954  
127             ['_l','length' ,1,sub{
128             my $v =$dataset->getV($_[0]);
129             defined($v) or return $dataset->encode(undef);
130             ref($v) or return $dataset->encode(length $v);
131             my $type = Text::Template::Compact::getDataType($v);
132             $type =~ /ARRAY/ and return $dataset->encode(scalar @$v);
133             $type =~ /HASH/ and return $dataset->encode(scalar keys %$v);
134             return $dataset->encode(length $v);
135             }],
136             ['l','pop',1,sub{
137             my $ra = $dataset->getV($_[0]);
138             if(Text::Template::Compact::getDataType($ra) =~ /ARRAY/ ){
139             return $dataset->encode( pop @$ra );
140             }
141             return $dataset->encode( undef );
142             }],
143             ['l','shift',1,sub{
144             my $ra = $dataset->getV($_[0]);
145             if(Text::Template::Compact::getDataType($ra) =~ /ARRAY/ ){
146             return $dataset->encode( shift @$ra );
147             }
148             return $dataset->encode( undef );
149             }],
150             [],
151 4     4   26 ['_m','<' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) < $dataset->getV($_[1])) }],
  4         7  
  4         231  
152 4     4   23 ['_m','>' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) > $dataset->getV($_[1])) }],
  4         14  
  4         231  
153 4     4   20 ['_m','<=' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) <= $dataset->getV($_[1])) }],
  4         10  
  4         208  
154 4     4   20 ['_m','>=' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) >= $dataset->getV($_[1])) }],
  4         9  
  4         192  
155 4     4   21 ['_m','lt' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) lt $dataset->getV($_[1])) }],
  4         27  
  4         202  
156 4     4   21 ['_m','gt' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) gt $dataset->getV($_[1])) }],
  4         7  
  4         225  
157 4     4   24 ['_m','le' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) le $dataset->getV($_[1])) }],
  4         7  
  4         218  
158 4     4   21 ['_m','ge' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) ge $dataset->getV($_[1])) }],
  4         8  
  4         217  
159             [],
160 4     4   24 ['_m','==' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) == $dataset->getV($_[1])) }],
  4         21  
  4         229  
161 4     4   22 ['_m','!=' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) != $dataset->getV($_[1])) }],
  4         6  
  4         211  
162 4     4   20 ['_m','<=>' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) <=> $dataset->getV($_[1])) }],
  4         8  
  4         199  
163 4     4   20 ['_m','eq' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) eq $dataset->getV($_[1])) }],
  4         8  
  4         243  
164 4     4   22 ['_m','ne' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) ne $dataset->getV($_[1])) }],
  4         11  
  4         189  
165 4     4   21 ['_m','cmp' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0]) cmp $dataset->getV($_[1])) }],
  4         8  
  4         247  
166             [],
167 4     4   26 ['Lm','&' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0])+0 & 0+$dataset->getV($_[1])) }],
  4         10  
  4         237  
168             [],
169 4     4   22 ['Lm','|' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0])+0 | 0+$dataset->getV($_[1])) }],
  4         7  
  4         232  
170 4     4   21 ['Lm','^' ,2,sub{ no warnings; return $dataset->encode($dataset->getV($_[0])+0 ^ 0+$dataset->getV($_[1])) }],
  4         8  
  4         801  
171             [],
172             ['Lm','&&',2,sub{ return $dataset->getV($_[0]) ?$_[1]:$_[0]; }],
173             [],
174             ['Lm','||',2,sub{ return $dataset->getV($_[0]) ?$_[0]:$_[1]; }],
175             ['Lm','//',2,sub{ return defined($dataset->getV($_[0])) ?$_[0]:$_[1]; }],
176             [],
177             ['Rt','?' ,3,sub{ return $dataset->getV($_[0])? $_[1]:$_[2]; },':'],
178             [],
179             [ 'Rm', '=',2,sub{ $dataset->setV($_[0],$dataset->getV($_[1]) ); return $_[0]; }],
180 4     4   25 [ 'Rm','**=',2,sub{ no warnings; $dataset->setV($_[0],$dataset->getV($_[0]) ** $dataset->getV($_[1]) ); return $_[0]; }],
  4         8  
  4         237  
181 4     4   28 [ 'Rm', '*=',2,sub{ no warnings; $dataset->setV($_[0],$dataset->getV($_[0]) * $dataset->getV($_[1]) ); return $_[0]; }],
  4         8  
  4         224  
182 4     4   26 [ 'Rm', '/=',2,sub{ no warnings; $dataset->setV($_[0],$dataset->getV($_[0]) / $dataset->getV($_[1]) ); return $_[0]; }],
  4         9  
  4         240  
183 4     4   22 [ 'Rm', '%=',2,sub{ no warnings; $dataset->setV($_[0],$dataset->getV($_[0]) % $dataset->getV($_[1]) ); return $_[0]; }],
  4         8  
  4         209  
184 4     4   21 [ 'Rm', '+=',2,sub{ no warnings; $dataset->setV($_[0],$dataset->getV($_[0]) + $dataset->getV($_[1]) ); return $_[0]; }],
  4         9  
  4         216  
185 4     4   21 [ 'Rm', '-=',2,sub{ no warnings; $dataset->setV($_[0],$dataset->getV($_[0]) - $dataset->getV($_[1]) ); return $_[0]; }],
  4         6  
  4         222  
186 4     4   20 [ 'Rm','<<=',2,sub{ no warnings; $dataset->setV($_[0],$dataset->getV($_[0]) << $dataset->getV($_[1]) ); return $_[0]; }],
  4         7  
  4         213  
187 4     4   19 [ 'Rm','>>=',2,sub{ no warnings; $dataset->setV($_[0],$dataset->getV($_[0]) >> $dataset->getV($_[1]) ); return $_[0]; }],
  4         9  
  4         213  
188 4     4   21 [ 'Rm','&=' ,2,sub{ no warnings; $dataset->setV($_[0],$dataset->getV($_[0])+0&0+$dataset->getV($_[1]) ); return $_[0]; }],
  4         8  
  4         228  
189 4     4   22 [ 'Rm','|=' ,2,sub{ no warnings; $dataset->setV($_[0],$dataset->getV($_[0])+0|0+$dataset->getV($_[1]) ); return $_[0]; }],
  4         8  
  4         282  
190 4     4   23 [ 'Rm','^=' ,2,sub{ no warnings; $dataset->setV($_[0],$dataset->getV($_[0])+0^0+$dataset->getV($_[1]) ); return $_[0]; }],
  4         6  
  4         10760  
191             [ 'Rm','&&=',2,sub{ $dataset->getV($_[0]) and $dataset->setV($_[0],$dataset->getV($_[1])); return $_[0]; }],
192             [ 'Rm','||=',2,sub{ $dataset->getV($_[0]) or $dataset->setV($_[0],$dataset->getV($_[1])); return $_[0]; }],
193             [ 'Rm','//=',2,sub{ defined($dataset->getV($_[0])) or $dataset->setV($_[0],$dataset->getV($_[1])); return $_[0]; }],
194            
195             [],
196             ['Lm',',',2,sub{ return $dataset->makepathlist($_[0],$_[1]);}],
197             [],
198             ['l','print',1,sub{
199             my @list;
200             $dataset->getVlist(\@list,$_[0]);
201             $dataset->print( @list);
202             return $dataset->encode('');
203             }],
204             ['l','join',1,sub{
205             my @list;
206             $dataset->getVlist(\@list,$_[0]);
207             @list or return $dataset->encode('');
208             my $delm = shift @list;
209             my $a = join $delm,@list;
210             return $dataset->encode( $a );
211             }],
212             ['l','scalar',1,sub{
213             my @list;
214             $dataset->getVlist(\@list,$_[0]);
215             return $dataset->encode( scalar @list );
216             }],
217             ['l','push',1,sub{
218             my @list;
219             $dataset->getVlist(\@list,$_[0]);
220             if( @list and Text::Template::Compact::getDataType($list[0]) =~ /ARRAY/ ){
221             my $ra = shift @list;
222             push @$ra,@list;
223             }
224             return $dataset->encode( undef );
225             }],
226             ['l','unshift',1,sub{
227             my @list;
228             $dataset->getVlist(\@list,$_[0]);
229             if(@list and Text::Template::Compact::getDataType($list[0]) =~ /ARRAY/ ){
230             my $ra = shift @list;
231             unshift @$ra,@list;
232             }
233             return $dataset->encode( undef );
234             }],
235             ['l','call',1,sub{
236             my @list;
237             $dataset->getVlist(\@list,$_[0]);
238             local $_ = $dataset->{tmpl}{param};
239             if(@list and Text::Template::Compact::getDataType($list[0]) =~ /CODE/ ){
240             my $coderef = shift @list;
241             my $rv = eval{ $coderef->(@list); };
242             $@ and $rv = "[Error: $@]";
243             return $dataset->encode( $rv );
244             }elsif( @list >= 2 ){
245             my $obj = shift @list;
246             my $method = shift @list;
247             my $rv = eval{ $obj->$method(@list); };
248             $@ and $rv = "[Error: $@]";
249             return $dataset->encode( $rv );
250             }
251             return $dataset->encode( undef );
252             }],
253             ['l','makearray',1,sub{
254             my @list;
255             $dataset->getVlist(\@list,$_[0]);
256             return $dataset->encode( \@list );
257             }],
258             ['l','makehash',1,sub{
259             my @list;
260             $dataset->getVlist(\@list,$_[0]);
261             return $dataset->encode( {@list} );
262             }],
263             [],
264             # not and or xor
265             ['Rl','not',1,sub{ return $dataset->encode(not $dataset->getV($_[0]));} ],
266             [],
267             ['Lm','and',2,sub{ return $dataset->encode( $dataset->getV($_[0]) and $dataset->getV($_[1])); }],
268             [],
269             ['Lm','or' ,2,sub{ return $dataset->encode( $dataset->getV($_[0]) or $dataset->getV($_[1])); }],
270             ['Lm','xor',2,sub{ return $dataset->encode( $dataset->getV($_[0]) xor $dataset->getV($_[1])); }],
271             ){
272             if(not @$_){ ++$prio; next; }
273             my $item = {
274             prio => $prio,
275             assoc => $_->[0],
276             key1 => $_->[1],
277             count => $_->[2],
278             _eval => $_->[3],
279             key2 => $_->[4],
280             };
281             ( $item->{key1} eq 'print') and $list_op_prio=$prio;
282            
283             push @ExprOperator,$item;
284             # defined($ExprOperator{ $item->{key1} }) or $ExprOperator{ $item->{key1} }=[];
285             push @{$ExprOperator{ $item->{key1} }},$item;
286             }
287             # make re
288             my %c;
289             for( '#',',',';','-|','$','$$',map {$_->{key1},$_->{key2}} @ExprOperator){
290             next if not defined or not length;
291             next if /^\w+$/;
292             my $text = $_;
293             my $map = \%c;
294             for(my $i=0;$i
295             my $c = substr($text,$i,1);
296             $map->{$c} or $map->{$c}={};
297             $map = $map->{$c};
298             }
299             }
300             sub toRe{
301 163     163   318 my($map)=@_;
302 163         266 my @list;
303 163         456 while(my($k,$v)=each %$map){
304 160         373 my $sub = toRe($v);
305 160 100       305 if($sub){
306 61         213 push @list,quotemeta($k).$sub."?";
307             }else{
308 100         357 push @list,quotemeta($k);
309             }
310             }
311 163 100       446 @list > 1 and return "(?:".join('|',@list).")";
312 136 100       311 @list and return $list[0];
313 100         226 return;
314             }
315             my $a = toRe(\%c);
316             $token_re = qr/$a/;
317             }
318            
319             sub findOp{
320 1522     1522   3009 my($token,$re)=@_;
321 1522 100       5301 my $list = $ExprOperator{ $token } or return;
322 467 100       960 for(@$list){ return $_ if $_->{assoc} =~ $re; }
  520         2928  
323 43         190 return;
324             }
325            
326             {
327             package Text::Template::Compact::ExprNode;
328             sub new{
329 523     523   1083 my($class,$op,$text)=@_;
330 523 100       1390 if(not ref $op){
331             # keyword?
332 286 50       696 return $text if $op eq 'k';
333             # root operator?
334             $op = {
335             assoc => '_',
336             key1 => 'root',
337 1283     1283   2445 _eval => sub{ return $_[0]; },
338 286         1703 count => 1,
339             prio => 999,
340             };
341             }
342 523         2605 return bless{ op=>$op, args=>[],},$class;
343             };
344             sub addArg{
345 844     844   1332 my $self=shift;
346 844         1200 push @{$self->{args}},@_;
  844         1965  
347             }
348             sub toString{
349 1     1   35 my($self,$mark)=@_;
350 1 0       7 defined($mark) or $mark = '';
351 1 0       2 if($self->{op}{key1} eq 'root'){
352 1 0       31 return join(',',map{ ref($_)?$_->toString($mark):$_} @{$self->{args}});
  1         7  
  1         2  
353             }
354             return
355             $self->{op}{key1}
356             .($self->{closed}?$self->{op}{key2}:'')
357             .($mark eq $self ?"<=HERE=>":'')
358             .'{'
359 1 0       32 .join(',',map{ ref($_)?$_->toString($mark):$_ } @{$self->{args}} )
  1 0       6  
  1 0       3  
360             .'}';
361             }
362             sub _eval{
363 2036     2036   3577 my($self)=@_;
364 2036 100       3098 my @args = map{ ref($_) ? $_->_eval() : $Text::Template::Compact::ExprParser::dataset->token2path($_) } @{$self->{args}};
  2524         6888  
  2036         4501  
365 2036   66     10189 my $r = ($self->{realop} || $self->{op})->{_eval}(@args);
366 2036         6870 return $r;
367             }
368             sub eval{
369 1283     1283   2453 my($self,$dataset)=@_;
370 1283         2197 local $Text::Template::Compact::ExprParser::dataset = $dataset;
371 1283         2076 my $r = CORE::eval{ $self->_eval();};
  1283         2701  
372 1283 50       3043 $@ and return ['i',"[Error: $@ in ".$self->toString." in Text::Template::Compact::ExprNode::eval]"];
373 1283         3264 return $r;
374             }
375             }
376            
377             our $verbose = 0;
378            
379             sub peekToken{
380 2394     2394   3720 my $self = shift;
381 2394 100       3457 @{$self->{token}} and return $self->{token}[0];
  2394         6562  
382 1172         2241 return;
383             }
384             sub reduce{
385 1000     1000   1953 my($self,$where)=@_;
386            
387 1000 100       2721 return if $self->{allow_child};
388            
389 514         1030 my $a = $self->peekToken;
390            
391             # スタックが空ならreduceできない
392 514 100       862 return if @{$self->{stack}}==1;
  514         1428  
393            
394 229         461 my $target = $self->{stack}[0];
395            
396             # 注目ノードの種別が () []だった
397 229 100       670 if( $target->{op}{assoc} =~ /[ba]/ ){
398 26 50 33     138 if( defined($a) and $a eq $target->{op}{key2}
      33        
399 26         124 and @{$target->{args}} == $target->{op}->{count}
400             ){
401 26 50       71 $verbose>0 and warn "remove end of braket $target->{op}{key2}. reduce in $where\n";
402 26         63 $target->{closed} = 1;
403 26         79 shift @{$self->{token}};
  26         59  
404 26         45 shift @{$self->{stack}};
  26         104  
405 26         45 $self->{allow_child} = 0;
406 26         82 return 1;
407             }
408             }else{
409 204 100       351 if( @{$target->{args}} == $target->{op}->{count} ){
  204         544  
410 200 50       432 $verbose>0 and warn "end of operator $target->{op}{key1} . reduce in $where\n";
411 200         343 shift @{$self->{stack}};
  200         360  
412 200         347 $self->{allow_child} = 0;
413 200         648 return 1;
414             }
415             # ?:
416 5 50 33     63 if( $target->{op}{assoc} =~ /t/ and $a and $a eq $target->{op}->{key2} ){
      33        
417 5 50       20 $verbose>0 and warn "eating ':' operator . reduce in $where\n";
418 5         41 shift @{$self->{token}};
  5         20  
419 5         12 $self->{allow_child} = 1;
420 5         48 return 1;
421             }
422             }
423 1         9 return;
424             }
425            
426             sub parse{
427 286     286   511 my($list)=@_;
428            
429 286         844 my $self = bless{
430             allow_child => 1,
431             stack=>[new Text::Template::Compact::ExprNode('')],
432             token=>$list,
433             };
434            
435 286         581 my($op,$node);
436            
437 286         440 Loop: for(;;){
438 1152         2049 my $target = $self->{stack}[0];
439 1152         2312 my $token = $self->peekToken;
440 1152 50 0     2701 $verbose>0 and warn "mode=$self->{allow_child} token:",($token||'')," stack:",join(',',map{$_->{op}{key1}} @{$self->{stack}}),"\n";
  1         51  
  1         7  
441            
442            
443             # reduce if possible
444 1152 100 100     4114 if( not defined($token)
445             or not findOp( $token,qr/[armtk]/ )
446             ){
447 970 100       2080 next if $self->reduce('loop');
448             }
449            
450 954 100       2728 last if not defined($token);
451            
452 719 100       1584 if( $self->{allow_child} ){
453 507         1544 my $op = findOp($token,qr/[bl]/);
454 507 100       1376 if($op){
455             # listop(b) ??
456 63 100 66     117 if( @{$self->{token}} >= 2
  63   100     508  
457             and $op->{key1} =~/^\w/
458             and $self->{token}[1] eq '('
459             ){
460 6 50       22 $verbose>0 and warn "start of term $token(?) \n";
461 6         13 shift @{$self->{token}};
  6         55  
462 6         17 shift @{$self->{token}};
  6         16  
463 6         51 $node = new Text::Template::Compact::ExprNode(findOp('(',qr/a/));
464 6         25 $target->addArg($node);
465 6         13 unshift @{$self->{stack}},$node;
  6         46  
466 6         18 $self->{allow_child} = 1;
467 6         16 $node->{realop} = $op;
468 6         40 next;
469             }
470            
471             # unary left or '('
472 58 50       162 $verbose>0 and warn "operator $token start\n";
473 58         104 shift @{$self->{token}};
  58         137  
474 58         170 $node = new Text::Template::Compact::ExprNode($op);
475 58         166 $target->addArg($node);
476 58         138 unshift @{$self->{stack}},$node;
  58         145  
477 58         114 $self->{allow_child} = 1;
478 58         161 next;
479             }
480            
481             # keyword or constant or $,$$
482 445 50       1731 if( $token =~/^["\w\d_\$]/ ){
483 445 50       1015 $verbose>0 and warn "constant or keyword $token\n";
484 445         754 $target->addArg(shift @{$self->{token}});
  445         1359  
485 445         904 $self->{allow_child} = 0;
486            
487             # $keyword
488 445         854 my $old_arg = $target->{args}[-1];
489 445         902 $token = $self->peekToken;
490 445 100 100     2043 if( defined($token) and $token =~/^["\w\d_]/ and $old_arg =~/^\$/ ){
      100        
491 14 50       79 $verbose>0 and warn "merge '$old_arg' and '$token'\n";
492 14         60 $node = new Text::Template::Compact::ExprNode(findOp('.',qr/[armtk]/));
493 14         39 $target->{args}[-1] = $node;
494 14         96 $node->addArg($old_arg,$token);
495 14         33 shift @{$self->{token}};
  14         30  
496             }
497 445         935 next;
498             }
499             }else{
500            
501 213         671 $op = findOp($token,qr/[armtk]/);
502 213 100       619 if($op){
503 163         415 $node = new Text::Template::Compact::ExprNode($op);
504 163         299 my $a;
505 163         279 while(@{$self->{stack}}){
  193         487  
506 193         411 my($left,$right) =($target->{op},$op);
507 193         379 my($left_prio,$right_prio) =($left->{prio},$right->{prio});
508 193         399 my($left_assoc,$right_assoc) =($left->{assoc},$right->{assoc});
509            
510 193 100 66     684 if( $left_assoc =~ /[ba]/ and not $target->{closed} ){
511             # if inside of non closed braket, always right combination
512 22         71 $a=1;
513             }else{
514             # List Operators (Leftward) has very high priority
515 172 50       387 if( $right_prio == $list_op_prio ){
516 1         25 $right_prio = 0;
517             }
518             # compare operator precedence
519 172         277 $a = $left_prio - $right_prio;
520 172 100       406 if(!$a){
521             # if same, check left or right associativity
522 25 50       97 if( $left_assoc =~/L/ ){ $a=-1;}
  25 0       57  
523 1         4 elsif( $left_assoc =~/R/ ){ $a= 1;}
524             else{
525 1         50 die "repeating non-assoc operator. $left->{key1} $right->{key1}\n";
526             }
527             }
528             }
529 193 50       469 $verbose>0 and warn "lr=$a $left->{key1} $right->{key1}\n";
530            
531 193 100       504 if($a>0){ #right a+(b*c)
532 163 50       384 $verbose>0 and warn "appending right combination\n";
533 163         295 shift @{$self->{token}};
  163         310  
534 163         374 my $b = pop @{$target->{args}};
  163         354  
535 163         441 $target->addArg($node);
536 163         288 unshift @{$self->{stack}},$node;
  163         399  
537 163         396 $node->addArg($b);
538 163         343 $target=$self->{stack}[0];
539 163 100       245 if( @{$target->{args}} < $target->{op}{count} ){
  163         462  
540 156         295 $self->{allow_child} =1;
541             }
542 163         412 next Loop;
543             }
544            
545 31 50       120 if( not $self->reduce("left combination $target->{op}->{key1} $op->{key1}") ){
546             # warn "reduce failed: ",Data::Dumper::Dumper($target),"\n";
547 1         20 die "cannot resolve operator precedence between '$target->{op}->{key1}' and '$op->{key1}'\n";
548             }
549 31         77 $target=$self->{stack}[0];
550             }
551             }
552             }
553 51         122 last;
554             }
555 286 50       644 $verbose>0 and warn "end. stack=",join(',',map{$_->{op}->{key1}} @{$self->{stack}}),"\n";
  1         3  
  1         19  
556 285         577 my $token = $self->peekToken;
557 285 0       462 @{$self->{stack}}==1 or die "expression is not completed at '",(defined($token)?"'$token'":"end of statement"),"'\n";
  285 50       695  
558 285 50       462 @{$self->{stack}[0]{args}} or die "expression not found\n";
  285         686  
559 285         978 return $self->{stack}[0];
560             }
561             }
562            
563             {
564             package Text::Template::Compact::Dataset;
565            
566             *getDataType = \&Text::Template::Compact::getDataType;
567             *decodeQuote = \&Text::Template::Compact::decodeQuote;
568            
569             sub new{
570 1282     1283   2536 my($class,$tmpl,$tag)=@_;
571             return bless {
572             tmpl=>$tmpl,
573             tag=>$tag,
574             enc => $tmpl->{paramEncoding},
575 1282         5408 },$class;
576             }
577            
578             sub print{
579 10     11   22 my($self)=shift;
580 10         25 my $printer = $self->{tmpl}{printer};
581 10   33     49 my $filter = ( $self->{tag}{filter} || $self->{tmpl}{filter_default} );
582 10         31 for(@_){ $printer->( $filter->( Text::Template::Compact::dor($_,$self->{tmpl}{undef_supply}))); }
  19         45  
583             }
584            
585             # make path from token
586             sub token2path{
587 1770     1771   3531 my($self,$token)=@_;
588 1770 100 100     9581 if( $token =~ /^"/
589             or $token =~ /^\d/
590             ){
591 439         1095 return ['i',decodeQuote($token)];
592             }
593 1331         5078 return ['k',$token];
594             }
595            
596             sub makepathlist{
597 92     93   159 my($self) = shift;
598 92         215 return [ 'l',@_];
599             }
600            
601             sub endoflist{
602 1945     1946   3439 my($self,$path)=@_;
603 1945 100       4684 ( $path->[0] eq 'l' ) and return $self->endoflist($path->[-1]);
604 1942         3529 return $path;
605             }
606            
607             sub getVlist{
608 582     583   1184 my($self,$result,$path)=@_;
609 582 100       1348 if( $path->[0] eq 'l' ){
610 88         246 for(my $i=1;$i<@$path;++$i){
611 176         405 $self->getVlist($result,$path->[$i]);
612             }
613             }else{
614 494         1226 my $val = $self->getV($path);
615 494         8216 push @$result,$val;
616             }
617             }
618            
619             # make data path from immediate value
620             sub encode{
621 518     519   4161 my($self,$value)=@_;
622 518         1510 return ['i',$value];
623             }
624            
625             # make relative data path
626             sub child{
627 55     56   105 my($self,$path,$rel)=@_;
628 55         139 my $r = ['p',[]];
629            
630             # get right item if arg is list
631 55         135 $path = $self->endoflist($path);
632 55         109 $rel = $self->endoflist($rel);
633            
634             # copy parent
635 55 100       118 if($path->[0] eq 'p'){
636 6         11 push @{$r->[1]} , @{$path->[1]};
  6         13  
  6         17  
637             }else{
638 49         81 push @{$r->[1]} , $path->[1];
  49         128  
639             }
640            
641             # copy child
642 55 100       125 if($rel->[0] eq 'p'){
643 2         5 push @{$r->[1]} , @{$rel->[1]};
  2         6  
  2         5  
644             }else{
645 53         75 push @{$r->[1]} , $rel->[1];
  53         105  
646             }
647            
648             # make
649 55         113 return $r;
650             }
651            
652             # get value in data path
653             sub getV{
654 1830     1831   7460 my($self,$path)=@_;
655 1830 50       4467 ref($path) or die "incorrect path\n";
656            
657             # get right item if arg is list
658 1830         3799 $path = $self->endoflist($path);
659            
660             # immidiate value
661 1830 100       5281 return $path->[1] if $path->[0] eq 'i';
662            
663 987         1675 my @path;
664 987 100       2803 if( $path->[0] eq 'p' ){
665 42         68 push @path,@{$path->[1]};
  42         90  
666             }else{
667 945         2051 push @path,$path->[1];
668             }
669            
670 987         1923 my $param = $self->{tmpl}{param};
671 987         1707 my $path_str = '$';
672 987 100       2733 if( $path[0] eq '$' ){
    100          
673 10         18 shift @path;
674             }elsif( $path[0] eq '$$' ){
675 2         5 shift @path;
676 2         5 $param = $self->{tmpl};
677             }
678            
679 987         2363 while(@path){
680 1025         2168 my $key = shift @path;
681 1025         2392 my $type = getDataType($param);
682 1025 100       2857 if( $type eq 'ARRAY' ){
    50          
683 4     4   45 no warnings;
  4         11  
  4         208  
684 27         71 $param = $param->[$key];
685             }elsif($type eq 'HASH' ){
686 4     4   28 no warnings;
  4         14  
  4         13132  
687 998         2035 $param = $param->{$key};
688             }else{
689 0         0 die "incorrect data path $path_str\n";
690             }
691 1025 50       2851 $path_str .= (length($path_str)?'.':'').$key;
692 1025 50 66     3945 (@path and not ref $param) and die "incorrect data path $path_str\n";
693             }
694 987 100 100     7528 if( defined $param
      100        
      66        
695             and not ref($param)
696             and not utf8::is_utf8($param)
697             and defined $self->{enc}
698             ){
699 701         2157 return Encode::decode($self->{enc},$param);
700             }
701 286         904 return $param;
702             }
703             # set value to data path
704             sub setV{
705 405     406   859 my($self,$path,$newval)=@_;
706            
707             # get right item if arg is list
708 405 50       1111 ($path->[0] eq 'l') and $path = $self->endoflist($path);
709            
710 405 50       969 if( $path->[0] eq 'i' ){
711 0         0 die "L-Value required\n";
712             }
713            
714 405         667 my @path;
715 405 100       864 if( $path->[0] eq 'p' ){
716 5         13 push @path,@{$path->[1]};
  5         20  
717             }else{
718 400         864 push @path,$path->[1];
719             }
720            
721 405         785 my $param = $self->{tmpl}{param};
722 405         692 my $path_str = '$';
723 405 100       1202 if( $path[0] eq '$' ){
    50          
724 5         12 shift @path;
725             }elsif( $path[0] eq '$$' ){
726 0         0 shift @path;
727 0         0 $param = $self->{tmpl};
728             }
729            
730 405         1173 while(@path){
731 405         841 my $key = shift @path;
732 405         975 my $type = getDataType($param);
733 405 50       1259 if( $type eq 'ARRAY' ){
    50          
734 0 0       0 if(not @path){
735 0         0 my $old = $param->[$key];
736 0         0 $param->[$key] = $newval;
737 0         0 return \$old;
738             }else{
739 0         0 $param = $param->[$key];
740             }
741             }elsif($type eq 'HASH' ){
742 405 50       895 if(not @path){
743 405         848 my $old = $param->{$key};
744 405         786 $param->{$key} = $newval;
745 405         1138 return \$old;
746             }else{
747 0         0 $param = $param->{$key};
748             }
749             }else{
750 0         0 die "incorrect data path $path_str\n";
751             }
752 0         0 $path_str .= '.'.$key;
753 0 0 0     0 (@path and not ref $param) and die "incorrect data path $path_str\n";
754             }
755             }
756             }
757            
758             # record parse error and die (should be catch in parser)
759             sub parseError{
760 0     1 0 0 my($self)=shift;
761 0         0 my $msg = join('',"$self->{source_name} $self->{lno}: ",@_);
762 0         0 $msg =~ s/[\x0d\x0a]+//g;
763 0         0 push @{$self->{error}},$msg;
  0         0  
764 0         0 croak $msg,"\n";
765             }
766            
767             sub parseExpr{
768 285     286 0 587 my($self,$list)=@_;
769 285         528 my $r = eval{ Text::Template::Compact::ExprParser::parse($list);};
  285         673  
770 285 50       675 $@ and $self->parseError($@);
771 285         750 return $r;
772             }
773            
774             sub evalExpr{
775 535     536 0 1050 my($tmpl,$tag,$expr)=@_;
776 535         1361 my $dataset = new Text::Template::Compact::Dataset($tmpl,$tag);
777 535         1006 my $r = eval{ $dataset->getV( $expr->eval($dataset) );};
  535         1220  
778 535 50       5473 if($@){
779 0         0 $r = "[Error: $@ in evalExpr]";
780 0         0 $r =~s/[\x0d\x0a]+//g;
781             }
782 535         1882 return $r;
783             }
784             sub evalExprList{
785 389     390 0 854 my($tmpl,$tag,$expr)=@_;
786 389         1023 my $dataset = new Text::Template::Compact::Dataset($tmpl,$tag);
787 389         725 my @list;
788 389         712 eval{ $dataset->getVlist( \@list,$expr->eval($dataset) ); };
  389         1010  
789 389 50       1156 if($@){
790 0         0 my $r = "[Error: $@ in evalExprList]";
791 0         0 $r =~s/[\x0d\x0a]+//g;
792 0         0 return $r;
793             }
794 389         1575 return @list;
795             }
796             sub evalExprKw{
797 2     3 0 6 my($tmpl,$tag,$expr)=@_;
798 2         6 my $dataset = new Text::Template::Compact::Dataset($tmpl,$tag);
799 2         4 my $path = eval{ $expr->eval($dataset); };
  2         156  
800 2 50       8 if($@){
801 0         0 my $r = "[Error: $@ in evalExprKw]";
802 0         0 $r =~s/[\x0d\x0a]+//g;
803 0         0 return $r;
804             }
805 2         7 $path = $dataset->endoflist($path);
806 2 50       18 return $path->[1] if $path->[0] =~/[ki]/;
807            
808 0         0 my $v = eval{ $dataset->getV( $path );};
  0         0  
809 0 0       0 if($@){
810 0         0 my $r = "[Error: $@ in evalExprKw]";
811 0         0 $r =~s/[\x0d\x0a]+//g;
812 0         0 return $r;
813             }
814 0         0 return $v;
815             }
816            
817            
818            
819             sub setExprValue{
820 356     357 0 804 my($tmpl,$tag,$expr,$newval)=@_;
821 356         966 my $dataset = new Text::Template::Compact::Dataset($tmpl,$tag);
822 356         954 my $path = $expr->eval($dataset);
823 356         632 my $r = eval{ $dataset->setV($path,$newval);};
  356         896  
824 356 50       936 if($@){
825 0         0 $r = "[Error: $@ in setExprValue]";
826 0         0 $r =~s/[\x0d\x0a]+//g;
827             }
828 356         1076 return $r;
829             }
830            
831             # eat specified token at head of the list. otherwise return undef.
832             sub eatType{
833 20     21 0 48 my($list,$type)=@_;
834 20 50 33     164 if( @$list and ref($list->[0]) and $list->[0]->{$type} ){
      33        
835 20         115 return shift @$list;
836             }
837 0         0 return;
838             }
839            
840             ######################################
841            
842            
843             # print %eval tag
844             sub print_eval{
845 6     7 0 18 my($tmpl,$tag)=@_;
846 6         26 $tmpl->evalExpr($tag,$tag->{expr});
847 6         12 return;
848             }
849            
850             # print %var tag
851             sub print_var{
852 389     390 0 820 my($tmpl,$tag)=@_;
853 389         710 my $printer = $tmpl->{printer};
854 389         1339 my $filter = dor( $tag->{filter} ,$tmpl->{filter_default} );
855 389         1377 for my $value ( $tmpl->evalExprList($tag,$tag->{expr}) ){
856 449         1174 $value = dor($value,$tmpl->{undef_supply});
857 449         1066 $printer->( $filter->($value));
858             }
859 389         7137 return;
860             }
861            
862             sub evalLabel($$$){
863 8     9 0 23 my($tmpl,$tag,$label)=@_;
864 8 100       33 return '' if not defined $label;
865 2         9 return $tmpl->evalExprKw($tag,$label);
866             }
867            
868             # print %for tag
869             sub print_for{
870 20     21 0 57 my($tmpl,$tag)=@_;
871 20         84 my $list = $tmpl->evalExpr($tag,$tag->{listname});
872 20         48 my $index = 0;
873 20 100       80 $tag->{indexstart} and $index = $tmpl->evalExpr($tag,$tag->{indexstart});
874 20         53 for my $v (@$list){
875 139         258 my $oldr;
876             my $oldi;
877 139 50       409 if($tag->{itemname}){
878 139         402 $oldr = $tmpl->setExprValue($tag,$tag->{itemname},$v);
879 139 50       421 ref($oldr) or $tmpl->{printer}->($oldr);
880             }
881 139 100       421 if($tag->{indexname}){
882 4         18 $oldi = $tmpl->setExprValue($tag,$tag->{indexname},$index++);
883 4 50       15 ref($oldi) or $tmpl->{printer}->($oldi);
884             }
885 139         415 my $exit_tag = $tmpl->printBlock( $tag->{block} );
886 139 50       697 ref($oldr) and $tmpl->setExprValue($tag,$tag->{itemname} ,$$oldr);
887 139 100       419 ref($oldi) and $tmpl->setExprValue($tag,$tag->{indexname} ,$$oldi);
888            
889 139 100       518 if($exit_tag){
890             # not for this block?
891 7 100       22 return $exit_tag if dor($tag->{label},'') ne evalLabel($tmpl,$tag,$exit_tag->{label});
892             # for this block.
893 6 50       21 next if $exit_tag->{continue};
894 6 50       20 last if $exit_tag->{break};
895             }
896             }
897 19         78 return;
898             }
899            
900             sub print_while{
901 3     4 0 9 my($tmpl,$tag)=@_;
902 3 50       17 $tag->{ex_init} and $tmpl->evalExpr($tag,$tag->{ex_init});
903 3         6 my $exit_tag;
904 3         43 for(;;){
905 30 100 100     100 last if $tag->{ex_precheck} and not $tmpl->evalExpr($tag,$tag->{ex_precheck});
906 29         78 $exit_tag = $tmpl->printBlock( $tag->{block} );
907 29 100       73 if($exit_tag){
908             # not for this block?
909 1 50       6 last if dor($tag->{label},'') ne evalLabel($tmpl,$tag,$exit_tag->{label});
910             # for this block.
911 1 50       6 if($exit_tag->{break}){
912 1         4 undef $exit_tag;
913 1         3 last;
914             }
915             }
916 28 100 100     106 last if $tag->{ex_postcheck} and not $tmpl->evalExpr($tag,$tag->{ex_postcheck});
917 27 50       116 $tag->{ex_step} and $tmpl->evalExpr($tag,$tag->{ex_step});
918             }
919 3 100       18 $tag->{ex_final} and $tmpl->evalExpr($tag,$tag->{ex_final});
920 3         11 return $exit_tag;
921             }
922            
923             # print %blockpaste tag
924             sub print_block{
925 6     7 0 20 my($tmpl,$tag)=@_;
926 6         20 my $block = $tmpl->{block}{$tag->{name}};
927 6 50       22 if(not defined($block) ){
928 0         0 $tmpl->{printer}->( "[Error: block '$tag->{name}' is not defined]" );
929 0         0 return;
930             }
931 6         26 my $exit_tag = $tmpl->printBlock( $block );
932 6 50       25 return if not $exit_tag;
933             # not for this block?
934 0 0       0 return $exit_tag if dor($tag->{label},'') ne evalLabel($tmpl,$tag,$exit_tag->{label});
935             # for this block.
936             # no difference between break or continue, just exit this block.
937 0         0 return;
938             }
939            
940             # print %eval tag
941             sub print_evalperl{
942 74     75 0 168 my($tmpl,$tag)=@_;
943 74         163 local $_ = $tmpl->{param};
944 74         167 my $code = $tag->{code};
945 74         136 my @data = map{ $tmpl->evalExpr($tag,$_) } @{$tag->{args}};
  34         97  
  74         231  
946 74         152 my $a_code =ord('a');
947 74 100       249 @data and $code = "my(".join(',',map{my $c=chr($_+$a_code);"\$$c"}(0..$#data)).")=\@data;$code";
  34         114  
  34         174  
948 74     3   5954 my $r = eval "{no warnings; $code;}";
  2     3   17  
  2     3   5  
  2     3   83  
  2     2   16  
  2     2   6  
  2     2   67  
  2     2   12  
  2     2   6  
  2     2   74  
  2     2   12  
  2     2   6  
  2     2   61  
  2     2   16  
  2     2   4  
  2     2   59  
  2     2   17  
  2     2   5  
  2     2   94  
  2     2   16  
  2     2   6  
  2     2   90  
  2     2   17  
  2     2   5  
  2     2   84  
  2     2   16  
  2         6  
  2         87  
  2         16  
  2         5  
  2         83  
  2         17  
  2         6  
  2         87  
  2         17  
  2         7  
  2         85  
  2         19  
  2         7  
  2         91  
  2         17  
  2         7  
  2         125  
  2         16  
  2         7  
  2         58  
  2         17  
  2         7  
  2         61  
  2         16  
  2         7  
  2         93  
  2         29  
  2         8  
  2         110  
  2         26  
  2         8  
  2         148  
  2         17  
  2         31  
  2         88  
  2         15  
  2         6  
  2         63  
  2         13  
  2         6  
  2         59  
  2         16  
  2         6  
  2         87  
  2         16  
  2         5  
  2         77  
  2         18  
  2         5  
  2         109  
  2         18  
  2         4  
  2         75  
949 74 100       331 $@ and $tmpl->{printer}->( "[eval failed: $@]");
950 74 100       476 $tag->{result} and $tmpl->setExprValue($tag,$tag->{result},$r);
951 74         226 return;
952             }
953            
954            
955             # print %else tag
956             sub print_else{
957 175     176 0 334 my($tmpl,$tag)=@_;
958            
959 175         503 my $exit_tag = $tmpl->printBlock( $tag->{block} );
960            
961             # normally 'if' is not match for break,continue
962             # match only label is explicitly specified in both of block and break.
963 175 50 100     506 if( $exit_tag
      66        
      33        
964             and defined($exit_tag->{label})
965             and defined($tag->{label})
966             and $exit_tag->{label} eq $tag->{label}
967             ){
968             # exit_tag is solved in this scope.
969 0         0 return;
970             }
971 175         393 return $exit_tag;
972             }
973            
974             # print %if tag
975             sub print_if_simple{
976 275     276 0 541 my($tmpl,$tag)=@_;
977 275         670 my $value = $tmpl->evalExpr($tag,$tag->{expr});
978 275 100       725 $value and return print_else($tmpl,$tag);
979 166         317 $tag=$tag->{next};
980 166 100       428 $tag->{printer} and return $tag->{printer}($tmpl,$tag);
981 142         263 return;
982             }
983            
984             # print %if tag
985             sub print_if_code{
986 126     127 0 320 my($tmpl,$tag)=@_;
987 126         211 my @data = map{ $tmpl->evalExpr($tag,$_) } @{$tag->{args}};
  138         360  
  126         327  
988 126         309 my $code = $tag->{code};
989 126         228 my $a_code =ord('a');
990 126 50       528 @data and $code = "my(".join(',',map{my $c=chr($_+$a_code);"\$$c"}(0..$#data)).")=\@data;$code";
  138         323  
  138         543  
991 126         270 local $_ = $tmpl->{param};
992 126     2   8624 my $value = eval "no warnings; $code";
  2     2   16  
  2     2   5  
  2     2   115  
  2     2   14  
  2     2   4  
  2     2   69  
  2     2   13  
  2     2   6  
  2     2   64  
  2     2   15  
  2     2   5  
  2     2   69  
  2     2   14  
  2     2   5  
  2         61  
  2         13  
  2         7  
  2         62  
  2         12  
  2         5  
  2         63  
  2         13  
  2         6  
  2         66  
  2         15  
  2         6  
  2         66  
  2         16  
  2         6  
  2         71  
  2         18  
  2         7  
  2         82  
  2         14  
  2         6  
  2         70  
  2         13  
  2         5  
  2         60  
  2         16  
  2         7  
  2         76  
  2         18  
  2         6  
  2         79  
993 126 50       453 $@ and $tmpl->{printer}->( "[eval failed: $@]");
994            
995 126 100       376 $value and return print_else($tmpl,$tag);
996 72         171 $tag=$tag->{next};
997 72 100       258 $tag->{printer} and return $tag->{printer}($tmpl,$tag);
998 48         132 return;
999             }
1000            
1001             #####################################################
1002            
1003             # parse template tag
1004             sub parseTemplateTag{
1005 300     301 0 633 my($self,$text)=@_;
1006            
1007             # split to token list
1008 300     3   3434 my @list = $text =~ /$Text::Template::Compact::ExprParser::token_re|"(?:[^"]|"")*"|[\w_]+|\p{IsWord}+/g;
  2         17  
  2         5  
  2         28  
1009 300 50       35748 @list or die $self->parseError("empty template tag");
1010            
1011             # parse filter
1012 300         500 my $filter;
1013 300 100 100     1536 if( @list >= 2 and $filter_map{ $list[-1] } and $list[-2] eq '#' ){
      66        
1014 8         21 $filter = $filter_map{ $list[@list-1] };
1015 8         21 splice @list,@list-2;
1016             }
1017            
1018 300         647 my @taglist;
1019             my $type;
1020 300         758 while(@list){
1021 352 100       928 if($list[0] eq ';'){
1022 29         47 shift @list;
1023 29         82 next;
1024             }
1025            
1026 323         958 my $item = {lno=>$self->{lno}};
1027 323 100       793 $filter and $item->{filter} = $filter;
1028            
1029             # read label:
1030 323 100 100     1423 if( @list >= 2
      66        
1031             and $list[1] eq ':'
1032             and $list[0] =~/^\w/
1033             ){
1034 1         5 $item->{label} = $list[0];
1035 1         3 splice @list,0,2;
1036 1 50       4 last if not @list;
1037             }
1038            
1039             # % type
1040 323 100       786 if( $list[0] eq '%' ){
1041             # skip '%'
1042 180         307 shift @list;
1043             # read type of tag
1044 180 50       432 @list or $self->parseError("no tag type after '%'");
1045 180         449 $type = lc decodeQuote(shift @list);
1046             }else{
1047 143         252 $type = 'print';
1048             }
1049            
1050 323         1192 $item->{$item->{type}=$type}=1;
1051            
1052 323 100 100     2173 if( $type eq 'print' ){
    100 100        
    100 66        
    100 100        
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    50          
1053             # %print expr,expr...
1054 150         364 $item->{printer}=\&print_var;
1055 150         436 $item->{expr} = $self->parseExpr(\@list);
1056            
1057             }elsif( $type eq 'eval' ){
1058             # %print expr,expr...
1059 6         19 $item->{printer}=\&print_eval;
1060 6         24 $item->{expr} = $self->parseExpr(\@list);
1061            
1062             }elsif( $type eq 'if' or $type eq 'elsif' ){
1063             # %if expr
1064             # %elsif expr
1065 25         73 $item->{printer}=\&print_if_simple;
1066 25         95 $item->{expr} = $self->parseExpr(\@list);
1067            
1068             }elsif( $type eq 'ifc' or $type eq 'elsifc' ){
1069             # %ifc "code" dataspec dataspec ...
1070             # %elsifc "code" dataspec dataspec ...
1071 14         32 $item->{printer}=\&print_if_code;
1072 14         32 $item->{code} =decodeQuote(shift @list);
1073 14         48 $item->{args}=[];
1074 14   66     69 while(@list and $list[0] ne ';' ){
1075 15         29 push @{$item->{args}},$self->parseExpr(\@list);
  15         106  
1076             }
1077            
1078             }elsif( $type eq 'else'){
1079             # %else
1080 4         16 $item->{printer}=\&print_else;
1081            
1082             }elsif( $type eq 'end'){
1083             # %end
1084            
1085             }elsif( $type eq 'break' or $type eq 'continue' ){
1086             # %break [label]
1087             # %continue [label]
1088 3 100 66     22 if( @list and $list[0] ne ';'){
1089 1         5 $item->{label} = $self->parseExpr(\@list);
1090             }
1091            
1092             }elsif( $type eq 'end'){
1093             # %end
1094            
1095             }elsif( $type eq 'for' ){
1096             # %for item in list indexname indexstart
1097 15         70 $item->{printer}=\&print_for;
1098 15         63 $item->{itemname} = $self->parseExpr(\@list);
1099            
1100 15 50 33     101 (not @list or not $list[0] eq 'in' ) and $self->parseError("expected 'in' keyword is not found.");
1101 15         32 shift @list;
1102            
1103 15         59 $item->{listname} = $self->parseExpr(\@list);
1104            
1105 15 100 100     91 (@list and $list[0] ne ';') and $item->{indexname} = $self->parseExpr(\@list);
1106 15 100 100     76 (@list and $list[0] ne ';') and $item->{indexstart} = $self->parseExpr(\@list);
1107            
1108             }elsif( $type eq 'while' ){
1109             # %for item in list indexname indexstart
1110 3         13 $item->{printer}=\&print_while;
1111 3   66     20 Loop: while( @list and $list[0] ne ';' ){
1112 10         25 for (qw( init precheck postcheck step final )){
1113 28 100       60 if( $list[0] eq $_ ){
1114 10         15 shift @list;
1115 10         32 $item->{"ex_$_"} = $self->parseExpr(\@list);
1116 10         50 next Loop;
1117             }
1118             }
1119 0         0 $self->parseError("expected 'init/precheck/postcheck/step/final' not found.");
1120             }
1121            
1122             }elsif( $type eq 'blockdefine' or $type eq 'blockpaste' ){
1123             # %blockdefine blockname
1124             # %blockpaste blockname
1125 8         22 $item->{printer}=\&print_block;
1126 8 50       22 @list or $self->parseError("no block name after $type");
1127 8         21 $item->{name} = decodeQuote(shift @list);
1128 8 100       26 if( $type eq 'blockdefine' ){
1129 2 50       10 defined( $self->{block}{$item->{name}} ) and $self->parseError("redefined block '$item->{name}'");
1130 2         9 $self->{block}{$item->{name}} = [];
1131             }
1132             }elsif( $type eq 'evalperl' ){
1133             # %evalperl "code" [result] [arg]...
1134 44         117 $item->{printer}=\&print_evalperl;
1135 44 50       108 @list or $self->parseError("no text after 'evalperl'");
1136 44 50       167 $list[0] =~ /^"/ or $self->parseError("you must quote code with \"...\"");
1137 44         99 $item->{code} = decodeQuote(shift @list);
1138 44 100       161 @list and $item->{result} = $self->parseExpr(\@list);
1139 44         112 $item->{args} = [];
1140 44   66     162 while(@list and $list[0] ne ';' ){
1141 4         13 push @{$item->{args}},$self->parseExpr(\@list);
  4         19  
1142             }
1143             }else{
1144             # unsupported tag type
1145 0         0 $self->parseError("unsupported tag type '$type'");
1146             }
1147 323 50 66     1012 @list and $list[0] ne ';' and $self->parseError("unexpected token '$list[0]' in template tag");
1148 323         968 push @taglist,$item;
1149             }
1150 300         952 return @taglist;
1151             }
1152            
1153             # compose tree of tag and text.
1154             sub parseBlock{
1155 125     126 0 253 my($self,$rList,$block)=@_;
1156            
1157 125         308 while(@$rList){
1158 569         1043 my $item = $rList->[0];
1159             # normal text
1160 569 100       1193 if( not ref($item) ){
1161 246         449 push @$block, shift @$rList;
1162 246         596 next;
1163             }
1164             # exit before end of block
1165 323 100       597 last if grep {$item->{type} eq $_} qw( end else elsif elsifc );
  1292         2823  
1166            
1167             # %blockdefine
1168 260 100       578 if( $item->{blockdefine} ){
1169 2         6 shift @$rList;
1170 2         11 $self->parseBlock( $rList,$self->{block}{$item->{name}});
1171 2 50       8 eatType($rList,'end') or $self->parseError("missing end of blockdefine (start at $item->{lno})");
1172 2         15 next;
1173             }
1174            
1175             # append to current block
1176 258         479 push @$block, shift @$rList;
1177            
1178             # %for
1179 258 100 100     1067 if( $item->{for} or $item->{while} ){
1180 18         49 $item->{block} = [];
1181 18         77 $self->parseBlock( $rList ,$item->{block});
1182 18 50       58 eatType($rList,'end') or $self->parseError("missing end of $item->{type} loop (start at $item->{lno})");
1183 18         66 next;
1184             }
1185            
1186             # %if ,%elsif,%else
1187 240 100 100     1044 if( $item->{if} or $item->{ifc}){
1188 31         49 for(;;$item = $item->{next}){
1189 43         88 $item->{block} = [];
1190 43         130 $self->parseBlock( $rList ,$item->{block});
1191 43 50       99 @$rList or $self->parseError("missing end of if/elsif/else/elsifc block (start at $item->{lno})");
1192 43         98 $item->{next} = shift @$rList;
1193 43 100       105 last if $item->{next}{end};
1194 12 50 33     44 $item->{label} and not defined($item->{next}->{label}) and $item->{next}->{label}=$item->{label};
1195             }
1196 31         71 next;
1197             }
1198             }
1199             }
1200            
1201             sub closeLine{
1202 399     400 0 778 my($rAll,$rLine)=@_;
1203             my $a = grep{
1204 399 100       724 if( ref($_) ){
  693         1363  
1205 323         781 $_->{print};
1206             }else{
1207 370 100       1255 $_ =~ /[^\s ]/ ?1:0;
1208             }
1209             } @$rLine;
1210 399 100       882 if($a){
1211 275         572 for (@$rLine,"\x0a"){
1212 875 100 100     4069 if(not ref($_)
      100        
1213             and @$rAll
1214             and not ref($rAll->[-1])
1215             ){
1216 386         1015 $rAll->[-1].= $_;
1217             }else{
1218 489         930 push @$rAll,$_;
1219             }
1220             }
1221             }else{
1222 124         249 for (@$rLine){
1223 93 100       295 ref($_) and push @$rAll,$_;
1224             }
1225             }
1226 399         979 @$rLine = ();
1227             }
1228            
1229             # convert from source text to template structure.
1230             # $ok = $tmpl->loadText($filename,\$text [,$blockname]);
1231             sub loadText{
1232 62     63 0 25125 my $self = shift;
1233 62         163 $self->{error}=[];
1234 62         152 $self->{lno}=1;
1235 62         137 $self->{source_name} = $_[0];
1236 62 50       189 my $rText = ref($_[1])?$_[1]:\$_[1];
1237 62   50     323 my $blockname = ($_[2] || "");
1238            
1239             # split source to tag and text
1240 62         125 my @list;
1241             my @line;
1242 62         109 my $lastend = 0;
1243 62         477 while( $$rText =~ /(\x0D\x0A|\x0D|\x0A)|(?
1244 699         18336 my $pre = substr($$rText,$lastend,$-[0] - $lastend); $lastend = $+[0];
  699         17426  
1245 699 100       1941 if( defined($1) ){
1246 399         864 $pre =~ s/\$\$\{/\$\{/g;
1247 399 100       1076 length($pre) and push @line,$pre;
1248 399         1124 closeLine(\@list,\@line);
1249 399         2599 ++$self->{lno};
1250             }else{
1251 300         805 my $inside = substr($2,2);
1252 300         696 $pre =~ s/\$\$\{/\$\{/g;
1253 300 100       866 length($pre) and push @line,$pre;
1254 300         598 push @line,eval{ $self->parseTemplateTag($inside);};
  300         775  
1255 300         2171 $self->{lno} += $inside =~ tr/\x0a/\x0a/;
1256             }
1257             }
1258 62 50       172 if( $lastend < length($$rText) ){
1259 0         0 my $text =substr($$rText,$lastend);
1260 0         0 $text =~ s/\$\$\{/\$\{/g;
1261 0         0 warn "left=[$text]\n";
1262 0         0 push @line,$text;
1263 0         0 closeLine(\@list,\@line);
1264             }
1265            
1266             # parse control block
1267 62         158 $self->{block}{$blockname} = [];
1268 62         590 eval{ $self->parseBlock( \@list,$self->{block}{$blockname} ); };
  62         218  
1269            
1270 62         109 return !@{$self->{error}};
  62         267  
1271             }
1272            
1273            
1274             # $ok = $tml->loadFile("filename","utf8" [,$blockname]);
1275             sub loadFile{
1276 1     2 0 35 my $self = shift;
1277            
1278 1         5 $self->{lno} = 0;
1279 1         3 $self->{source_name} = $_[0];
1280 1         3 my $enc = $_[1];
1281 1         3 my $blockname = $_[2];
1282            
1283             # find encoding object for source
1284 1 50       4 if(defined $enc){
1285 1 50       5 ref($enc) or $enc = Encode::find_encoding($enc);
1286 1 50       16 if(not ref($enc) =~/Encode/){
1287 0         0 push @{$self->{error}},"$self->{source_name} $self->{lno}: incorrect encode spec.";
  0         0  
1288 0         0 return;
1289             }
1290             }
1291            
1292             # read source text
1293 1         3 my $source;
1294             my $fh;
1295 1 50       29 if(not open $fh,"<",$self->{source_name} ){
1296 0         0 push @{$self->{error}},"$self->{source_name} $self->{lno}: $!";
  0         0  
1297 0         0 return;
1298             }else{
1299 1         4 local $/=undef;
1300 1         28 $source = <$fh>;
1301 1 50       8 defined($enc) and $source = Encode::decode($enc,$source);
1302 1 50       231 if(not close $fh ){
1303 0         0 push @{$self->{error}},"$self->{source_name} $self->{lno}: $!";
  0         0  
1304 0         0 return;
1305             }
1306             }
1307 1         6 return $self->loadText($self->{source_name},\$source,$blockname);
1308             }
1309            
1310             # $teml = Text::Template::Compact->new();
1311             sub new{
1312             return bless{
1313             error => [],
1314             paramEncoding => Encode::find_encoding('utf8'),
1315 2     3 0 8083 filter_default => $filter_map{'html'},
1316             undef_supply => '',
1317             } , shift;
1318             }
1319            
1320             # get error as string.
1321             sub error{
1322 0     1 0 0 return join("\n",@{$_[0]->{error}},'');
  0         0  
1323             }
1324             # get error as string.
1325             sub undef_supply{
1326 1     2 0 5 my $self = shift;
1327 1 50       19 if(@_){
1328 1         9 $self->{undef_supply} = $_[0];
1329             }
1330 1         3 return $self->{undef_supply};
1331             }
1332            
1333             # set encoding for decode parameter
1334             sub param_encoding{
1335 2     3 0 7911 my $self = shift;
1336 2 50       13 if(@_){
1337 2         6 my $enc = $_[0];
1338             # find encoding object for source
1339 2 50       8 if(defined $enc){
1340 2 50       13 ref($enc) or $enc = Encode::find_encoding($enc);
1341 2 50       42 ref($enc) =~/Encode/ or croak "incorrect encode spec.";
1342             }
1343 2         11 $self->{paramEncoding} = $enc;
1344             }
1345 2         7 return;
1346             }
1347            
1348             # set default of filter for variable expand.
1349             sub filter_default{
1350 2     3 0 13 my $self = shift;
1351 2 50       9 if(@_){
1352 2         5 my $filtername = $_[0];
1353 2 50 33     39 if( not $filtername or not $filter_map{$filtername} ){
1354 0         0 croak "unknown filter '$filtername'";
1355             }
1356 2         8 $self->{filter_default} = $filter_map{$filtername};
1357             }
1358 2         6 return;
1359             }
1360            
1361             # print template block(low-level method)
1362             sub printBlock{
1363 411     412 0 830 my($self,$block)=@_;
1364 411         934 for my $item ( @$block ){
1365 1753 100 66     19091 if( not ref $item ){
    100          
1366 883         2367 $self->{printer}->($item);
1367             }elsif( $item->{break} or $item->{continue} ){
1368 7         17 return $item;
1369             }else{
1370 863         2177 my $exit_tag = $item->{printer}($self,$item);
1371 863 100       2411 $exit_tag and return $exit_tag;
1372             }
1373             }
1374 396         5828 return;
1375             }
1376            
1377             # print to filehandle
1378             sub print{
1379 1     2 0 636 my($self,$param,$fh,$enc)=@_;
1380            
1381             # generate closure to print
1382 1 50       7 if(defined $enc){
1383             # find encoding object for source
1384 1 50       10 ref($enc) or $enc = Encode::find_encoding($enc);
1385 1 50       30 ref($enc) =~/Encode/ or croak "incorrect encode spec.";
1386 1     804   9 $self->{printer} = sub{ for(@_){ print $fh Encode::encode($enc,$_); } };
  803         1697  
  803         2401  
1387             }else{
1388 0     1   0 $self->{printer} = sub{ print $fh @_; };
  0         0  
1389             }
1390 1         8 $self->{param} = $param;
1391            
1392             # start root node
1393 1         6 my $exit_tag = $self->printBlock( $self->{block}{""});
1394             }
1395            
1396             sub toString{
1397 61     62 0 20717 my($self,$param)=@_;
1398 61         121 my $result='';
1399 61         123 $self->{param} = $param;
1400 61     551   290 $self->{printer} = sub{ for(@_){ $result .= $_; } };
  550         1105  
  550         1503  
1401 61         342 $self->printBlock( $self->{block}{""} );
1402 61         177 return $result;
1403             }
1404            
1405             1;
1406            
1407             __END__