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   493 use strict;
  73         124  
  73         2267  
3 73     73   338 use warnings;
  73         121  
  73         2196  
4 73     73   355 use base qw /MKDoc::XML::Decode/;
  73         141  
  73         17078  
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   1032 (@_ == 2) or warn "MKDoc::XML::Encode::process() should be called with two arguments";
16              
17 500         640 my $self = shift;
18 500         1050 my $data = join '', @_;
19 500         1052 $data =~ s/&(#?[0-9A-Za-z]+)\\;/$self->entity_to_char ($1)/eg;
  72         1151  
20              
21 500         2259 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   655 use MKDoc::XML::Decode;
  73         178  
  73         1884  
35 73     73   384 use strict;
  73         167  
  73         2004  
36 73     73   367 use warnings;
  73         151  
  73         2437  
37 73     73   440 use Carp;
  73         132  
  73         7245  
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   541 use vars qw /$petal_object $tokens $variables @code $indentLevel $token_name %token_hash $token $my_array/;
  73         113  
  73         244443  
41              
42              
43             sub indent_increment
44             {
45 797     797 0 1010 my $class = shift;
46 797         999 $Petal::CodeGenerator::indentLevel++;
47             }
48              
49              
50             sub indent_decrement
51             {
52 795     795 0 936 my $class = shift;
53 795         929 $Petal::CodeGenerator::indentLevel--;
54             }
55              
56              
57             sub indent
58             {
59 4926     4926 0 4868 my $class = shift;
60 4926         11667 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   632 return '$res = ""';
69             }
70              
71              
72             sub _add_res
73             {
74 1078     1078   1364 my $class = shift;
75 1078         1170 my $thing = shift;
76 1078         2782 return qq{\$res .= $thing};
77             }
78              
79              
80             sub _final_res
81             {
82 196     196   639 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 5272 my $class = shift;
95 4795         6501 push(@code, " " x $class->indent() . shift);
96             }
97              
98              
99             sub comp_expr
100             {
101 91     91 0 138 my $self = shift;
102 91         114 my $expr = shift;
103 91         467 return "\$hash->get ('$expr')";
104             }
105              
106              
107             sub comp_expr_encoded
108             {
109 257     257 0 325 my $self = shift;
110 257         291 my $expr = shift;
111 257         1085 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 344 my $class = shift;
121 198         536 $class->add_code("\$VAR1 = sub {");
122 198         570 $class->indent_increment();
123 198         424 $class->add_code("my \$hash = shift;");
124 198         454 $class->add_code("my ".$class->_init_res.";");
125 198 50       714 $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 286 my $class = shift;
135 196         397 $class->add_code("return ". $class->_final_res() .";");
136 196         511 $class->indent_decrement();
137 196         367 $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 340 my $class = shift;
148 198         299 my $data_ref = shift;
149              
150 198   50     819 local $petal_object = shift || die "$class::" . "process: \$petal_object was not defined";
151              
152 198         581 local $tokens = $class->_tokenize ($data_ref);
153 198         378 local $variables = {};
154 198         428 local @code = ();
155 198         294 local $Petal::CodeGenerator::indentLevel = 0;
156 198         308 local $token_name = undef;
157 198         322 local %token_hash = ();
158 198         261 local $token = undef;
159 198         305 local $my_array = {};
160              
161 198         554 $class->code_header();
162              
163 198         275 foreach $token (@{$tokens})
  198         502  
164             {
165 1270 100       9919 if ($token =~ /$PI_RE/s)
166             {
167 534         5250 ($token_name) = $token =~ /$PI_RE/s;
168 534         2990 my @atts1 = $token =~ /(\S+)\=\"(.*?)\"/gos;
169 534         986 my @atts2 = $token =~ /(\S+)\=\'(.*?)\'/gos;
170 534         1533 %token_hash = (@atts1, @atts2);
171 534         1309 foreach my $key (%token_hash)
172             {
173             $token_hash{$key} = $class->_decode_backslash_semicolon ($token_hash{$key})
174 994 100       2940 if (defined $token_hash{$key});
175             }
176              
177             CASE:
178 534         800 for ($token_name)
179             {
180 534 100       1093 /^attr$/ and do { $class->_attr; last CASE };
  53         133  
  53         156  
181 481 100       905 /^include$/ and do { $class->_include; last CASE };
  71         172  
  69         211  
182 410 100       1020 /^var$/ and do { $class->_var; last CASE };
  204         611  
  204         508  
183 206 100       519 /^if$/ and do { $class->_if; last CASE };
  59         163  
  59         163  
184 147 50       267 /^condition$/ and do { $class->_if; last CASE };
  0         0  
  0         0  
185 147 50       269 /^else$/ and do { $class->_else; last CASE };
  0         0  
  0         0  
186 147 100       279 /^repeat$/ and do { $class->_for; last CASE };
  2         7  
  2         8  
187 145 50       249 /^loop$/ and do { $class->_for; last CASE };
  0         0  
  0         0  
188 145 50       257 /^foreach$/ and do { $class->_for; last CASE };
  0         0  
  0         0  
189 145 100       339 /^for$/ and do { $class->_for; last CASE };
  30         101  
  30         103  
190 115 100       248 /^eval$/ and do { $class->_eval; last CASE };
  6         17  
  6         14  
191 109 100       246 /^endeval$/ and do { $class->_endeval; last CASE };
  6         18  
  6         35  
192 103 100       228 /^defslot$/ and do { $class->_defslot; last CASE };
  4         15  
  4         32  
193              
194             /^end$/ and do
195 99 50       324 {
196 99         231 my $idt = $class->indent();
197 99         187 delete $my_array->{$idt};
198 99         351 $class->indent_decrement();
199 99         194 $class->add_code("};");
200 99         202 last CASE;
201             };
202             }
203             }
204             else
205             {
206 736         2205 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         2118 $class->add_code($class->_add_res( '"' . $string . '";'));
213             }
214             }
215              
216 196         539 $class->code_footer();
217 196         2854 return join "\n", @code;
218             }
219              
220              
221             # $class->_include;
222             # -----------------
223             # process a file
224             sub _include
225             {
226 71     71   95 my $class = shift;
227 71         133 my $file = $token_hash{file};
228 71         240 my $path = $petal_object->_include_compute_path ($file);
229 69         183 my $lang = $petal_object->language();
230 69         147 $class->add_code ($class->_add_res ("do {"));
231 69         155 $class->indent_increment();
232              
233 69         181 my $included_from = $petal_object->_file();
234 69         128 $included_from =~ s/\#.*$//;
235              
236 69         149 $class->add_code ("do {");
237 69         148 $class->indent_increment();
238              
239 69         139 $class->add_code ("my \$new_hash = \$hash->new();");
240 69         227 $class->add_code ("\$new_hash->{__included_from__} = '$included_from';");
241              
242 69 100 66     428 (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       185 $class->add_code ("if (\$@) { confess(\$@); }") if $Petal::ERROR_ON_INCLUDE_ERROR;
247              
248 69         143 $class->add_code ("\$res = \"\" if (defined \$\@ and \$\@);");
249 69         153 $class->add_code ("\$res;");
250 69         145 $class->indent_decrement();
251 69         127 $class->add_code ("} || '';");
252              
253 69         142 $class->indent_decrement();
254 69         124 $class->add_code ("};");
255             }
256              
257              
258             # $class->_defslot;
259             # ---------------------
260             # process a statement
261             sub _defslot
262             {
263 4     4   6 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     17 (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         13 my $tmp = $variable;
272 4         21 $tmp =~ s/\..*//;
273 4         12 $variables->{$tmp} = 1;
274              
275 4         17 $variable =~ s/\'/\\\'/g;
276              
277 4         13 $class->add_code ("do {");
278 4         10 $class->indent_increment();
279              
280 4         8 $class->add_code ("my \$tmp = undef;");
281 4         9 $class->add_code ("\$hash->{__included_from__} && do {");
282 4         10 $class->indent_increment();
283              
284 4         15 $class->add_code ("my \$path = \$hash->{__included_from__} . '#$variable';");
285 4         11 $class->add_code ("my \$new_hash = \$hash->new();");
286 4         9 $class->add_code ("delete \$new_hash->{__included_from__};");
287              
288 4         16 my $lang = $petal_object->language();
289 4 50 33     17 (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       11 $class->add_code ("if (\$@) { confess(\$@); }") if $Petal::ERROR_ON_INCLUDE_ERROR;
294              
295 4         10 $class->indent_decrement();
296 4         9 $class->add_code ("};");
297              
298 4         20 $class->add_code ("if (\$tmp) {");
299 4         10 $class->indent_increment();
300              
301 4         7 $class->add_code ( $class->_add_res ("\$tmp") );
302 4         7 $class->indent_decrement();
303              
304 4         11 $class->add_code ( "} else {" );
305 4         19 $class->indent_increment();
306             }
307              
308              
309             # $class->_var;
310             # -------------
311             # process a statement
312             sub _var
313             {
314 204     204   265 my $class = shift;
315             my $variable = $token_hash{name} or
316 204 50       493 confess "Cannot parse $token : 'name' attribute is not defined";
317              
318 204 50 33     761 (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         481 my $tmp = $variable;
323 204         432 $tmp =~ s/\..*//;
324 204         523 $variables->{$tmp} = 1;
325              
326 204         388 $variable =~ s/\'/\\\'/g;
327 204         416 $class->add_code ( $class->_add_res (('do {')) );
328 204         477 $class->indent_increment();
329 204         410 $class->add_code ('my $res = ' . $class->comp_expr_encoded ($variable) . ';');
330 204         471 $class->add_code ('(defined $res) ? $res : "";');
331 204         435 $class->indent_decrement();
332 204         357 $class->add_code ('};');
333             }
334              
335              
336             # $class->_if;
337             # ------------
338             # process a statement
339             sub _if
340             {
341 59     59   82 my $class = shift;
342             my $variable = $token_hash{name} or
343 59 50       135 confess "Cannot parse $token : 'name' attribute is not defined";
344              
345 59 50 33     265 (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         92 my $tmp = $variable;
350 59         117 $tmp =~ s/\..*//;
351 59         144 $variables->{$tmp} = 1;
352              
353 59         105 $variable =~ s/\'/\\\'/g;
354 59         143 $class->add_code("if (".$class->comp_expr($variable).") {");
355 59         154 $class->indent_increment();
356             }
357              
358              
359             # $class->_eval;
360             # -------------------
361             # process a statement
362             sub _eval
363             {
364 6     6   9 my $class = shift;
365 6         10 $class->add_code($class->_add_res("eval {"));
366 6         16 $class->indent_increment();
367 6         10 $class->add_code("my " . $class->_init_res() .";");
368 6         11 $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   10 my $class = shift;
379 6 50       27 my $variable = $token_hash{'errormsg'} or
380             confess "Cannot parse $token : 'errormsg' attribute is not defined";
381              
382 6         14 $class->add_code("return " . $class->_get_res() . ";");
383 6         14 $class->indent_decrement();
384 6         12 $class->add_code("} || '';");
385              
386 6         11 $class->add_code("if (defined \$\@ and \$\@) {");
387 6         12 $class->indent_increment();
388              
389 6         12 $variable =~ s/\&/&/g;
390 6         15 $variable =~ s/\
391 6         11 $variable =~ s/\>/>/g;
392 6         9 $variable =~ s/\"/"/g;
393 6         12 $variable = quotemeta ($variable);
394 6         17 $class->add_code($class->_add_res("\"$variable\";"));
395 6         14 $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   76 my $class = shift;
406             my $attribute = $token_hash{name} or
407 53 50       115 confess "Cannot parse $token : 'name' attribute is not defined";
408              
409             my $variable = $token_hash{value} or
410 53 50       114 confess "Cannot parse $token : 'value' attribute is not defined";
411              
412 53 50 33     360 (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         74 my $tmp = $variable;
417 53         126 $tmp =~ s/\..*//;
418 53         118 $variables->{$tmp} = 1;
419              
420 53         177 $variable =~ s/\'/\\\'/g;
421 53         148 $class->add_code('{');
422 53         167 $class->indent_increment();
423 53         99 $class->add_code ("my \$value = " . $class->comp_expr_encoded($variable) . ";");
424 53         145 $class->add_code ("if (defined(\$value)) {");
425             # $class->add_code ("if (defined(\$value) and length(\$value)) {");
426 53         105 $class->indent_increment();
427 53         135 $class->add_code ($class->_add_res (qq {"$attribute=\\"\$value\\""}) );
428 53         128 $class->indent_decrement();
429 53         101 $class->add_code ("}");
430 53         104 $class->indent_decrement();
431 53         93 $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   49 my $class = shift;
454             my $variable = $token_hash{name} or
455 32 50       92 confess "Cannot parse $token : 'name' attribute is not defined";
456              
457 32 50 33     178 (defined $variable and $variable) or
458             confess "Cannot parse $token : 'name' attribute is not defined";
459              
460 32         243 $variable =~ s/^\s+//;
461 32         65 my $as;
462 32         130 ($as, $variable) = split /\s+/, $variable, 2;
463              
464 32 50 33     179 (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         61 my $tmp = $variable;
469 32         133 $tmp =~ s/\..*//;
470 32         98 $variables->{$tmp} = 1;
471              
472 32         87 my $idt = $class->indent();
473 32         67 $variable =~ s/\'/\\\'/g;
474 32 100       116 unless (defined $my_array->{$idt})
475             {
476 29         86 $class->add_code("my \$array = ".$class->comp_expr($variable).";");
477 29         155 $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         87 $class->add_code("my \@array = \@\$array;");
482 29         77 $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         13 $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         8 $class->add_code("\@array = \@\$array;");
493             }
494              
495              
496 32         94 $class->add_code ("for (my \$i=0; \$i < \@array; \$i++) {");
497 32         85 $class->indent_increment();
498 32         62 $class->add_code ("my \$hash = \$hash->new();");
499              
500             # compute various might-be-useful variables
501 32         85 $class->add_code ("my \$number = \$i + 1;");
502 32         75 $class->add_code ("my \$odd = \$number % 2;");
503 32         67 $class->add_code ("my \$even = \$i % 2;");
504 32         78 $class->add_code ("my \$start = (\$i == 0);");
505 32         68 $class->add_code ("my \$end = (\$i == \$#array);");
506 32         120 $class->add_code ("my \$inner = (\$i and \$i < \@array);");
507              
508             # backwards compatibility
509 32         70 $class->add_code ("\$hash->{__count__} = \$number;");
510 32         80 $class->add_code ("\$hash->{__is_first__} = \$start;");
511 32         82 $class->add_code ("\$hash->{__is_last__} = \$end;");
512 32         78 $class->add_code ("\$hash->{__is_inner__} = \$inner;");
513 32         93 $class->add_code ("\$hash->{__even__} = \$even;");
514 32         69 $class->add_code ("\$hash->{__odd__} = \$odd;");
515              
516             # new repeat style object
517 32         74 $class->add_code ("\$hash->{repeat} = {");
518 32         73 $class->indent_increment();
519 32         65 $class->add_code ("index => \$i,");
520 32         137 $class->add_code ("number => \$number,");
521 32         75 $class->add_code ("even => \$even,");
522 32         1032 $class->add_code ("odd => \$odd,");
523 32         75 $class->add_code ("start => \$start,");
524 32         68 $class->add_code ("end => \$end,");
525 32         69 $class->add_code ("inner => \$inner,");
526 32         69 $class->add_code ("};");
527 32         93 $class->indent_decrement();
528              
529 32         103 $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   2494 my $self = shift;
540 2106         2223 my $data_ref = shift;
541              
542 2106         4973 my @tags = $$data_ref =~ /(<\?.*?\?>)/gs;
543 2106         6321 my @split = split /(?:<\?.*?\?>)/s, $$data_ref;
544              
545 2106         3098 my $tokens = [];
546 2106         4059 while (@split)
547             {
548 2633         2895 push @{$tokens}, shift (@split);
  2633         4264  
549 2633 100       5708 push @{$tokens}, shift (@tags) if (@tags);
  545         1056  
550             }
551 2106         2305 push @{$tokens}, (@tags);
  2106         2393  
552 2106         3805 return $tokens;
553             }
554              
555              
556             sub _decode_backslash_semicolon
557             {
558 500     500   699 my $class = shift;
559 500         586 my $data = shift;
560              
561 500         1458 my $decode = new Petal::CodeGenerator::Decode ('xml');
562 500         5713 return $decode->process ($data);
563             }
564              
565              
566             1;