File Coverage

lib/Petal/CodeGenerator.pm
Criterion Covered Total %
statement 294 307 95.7
branch 51 74 68.9
condition 10 26 38.4
subroutine 32 33 96.9
pod 0 9 0.0
total 387 449 86.1


line stmt bran cond sub pod time code
1             package Petal::CodeGenerator::Decode;
2 73     73   449 use strict;
  73         122  
  73         2177  
3 73     73   338 use warnings;
  73         111  
  73         2479  
4 73     73   373 use base qw /MKDoc::XML::Decode/;
  73         109  
  73         16981  
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   936 (@_ == 2) or warn "MKDoc::XML::Encode::process() should be called with two arguments";
16            
17 500         613 my $self = shift;
18 500         959 my $data = join '', @_;
19 500         979 $data =~ s/&(#?[0-9A-Za-z]+)\\;/$self->entity_to_char ($1)/eg;
  72         1029  
20            
21 500         1965 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   552 use MKDoc::XML::Decode;
  73         157  
  73         1741  
35 73     73   322 use strict;
  73         168  
  73         1737  
36 73     73   375 use warnings;
  73         137  
  73         2211  
37 73     73   355 use Carp;
  73         134  
  73         6811  
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   489 use vars qw /$petal_object $tokens $variables @code $indentLevel $token_name %token_hash $token $my_array/;
  73         137  
  73         230153  
41              
42              
43             sub indent_increment
44             {
45 797     797 0 938 my $class = shift;
46 797         944 $Petal::CodeGenerator::indentLevel++;
47             }
48              
49              
50             sub indent_decrement
51             {
52 795     795 0 1024 my $class = shift;
53 795         907 $Petal::CodeGenerator::indentLevel--;
54             }
55              
56              
57             sub indent
58             {
59 4926     4926 0 4907 my $class = shift;
60 4926         11008 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   687 return '$res = ""';
69             }
70              
71              
72             sub _add_res
73             {
74 1078     1078   1342 my $class = shift;
75 1078         1140 my $thing = shift;
76 1078         3044 return qq{\$res .= $thing};
77             }
78              
79              
80             sub _final_res
81             {
82 196     196   600 return q{$res};
83             }
84              
85              
86             sub _get_res
87             {
88 6     6   18 return q{$res};
89             }
90              
91              
92             sub add_code
93             {
94 4795     4795 0 5042 my $class = shift;
95 4795         6431 push(@code, " " x $class->indent() . shift);
96             }
97              
98              
99             sub comp_expr
100             {
101 91     91 0 111 my $self = shift;
102 91         108 my $expr = shift;
103 91         403 return "\$hash->get ('$expr')";
104             }
105              
106              
107             sub comp_expr_encoded
108             {
109 257     257 0 374 my $self = shift;
110 257         347 my $expr = shift;
111 257         1123 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 322 my $class = shift;
121 198         529 $class->add_code("\$VAR1 = sub {");
122 198         556 $class->indent_increment();
123 198         406 $class->add_code("my \$hash = shift;");
124 198         457 $class->add_code("my ".$class->_init_res.";");
125 198 50       702 $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 319 my $class = shift;
135 196         418 $class->add_code("return ". $class->_final_res() .";");
136 196         514 $class->indent_decrement();
137 196         354 $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 317 my $class = shift;
148 198         296 my $data_ref = shift;
149            
150 198   50     831 local $petal_object = shift || die "$class::" . "process: \$petal_object was not defined";
151            
152 198         512 local $tokens = $class->_tokenize ($data_ref);
153 198         353 local $variables = {};
154 198         385 local @code = ();
155 198         276 local $Petal::CodeGenerator::indentLevel = 0;
156 198         295 local $token_name = undef;
157 198         338 local %token_hash = ();
158 198         242 local $token = undef;
159 198         314 local $my_array = {};
160            
161 198         572 $class->code_header();
162              
163 198         276 foreach $token (@{$tokens})
  198         480  
164             {
165 1270 100       9377 if ($token =~ /$PI_RE/s)
166             {
167 534         4894 ($token_name) = $token =~ /$PI_RE/s;
168 534         2860 my @atts1 = $token =~ /(\S+)\=\"(.*?)\"/gos;
169 534         981 my @atts2 = $token =~ /(\S+)\=\'(.*?)\'/gos;
170 534         1459 %token_hash = (@atts1, @atts2);
171 534         1269 foreach my $key (%token_hash)
172             {
173             $token_hash{$key} = $class->_decode_backslash_semicolon ($token_hash{$key})
174 994 100       2949 if (defined $token_hash{$key});
175             }
176            
177             CASE:
178 534         846 for ($token_name)
179             {
180 534 100       1061 /^attr$/ and do { $class->_attr; last CASE };
  53         133  
  53         142  
181 481 100       835 /^include$/ and do { $class->_include; last CASE };
  71         170  
  69         206  
182 410 100       944 /^var$/ and do { $class->_var; last CASE };
  204         516  
  204         503  
183 206 100       446 /^if$/ and do { $class->_if; last CASE };
  59         144  
  59         137  
184 147 50       275 /^condition$/ and do { $class->_if; last CASE };
  0         0  
  0         0  
185 147 50       261 /^else$/ and do { $class->_else; last CASE };
  0         0  
  0         0  
186 147 100       288 /^repeat$/ and do { $class->_for; last CASE };
  2         5  
  2         6  
187 145 50       264 /^loop$/ and do { $class->_for; last CASE };
  0         0  
  0         0  
188 145 50       229 /^foreach$/ and do { $class->_for; last CASE };
  0         0  
  0         0  
189 145 100       331 /^for$/ and do { $class->_for; last CASE };
  30         91  
  30         89  
190 115 100       227 /^eval$/ and do { $class->_eval; last CASE };
  6         18  
  6         14  
191 109 100       249 /^endeval$/ and do { $class->_endeval; last CASE };
  6         25  
  6         15  
192 103 100       207 /^defslot$/ and do { $class->_defslot; last CASE };
  4         15  
  4         12  
193            
194             /^end$/ and do
195 99 50       337 {
196 99         225 my $idt = $class->indent();
197 99         178 delete $my_array->{$idt};
198 99         195 $class->indent_decrement();
199 99         219 $class->add_code("};");
200 99         203 last CASE;
201             };
202             }
203             }
204             else
205             {
206 736         2158 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         2256 $class->add_code($class->_add_res( '"' . $string . '";'));
213             }
214             }
215            
216 196         679 $class->code_footer();
217 196         2705 return join "\n", @code;
218             }
219              
220              
221             # $class->_include;
222             # -----------------
223             # process a file
224             sub _include
225             {
226 71     71   104 my $class = shift;
227 71         110 my $file = $token_hash{file};
228 71         264 my $path = $petal_object->_include_compute_path ($file);
229 69         190 my $lang = $petal_object->language();
230 69         214 $class->add_code ($class->_add_res ("do {"));
231 69         169 $class->indent_increment();
232              
233 69         181 my $included_from = $petal_object->_file();
234 69         135 $included_from =~ s/\#.*$//;
235              
236 69         144 $class->add_code ("do {");
237 69         172 $class->indent_increment();
238              
239 69         178 $class->add_code ("my \$new_hash = \$hash->new();");
240 69         194 $class->add_code ("\$new_hash->{__included_from__} = '$included_from';");
241              
242 69 100 66     424 (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 50       187 $class->add_code ("if (\$@) { confess(\$@); }") if $Petal::ERROR_ON_INCLUDE_ERROR;
247            
248 69         141 $class->add_code ("\$res = \"\" if (defined \$\@ and \$\@);");
249 69         136 $class->add_code ("\$res;");
250 69         161 $class->indent_decrement();
251 69         144 $class->add_code ("} || '';");
252              
253 69         161 $class->indent_decrement();
254 69         121 $class->add_code ("};");
255             }
256              
257              
258             # $class->_defslot;
259             # ---------------------
260             # process a statement
261             sub _defslot
262             {
263 4     4   8 my $class = shift;
264             my $variable = $token_hash{name} or
265 4 50       12 confess "Cannot parse $token : 'name' attribute is not defined";
266            
267 4 50 33     20 (defined $variable and $variable) or
268             confess "Cannot parse $token : 'name' attribute is not defined";
269            
270             # set the variable in the $variables hash
271 4         7 my $tmp = $variable;
272 4         8 $tmp =~ s/\..*//;
273 4         11 $variables->{$tmp} = 1;
274            
275 4         9 $variable =~ s/\'/\\\'/g;
276              
277 4         11 $class->add_code ("do {");
278 4         14 $class->indent_increment();
279              
280 4         9 $class->add_code ("my \$tmp = undef;");
281 4         9 $class->add_code ("\$hash->{__included_from__} && do {");
282 4         9 $class->indent_increment();
283              
284 4         25 $class->add_code ("my \$path = \$hash->{__included_from__} . '#$variable';");
285 4         10 $class->add_code ("my \$new_hash = \$hash->new();");
286 4         9 $class->add_code ("delete \$new_hash->{__included_from__};");
287              
288 4         15 my $lang = $petal_object->language();
289 4 50 33     18 (defined $lang and $lang) ?
290             $class->add_code ("\$tmp = eval { Petal->new (file => \$path, lang => '$lang')->process (\$new_hash) };") :
291             $class->add_code ("\$tmp = eval { Petal->new (\$path)->process (\$new_hash) };");
292              
293 4 50       16 $class->add_code ("if (\$@) { confess(\$@); }") if $Petal::ERROR_ON_INCLUDE_ERROR;
294              
295 4         13 $class->indent_decrement();
296 4         9 $class->add_code ("};");
297              
298 4         9 $class->add_code ("if (\$tmp) {");
299 4         9 $class->indent_increment();
300              
301 4         8 $class->add_code ( $class->_add_res ("\$tmp") );
302 4         8 $class->indent_decrement();
303              
304 4         16 $class->add_code ( "} else {" );
305 4         9 $class->indent_increment();
306             }
307            
308              
309             # $class->_var;
310             # -------------
311             # process a statement
312             sub _var
313             {
314 204     204   258 my $class = shift;
315             my $variable = $token_hash{name} or
316 204 50       476 confess "Cannot parse $token : 'name' attribute is not defined";
317            
318 204 50 33     773 (defined $variable and $variable) or
319             confess "Cannot parse $token : 'name' attribute is not defined";
320            
321             # set the variable in the $variables hash
322 204         298 my $tmp = $variable;
323 204         387 $tmp =~ s/\..*//;
324 204         473 $variables->{$tmp} = 1;
325            
326 204         470 $variable =~ s/\'/\\\'/g;
327 204         456 $class->add_code ( $class->_add_res (('do {')) );
328 204         486 $class->indent_increment();
329 204         405 $class->add_code ('my $res = ' . $class->comp_expr_encoded ($variable) . ';');
330 204         451 $class->add_code ('(defined $res) ? $res : "";');
331 204         425 $class->indent_decrement();
332 204         348 $class->add_code ('};');
333             }
334              
335              
336             # $class->_if;
337             # ------------
338             # process a statement
339             sub _if
340             {
341 59     59   96 my $class = shift;
342             my $variable = $token_hash{name} or
343 59 50       123 confess "Cannot parse $token : 'name' attribute is not defined";
344            
345 59 50 33     278 (defined $variable and $variable) or
346             confess "Cannot parse $token : 'name' attribute is not defined";
347            
348             # set the variable in the $variables hash
349 59         87 my $tmp = $variable;
350 59         102 $tmp =~ s/\..*//;
351 59         131 $variables->{$tmp} = 1;
352            
353 59         94 $variable =~ s/\'/\\\'/g;
354 59         144 $class->add_code("if (".$class->comp_expr($variable).") {");
355 59         127 $class->indent_increment();
356             }
357              
358              
359             # $class->_eval;
360             # -------------------
361             # process a statement
362             sub _eval
363             {
364 6     6   11 my $class = shift;
365 6         11 $class->add_code($class->_add_res("eval {"));
366 6         14 $class->indent_increment();
367 6         11 $class->add_code("my " . $class->_init_res() .";");
368 6         12 $class->add_code("local %SIG;");
369 6         10 $class->add_code("\$SIG{__DIE__} = sub { \$\@ = shift };");
370             }
371              
372              
373             # $class->_endeval;
374             # -----------------
375             # process a statement
376             sub _endeval
377             {
378 6     6   7 my $class = shift;
379 6 50       15 my $variable = $token_hash{'errormsg'} or
380             confess "Cannot parse $token : 'errormsg' attribute is not defined";
381            
382 6         12 $class->add_code("return " . $class->_get_res() . ";");
383 6         13 $class->indent_decrement();
384 6         11 $class->add_code("} || '';");
385            
386 6         13 $class->add_code("if (defined \$\@ and \$\@) {");
387 6         12 $class->indent_increment();
388              
389 6         13 $variable =~ s/\&/&/g;
390 6         15 $variable =~ s/\
391 6         13 $variable =~ s/\>/>/g;
392 6         9 $variable =~ s/\"/"/g;
393 6         19 $variable = quotemeta ($variable);
394 6         18 $class->add_code($class->_add_res("\"$variable\";"));
395 6         15 $class->indent_decrement();
396 6         9 $class->add_code("}");
397             }
398              
399              
400             # $class->_attr;
401             # --------------
402             # process a statement
403             sub _attr
404             {
405 53     53   102 my $class = shift;
406             my $attribute = $token_hash{name} or
407 53 50       126 confess "Cannot parse $token : 'name' attribute is not defined";
408            
409             my $variable = $token_hash{value} or
410 53 50       107 confess "Cannot parse $token : 'value' attribute is not defined";
411            
412 53 50 33     304 (defined $variable and $variable) or
413             confess "Cannot parse $token : 'value' attribute is not defined";
414            
415             # set the variable in the $variables hash
416 53         79 my $tmp = $variable;
417 53         105 $tmp =~ s/\..*//;
418 53         118 $variables->{$tmp} = 1;
419            
420 53         114 $variable =~ s/\'/\\\'/g;
421 53         112 $class->add_code('{');
422 53         127 $class->indent_increment();
423 53         191 $class->add_code ("my \$value = " . $class->comp_expr_encoded($variable) . ";");
424 53         134 $class->add_code ("if (defined(\$value)) {");
425             # $class->add_code ("if (defined(\$value) and length(\$value)) {");
426 53         167 $class->indent_increment();
427 53         125 $class->add_code ($class->_add_res (qq {"$attribute=\\"\$value\\""}) );
428 53         133 $class->indent_decrement();
429 53         100 $class->add_code ("}");
430 53         107 $class->indent_decrement();
431 53         85 $class->add_code ("}");
432             }
433              
434              
435             # $class->_else;
436             # --------------
437             # process a statement
438             sub _else
439             {
440 0     0   0 my $class = shift;
441 0         0 $class->indent_decrement();
442 0         0 $class->add_code("}");
443 0         0 $class->add_code("else {");
444 0         0 $class->indent_increment();
445             }
446              
447              
448             # $class->_for;
449             # -------------
450             # process a statement
451             sub _for
452             {
453 32     32   48 my $class = shift;
454             my $variable = $token_hash{name} or
455 32 50       102 confess "Cannot parse $token : 'name' attribute is not defined";
456            
457 32 50 33     144 (defined $variable and $variable) or
458             confess "Cannot parse $token : 'name' attribute is not defined";
459            
460 32         79 $variable =~ s/^\s+//;
461 32         40 my $as;
462 32         135 ($as, $variable) = split /\s+/, $variable, 2;
463            
464 32 50 33     272 (defined $as and defined $variable) or
465             confess "Cannot parse $token : loop name not specified";
466            
467             # set the variable in the $variables hash
468 32         52 my $tmp = $variable;
469 32         70 $tmp =~ s/\..*//;
470 32         100 $variables->{$tmp} = 1;
471            
472 32         81 my $idt = $class->indent();
473 32         67 $variable =~ s/\'/\\\'/g;
474 32 100       109 unless (defined $my_array->{$idt})
475             {
476 29         84 $class->add_code("my \$array = ".$class->comp_expr($variable).";");
477 29         129 $class->add_code(
478             qq{die 'tried to repeat but $variable gave no array reference'}
479             . qq{ unless defined \$array and ref \$array eq 'ARRAY';}
480             );
481 29         71 $class->add_code("my \@array = \@\$array;");
482 29         57 $my_array->{$idt} = 1;
483             }
484             else
485             {
486             #$class->add_code("\@array = \@{".$class->comp_expr($variable)."};");
487 3         8 $class->add_code("\$array = ".$class->comp_expr($variable).";");
488 3         16 $class->add_code(
489             qq{die 'tried to repeat but $variable gave no array reference'}
490             . qq{ unless defined \$array and ref \$array eq 'ARRAY';}
491             );
492 3         6 $class->add_code("\@array = \@\$array;");
493             }
494              
495            
496 32         84 $class->add_code ("for (my \$i=0; \$i < \@array; \$i++) {");
497 32         72 $class->indent_increment();
498 32         86 $class->add_code ("my \$hash = \$hash->new();");
499            
500             # compute various might-be-useful variables
501 32         58 $class->add_code ("my \$number = \$i + 1;");
502 32         71 $class->add_code ("my \$odd = \$number % 2;");
503 32         63 $class->add_code ("my \$even = \$i % 2;");
504 32         75 $class->add_code ("my \$start = (\$i == 0);");
505 32         71 $class->add_code ("my \$end = (\$i == \$#array);");
506 32         70 $class->add_code ("my \$inner = (\$i and \$i < \@array);");
507            
508             # backwards compatibility
509 32         89 $class->add_code ("\$hash->{__count__} = \$number;");
510 32         70 $class->add_code ("\$hash->{__is_first__} = \$start;");
511 32         65 $class->add_code ("\$hash->{__is_last__} = \$end;");
512 32         65 $class->add_code ("\$hash->{__is_inner__} = \$inner;");
513 32         73 $class->add_code ("\$hash->{__even__} = \$even;");
514 32         60 $class->add_code ("\$hash->{__odd__} = \$odd;");
515            
516             # new repeat style object
517 32         67 $class->add_code ("\$hash->{repeat} = {");
518 32         67 $class->indent_increment();
519 32         65 $class->add_code ("index => \$i,");
520 32         67 $class->add_code ("number => \$number,");
521 32         60 $class->add_code ("even => \$even,");
522 32         66 $class->add_code ("odd => \$odd,");
523 32         59 $class->add_code ("start => \$start,");
524 32         72 $class->add_code ("end => \$end,");
525 32         55 $class->add_code ("inner => \$inner,");
526 32         62 $class->add_code ("};");
527 32         76 $class->indent_decrement();
528            
529 32         117 $class->add_code ("\$hash->{'$as'} = \$array[\$i];");
530             }
531              
532              
533             # $class->_tokenize ($data_ref);
534             # ------------------------------
535             # Returns the data to process as a list of tokens:
536             # ( 'some text', '<% a_tag %>', 'some more text', '<% end-a_tag %>' etc.
537             sub _tokenize
538             {
539 2106     2106   2693 my $self = shift;
540 2106         2382 my $data_ref = shift;
541            
542 2106         4952 my @tags = $$data_ref =~ /(<\?.*?\?>)/gs;
543 2106         6780 my @split = split /(?:<\?.*?\?>)/s, $$data_ref;
544            
545 2106         3113 my $tokens = [];
546 2106         3850 while (@split)
547             {
548 2633         2851 push @{$tokens}, shift (@split);
  2633         4228  
549 2633 100       5637 push @{$tokens}, shift (@tags) if (@tags);
  545         976  
550             }
551 2106         2370 push @{$tokens}, (@tags);
  2106         2466  
552 2106         3844 return $tokens;
553             }
554              
555              
556             sub _decode_backslash_semicolon
557             {
558 500     500   728 my $class = shift;
559 500         566 my $data = shift;
560            
561 500         1448 my $decode = new Petal::CodeGenerator::Decode ('xml');
562 500         5561 return $decode->process ($data);
563             }
564              
565              
566             1;