File Coverage

blib/lib/Text/Handlebars/Compiler.pm
Criterion Covered Total %
statement 194 195 99.4
branch 27 30 90.0
condition 3 9 33.3
subroutine 42 42 100.0
pod 0 24 0.0
total 266 300 88.6


line stmt bran cond sub pod time code
1             package Text::Handlebars::Compiler;
2             our $AUTHORITY = 'cpan:DOY';
3             $Text::Handlebars::Compiler::VERSION = '0.05';
4 12     12   382188 use Mouse;
  12         28  
  12         102  
5              
6             extends 'Text::Xslate::Compiler';
7              
8 12     12   4291 use Try::Tiny;
  12         24  
  12         35207  
9              
10             has '+syntax' => (
11             default => 'Handlebars',
12             );
13              
14 162     162 0 749 sub define_helper { shift->parser->define_helper(@_) }
15              
16             sub _generate_block_body {
17 70     70   15341 my $self = shift;
18 70         100 my ($node) = @_;
19              
20 70         103 my @compiled = map { $self->compile_ast($_) } @{ $node->second };
  70         198  
  70         192  
21              
22 70 100       14653 unshift @compiled, $self->_localize_vars($node->first)
23             if $node->first;
24              
25 70         3349 return @compiled;
26             }
27              
28             sub _generate_key {
29 685     685   125171 my $self = shift;
30 685         945 my ($node) = @_;
31              
32 685         1873 my $var = $node->clone(arity => 'variable');
33              
34 685         15272 return $self->compile_ast($self->check_lambda($var));
35             }
36              
37             sub _generate_key_field {
38 66     66   15416 my $self = shift;
39 66         98 my ($node) = @_;
40              
41 66         191 my $field = $node->clone(arity => 'field');
42              
43 66         1474 return $self->compile_ast($self->check_lambda($field));
44             }
45              
46             sub _generate_call {
47 1683     1683   59587 my $self = shift;
48 1683         2441 my ($node) = @_;
49              
50 1683 100       4943 if ($node->is_helper) {
51 42         142 my @args;
52             my @hash;
53 42         65 for my $arg (@{ $node->second }) {
  42         130  
54 43 100       138 if ($arg->arity eq 'pair') {
55 4         21 push @hash, $arg->first, $arg->second;
56             }
57             else {
58 39         101 push @args, $arg;
59             }
60             }
61              
62 42         140 my $hash = $self->make_hash(@hash);
63              
64 42         1338 unshift @args, $self->vars;
65              
66 42 100       1296 if ($node->is_block_helper) {
67 34         49 push @{ $node->first->second }, $hash;
  34         142  
68 34         135 $node->second(\@args);
69             }
70             else {
71 8         35 $node->second([ @args, $hash ]);
72             }
73             }
74              
75 1683         4476 return $self->SUPER::_generate_call($node);
76             }
77              
78             sub _generate_partial {
79 3     3   237 my $self = shift;
80 3         7 my ($node) = @_;
81              
82 3         10 my $file = $node->first;
83 3 50       12 if (ref($file) eq 'ARRAY') {
84 3         14 $file = $node->clone(
85             arity => 'binary',
86             id => '~',
87             first => $file->[0],
88             second => $node->clone(arity => 'suffix'),
89             );
90             }
91              
92 3         144 my $args = $node->second;
93 3 100       76 if ($args) {
94 1         4 $args = [ $self->new_vars($args) ];
95             }
96              
97             return (
98 3         73 $self->compile_ast(
99             $self->make_ternary(
100             $self->find_file($file->clone),
101             $node->clone(
102             arity => 'include',
103             id => 'include',
104             first => $file->clone,
105             second => $args,
106             ),
107             $self->literal(''),
108             ),
109             ),
110             );
111             }
112              
113             sub _generate_suffix {
114 6     6   545 my $self = shift;
115 6         11 my ($node) = @_;
116              
117             return (
118 6         17 $self->opcode('suffix'),
119             );
120             }
121              
122             sub find_file {
123 3     3 0 61 my $self = shift;
124 3         7 my ($filename) = @_;
125              
126 3         13 return $filename->clone(
127             arity => 'unary',
128             id => 'find_file',
129             first => $filename,
130             );
131             }
132              
133             sub _generate_for {
134 35     35   13926 my $self = shift;
135 35         63 my ($node) = @_;
136              
137 35         156 my @opcodes = $self->SUPER::_generate_for(@_);
138             return (
139 35         36338 @opcodes,
140             $self->opcode('nil'),
141             );
142             }
143              
144             sub _generate_block {
145 69     69   11027 my $self = shift;
146 69         176 my ($node) = @_;
147              
148 69         187 my $name = $node->first;
149 69         111 my %block = %{ $node->second };
  69         311  
150              
151 69 100       284 if ($name->arity eq 'call') {
152             return $self->compile_ast(
153             $name->clone(
154             first => $self->call(
155             $node,
156             '(make_block_helper)',
157             $self->vars,
158             $name->first,
159             $block{if}{raw_text}->clone,
160             ($block{else}
161             ? $block{else}{raw_text}->clone
162 34 100       102 : $self->literal('')),
163             ),
164             is_block_helper => 1,
165             ),
166             );
167             }
168              
169 35         114 my $iterations = $self->make_ternary(
170             $self->is_falsy($name->clone),
171             $self->make_array($self->literal(1)),
172             $self->make_ternary(
173             $self->is_array_ref($name->clone),
174             $name->clone,
175             $self->make_array($self->literal(1)),
176             ),
177             );
178              
179 35         1328 my $loop_var = $self->parser->symbol('(loop_var)')->clone(arity => 'variable');
180              
181             my $body_block = [
182             $self->make_ternary(
183             $self->is_falsy($name->clone),
184             $name->clone(
185             arity => 'block_body',
186             first => undef,
187             second => [ $block{else}{body} ],
188             ),
189             $name->clone(
190             arity => 'block_body',
191             first => [
192             $self->new_vars($name->clone, $self->iterator_index),
193             ],
194 35         1132 second => [ $block{if}{body} ],
195             ),
196             ),
197             ];
198              
199 35         1336 my $var = $name->clone(arity => 'variable');
200             return $self->compile_ast(
201             $self->make_ternary(
202             $self->is_code_ref($var->clone),
203             $self->run_code(
204             $var->clone,
205             $block{if}{raw_text}->clone,
206             $block{if}{open_tag}->clone,
207             $block{if}{close_tag}->clone,
208 35         744 ),
209             $self->parser->symbol('(for)')->clone(
210             arity => 'for',
211             first => $iterations,
212             second => [$loop_var],
213             third => $body_block,
214             ),
215             ),
216             );
217             }
218              
219             sub _generate_unary {
220 1071     1071   108819 my $self = shift;
221 1071         1495 my ($node) = @_;
222              
223             # XXX copied from Text::Xslate::Compiler because it uses a hardcoded list
224             # of unary ops
225 1071 100       3141 if ($self->is_unary($node->id)) {
226 1001         3703 my @code = (
227             $self->compile_ast($node->first),
228             $self->opcode($node->id)
229             );
230             # render_string can't be constant folded, because it depends on the
231             # current vars
232 1001 0 33     121100 if ($Text::Xslate::Compiler::OPTIMIZE and $self->_code_is_literal(@code) && $node->id ne 'render_string' && $node->id ne 'find_file') {
      33        
      33        
233 0         0 $self->_fold_constants(\@code);
234             }
235 1001         9666 return @code;
236             }
237             else {
238 70         247 return $self->SUPER::_generate_unary(@_);
239             }
240             }
241              
242             sub is_unary {
243 1071     1071 0 1319 my $self = shift;
244 1071         1438 my ($id) = @_;
245              
246             my %unary = (
247 1071         1613 map { $_ => 1 } qw(builtin_is_array_ref builtin_is_hash_ref is_code_ref
  4284         9159  
248             find_file)
249             );
250              
251 1071         4081 return $unary{$id};
252             }
253              
254             sub _generate_array_length {
255 70     70   1765 my $self = shift;
256 70         103 my ($node) = @_;
257              
258 70         288 my $max_index = $self->parser->symbol('(max_index)')->clone(
259             id => 'max_index',
260             arity => 'unary',
261             first => $node->first,
262             );
263              
264             return (
265 70         2399 $self->compile_ast($max_index),
266             $self->opcode('move_to_sb'),
267             $self->opcode('literal', 1),
268             $self->opcode('add'),
269             );
270             }
271              
272             sub _generate_run_code {
273 786     786   18650 my $self = shift;
274 786         1112 my ($node) = @_;
275              
276 786         2039 my $to_render = $node->clone(arity => 'call');
277              
278 786 100       18040 if ($node->third) {
279 35         49 my ($open_tag, $close_tag) = @{ $node->third };
  35         92  
280 35         155 $to_render = $self->make_ternary(
281             $self->parser->symbol('==')->clone(
282             arity => 'binary',
283             first => $close_tag->clone,
284             second => $self->literal('}}'),
285             ),
286             $to_render,
287             $self->join('{{= ', $open_tag, ' ', $close_tag, ' =}}', $to_render)
288             );
289             }
290              
291             # XXX turn this into an opcode
292 786         2840 my $render_string = $self->call(
293             $node,
294             '(render_string)',
295             $to_render,
296             $self->vars,
297             );
298              
299 786         2776 return $self->compile_ast($render_string);
300             }
301              
302             sub _generate_new_vars {
303 36     36   972 my $self = shift;
304 36         57 my ($node) = @_;
305              
306 36         170 my ($vars, $value, $i) = ($node->first, $node->second, $node->third);
307              
308 36         89 my $lvar_id = $self->lvar_id;
309 36         103 local $self->{lvar_id} = $self->lvar_use(1);
310              
311 36         195 my @code;
312              
313 36         96 push @code, $self->compile_ast($value);
314 36         12179 push @code, $self->opcode('save_to_lvar', $lvar_id);
315 36         653 my $lvar_value = $value->clone(arity => 'lvar', id => $lvar_id);
316              
317 36 100       1107 if ($i) {
318 35         147 my $value_at_index = $value->clone(
319             arity => 'field',
320             first => $value->clone,
321             second => $i->clone,
322             );
323              
324 35         1981 push @code, $self->compile_ast(
325             $self->make_ternary(
326             $self->is_array_ref($lvar_value->clone),
327             $self->save_lvar(
328             $lvar_id,
329             $self->make_ternary(
330             $self->is_hash_ref($value_at_index->clone),
331             $self->merge_hash(
332             $self->make_hash(
333             $self->literal('.'),
334             $value_at_index->clone,
335             ),
336             $value_at_index->clone,
337             ),
338             $self->make_hash(
339             $self->literal('.'),
340             $value_at_index->clone,
341             ),
342             ),
343             ),
344             ),
345             );
346             }
347             else {
348 1         5 push @code, $self->compile_ast(
349             $self->make_ternary(
350             $self->is_array_ref($lvar_value->clone),
351             $self->save_lvar(
352             $lvar_id,
353             $self->make_hash(
354             $self->literal('.'),
355             $value->clone,
356             ),
357             ),
358             ),
359             );
360             }
361              
362 36 100       25141 push @code, $self->compile_ast(
363             $self->save_lvar(
364             $lvar_id,
365             $self->make_ternary(
366             $self->is_hash_ref($lvar_value->clone),
367             $self->merge_hash(
368             ($i
369             ? (
370             $self->make_hash(
371             $self->literal('@index'), $i->clone
372             )
373             )
374             : ()),
375             $vars->clone,
376             $lvar_value->clone,
377             $self->make_hash(
378             $self->literal('..'),
379             $vars->clone,
380             ),
381             ),
382             $vars->clone,
383             ),
384             ),
385             );
386              
387 36         15345 push @code, $self->opcode('load_lvar', $lvar_id);
388              
389 36         1666 return @code;
390             }
391              
392             sub _generate_lvar {
393 108     108   10612 my $self = shift;
394 108         152 my ($node) = @_;
395              
396             return (
397 108         391 $self->opcode('load_lvar', $node->id),
398             );
399             }
400              
401             sub _generate_save_lvar {
402 72     72   2013 my $self = shift;
403 72         110 my ($node) = @_;
404              
405             return (
406 72         260 $self->compile_ast($node->first),
407             $self->opcode('save_to_lvar', $node->id),
408             );
409             }
410              
411             sub _generate_merge_hash {
412 142     142   2688 my $self = shift;
413 142         202 my ($node) = @_;
414              
415 142         327 my $lvar_id = $self->lvar_id;
416 142         351 local $self->{lvar_id} = $self->lvar_use(1);
417              
418             return (
419 142         990 $self->compile_ast($node->first),
420             $self->opcode('save_to_lvar', $lvar_id),
421             $self->compile_ast($node->second),
422             $self->opcode('move_to_sb'),
423             $self->opcode('load_lvar', $lvar_id),
424             $self->opcode('merge_hash'),
425             );
426             }
427              
428             sub join {
429 35     35 0 1775 my $self = shift;
430 35         95 my (@args) = @_;
431              
432 35         60 @args = map { $self->literalize($_) } @args;
  210         4390  
433              
434 35         714 my $joined = shift @args;
435 35         78 for my $arg (@args) {
436 175         4749 $joined = $self->parser->symbol('~')->clone(
437             arity => 'binary',
438             first => $joined,
439             second => $arg,
440             );
441             }
442              
443 35         1052 return $joined;
444             }
445              
446             sub literalize {
447 210     210 0 326 my $self = shift;
448 210         297 my ($val) = @_;
449              
450 210 100       797 return $val->clone if blessed($val);
451 105         211 return $self->literal($val);
452             }
453              
454             sub call {
455 820     820 0 22425 my $self = shift;
456 820         1648 my ($node, $name, @args) = @_;
457              
458 820         2865 my $code = $self->parser->symbol('(name)')->clone(
459             arity => 'name',
460             id => $name,
461             line => $node->line,
462             );
463              
464 820         28638 return $self->parser->call($code, @args);
465             }
466              
467             sub make_ternary {
468 1106     1106 0 33088 my $self = shift;
469 1106         1698 my ($if, $then, $else) = @_;
470 1106         3918 return $self->parser->symbol('?:')->clone(
471             arity => 'if',
472             first => $if,
473             second => $then,
474             third => $else,
475             );
476             }
477              
478             sub vars {
479 898     898 0 1151 my $self = shift;
480 898         3138 return $self->parser->symbol('(vars)')->clone(arity => 'vars');
481             }
482              
483             sub iterator_index {
484 35     35 0 2243 my $self = shift;
485              
486 35         138 return $self->parser->symbol('(iterator)')->clone(
487             arity => 'iterator',
488             id => '$~(loop_var)',
489             first => $self->parser->symbol('(loop_var)')->clone,
490             ),
491             }
492              
493             sub check_lambda {
494 751     751 0 991 my $self = shift;
495 751         1016 my ($var) = @_;
496              
497 751         1997 return $self->make_ternary(
498             $self->is_code_ref($var->clone),
499             $self->run_code($var->clone),
500             $var,
501             );
502             }
503              
504             sub is_array_ref {
505 141     141 0 3268 my $self = shift;
506 141         204 my ($var) = @_;
507              
508 141         589 return $self->parser->symbol('(is_array_ref)')->clone(
509             id => 'builtin_is_array_ref',
510             arity => 'unary',
511             first => $var,
512             );
513             }
514              
515             sub is_hash_ref {
516 71     71 0 2227 my $self = shift;
517 71         104 my ($var) = @_;
518              
519 71         273 return $self->parser->symbol('(is_hash_ref)')->clone(
520             id => 'builtin_is_hash_ref',
521             arity => 'unary',
522             first => $var,
523             );
524             }
525              
526             sub is_code_ref {
527 786     786 0 13465 my $self = shift;
528 786         1099 my ($var) = @_;
529              
530 786         2813 return $self->parser->symbol('(is_code_ref)')->clone(
531             id => 'is_code_ref',
532             arity => 'unary',
533             first => $var,
534             );
535             }
536              
537             sub make_array {
538 70     70 0 1857 my $self = shift;
539 70         150 my (@contents) = @_;
540              
541 70         246 return $self->parser->symbol('[')->clone(
542             arity => 'composer',
543             first => \@contents,
544             );
545             }
546              
547             sub make_hash {
548 184     184 0 5922 my $self = shift;
549 184         367 my (@contents) = @_;
550              
551 184         682 return $self->parser->symbol('{')->clone(
552             arity => 'composer',
553             first => \@contents,
554             );
555             }
556              
557             sub is_falsy {
558 70     70 0 1246 my $self = shift;
559 70         105 my ($node) = @_;
560              
561 70         195 return $self->not(
562             $self->make_ternary(
563             $self->is_array_ref($node->clone),
564             $self->array_length($node->clone),
565             $node
566             )
567             );
568             }
569              
570             sub not {
571 70     70 0 2118 my $self = shift;
572 70         102 my ($node) = @_;
573              
574 70         281 return $self->parser->symbol('!')->clone(
575             arity => 'unary',
576             first => $node,
577             );
578             }
579              
580             sub array_length {
581 70     70 0 3211 my $self = shift;
582 70         105 my ($node) = @_;
583              
584 70         248 return $self->parser->symbol('(array_length)')->clone(
585             arity => 'array_length',
586             first => $node,
587             );
588             }
589              
590             sub run_code {
591 786     786 0 36318 my $self = shift;
592 786         1259 my ($code, $raw_text, $open_tag, $close_tag) = @_;
593              
594 786 100       2734 return $self->parser->symbol('(run_code)')->clone(
595             arity => 'run_code',
596             first => $code,
597             (@_ > 1
598             ? (second => [ $raw_text ], third => [ $open_tag, $close_tag ])
599             : (second => [])),
600             );
601             }
602              
603             sub new_vars {
604 36     36 0 1904 my $self = shift;
605 36         60 my ($value, $i) = @_;
606              
607 36         101 return $value->clone(
608             arity => 'new_vars',
609             first => $self->vars,
610             second => $value,
611             third => $i,
612             );
613             }
614              
615             sub save_lvar {
616 72     72 0 1970 my $self = shift;
617 72         116 my ($id, $value) = @_;
618              
619 72         201 return $value->clone(
620             arity => 'save_lvar',
621             id => $id,
622             first => $value,
623             );
624             }
625              
626             sub merge_hash {
627 71     71 0 2520 my $self = shift;
628 71         138 my (@hashes) = @_;
629              
630 71         117 my $merged = shift @hashes;
631 71         142 for my $hash (@hashes) {
632 142         1791 $merged = $self->merge_single_hash($merged, $hash);
633             }
634              
635 71         1711 return $merged;
636             }
637              
638             sub merge_single_hash {
639 142     142 0 189 my $self = shift;
640 142         186 my ($left, $right) = @_;
641              
642 142         393 return $left->clone(
643             arity => 'merge_hash',
644             first => $left,
645             second => $right,
646             );
647             }
648              
649 382     382 0 10885 sub literal { shift->parser->literal(@_) }
650              
651             __PACKAGE__->meta->make_immutable;
652 12     12   73 no Mouse;
  12         21  
  12         65  
653              
654             =for Pod::Coverage
655             define_helper
656             find_file
657             is_unary
658             join
659             literalize
660             call
661             make_ternary
662             vars
663             iterator_index
664             check_lambda
665             is_array_ref
666             is_hash_ref
667             is_code_ref
668             make_array
669             make_hash
670             is_falsy
671             not
672             array_length
673             run_code
674             new_vars
675             save_lvar
676             merge_hash
677             merge_single_hash
678             literal
679              
680             =cut
681              
682             1;