File Coverage

blib/lib/Text/Xslate/Syntax/TTerse.pm
Criterion Covered Total %
statement 296 301 98.3
branch 48 54 88.8
condition 11 14 78.5
subroutine 32 32 100.0
pod 0 24 0.0
total 387 425 91.0


line stmt bran cond sub pod time code
1             package Text::Xslate::Syntax::TTerse;
2 50     50   72528 use Mouse;
  50         655433  
  50         301  
3 50     50   17337 use Text::Xslate::Util qw(p any_in);
  50         108  
  50         3334  
4 50     50   264 use Scalar::Util ();
  50         104  
  50         169761  
5              
6             extends qw(Text::Xslate::Parser);
7              
8             sub _build_identity_pattern {
9 59     59   9244 return qr/(?: [A-Za-z_] [A-Za-z0-9_]* )/xms;
10             }
11              
12             # [% ... %] and %% ...
13 59     59   579 sub _build_line_start { '%%' }
14 57     57   404 sub _build_tag_start { '[%' }
15 57     57   618 sub _build_tag_end { '%]' }
16              
17             around trim_code => sub {
18             my($super, $self, $code) = @_;
19              
20             if($code =~ /^\#/) { # multiline comments
21             return '';
22             }
23              
24             return $super->($self, $code);
25             };
26              
27             sub init_symbols {
28 59     59 0 126 my($parser) = @_;
29 59         117 my $s;
30              
31 59         450 $parser->init_basic_operators();
32              
33 59         249 $parser->symbol('$')->set_nud(\&nud_dollar);
34 59         368 $parser->make_alias('~' => '_');
35 59         279 $parser->make_alias('|' => 'FILTER');
36 59         282 $parser->symbol('.')->set_led(\&led_dot); # redefine
37              
38 59         214 $parser->symbol('END') ->is_block_end(1);
39 59         207 $parser->symbol('ELSE') ->is_block_end(1);
40 59         195 $parser->symbol('ELSIF')->is_block_end(1);
41 59         194 $parser->symbol('CASE') ->is_block_end(1);
42              
43 59         198 $parser->symbol('IN');
44              
45 59         201 $s = $parser->symbol('IF');
46 59         238 $s->set_std(\&std_if);
47 59         281 $s->can_be_modifier(1);
48 59         219 $s = $parser->symbol('UNLESS');
49 59         235 $s->set_std(\&std_if);
50 59         180 $s->can_be_modifier(1);
51              
52 59         199 $parser->symbol('FOREACH') ->set_std(\&std_for);
53 59         203 $parser->symbol('FOR') ->set_std(\&std_for);
54 59         202 $parser->symbol('WHILE') ->set_std(\&std_while);
55 59         210 $parser->symbol('SWITCH') ->set_std(\&std_switch);
56 59         210 $parser->symbol('CASE') ->set_std(\&std_case);
57              
58 59         205 $parser->symbol('INCLUDE') ->set_std(\&std_include);
59 59         200 $parser->symbol('WITH');
60              
61 59         213 $parser->symbol('GET') ->set_std(\&std_get);
62 59         202 $parser->symbol('SET') ->set_std(\&std_set);
63 59         202 $parser->symbol('DEFAULT') ->set_std(\&std_set);
64 59         209 $parser->symbol('CALL') ->set_std(\&std_call);
65              
66 59         210 $parser->symbol('NEXT') ->set_std( $parser->can('std_next') );
67 59         246 $parser->symbol('LAST') ->set_std( $parser->can('std_last') );
68              
69 59         196 $parser->symbol('MACRO') ->set_std(\&std_macro);
70 59         206 $parser->symbol('BLOCK');
71 59         195 $parser->symbol('WRAPPER')->set_std(\&std_wrapper);
72 59         197 $parser->symbol('INTO');
73              
74 59         203 $parser->symbol('FILTER')->set_std(\&std_filter);
75              
76             # unsupported directives
77 59         358 my $nos = $parser->can('not_supported');
78 59         177 foreach my $keyword (qw(
79             INSERT PROCESS PERL RAWPERL TRY THROW RETURN
80             STOP CLEAR META TAGS DEBUG VIEW)) {
81 767         2071 $parser->symbol($keyword)->set_std($nos);
82             }
83              
84             # not supported, but ignored (synonym to CALL)
85 59         233 $parser->symbol('USE')->set_std(\&std_call);
86              
87 59         109 foreach my $id(keys %{$parser->symbol_table}) {
  59         1228  
88 6195 100       17842 if($id =~ /\A [A-Z]+ \z/xms) { # upper-cased keywords
89 2242         7500 $parser->make_alias($id => lc $id)->set_nud(\&aliased_nud);
90             }
91             }
92              
93 59         587 $parser->make_alias('not' => 'NOT');
94 59         275 $parser->make_alias('and' => 'AND');
95 59         276 $parser->make_alias('or' => 'OR');
96              
97 59         222 return;
98             }
99              
100             around _build_iterator_element => sub {
101             my($super, $parser) = @_;
102              
103             my $table = $super->($parser);
104              
105             # make aliases
106             $table->{first} = $table->{is_first};
107             $table->{last} = $table->{is_last};
108             $table->{next} = $table->{peek_next};
109             $table->{prev} = $table->{peek_prev};
110             $table->{max} = $table->{max_index};
111              
112             return $table;
113             };
114              
115             sub default_nud {
116 85     85 0 130 my($parser, $symbol) = @_;
117 85         235 return $symbol->clone(
118             arity => 'variable',
119             );
120             }
121              
122             # same as default_nud, except for aliased symbols
123             sub aliased_nud {
124 2     2 0 6 my($parser, $symbol) = @_;
125 2         15 return $symbol->clone(
126             arity => 'name',
127             id => lc( $symbol->id ),
128             value => $symbol->id,
129             );
130             }
131              
132             sub nud_dollar {
133 28     28 0 50 my($parser, $symbol) = @_;
134 28         42 my $expr;
135 28         78 my $t = $parser->token;
136 28 100       126 if($t->id eq "{") {
137 6         24 $parser->advance("{");
138 6         23 $expr = $parser->expression(0);
139 6         20 $parser->advance("}");
140             }
141             else {
142 22 100       99 if(!any_in($t->arity, qw(name variable))) {
143 1         8 $parser->_unexpected("a name", $t);
144             }
145 21         70 $parser->advance();
146 21         67 $expr = $t->clone( arity => 'variable' );
147             }
148 27         452 return $expr;
149             }
150              
151             sub undefined_name {
152 464     464 0 740 my($parser, $name) = @_;
153             # undefined names are always variables
154 464         1874 return $parser->symbol_table->{'(variable)'}->clone(
155             id => $name,
156             );
157             }
158              
159             sub is_valid_field {
160 50     50 0 76 my($parser, $token) = @_;
161 50   66     182 return $parser->SUPER::is_valid_field($token)
162             || $token->arity eq "variable";
163             }
164              
165             sub led_dot {
166 55     55 0 91 my($parser, $symbol, $left) = @_;
167              
168             # special case: foo.$field, foo.${expr}
169 55 100       264 if($parser->token->id eq '$') {
170 5         25 return $symbol->clone(
171             arity => "field",
172             first => $left,
173             second => $parser->expression( $symbol->lbp ),
174             );
175             }
176              
177 50         243 return $parser->SUPER::led_dot($symbol, $left);
178             }
179              
180             sub led_assignment {
181 26     26 0 177 my($parser, $symbol, $left) = @_;
182              
183 26         114 my $assign = $parser->led_infixr($symbol, $left);
184 26         568 $assign->arity('assign');
185 26         120 $assign->is_statement(1);
186              
187 26         64 my $name = $assign->first;
188 26 100       172 if(not $parser->find_or_create($name->id)->is_defined) {
189 15         328 $parser->define($name);
190 15         56 $assign->third('declare');
191             }
192              
193 26         459 return $assign;
194             }
195              
196             sub assignment {
197 590     590 0 987 my($parser, $id, $bp) = @_;
198              
199 590         1481 $parser->symbol($id, $bp)->set_led(\&led_assignment);
200 590         1324 return;
201             }
202              
203             sub std_if {
204 69     69 0 119 my($parser, $symbol, $expr) = @_;
205 69         198 my $if = $symbol->clone(arity => "if");
206              
207 69         1345 my $is_modifier = defined $expr;
208              
209 69 100       305 $parser->new_scope() unless $is_modifier; # whole if block
210              
211 69         213 my $cond = $parser->expression(0);
212              
213 69 100       266 if($symbol->id eq 'UNLESS') {
214 7         23 $cond = $parser->symbol('!')->clone(
215             arity => 'unary',
216             first => $cond,
217             );
218             }
219 69         376 $if->first($cond);
220              
221 69 100       156 if($is_modifier) {
222 17         57 $if->second([ $expr ]);
223 17         56 return $if;
224             }
225              
226             # then block
227             {
228 52         72 $parser->new_scope();
  52         152  
229 52         183 $if->second( $parser->statements() );
230 52         194 $parser->pop_scope();
231             }
232              
233 52         121 my $t = $parser->token;
234              
235 52         71 my $top_if = $if;
236              
237 52         206 while($t->id eq "ELSIF") {
238 10         31 $parser->reserve($t);
239 10         60 $parser->advance(); # "ELSIF"
240              
241 10         33 my $elsif = $t->clone(arity => "if");
242 10         208 $elsif->first( $parser->expression(0) );
243              
244             {
245 10         18 $parser->new_scope();
  10         30  
246 10         29 $elsif->second( $parser->statements() );
247 10         38 $parser->pop_scope();
248             }
249              
250 10         37 $if->third([$elsif]);
251 10         14 $if = $elsif;
252 10         50 $t = $parser->token;
253             }
254              
255 52 100       180 if($t->id eq "ELSE") {
256 16         40 my $else_line = $t->line;
257 16         49 $parser->reserve($t);
258 16         44 $t = $parser->advance(); # "ELSE"
259              
260 16 50 33     131 if($t->id eq "IF" and $t->line != $else_line) {
261 0         0 Carp::carp(sprintf "%s: Parsing ELSE-IF sequense as ELSIF, but it is likely to be a misuse of ELSE-IF. Please insert semicolon as ELSE; IF, or write it in the same line (around input line %d)", ref $parser, $t->line);
262             }
263              
264             {
265 16         21 $parser->new_scope();
  16         47  
266 16 50       80 $if->third( $t->id eq "IF"
267             ? [$parser->statement()]
268             : $parser->statements());
269 16         47 $parser->pop_scope();
270             }
271             }
272              
273 52         151 $parser->advance("END");
274 51         143 $parser->pop_scope();
275 51         386 return $top_if;
276             }
277              
278             sub std_switch {
279 10     10 0 17 my($parser, $symbol) = @_;
280              
281 10         36 $parser->new_scope();
282              
283 10         30 my $topic = $parser->symbol('$_')->clone(arity => 'variable' );
284 10         200 my $switch = $symbol->clone(
285             arity => 'given',
286             first => $parser->expression(0),
287             second => [ $topic ],
288             );
289              
290 10         214 local $parser->{in_given} = 1;
291              
292 10         14 my @cases;
293 10   100     91 while(!($parser->token->id eq "END" or $parser->token->id eq '(end)')) {
294 39         133 push @cases, $parser->statement();
295             }
296 10         31 $switch->third( \@cases );
297              
298 10         40 $parser->build_given_body($switch, "case");
299              
300 9         27 $parser->advance("END");
301 9         28 $parser->pop_scope();
302 9         116 return $switch;
303             }
304              
305             sub std_case {
306 19     19 0 31 my($parser, $symbol) = @_;
307 19 50       54 if(!$parser->in_given) {
308 0         0 $parser->_error("You cannot use $symbol statements outside switch statements");
309             }
310 19         49 my $case = $symbol->clone(arity => "case");
311              
312 19 100       398 if($parser->token->id ne "DEFAULT") {
313 12         34 $case->first( $parser->expression(0) );
314             }
315             else {
316 7         21 $parser->advance();
317             }
318 19         55 $case->second( $parser->statements() );
319 19         147 return $case;
320             }
321              
322             sub iterator_name {
323 35     35 0 116 return 'loop'; # always 'loop'
324             }
325              
326             # FOR ... IN ...; ...; END
327             sub std_for {
328 35     35 0 58 my($parser, $symbol) = @_;
329              
330 35         107 my $proc = $symbol->clone(arity => "for");
331              
332 35         721 my $var = $parser->token;
333 35 50       166 if(!any_in($var->arity, qw(variable name))) {
334 0         0 $parser->_unexpected("a variable name", $var);
335             }
336 35         119 $parser->advance();
337 35         105 $parser->advance("IN");
338 35         137 $proc->first( $parser->expression(0) );
339 35         138 $proc->second([$var]);
340              
341 35         136 $parser->new_scope();
342 35         142 $parser->define_iterator($var);
343              
344 35         129 $proc->third( $parser->statements() );
345              
346             # for-else
347 35 100       172 if($parser->token->id eq 'ELSE') {
348 3         10 $parser->reserve($parser->token);
349 3         8 $parser->advance();
350 3         9 my $else = $parser->statements();
351 3         10 $proc = $symbol->clone( arity => 'for_else',
352             first => $proc,
353             second => $else,
354             );
355             }
356 35         170 $parser->advance("END");
357 35         167 $parser->pop_scope();
358 35         240 return $proc;
359             }
360              
361             sub std_while {
362 10     10 0 16 my($parser, $symbol) = @_;
363              
364 10         28 my $while = $symbol->clone(arity => "while");
365              
366 10         206 $while->first( $parser->expression(0) );
367 10         33 $while->second([]); # no vars
368 10         36 $parser->new_scope();
369 10         31 $while->third( $parser->statements() );
370 10         31 $parser->advance("END");
371 10         39 $parser->pop_scope();
372 10         65 return $while;
373             }
374              
375             around std_include => sub {
376             my($super, $self, $symbol) = @_;
377             $symbol->id( lc $symbol->id );
378             return $self->$super( $symbol );
379             };
380              
381             sub localize_vars {
382 28     28 0 49 my($parser, $symbol) = @_;
383              
384             # should make 'WITH' optional?
385             # my $t = $parser->token;
386             # if($t->id eq "WITH" or $t->arity eq "variable") {
387             # $parser->advance() if $t->id eq "WITH";
388 28 100       165 if($parser->token->id eq "WITH") {
389 9         33 $parser->advance();
390 9         196 $parser->new_scope();
391 9         34 my $vars = $parser->set_list();
392 9         42 $parser->pop_scope();
393 9         24 return $vars;
394             }
395 19         81 return undef;
396             }
397              
398             sub set_list {
399 39     39 0 59 my($parser) = @_;
400 39         60 my @args;
401 39         52 while(1) {
402 84         196 my $key = $parser->token;
403              
404 84 100 100     374 if(!(any_in($key->arity, qw(variable name))
405             && $parser->next_token_is("="))) {
406 39         70 last;
407             }
408 45         147 $parser->advance();
409 45         129 $parser->advance("=");
410              
411 45         147 my $value = $parser->expression(0);
412              
413 45         98 push @args, $key => $value;
414              
415 45 100       219 if($parser->token->id eq ",") { # , is optional
416 3         10 $parser->advance();
417             }
418             }
419              
420 39         86 return \@args;
421             }
422              
423             sub std_get {
424 5     5 0 9 my($parser, $symbol) = @_;
425              
426 5         17 my $stmt = $parser->print( $parser->expression(0) );
427 5         108 return $parser->finish_statement($stmt);
428             }
429              
430             sub std_set {
431 30     30 0 52 my($parser, $symbol) = @_;
432              
433 30         91 my $is_default = ($symbol->id eq 'DEFAULT');
434              
435 30         81 my $set_list = $parser->set_list();
436 30         51 my @assigns;
437 30         80 for(my $i = 0; $i < @{$set_list}; $i += 2) {
  63         179  
438 33         113 my($name, $value) = @{$set_list}[$i, $i+1];
  33         83  
439              
440 33 100       88 if($is_default) { # DEFAULT a = b -> a = a || b
441 7         23 my $var = $parser->symbol('(variable)')->clone(
442             id => $name->id,
443             );
444              
445 7         156 $value = $parser->binary('||', $var, $value);
446             }
447 33         376 my $assign = $symbol->clone(
448             id => '=',
449             arity => 'assign',
450             first => $name,
451             second => $value,
452             );
453              
454 33 100       822 if(not $parser->find_or_create($name->id)->is_defined) {
455 32         700 $parser->define($name);
456 32         141 $assign->third('declare');
457             }
458 33         204 push @assigns, $assign;
459             }
460 30         240 return @assigns;
461             }
462              
463             sub std_call {
464 7     7 0 14 my($parser, $symbol) = @_;
465 7         23 my $stmt = $parser->expression(0);
466 7         27 return $parser->finish_statement($stmt);
467             }
468              
469             sub std_macro {
470 18     18 0 37 my($parser, $symbol) = @_;
471 18         66 my $proc = $symbol->clone(
472             arity => 'proc',
473             id => 'macro',
474             );
475              
476 18         401 my $name = $parser->token;
477 18 50       79 if($name->arity ne "variable") {
478 0         0 $parser->_error("a name", $name);
479             }
480              
481 18         86 $parser->define_function($name->id);
482              
483 18         56 $proc->first($name);
484 18         54 $parser->advance();
485              
486 18         94 $parser->new_scope();
487              
488 18         73 my $paren = ($parser->token->id eq "(");
489              
490 18 100       76 $parser->advance("(") if $paren;
491              
492 18         50 my $t = $parser->token;
493 18         28 my @vars;
494 18         88 while($t->arity eq "variable") {
495 9         17 push @vars, $t;
496 9         38 $parser->define($t);
497              
498 9         28 $t = $parser->advance();
499              
500 9 100       35 if($t->id eq ",") {
501 1         3 $t = $parser->advance(); # ","
502             }
503             else {
504 8         16 last;
505             }
506             }
507 18 100       70 $parser->advance(")") if $paren;
508              
509 18         77 $proc->second(\@vars);
510              
511 18         59 $parser->advance("BLOCK");
512 18         84 $proc->third( $parser->statements() );
513 18         59 $parser->advance("END");
514 18         80 $parser->pop_scope();
515 18         163 return $proc;
516             }
517              
518              
519             # WRAPPER "foo.tt" ... END
520             # is
521             # cascade "foo.tt" { content => lambda@xxx() }
522             # macro content@xxx -> { ... }
523             sub std_wrapper {
524 10     10 0 20 my($parser, $symbol) = @_;
525              
526 10         45 my $base = $parser->barename();
527 10         15 my $into;
528 10 100       53 if($parser->token->id eq "INTO") {
529 1         4 my $t = $parser->advance();
530 1 50       7 if(!any_in($t->arity, qw(name variable))) {
531 0         0 $parser->_unexpected("a variable name", $t);
532             }
533 1         4 $parser->advance();
534 1         4 $into = $t->id;
535             }
536             else {
537 9         18 $into = 'content';
538             }
539 10   100     29 my $vars = $parser->localize_vars() || [];
540 10         40 my $body = $parser->statements();
541 10         33 $parser->advance("END");
542              
543 10         54 return $parser->wrap(
544             $symbol,
545             $base,
546             $into,
547             $vars,
548             $body,
549             );
550             }
551              
552             sub wrap {
553 10     10 0 19 my($parser, $proto, $base, $into, $vars, $body) = @_;
554 10         30 my $cascade = $proto->clone(
555             arity => 'cascade',
556             first => $base,
557             );
558              
559 10         220 my $content = $parser->lambda($proto);
560 10         260 $content->second([]); # args
561 10         27 $content->third($body);
562              
563 10         66 my $call_content = $parser->call($content->first);
564              
565 10         214 my $into_name = $proto->clone(
566             arity => 'literal',
567             id => $into,
568             );
569              
570 10         185 push @{$vars}, $into_name => $call_content;
  10         26  
571 10         34 $cascade->third($vars);
572 10         79 return( $cascade, $content );
573             }
574              
575             # [% FILTER html %]
576             # ...
577             # [% END %]
578             # is
579             # : block filter_xxx | html -> {
580             # ...
581             # : }
582             # in Kolon
583              
584             sub std_filter {
585 7     7 0 9 my($parser, $symbol) = @_;
586              
587 7         17 my $filter = $parser->expression(0);
588              
589 7         23 my $proc = $parser->lambda($symbol);
590              
591 7         156 $proc->second([]);
592 7         23 $proc->third( $parser->statements() );
593 7         27 $parser->advance("END");
594              
595 7         31 my $callmacro = $parser->call($proc->first);
596              
597 7 100       160 if($filter->id eq 'html') {
598             # for compatibility with TT2
599 4         7 $filter = 'unmark_raw';
600             }
601 7         30 my $callfilter = $parser->call($filter, $callmacro);
602 7         158 return( $proc, $parser->print($callfilter) );
603             }
604              
605 50     50   343 no Mouse;
  50         121  
  50         339  
606             __PACKAGE__->meta->make_immutable();
607             __END__