File Coverage

blib/lib/Text/Handlebars/Compiler.pm
Criterion Covered Total %
statement 195 196 99.4
branch 27 30 90.0
condition 3 9 33.3
subroutine 43 43 100.0
pod 0 24 0.0
total 268 302 88.7


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