File Coverage

blib/lib/Pinwheel/View/ERB.pm
Criterion Covered Total %
statement 468 468 100.0
branch 212 212 100.0
condition 56 56 100.0
subroutine 156 156 100.0
pod 1 21 4.7
total 893 913 97.8


line stmt bran cond sub pod time code
1             package Pinwheel::View::ERB;
2              
3 10     10   74203 use strict;
  10         22  
  10         575  
4 10     10   60 use warnings;
  10         25  
  10         444  
5              
6 10     10   60 use Carp;
  10         21  
  10         1129  
7 10     10   8679 use Exporter;
  10         24  
  10         558  
8              
9 10     10   2980 use Pinwheel::View::String;
  10         28  
  10         478  
10 10     10   25360 use Pinwheel::View::Wrap;
  10         29  
  10         2675  
11              
12             our @ISA = qw(Exporter);
13             our @EXPORT_OK = qw(parse_template);
14              
15              
16             our $OPEN_TAG_RE = qr{
17             (.*?)
18             <%(=?)
19             }x;
20             our $CLOSE_TAG_RE = qr{
21             \s*
22             ((?:
23             (?:(['"])(?:\\.|[^\2])*?\2)
24             | (?:\#.*?(?=-?%>))
25             | [^'"]
26             )*?)
27             \s*
28             (-?)%>
29             }x;
30             our $TEXT = 1;
31             our $CODE = 2;
32              
33             our $slow_attrs;
34              
35             BEGIN {
36 10     10   35 my ($pkg, %attrs, $s);
37              
38 10         37 $pkg = \%Pinwheel::View::Wrap::;
39 10         318 foreach (keys %$pkg) {
40 50         84 map { $attrs{$_} = 1 } @{$pkg->{$_}{'WRAP_METHODS'}};
  114         222  
  50         481  
41             }
42              
43 10         118 $s = '(?:' . join('|', keys %attrs) . ')';
44 10         55631 $slow_attrs = qr/^${s}$/;
45             }
46              
47              
48             sub parse_template
49             {
50 111     111 1 46475 my ($s, $name) = @_;
51 111         205 my ($writer, $lineno, $line, $addnl);
52              
53 111 100       376 $name = 'anonymous' if (!$name);
54 111         662 $writer = code_writer($name);
55 111         331 $addnl = 0;
56 111         1135 foreach $line (split(/\r?\n/, $s)) {
57 252         637 $lineno++;
58 252         618 $addnl = parse_template_line($line, $lineno, $writer, $addnl);
59             }
60 99 100 100     2889 if ($addnl && $s =~ /\n\s*$/) {
61 53         389 $writer->{echo_raw}('"\n"');
62             }
63 99         282 return compile($writer->{eof}(), $name);
64             }
65              
66             sub parse_template_line
67             {
68 252     252 0 564 my ($line, $lineno, $writer, $addnl) = @_;
69 252         583 my ($linetype, @parts, $echo);
70 2         18 my ($text, $type, $data);
71              
72             # Collect the parts (text and code) and classify the line:
73             # bit 0: contains non-whitespace text
74             # bit 1: contains code which outputs something, ie <%= ... %>
75             # bit 2: contains code which does not output anything, ie <% ... %>
76 252         422 $linetype = 0;
77 252         3643 while ($line =~ /\G$OPEN_TAG_RE/gc) {
78 124         419 $echo = ($2 eq '=');
79 124 100       393 if ($1 ne '') {
80 55         527 push @parts, [$TEXT, $1];
81 55 100       455 $linetype |= 1 if ($1 !~ /^\s*$/);
82             }
83 124 100       3384 $line =~ /\G$CLOSE_TAG_RE/gc ||
84             $writer->{error}("Missing %>", $lineno);
85 123 100       1233 if ($1 !~ /^\s*$/) {
86 121         714 push @parts, [$CODE, $1, $echo];
87 121 100       988 $linetype |= $echo ? 2 : 4;
88             }
89             }
90 251 100       1290 if ($line =~ /\G(.*[^\s])\s*$/) {
91 129         442 push @parts, [$TEXT, $1];
92 129         225 $linetype |= 1;
93             }
94              
95             # If the line contains code, supply the line number for error messages
96             # (both compile time and runtime)
97 251 100       1164 $writer->{line}($lineno) if ($linetype & 6);
98              
99             # Write this line of the template
100 251 100       1082 $text = $addnl ? "\n" : '';
101 251         667 push @parts, [-1, undef];
102 251         509 do {
103 539         657 ($type, $data, $echo) = @{shift(@parts)};
  539         1993  
104 539 100 100     15779 if ($type != $TEXT && $text ne '') {
105 226         761 $writer->{echo_raw}($writer->{string}($text));
106 226         392 $text = '';
107             }
108 539 100 100     2491 $text .= $data if ($type == $TEXT && $linetype != 4);
109 539 100       2469 parse_code(lexer($data), $writer, $echo) if ($type == $CODE);
110             } while ($type != -1);
111              
112 240         1189 return ($linetype != 4);
113             }
114              
115              
116             sub code_writer
117             {
118 380     380 0 44111 my $name = shift;
119 380         615 my ($strings, %stridx, %functions);
120 2         5 my ($code, @blocks, $lineno);
121              
122 380         10008 $strings = [];
123 380         838 $code = '';
124 380         574 $lineno = '?';
125              
126             return {
127             open => sub {
128 84     84   323 push @blocks, [$_[0], $_[1], $lineno];
129             },
130             need => sub {
131 31 100 100 31   229 _error("Unexpected '$_[1]'", $lineno, $name)
132             if (scalar(@blocks) < 1 || $blocks[-1][0] ne $_[0]);
133             },
134             close => sub {
135 57     57   340 my $block = pop(@blocks);
136 57 100       144 _error("Unexpected 'end'", $lineno, $name) unless $block;
137 55         163 return $block->[1];
138             },
139             eof => sub {
140 215 100   215   1775 _error("Unclosed '$blocks[-1][0]'", $blocks[-1][2], $name)
141             if (scalar(@blocks) > 0);
142 212         652 my @fnlist = keys(%functions);
143 212         962 return ($code, $strings, \@fnlist);
144             },
145             string => sub {
146 312 100   312   1388 if (!exists($stridx{$_[0]})) {
147 285         2295 $stridx{$_[0]} = push(@$strings, $_[0]) - 1;
148             }
149 312         2591 return '$strings->[' . $stridx{$_[0]} . ']';
150             },
151             error => sub {
152 55 100   55   549 _error($_[0], $_[1] ? $_[1] : $lineno, $name);
153             },
154             function => sub {
155 95     95   280 $functions{$_[0]} = 1;
156             },
157             echo => sub {
158 202     202   621 $code .= "\$r .= $_[0];\n";
159             },
160             echo_raw => sub {
161 277     277   1133 $code .= "\$r->concat_raw($_[0]);\n";
162             },
163             do => sub {
164 256     256   885 $code .= "$_[0];\n";
165             },
166             line => sub {
167 115     115   214 $lineno = $_[0];
168 115         623 $code .= "\$lineno = $_[0];\n";
169             }
170 380         23041 };
171             }
172              
173              
174             # ==============================================================================
175              
176              
177             sub parse_code
178             {
179 515     516 0 1508 my ($lexer, $writer, $echo) = @_;
180 515         769 my ($left, $conditional, $type, $next_type);
181              
182 515         1453 $type = $lexer->(1)[0];
183 515         1153 $next_type = $lexer->(2)[0];
184 515 100 100     4849 if ($type eq '') {
    100 100        
    100 100        
    100          
185 12 100       139 $writer->{error}('Invalid syntax') if ($lexer->(1)[1] ne '');
186 8         22 $left = '';
187             } elsif ($type eq 'STMT') {
188 157         340 $left = parse_statement($lexer, $writer);
189 142         643 $writer->{do}($left);
190             } elsif (($type eq 'ID' || $type eq '@ID') && $next_type eq '=') {
191 17         60 $left = parse_assign($lexer, $writer);
192 16         48 $writer->{do}($left);
193             } elsif ($type eq 'ID' && $next_type eq ',') {
194 8         140 $left = parse_unpack($lexer, $writer);
195 6         28 $writer->{do}($left);
196             } else {
197 325         825 $left = parse_expr($lexer, $writer);
198 294   100     887 $conditional = parse_conditional($lexer, $writer) || '';
199 294 100       1252 $writer->{$echo ? 'echo' : 'do'}($left . $conditional);
200             }
201 462 100       976 $writer->{error}('Invalid syntax') if ($lexer->(1)[0] ne '');
202 457         9492 return $left;
203             }
204              
205              
206             sub parse_statement
207             {
208 157     158 0 236 my ($lexer, $writer) = @_;
209 157         184 my ($left, $token, $stmt, $expr);
210              
211 157         511 $token = $lexer->(1);
212 157         430 $stmt = $token->[1];
213 157 100       643 if ($stmt eq 'for') {
    100          
    100          
    100          
214 20         149 $left = parse_for($lexer, $writer);
215             } elsif ($stmt eq 'if') {
216 54         168 $writer->{open}('if', '}');
217 54         128 $lexer->(); # Absorb 'if'
218 54         270 $expr = parse_test_expr($lexer, $writer);
219 54         166 $left = "if ($expr) {";
220             } elsif ($stmt eq 'elsif') {
221 7         27 $writer->{need}('if', 'elsif');
222 5         119 $lexer->(); # Absorb 'elsif'
223 5         21 $expr = parse_test_expr($lexer, $writer);
224 5         14 $left = "} elsif ($expr) {";
225             } elsif ($stmt eq 'else') {
226 24         174 $writer->{need}('if', 'else');
227 22         54 $lexer->(); # Absorb 'else'
228 22         39 $left = "} else {";
229             } else { # elsif ($stmt eq 'end') {
230 56         224 $lexer->(); # Absorb 'end'
231 56         191 $left = $writer->{close}();
232             }
233 142         430 return $left;
234             }
235              
236             sub parse_conditional
237             {
238 294     295 0 605 my ($lexer, $writer) = @_;
239 294         425 my ($expr, $token);
240              
241 294         581 $token = $lexer->(1);
242 294 100 100     2513 return if ($token->[0] ne 'STMT' || $token->[1] ne 'if');
243              
244 3         15 $lexer->(); # Absorb 'if'
245 3         8 $expr = parse_test_expr($lexer, $writer);
246 3         128 return " if ($expr)";
247             }
248              
249             sub parse_for
250             {
251 20     21 0 40 my ($lexer, $writer) = @_;
252 20         27 my ($left, $token, @vars);
253              
254 20         136 $lexer->(); # Absorb 'for'
255 20         72 $writer->{open}('for', '}');
256 20         31 do {
257 24         256 $token = $lexer->();
258 24 100       86 $writer->{error}('Expected variable') unless ($token->[0] eq 'ID');
259 20         44 push @vars, $token->[1];
260 20         138 $token = $lexer->();
261             } while ($token->[0] eq ',');
262 16 100       61 $writer->{error}("Expected 'in'") unless ($token->[0] eq 'in');
263              
264 12         34 $left = 'foreach (@{' . parse_expr($lexer, $writer) . '}) ';
265 11 100       142 if (scalar(@vars) == 1) {
266 9         241 $left .= '{ $locals->{\'' . $vars[0] . '\'} = $_;';
267             } else {
268 3         16 $left .= '{ @$locals{qw(' . join(' ', @vars) . ')} = @$_;';
269             }
270              
271 11         227 return $left;
272             }
273              
274             sub parse_test_expr
275             {
276 77     78 0 155 my ($lexer, $writer) = @_;
277 77         132 my ($left, $right, $token);
278              
279 77         309 $left = parse_test_cmp($lexer, $writer);
280 76         198 while ($token = $lexer->(1)) {
281 86 100 100     465 last if ($token->[0] ne 'or' && $token->[0] ne 'and');
282 11         152 $lexer->(); # Absorb 'or'/'and'
283 11         31 $right = parse_test_cmp($lexer, $writer);
284 11 100       33 if ($token->[0] eq 'or') {
285 7         111 $left = "($left || $right)";
286             } else {
287 5         26 $left = "($left && $right)";
288             }
289             }
290 76         197 return $left;
291             }
292              
293             sub parse_test_cmp
294             {
295 87     88 0 211 my ($lexer, $writer) = @_;
296 87         119 my ($left, $right, $token, $cmp);
297              
298 87         236 $left = parse_expr($lexer, $writer);
299 86         303 while ($token = $lexer->(1)) {
300 107         208 $cmp = $token->[0];
301 107 100       385 last unless ($cmp =~ /^==|!=|<=|>=|<|>$/);
302 22         174 $lexer->(); # Absorb comparison operator
303 22         59 $right = parse_expr($lexer, $writer);
304 22 100       85 if ($cmp eq '==') {
    100          
305 8         251 $left = "($left eq $right)";
306             } elsif ($cmp eq '!=') {
307 5         25 $left = "($left ne $right)";
308             } else {
309 11         40 $left = "($left $cmp $right)";
310             }
311             }
312 86         468 return $left;
313             }
314              
315              
316             sub parse_assign
317             {
318 17     18 0 35 my ($lexer, $writer) = @_;
319 17         22 my ($right, $ns, $token);
320              
321 17         159 $token = $lexer->();
322 17 100       61 $ns = ($token->[0] eq 'ID') ? "\$locals->" : "\$globals->";
323 17         32 $lexer->(); # Absorb '='
324 17         244 $right = parse_expr($lexer, $writer);
325 16         83 return "$ns\{'$token->[1]'\} = $right";
326             }
327              
328             sub parse_unpack
329             {
330 8     9 0 24 my ($lexer, $writer) = @_;
331 8         100 my ($left, $right, $token, @vars);
332              
333 8         19 do {
334 17         30 $token = $lexer->();
335 17 100       151 $writer->{error}('Expected variable') unless ($token->[0] eq 'ID');
336 16         58 push @vars, $token->[1];
337 16         31 $token = $lexer->();
338             } while ($token->[0] eq ',');
339 7 100       189 $writer->{error}("Expected '='") unless ($token->[0] eq '=');
340              
341 6         29 $left = '@$locals{qw(' . join(' ', @vars) . ')}';
342 6         20 $right = parse_expr($lexer, $writer);
343 6         150 return $left . ' = @{' . $right . '}';
344             }
345              
346              
347             sub parse_expr
348             {
349 565     566 0 837 my ($lexer, $writer) = @_;
350 565         708 my ($left, $right, $token);
351              
352 565         1415 $left = parse_product($lexer, $writer);
353 530         1092 while ($token = $lexer->(1)) {
354 585 100 100     3017 last if ($token->[0] ne '+' && $token->[0] ne '-');
355 59         256 $lexer->(); # Absorb '+' or '-'
356 59         127 $right = parse_product($lexer, $writer);
357 56 100       142 if ($token->[0] eq '+') {
358 43         291 $left = "_add($left, $right)";
359             } else {
360 14         56 $left = "($left - $right)";
361             }
362             }
363 527         1335 return $left;
364             }
365              
366             sub parse_product
367             {
368 623     623 0 1183 my ($lexer, $writer) = @_;
369 623         711 my ($left, $right, $token, $op);
370              
371 623         1269 $left = parse_neg($lexer, $writer);
372 585         1299 while ($token = $lexer->(1)) {
373 603         1228 $op = $token->[0];
374 603 100 100     4627 last if ($op ne '*' && $op ne '/' && $op ne '%');
      100        
375 19         222 $lexer->(); # Absorb '*', '/', or '%'
376 19         47 $right = parse_neg($lexer, $writer);
377 19         66 $left = "($left $op $right)";
378             }
379 585         1515 return $left;
380             }
381              
382             sub parse_neg
383             {
384 641     641 0 764 my ($lexer, $writer) = @_;
385 641         691 my ($left, $token);
386              
387 641         1295 $token = $lexer->(1);
388 641 100       2480 if ($token->[0] eq '') {
    100          
    100          
389 5         15 $writer->{error}('Missing or invalid expression');
390             } elsif ($token->[0] eq '!') {
391 20         153 my ($n, $fn);
392 20         37 do { $lexer->(); $n++; } while ($lexer->(1)[0] eq '!');
  25         48  
  25         307  
393 20 100       64 $left = ($n & 1) ? '!' : '!!';
394 20         52 $left .= "(" . parse_atom($lexer, $writer) . ')';
395             } elsif ($token->[0] eq '-') {
396 11         128 $lexer->(); # Absorb '-'
397 11         30 $left = '-' . parse_atom($lexer, $writer);
398             } else {
399 608         1189 $left = parse_atom($lexer, $writer);
400             }
401 603         1771 return $left;
402             }
403              
404             sub parse_atom
405             {
406 637     637 0 3167 my ($lexer, $writer) = @_;
407 637         977 my ($left, $token);
408              
409 637         1306 $token = $lexer->(1);
410 637 100 100     3765 if ($token->[0] eq 'NUM') {
    100 100        
    100          
    100          
    100          
    100          
    100          
    100          
411 293         454 $left = $lexer->()->[1];
412             } elsif ($token->[0] eq 'STR') {
413 79         303 $left = $writer->{string}($lexer->()->[1]);
414             } elsif ($token->[0] eq 'SYM') {
415 9         22 $left = $writer->{string}($lexer->()->[1]);
416             } elsif ($token->[0] eq '(') {
417 18         108 $lexer->(); # Absorb '('
418 18         167 $left = parse_test_expr($lexer, $writer);
419 17         41 $token = $lexer->();
420 17 100       53 $writer->{error}('Missing )') unless ($token->[0] eq ')');
421             } elsif ($token->[0] eq '{') {
422 12         130 $left = parse_hash($lexer, $writer);
423             } elsif ($token->[0] eq '[') {
424 9         25 $left = parse_array($lexer, $writer);
425             } elsif ($token->[0] eq 'ID' && $lexer->(2)[0] eq '(') {
426 94         270 $left = parse_call($lexer, $writer);
427             } elsif ($token->[0] eq 'ID' || $token->[0] eq '@ID') {
428 124         422 $left = parse_attr($lexer, $writer);
429             } else {
430 7         30 $writer->{error}('Missing or invalid expression');
431             }
432 603         1785 return $left;
433             }
434              
435             sub parse_array
436             {
437 9     9 0 109 my ($lexer, $writer) = @_;
438 9         16 my ($left, $token);
439              
440 9         11 $left = '[';
441 9         119 $token = $lexer->(); # Absorb '['
442 9         20 $token = $lexer->(1);
443 9         21 while ($token->[0] ne ']') {
444 13         129 $left .= parse_expr($lexer, $writer) . ', ';
445 12         28 $token = $lexer->(1);
446 12 100       30 last if ($token->[0] ne ',');
447 6         101 $token = $lexer->();
448             }
449 8         17 $token = $lexer->(); # Absorb ']'
450 8 100       18 $writer->{error}('Missing ]') if ($token->[0] ne ']');
451 7         122 return $left . ']';
452             }
453              
454             sub parse_hash
455             {
456 12     12 0 23 my ($lexer, $writer) = @_;
457 12         15 my ($left, $token);
458              
459 12         120 $left = '{';
460 12         40 $token = $lexer->(); # Absorb '{'
461 12         23 $token = $lexer->();
462 12         138 while ($token->[0] ne '}') {
463 12 100       37 $writer->{error}('Expected key') if ($token->[0] ne 'SYM');
464 11         26 $left .= "'$token->[1]' => ";
465 11         128 $token = $lexer->();
466 11 100       49 $writer->{error}("Expected '=>'") if ($token->[0] ne '=>');
467 10         26 $left .= parse_expr($lexer, $writer) . ', ';
468 10         136 $token = $lexer->();
469 10 100       39 last if ($token->[0] ne ',');
470 3         11 $token = $lexer->();
471             }
472 10 100       155 $writer->{error}('Missing }') if ($token->[0] ne '}');
473 9         39 return $left . '}';
474             }
475              
476             sub parse_call
477             {
478 94     94 0 151 my ($lexer, $writer) = @_;
479 94         260 my ($left, $fn, $token, @params);
480              
481 94         217 $fn = $lexer->()->[1];
482 94         278 $writer->{function}($fn);
483 94         710 $lexer->(); # Absorb '('
484              
485 94         213 $token = $lexer->(1);
486 94 100       249 if ($token->[0] ne ')') {
487 56         836 do {
488 69 100 100     145 if ($lexer->(1)[0] eq 'SYM' && $lexer->(2)[0] eq '=>') {
489 13         32 push @params, "'" . $lexer->()->[1] . "'";
490 13         661 $lexer->(); # Absorb '=>'
491             }
492 69         187 push @params, parse_expr($lexer, $writer);
493 66         213 $token = $lexer->();
494             } while ($token->[0] eq ',');
495             } else {
496 39         213 $lexer->(); # Absorb ')'
497             }
498 91 100       268 $writer->{error}('Missing )') if ($token->[0] ne ')');
499              
500 85 100       267 if ($lexer->(1)[0] eq 'do') {
501 13         129 $lexer->(); # Absorb 'do'
502 13 100       37 $writer->{error}('Invalid syntax') if ($lexer->(1)[0] ne '');
503 11         28 push @params, "sub { my \$r = \$r->clone([])";
504 11         235 $writer->{open}('do', '$r->to_string(); })');
505 11         54 $left = "\$fns->{'$fn'}->(" . join(', ', @params);
506             } else {
507 73         364 $left = "\$fns->{'$fn'}->(" . join(', ', @params) . ')';
508             }
509              
510 83         1032 return $left;
511             }
512              
513             sub parse_attr
514             {
515 124     124 0 194 my ($lexer, $writer) = @_;
516 124         152 my ($left, $token, @attribs, $ns, $fn, $s);
517              
518 124         418 $token = $lexer->();
519 124 100       366 $ns = ($token->[0] eq 'ID') ? "\$locals->" : "\$globals->";
520 124         330 $left = "$ns\{'$token->[1]'\}";
521 124         310 $fn = '_getattr';
522 124         232 $s = $lexer->(1)[0];
523 124   100     594 while ($s eq '.' || $s eq '[') {
524 65         237 $lexer->(); # Absorb '.'
525 65 100       148 if ($s eq '.') {
526 53         89 $token = $lexer->();
527 53 100 100     394 $writer->{error}('Missing attribute')
528             if ($token->[0] ne 'ID' && $token->[0] ne 'STMT');
529 46 100       374 $fn = '_getattr_slow' if ($token->[1] =~ $slow_attrs);
530 46         213 push @attribs, "'$token->[1]'";
531             } else {
532 13         149 push @attribs, parse_expr($lexer, $writer);
533 13         34 $token = $lexer->();
534 13 100       44 $writer->{error}("Expected ']'") if ($token->[0] ne ']');
535             }
536 57         452 $s = $lexer->(1)[0];
537             }
538 116 100       348 if (scalar(@attribs) > 0) {
539 43         169 $left = "$fn($left, " . join(', ', @attribs) . ')';
540             }
541              
542 116         620 return $left;
543             }
544              
545              
546             # ==============================================================================
547              
548              
549             sub _add
550             {
551 23 100   23   104 return $_[0]->add($_[1]) if ref($_[0]);
552 22 100       78 return $_[1]->radd($_[0]) if ref($_[1]);
553 21 100 100     483 return $_[0] + $_[1] if ($_[0] =~ /^-?\d+$/ && $_[1] =~ /^-?\d+$/);
554 6         116 return $_[0] . $_[1];
555             }
556              
557             sub _getattr
558             {
559 16     16   39 my $obj = shift;
560 16 100       374 $obj = ((ref($obj) eq 'HASH') ? $obj->{$_} : $obj->$_) foreach (@_);
561 14         113 return $obj;
562             }
563              
564             sub _getattr_slow
565             {
566 21     21   51 my $obj = shift;
567              
568 21         130 foreach (@_) {
569 30 100       241 $obj = ref($obj) ? (
    100          
    100          
570             ref($obj) eq 'ARRAY' ? $Pinwheel::View::Wrap::array->$_($obj) : (
571             ref($obj) eq 'HASH' ? $obj->{$_} : $obj->$_
572             )
573             ) : $Pinwheel::View::Wrap::scalar->$_($obj);
574             }
575              
576 19         154 return $obj;
577             }
578              
579             sub _error
580             {
581 69     69   241 my ($msg, $lineno, $name) = @_;
582 69         2135 die "$msg in '$name' at line $lineno\n";
583             }
584              
585              
586             sub compile
587             {
588 211     211 0 601 my ($code, $strings, $fns, $name) = @_;
589 211         408 my $checkfns;
590 211 100       628 $name = 'anonymous' if (!$name);
591 211         356 $checkfns = '';
592 211         966 foreach (@$fns) {
593 50         265 $checkfns .=
594             "die \"Unknown function '$_' in '$name'\"" .
595             " unless exists(\$fns->{'$_'});\n";
596             }
597             return eval <
598             sub {
599             my (\$locals, \$globals, \$fns) = \@_;
600             my (\$r, \$lineno);
601             \$r = Pinwheel::View::String->new('', \\&_escape);
602             \$lineno = 0;
603             $checkfns
604             eval {
605             local \$SIG{__WARN__} = sub {
606             chomp(my \$msg = shift);
607             die "\$msg at \$name line \$lineno";
608             };
609             no warnings qw(uninitialized);
610             $code
611             1
612             };
613             _error(\$@, \$lineno, \$name) if (\$@);
614             return \$r;
615             }
616             EOF
617 5     5   261 }
  5     5   19  
  5     5   822  
  5     5   248  
  5     5   24  
  5     5   514  
  5     1   369  
  5     1   23  
  5     1   684  
  5     1   254  
  5     1   21  
  5     1   789  
  5     1   251  
  5     1   29  
  5     1   856  
  5     1   268  
  5     1   25  
  5     1   448  
  211     1   107272  
  1     1   2  
  1     1   142  
  1     1   10  
  1     1   2  
  1     1   144  
  1     1   10  
  1     1   2  
  1     1   155  
  1     1   8  
  1     1   3  
  1     1   812  
  1     1   57  
  1     1   5  
  1     1   166  
  1     1   9  
  1     1   2  
  1     1   125  
  1     1   6  
  1     1   2  
  1     1   133  
  1     1   9  
  1     1   3  
  1     1   153  
  1     1   6  
  1     1   3  
  1     1   119  
  1     1   7  
  1     1   2  
  1     1   113  
  1     1   10  
  1     1   4  
  1     1   127  
  1     1   9  
  1     1   2  
  1     1   261  
  1     1   8  
  1     1   3  
  1     1   167  
  1     1   8  
  1     1   9  
  1     1   128  
  1     1   17  
  1     1   4  
  1     1   116  
  1     1   8  
  1     1   2  
  1     1   121  
  1     1   8  
  1     1   2  
  1     1   115  
  1     1   7  
  1     1   3  
  1     1   114  
  1     1   10  
  1     1   2  
  1     1   152  
  1     1   11  
  1     1   3  
  1     1   151  
  1     1   8  
  1     1   2  
  1     1   144  
  1     1   10  
  1     1   2  
  1     1   121  
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
        1      
618              
619             sub _escape
620             {
621 184     184   406 my ($s) = @_;
622 184 100       505 return unless defined($s);
623 181 100       1963 return $s unless ($s =~ /[&<>'"\x80-\xff]/);
624 7         100 $s =~ s/&/&/g;
625 7         26 $s =~ s/
626 7         20 $s =~ s/>/>/g;
627 7         131 $s =~ s/'/'/g;
628 7         19 $s =~ s/\"/"/g;
629 7         24 $s =~ s/([\xc0-\xef][\x80-\xbf]+)/_make_utf8_entity($1)/ge;
  3         120  
630 7         42 return $s;
631             }
632              
633             sub _make_utf8_entity
634             {
635 3     3   14 my ($i, @bytes) = split(//, shift());
636 3 100       407 $i = ord($i) & ((ord($i) < 0xe0) ? 0x1f : 0x0f);
637 3         20 $i = ($i << 6) + (ord($_) & 0x3f) foreach @bytes;
638 3         13 return "&#$i;";
639             }
640              
641              
642             # ==============================================================================
643              
644              
645             sub lexer
646             {
647 565     565 0 57111 my $s = shift;
648 565         856 my @buf;
649             my $lexer = sub {
650 2091     2091   2526 while (1) {
651 2590 100       9237 return ['STMT', $1] if $s =~ /\G(if|elsif|else|for|end)(?!\w)/gcx;
652 2425 100       25240 return [',', ''] if $s =~ /\G,/gc;
653 2385 100       5320 return ['=>', ''] if $s =~ /\G=>/gc;
654 2362 100       5218 return ['.', ''] if $s =~ /\G\./gc;
655 2307 100       7089 return [$1, ''] if $s =~ /\G(==|!=|<=|>=|[-=+*\/%<>!]|[{}])/gc;
656 2116 100       5071 return ['do', ''] if $s =~ /\Gdo(?!\w)/gc;
657 2102 100       4479 return ['in', ''] if $s =~ /\Gin(?!\w)/gc;
658 2089 100       5543 return ['or', ''] if $s =~ /\G(\|\||or(?!\w))/gc;
659 2081 100       4590 return ['and', ''] if $s =~ /\G(\&\&|and(?!\w))/gc;
660 2075 100       4773 return ['(', ''] if $s =~ /\G\(/gc;
661 1959 100       4386 return [')', ''] if $s =~ /\G\)/gc;
662 1856 100       4407 return ['[', ''] if $s =~ /\G\[/gc;
663 1836 100       3906 return [']', ''] if $s =~ /\G\]/gc;
664 1819 100       6940 return ['NUM', $1] if $s =~ /\G(\d+)/gc;
665 1501 100       4745 return ['STR', $2] if $s =~ /\G(['"])(.*?)\1/gc;
666 1420 100       5465 return ['ID', $1] if $s =~ /\G([A-Za-z_]\w*)/gc;
667 1107 100       2614 return ['@ID', $1] if $s =~ /\G@([A-Za-z_]\w*)/gc;
668 1081 100       2413 return ['SYM', $1] if $s =~ /\G:([A-Za-z_]\w*)/gc;
669 1047 100       3291 last if $s !~ /\G(?:\s+|#.*)/gc;
670             }
671 548         1732 $s =~ /\G(.*)/;
672 548         3389 return ['', $1];
673 565         2280 };
674             return sub {
675 6890 100   6890   13720 if ($_[0]) {
676 5300         6930 my $n = shift;
677 5300         14668 push @buf, &$lexer() while (@buf < $n);
678 5300         17137 return $buf[$n - 1];
679             } else {
680 1591 100       5224 return shift(@buf) if (@buf > 0);
681 245         550 return &$lexer();
682             }
683 565         11435 };
684             }
685              
686              
687             1;
688              
689              
690             __DATA__