File Coverage

lib/Petal/CodeGenerator.pm
Criterion Covered Total %
statement 292 305 95.7
branch 49 70 70.0
condition 10 26 38.4
subroutine 32 33 96.9
pod 0 9 0.0
total 383 443 86.4


line stmt bran cond sub pod time code
1             package Petal::CodeGenerator::Decode;
2 73     73   236 use strict;
  73         145  
  73         1863  
3 73     73   273 use warnings;
  73         68  
  73         2104  
4 73     73   234 use base qw /MKDoc::XML::Decode/;
  73         140  
  73         11946  
5              
6              
7             ##
8             # $class->process ($xml);
9             # -----------------------
10             # Expands the entities ' " > < and &
11             # into their ascii equivalents.
12             ##
13             sub process
14             {
15 500 50   500   829 (@_ == 2) or warn "MKDoc::XML::Encode::process() should be called with two arguments";
16            
17 500         361 my $self = shift;
18 500         674 my $data = join '', @_;
19 500         676 $data =~ s/&(#?[0-9A-Za-z]+)\\;/$self->entity_to_char ($1)/eg;
  72         644  
20            
21 500         1381 return $data;
22             }
23              
24              
25             # ------------------------------------------------------------------
26             # Petal::CodeGenerator - Generates Perl code from canonical syntax
27             # ------------------------------------------------------------------
28             # Author: Jean-Michel Hiver
29             # Description: This class parses a template in 'canonical syntax'
30             # (referred as the 'UGLY SYNTAX' in the manual) and generates Perl
31             # code that can be turned into a subroutine using eval().
32             # ------------------------------------------------------------------
33             package Petal::CodeGenerator;
34 73     73   393 use MKDoc::XML::Decode;
  73         70  
  73         1192  
35 73     73   199 use strict;
  73         71  
  73         1189  
36 73     73   219 use warnings;
  73         94  
  73         1957  
37 73     73   224 use Carp;
  73         72  
  73         5765  
38              
39             our $PI_RE = '^<\?(?:\s|\r|\n)*(attr|include|var|if|condition|else|repeat|loop|foreach|for|eval|endeval|end|defslot).*?\?>$';
40 73     73   261 use vars qw /$petal_object $tokens $variables @code $indentLevel $token_name %token_hash $token $my_array/;
  73         92  
  73         160755  
41              
42              
43             sub indent_increment
44             {
45 797     797 0 620 my $class = shift;
46 797         666 $Petal::CodeGenerator::indentLevel++;
47             }
48              
49              
50             sub indent_decrement
51             {
52 795     795 0 599 my $class = shift;
53 795         656 $Petal::CodeGenerator::indentLevel--;
54             }
55              
56              
57             sub indent
58             {
59 4926     4926 0 3049 my $class = shift;
60 4926         7975 return $Petal::CodeGenerator::indentLevel;
61             }
62              
63              
64             # these _xxx_res primitives have been contributed by Fergal Daly
65             # they speed up string construction a little bit
66             sub _init_res
67             {
68 204     204   455 return '$res = ""';
69             }
70              
71              
72             sub _add_res
73             {
74 1078     1078   792 my $class = shift;
75 1078         728 my $thing = shift;
76 1078         1901 return qq{\$res .= $thing};
77             }
78              
79              
80             sub _final_res
81             {
82 196     196   426 return q{$res};
83             }
84              
85              
86             sub _get_res
87             {
88 6     6   16 return q{$res};
89             }
90              
91              
92             sub add_code
93             {
94 4795     4795 0 3111 my $class = shift;
95 4795         5051 push(@code, " " x $class->indent() . shift);
96             }
97              
98              
99             sub comp_expr
100             {
101 91     91 0 80 my $self = shift;
102 91         77 my $expr = shift;
103 91         279 return "\$hash->get ('$expr')";
104             }
105              
106              
107             sub comp_expr_encoded
108             {
109 257     257 0 185 my $self = shift;
110 257         218 my $expr = shift;
111 257         690 return "\$hash->get_encoded ('$expr')";
112             }
113              
114              
115             # $class->code_header();
116             # ----------------------
117             # This generates the beginning of the anonymous subroutine.
118             sub code_header
119             {
120 198     198 0 192 my $class = shift;
121 198         348 $class->add_code("\$VAR1 = sub {");
122 198         346 $class->indent_increment();
123 198         308 $class->add_code("my \$hash = shift;");
124 198         332 $class->add_code("my ".$class->_init_res.";");
125 198 50       571 $class->add_code('local $^W = 0;') unless $Petal::WARN_UNINIT;
126             }
127              
128              
129             # $class->code_footer();
130             # ----------------------
131             # This generates the tail of the anonymous subroutine
132             sub code_footer
133             {
134 196     196 0 197 my $class = shift;
135 196         333 $class->add_code("return ". $class->_final_res() .";");
136 196         334 $class->indent_decrement();
137 196         294 $class->add_code("};");
138             }
139              
140              
141             # $class->process ($data_ref, $petal_object);
142             # -------------------------------------------
143             # This (too big) subroutine converts the canonicalized template
144             # data into Perl code which is ready to be evaled and executed.
145             sub process
146             {
147 198     198 0 194 my $class = shift;
148 198         203 my $data_ref = shift;
149            
150 198   50     601 local $petal_object = shift || die "$class::" . "process: \$petal_object was not defined";
151            
152 198         353 local $tokens = $class->_tokenize ($data_ref);
153 198         227 local $variables = {};
154 198         294 local @code = ();
155 198         192 local $Petal::CodeGenerator::indentLevel = 0;
156 198         206 local $token_name = undef;
157 198         262 local %token_hash = ();
158 198         155 local $token = undef;
159 198         181 local $my_array = {};
160            
161 198         356 $class->code_header();
162              
163 198         168 foreach $token (@{$tokens})
  198         380  
164             {
165 1270 100       6927 if ($token =~ /$PI_RE/s)
166             {
167 534         3611 ($token_name) = $token =~ /$PI_RE/s;
168 534         2083 my @atts1 = $token =~ /(\S+)\=\"(.*?)\"/gos;
169 534         613 my @atts2 = $token =~ /(\S+)\=\'(.*?)\'/gos;
170 534         1089 %token_hash = (@atts1, @atts2);
171 534         883 foreach my $key (%token_hash)
172             {
173             $token_hash{$key} = $class->_decode_backslash_semicolon ($token_hash{$key})
174 994 100       2387 if (defined $token_hash{$key});
175             }
176            
177             CASE:
178 534         605 for ($token_name)
179             {
180 534 100       942 /^attr$/ and do { $class->_attr; last CASE };
  53         87  
  53         102  
181 481 100       704 /^include$/ and do { $class->_include; last CASE };
  71         126  
  69         171  
182 410 100       791 /^var$/ and do { $class->_var; last CASE };
  204         315  
  204         394  
183 206 100       359 /^if$/ and do { $class->_if; last CASE };
  59         91  
  59         100  
184 147 50       195 /^condition$/ and do { $class->_if; last CASE };
  0         0  
  0         0  
185 147 50       194 /^else$/ and do { $class->_else; last CASE };
  0         0  
  0         0  
186 147 100       210 /^repeat$/ and do { $class->_for; last CASE };
  2         4  
  2         6  
187 145 50       195 /^loop$/ and do { $class->_for; last CASE };
  0         0  
  0         0  
188 145 50       183 /^foreach$/ and do { $class->_for; last CASE };
  0         0  
  0         0  
189 145 100       252 /^for$/ and do { $class->_for; last CASE };
  30         61  
  30         71  
190 115 100       170 /^eval$/ and do { $class->_eval; last CASE };
  6         10  
  6         11  
191 109 100       174 /^endeval$/ and do { $class->_endeval; last CASE };
  6         12  
  6         43  
192 103 100       132 /^defslot$/ and do { $class->_defslot; last CASE };
  4         10  
  4         9  
193            
194             /^end$/ and do
195 99 50       225 {
196 99         155 my $idt = $class->indent();
197 99         128 delete $my_array->{$idt};
198 99         129 $class->indent_decrement();
199 99         130 $class->add_code("};");
200 99         152 last CASE;
201             };
202             }
203             }
204             else
205             {
206 736         1389 my $string = quotemeta ($token);
207             # $string =~ s/\@/\\\@/gsm;
208             # $string =~ s/\$/\\\$/gsm;
209             # $string =~ s/\n/\\n/gsm;
210             # $string =~ s/\n//gsm;
211             # $string =~ s/\"/\\\"/gsm;
212 736         1620 $class->add_code($class->_add_res( '"' . $string . '";'));
213             }
214             }
215            
216 196         336 $class->code_footer();
217 196         1772 return join "\n", @code;
218             }
219              
220              
221             # $class->_include;
222             # -----------------
223             # process a file
224             sub _include
225             {
226 71     71   68 my $class = shift;
227 71         77 my $file = $token_hash{file};
228 71         179 my $path = $petal_object->_include_compute_path ($file);
229 69         132 my $lang = $petal_object->language();
230 69         123 $class->add_code ($class->_add_res ("do {"));
231 69         105 $class->indent_increment();
232              
233 69         144 my $included_from = $petal_object->_file();
234 69         81 $included_from =~ s/\#.*$//;
235              
236 69         101 $class->add_code ("do {");
237 69         93 $class->indent_increment();
238              
239 69         86 $class->add_code ("my \$new_hash = \$hash->new();");
240 69         134 $class->add_code ("\$new_hash->{__included_from__} = '$included_from';");
241              
242 69 100 66     311 (defined $lang and $lang) ?
243             $class->add_code ("my \$res = eval { Petal->new (file => '$path', lang => '$lang')->process (\$new_hash) };") :
244             $class->add_code ("my \$res = eval { Petal->new ('$path')->process (\$new_hash) };");
245            
246 69         112 $class->add_code ("\$res = \"\" if (defined \$\@ and \$\@);");
247 69         97 $class->add_code ("\$res;");
248 69         96 $class->indent_decrement();
249 69         89 $class->add_code ("} || '';");
250              
251 69         93 $class->indent_decrement();
252 69         87 $class->add_code ("};");
253             }
254              
255              
256             # $class->_defslot;
257             # ---------------------
258             # process a statement
259             sub _defslot
260             {
261 4     4   4 my $class = shift;
262             my $variable = $token_hash{name} or
263 4 50       7 confess "Cannot parse $token : 'name' attribute is not defined";
264            
265 4 50 33     18 (defined $variable and $variable) or
266             confess "Cannot parse $token : 'name' attribute is not defined";
267            
268             # set the variable in the $variables hash
269 4         3 my $tmp = $variable;
270 4         6 $tmp =~ s/\..*//;
271 4         6 $variables->{$tmp} = 1;
272            
273 4         6 $variable =~ s/\'/\\\'/g;
274              
275 4         6 $class->add_code ("do {");
276 4         6 $class->indent_increment();
277              
278 4         5 $class->add_code ("my \$tmp = undef;");
279 4         6 $class->add_code ("\$hash->{__included_from__} && do {");
280 4         6 $class->indent_increment();
281              
282 4         10 $class->add_code ("my \$path = \$hash->{__included_from__} . '#$variable';");
283 4         7 $class->add_code ("my \$new_hash = \$hash->new();");
284 4         7 $class->add_code ("delete \$new_hash->{__included_from__};");
285              
286 4         13 my $lang = $petal_object->language();
287 4 50 33     12 (defined $lang and $lang) ?
288             $class->add_code ("\$tmp = eval { Petal->new (file => \$path, lang => '$lang')->process (\$new_hash) };") :
289             $class->add_code ("\$tmp = eval { Petal->new (\$path)->process (\$new_hash) };");
290              
291 4         6 $class->indent_decrement();
292 4         4 $class->add_code ("};");
293              
294 4         4 $class->add_code ("if (\$tmp) {");
295 4         6 $class->indent_increment();
296              
297 4         6 $class->add_code ( $class->_add_res ("\$tmp") );
298 4         5 $class->indent_decrement();
299              
300 4         4 $class->add_code ( "} else {" );
301 4         4 $class->indent_increment();
302             }
303            
304              
305             # $class->_var;
306             # -------------
307             # process a statement
308             sub _var
309             {
310 204     204   183 my $class = shift;
311             my $variable = $token_hash{name} or
312 204 50       366 confess "Cannot parse $token : 'name' attribute is not defined";
313            
314 204 50 33     763 (defined $variable and $variable) or
315             confess "Cannot parse $token : 'name' attribute is not defined";
316            
317             # set the variable in the $variables hash
318 204         176 my $tmp = $variable;
319 204         341 $tmp =~ s/\..*//;
320 204         337 $variables->{$tmp} = 1;
321            
322 204         218 $variable =~ s/\'/\\\'/g;
323 204         300 $class->add_code ( $class->_add_res (('do {')) );
324 204         293 $class->indent_increment();
325 204         290 $class->add_code ('my $res = ' . $class->comp_expr_encoded ($variable) . ';');
326 204         296 $class->add_code ('(defined $res) ? $res : "";');
327 204         276 $class->indent_decrement();
328 204         263 $class->add_code ('};');
329             }
330              
331              
332             # $class->_if;
333             # ------------
334             # process a statement
335             sub _if
336             {
337 59     59   46 my $class = shift;
338             my $variable = $token_hash{name} or
339 59 50       117 confess "Cannot parse $token : 'name' attribute is not defined";
340            
341 59 50 33     216 (defined $variable and $variable) or
342             confess "Cannot parse $token : 'name' attribute is not defined";
343            
344             # set the variable in the $variables hash
345 59         50 my $tmp = $variable;
346 59         68 $tmp =~ s/\..*//;
347 59         91 $variables->{$tmp} = 1;
348            
349 59         62 $variable =~ s/\'/\\\'/g;
350 59         97 $class->add_code("if (".$class->comp_expr($variable).") {");
351 59         99 $class->indent_increment();
352             }
353              
354              
355             # $class->_eval;
356             # -------------------
357             # process a statement
358             sub _eval
359             {
360 6     6   4 my $class = shift;
361 6         10 $class->add_code($class->_add_res("eval {"));
362 6         10 $class->indent_increment();
363 6         8 $class->add_code("my " . $class->_init_res() .";");
364 6         8 $class->add_code("local %SIG;");
365 6         10 $class->add_code("\$SIG{__DIE__} = sub { \$\@ = shift };");
366             }
367              
368              
369             # $class->_endeval;
370             # -----------------
371             # process a statement
372             sub _endeval
373             {
374 6     6   5 my $class = shift;
375 6 50       12 my $variable = $token_hash{'errormsg'} or
376             confess "Cannot parse $token : 'errormsg' attribute is not defined";
377            
378 6         8 $class->add_code("return " . $class->_get_res() . ";");
379 6         10 $class->indent_decrement();
380 6         7 $class->add_code("} || '';");
381            
382 6         7 $class->add_code("if (defined \$\@ and \$\@) {");
383 6         8 $class->indent_increment();
384              
385 6         11 $variable =~ s/\&/&/g;
386 6         10 $variable =~ s/\
387 6         7 $variable =~ s/\>/>/g;
388 6         7 $variable =~ s/\"/"/g;
389 6         7 $variable = quotemeta ($variable);
390 6         14 $class->add_code($class->_add_res("\"$variable\";"));
391 6         11 $class->indent_decrement();
392 6         8 $class->add_code("}");
393             }
394              
395              
396             # $class->_attr;
397             # --------------
398             # process a statement
399             sub _attr
400             {
401 53     53   54 my $class = shift;
402             my $attribute = $token_hash{name} or
403 53 50       100 confess "Cannot parse $token : 'name' attribute is not defined";
404            
405             my $variable = $token_hash{value} or
406 53 50       93 confess "Cannot parse $token : 'value' attribute is not defined";
407            
408 53 50 33     196 (defined $variable and $variable) or
409             confess "Cannot parse $token : 'value' attribute is not defined";
410            
411             # set the variable in the $variables hash
412 53         47 my $tmp = $variable;
413 53         72 $tmp =~ s/\..*//;
414 53         86 $variables->{$tmp} = 1;
415            
416 53         59 $variable =~ s/\'/\\\'/g;
417 53         69 $class->add_code('{');
418 53         80 $class->indent_increment();
419 53         168 $class->add_code ("my \$value = " . $class->comp_expr_encoded($variable) . ";");
420 53         108 $class->add_code ("if (defined(\$value)) {");
421             # $class->add_code ("if (defined(\$value) and length(\$value)) {");
422 53         65 $class->indent_increment();
423 53         102 $class->add_code ($class->_add_res (qq {"$attribute=\\"\$value\\""}) );
424 53         97 $class->indent_decrement();
425 53         61 $class->add_code ("}");
426 53         64 $class->indent_decrement();
427 53         73 $class->add_code ("}");
428             }
429              
430              
431             # $class->_else;
432             # --------------
433             # process a statement
434             sub _else
435             {
436 0     0   0 my $class = shift;
437 0         0 $class->indent_decrement();
438 0         0 $class->add_code("}");
439 0         0 $class->add_code("else {");
440 0         0 $class->indent_increment();
441             }
442              
443              
444             # $class->_for;
445             # -------------
446             # process a statement
447             sub _for
448             {
449 32     32   29 my $class = shift;
450             my $variable = $token_hash{name} or
451 32 50       79 confess "Cannot parse $token : 'name' attribute is not defined";
452            
453 32 50 33     151 (defined $variable and $variable) or
454             confess "Cannot parse $token : 'name' attribute is not defined";
455            
456 32         60 $variable =~ s/^\s+//;
457 32         28 my $as;
458 32         92 ($as, $variable) = split /\s+/, $variable, 2;
459            
460 32 50 33     133 (defined $as and defined $variable) or
461             confess "Cannot parse $token : loop name not specified";
462            
463             # set the variable in the $variables hash
464 32         30 my $tmp = $variable;
465 32         49 $tmp =~ s/\..*//;
466 32         55 $variables->{$tmp} = 1;
467            
468 32         49 my $idt = $class->indent();
469 32         42 $variable =~ s/\'/\\\'/g;
470 32 100       83 unless (defined $my_array->{$idt})
471             {
472 29         45 $class->add_code("my \$array = ".$class->comp_expr($variable).";");
473 29         93 $class->add_code(
474             qq{die 'tried to repeat but $variable gave no array reference'}
475             . qq{ unless defined \$array and ref \$array eq 'ARRAY';}
476             );
477 29         55 $class->add_code("my \@array = \@\$array;");
478 29         50 $my_array->{$idt} = 1;
479             }
480             else
481             {
482             #$class->add_code("\@array = \@{".$class->comp_expr($variable)."};");
483 3         34 $class->add_code("\$array = ".$class->comp_expr($variable).";");
484 3         13 $class->add_code(
485             qq{die 'tried to repeat but $variable gave no array reference'}
486             . qq{ unless defined \$array and ref \$array eq 'ARRAY';}
487             );
488 3         5 $class->add_code("\@array = \@\$array;");
489             }
490              
491            
492 32         50 $class->add_code ("for (my \$i=0; \$i < \@array; \$i++) {");
493 32         55 $class->indent_increment();
494 32         49 $class->add_code ("my \$hash = \$hash->new();");
495            
496             # compute various might-be-useful variables
497 32         50 $class->add_code ("my \$number = \$i + 1;");
498 32         48 $class->add_code ("my \$odd = \$number % 2;");
499 32         42 $class->add_code ("my \$even = \$i % 2;");
500 32         45 $class->add_code ("my \$start = (\$i == 0);");
501 32         44 $class->add_code ("my \$end = (\$i == \$#array);");
502 32         50 $class->add_code ("my \$inner = (\$i and \$i < \@array);");
503            
504             # backwards compatibility
505 32         45 $class->add_code ("\$hash->{__count__} = \$number;");
506 32         50 $class->add_code ("\$hash->{__is_first__} = \$start;");
507 32         42 $class->add_code ("\$hash->{__is_last__} = \$end;");
508 32         128 $class->add_code ("\$hash->{__is_inner__} = \$inner;");
509 32         41 $class->add_code ("\$hash->{__even__} = \$even;");
510 32         40 $class->add_code ("\$hash->{__odd__} = \$odd;");
511            
512             # new repeat style object
513 32         41 $class->add_code ("\$hash->{repeat} = {");
514 32         43 $class->indent_increment();
515 32         37 $class->add_code ("index => \$i,");
516 32         48 $class->add_code ("number => \$number,");
517 32         39 $class->add_code ("even => \$even,");
518 32         51 $class->add_code ("odd => \$odd,");
519 32         44 $class->add_code ("start => \$start,");
520 32         47 $class->add_code ("end => \$end,");
521 32         42 $class->add_code ("inner => \$inner,");
522 32         43 $class->add_code ("};");
523 32         46 $class->indent_decrement();
524            
525 32         83 $class->add_code ("\$hash->{'$as'} = \$array[\$i];");
526             }
527              
528              
529             # $class->_tokenize ($data_ref);
530             # ------------------------------
531             # Returns the data to process as a list of tokens:
532             # ( 'some text', '<% a_tag %>', 'some more text', '<% end-a_tag %>' etc.
533             sub _tokenize
534             {
535 2106     2106   1542 my $self = shift;
536 2106         1345 my $data_ref = shift;
537            
538 2106         3622 my @tags = $$data_ref =~ /(<\?.*?\?>)/gs;
539 2106         4332 my @split = split /(?:<\?.*?\?>)/s, $$data_ref;
540            
541 2106         1964 my $tokens = [];
542 2106         3052 while (@split)
543             {
544 2633         1758 push @{$tokens}, shift (@split);
  2633         2870  
545 2633 100       4955 push @{$tokens}, shift (@tags) if (@tags);
  545         850  
546             }
547 2106         1385 push @{$tokens}, (@tags);
  2106         1520  
548 2106         2896 return $tokens;
549             }
550              
551              
552             sub _decode_backslash_semicolon
553             {
554 500     500   413 my $class = shift;
555 500         367 my $data = shift;
556            
557 500         1135 my $decode = new Petal::CodeGenerator::Decode ('xml');
558 500         3430 return $decode->process ($data);
559             }
560              
561              
562             1;