File Coverage

blib/lib/Perl6/Classes.pm
Criterion Covered Total %
statement 141 158 89.2
branch 53 74 71.6
condition 8 14 57.1
subroutine 13 14 92.8
pod 0 6 0.0
total 215 266 80.8


line stmt bran cond sub pod time code
1             package Perl6::Classes;
2              
3 1     1   50428 use Filter::Simple;
  1         35268  
  1         10  
4 1         4330 use Text::Balanced qw{extract_quotelike extract_codeblock
5 1     1   65 extract_variable extract_multiple};
  1         3  
6              
7             our $VERSION = "0.22";
8              
9             # This whole file is a pile of ascii feces.
10              
11             my $identifier = qr/(?: :: )?
12             [a-zA-Z_] \w*
13             (?: :: [a-zA-Z_] \w*)*/x;
14              
15             my $signature = qr/(?:
16             \s* [\\;\&\%\@\$]
17             )* \s*/x;
18              
19             my $traits = qr/is \s+ [a-zA-Z_]\w*
20             (?: \s+ is \s+ [a-zA-Z_]\w* )*/x;
21              
22             my $scope_trait = qr/^ (?: public|protected|private ) $/x;
23              
24              
25             { my $symno = '000000';
26             sub newclass {
27 3     3 0 17 "__class_" . $symno++
28             }
29             }
30              
31             sub proccode {
32 15     15 0 30 my ($str, $cls) = @_;
33              
34 15         27 my $pos = pos; # Ugh, why doesn't pos localize with $_ ?!
35 15         40 $str = filter($str);
36 15         332 pos = $pos;
37              
38 15         100 $str =~ s/^\s*\{/{my \$self = local \$_ = shift; /;
39 15         84 $str =~ s/([\$\@\%])\.([a-zA-Z_]\w*)/$1\{\$self->{_data_$cls}{_attr_$2}}/g;
40 15         185 $str
41             }
42              
43             sub parse {
44 13     13 0 30 local $_ = shift;
45 13         22 my $cls = shift;
46              
47 13         18 my @components;
48             my $ws;
49              
50 13 50       71 die unless m/\G \s*\{/gx;
51              
52             CHUNK:
53 13         40 while (length > pos) {
54 62 100       955 if (/\G (\s+)/cgx) { $ws .= $1; }
  31 50       106  
    100          
    50          
    100          
    100          
    50          
55 0         0 elsif (/\G (\# [^\n]* \n)/cgx) { $ws .= $1; }
56             elsif (/\G has \s* ([\$\@\%])\.([a-zA-Z_]\w*) \s* \;/cgx) {
57 3         23 push @components, {
58             ws => $ws,
59             type => 'attr',
60             name => $2,
61             sigil => $1
62             };
63 3         11 undef $ws;
64             }
65             elsif (/\G sub \s+ ($identifier)
66             \s*? ( \( ( $signature ) \) )? (\s+ $traits)?/cgx) {
67 0 0       0 my @traits = grep { /\S/ && $_ ne 'is' } split /\s+/, $4;
  0         0  
68 0         0 my ($scope) = grep { /$scope_trait/ } @traits;
  0         0  
69 0         0 my $code = (extract_codeblock)[0];
70 0 0 0     0 push @components, {
71             ws => $ws,
72             type => 'sub',
73             name => $1,
74             sig => ($2 ? "(\$$3)" : "(\$@)"),
75             code => proccode($code, $cls),
76             scope => ($scope || 'public'),
77             };
78 0         0 undef $ws;
79             }
80             elsif (/\G method \s+ ($identifier)
81             \s*? ( \( ( $signature ) \) )? (\s+ $traits)?/cgx) {
82 13 0       50 my @traits = grep { /\S/ && $_ ne 'is' } split /\s+/, $4;
  0         0  
83 13         25 my ($scope) = grep { /$scope_trait/ } @traits;
  0         0  
84 13         41 my ($name, $sig, $insig) = ($1, $2, $3);
85 13         39 my $code = (extract_codeblock)[0];
86 13 50 50     5499 push @components, {
87             ws => $ws,
88             type => 'method',
89             name => $name,
90             sig => ($sig ? "(\$$insig)" : "(\$@)"),
91             code => proccode($code, $cls),
92             scope => ($scope || 'public'),
93             };
94 13         70 undef $ws;
95             }
96             elsif (/\G submethod \s+ ($identifier)
97             \s*? ( \( ( $signature ) \) )? (\s+ $traits)?/cgx) {
98 2 0       8 my @traits = grep { /\S/ && $_ ne 'is' } split /\s+/, $4;
  0         0  
99 2         6 my ($scope) = grep { /$scope_trait/ } @traits;
  0         0  
100 2         7 my $code = (extract_codeblock)[0];
101              
102 2 50 50     1443 push @components, {
103             ws => $ws,
104             type => 'submethod',
105             name => $1,
106             sig => ($2 ? "(\$$3)" : "(\$@)"),
107             code => proccode($code, $cls),
108             scope => ($scope || 'private'),
109             };
110 2         11 undef $ws;
111             }
112             elsif (/\G \}/cgx) {
113 13         140 push @components, {
114             ws => $ws,
115             type => 'empty',
116             };
117 13         182 last CHUNK;
118             }
119             else {
120 0         0 die "Bad token (near '" .
121             substr($_, pos, 15) . "')";
122             }
123             }
124            
125 13         50 \@components;
126             }
127              
128             sub generate_class {
129 13     13 0 23 my ($name, $data, $base) = @_;
130              
131             my %scopecode = (
132 2     2   17 private => sub { qq{require Carp; Carp::croak("Private $_[1] $name\::$_[0]") unless }.
133             qq{caller =~ /^$name(?:__|\$)/; } },
134 0     0   0 protected => sub { qq{require Carp; Carp::croak("Protected $_[1] $name\::$_[0]") unless } .
135             qq{caller->isa('$name') || $name->isa(scalar caller); } },
136 39     39   167 public => sub { "" },
137 13         213 );
138              
139 13         26 my ($newstruct, $destroystruct, $emptystruct);
140 13 50       54 $emptystruct = pop @$data if $data->[-1]->{type} eq 'empty';
141              
142 13         31 for (@$data) {
143 18 50       166 if ($_->{name} eq 'new') {
    50          
144 0         0 $newstruct = $_;
145             }
146             elsif ($_->{name} eq 'DESTROY') {
147 0         0 $destroystruct = $_;
148             }
149             }
150              
151 13 50       37 unless ($newstruct) {
152 13         58 $newstruct = {
153             type => 'sub',
154             name => 'new',
155             sig => '',
156             scope => 'public',
157             };
158 13         27 push @$data, $newstruct;
159             }
160              
161 13 50       39 unless ($destroystruct) {
162 13   50     85 $destroystruct ||= {
163             type => 'submethod',
164             name => 'DESTROY',
165             sig => '',
166             scope => 'public',
167             };
168 13         24 push @$data, $destroystruct;
169             }
170              
171             # Checks
172              
173 13         265 { my %seen;
  13         17  
174 13         30 for (@$data) {
175 44 100 66     230 if ($_->{type} ne 'attr' && $_->{type}) {
176 41 50       181 if (exists $seen{$_->{name}}) {
177 0         0 die "Duplicate name $_";
178             }
179 41         197 $seen{$_}++;
180             }
181             }
182             }
183              
184             # New routine
185              
186             {
187 13         23 my $newcode = " { ";
  13         23  
188              
189 13         25 for (@$data) { # update the closures (???)
190 44 100       129 if ($_->{type} eq 'sub') {
191 13         41 $newcode .= "\$_sub_$_->{name}; ";
192             }
193             }
194              
195 13         32 $newcode .= 'my $_class = shift; my $_self = bless {';
196              
197 13         31 for (@$base) {
198 3         12 $newcode .= "do { my \$_cl = $_->new; (_parent_$_ => \$_cl, \%\$_cl) }, ";
199             }
200              
201 13         33 $newcode .= "_data_$name => {";
202            
203 13         25 for (@$data) {
204 44 100       108 if ($_->{type} eq 'attr') {
205 3         10 $newcode .= "_attr_$_->{name} => undef, ";
206             }
207             }
208              
209 13         31 $newcode .= "}, ";
210            
211 13         26 for (@$data) {
212 44 100 100     190 if ($_->{type} eq 'method' || $_->{type} eq 'submethod') {
213 28         119 $newcode .= "_$_->{type}_$_->{name} => \$_$_->{type}_$_->{name}, ";
214             }
215             }
216 13         30 for (@$data) {
217 44 100       115 if ($_->{type} eq 'sub') {
218 13         55 $newcode .= "_sub_$_->{name} => sub { my \$self = shift; " .
219             "\$_sub_$_->{name}->(\${\$self->{_class}}, \@_) }, ";
220             }
221             }
222            
223 13         36 $newcode .= "_class => \\\$_ret, } => '${name}__object'; " .
224             "\$_self->BUILD(\@_) if \$_self->can('BUILD'); \$_self }; ";
225              
226 13         35 $newstruct->{code} = $newcode;
227             } # End of new routine
228              
229             # DESTROY routine
230             {
231 13         16 my $descode = "{ \$_[0]->DESTRUCT if \$_[0]->can('DESTRUCT'); ";
  13         24  
232 13         22 for (reverse @$base) {
233 3         12 $descode .= "\$_[0]->$_\::DESTROY;";
234             }
235 13         22 $descode .= " }";
236 13         242 $destroystruct->{code} = $descode;
237             } # End of DESTROY routine
238              
239 13         26 my $ret = "{ package $name; my \$_ret; ";
240 13         27 for (@$data) {
241 44 100       194 if ($_->{type} ne 'attr') {
242 41         127 $ret .= "my \$_$_->{type}_$_->{name}; "
243             }
244             }
245 13         28 for (@$data) {
246 44 100       88 if ($_->{type} eq 'attr') {
247 3         9 $ret .= "$_->{ws}";
248             }
249             else {
250 41         207 $ret .= "$_->{ws} \$_$_->{type}_$_->{name} = sub $_->{sig} $_->{code}; ";
251             }
252             }
253              
254 13         24 $ret .= '$_ret = bless { ';
255              
256 13         24 for (@$data) {
257 44 100       109 if ($_->{type} eq 'sub') {
258 13         37 $ret .= "_sub_$_->{name} => \$_sub_$_->{name}, ";
259             }
260             }
261 13         37 $ret .= "} => '${name}__class'; ";
262              
263             # Class methods
264 13         21 $ret .= "{ package ${name}__class; ";
265              
266 13         24 for (@$data) {
267 44 100       123 if ($_->{type} eq 'sub') {
268 13         63 $ret .= "sub $_->{name} $_->{sig} { " . $scopecode{$_->{scope}}->($_->{name}, $_->{type}) .
269             "goto &{ref \$_[0] ? \$_[0]{_sub_$_->{name}} : \$_sub_$_->{name}} } ";
270             }
271             }
272              
273             # Inheritable methods
274              
275 13         28 $ret .= "package ${name}; ";
276 13         24 $ret .= "use base '${name}__class'; ";
277              
278 13         29 for (@$base) {
279 3         9 $ret .= "use base '$_'; ";
280             }
281            
282 13         22 for (@$data) {
283 44 100       131 if ($_->{type} eq 'method') {
284 13         58 $ret .= "sub $_->{name} $_->{sig} { " . $scopecode{$_->{scope}}->($_->{name}, $_->{type}) .
285             "goto &{\$_[0]{_method_$_->{name}}} }";
286             }
287             }
288            
289             # Object methods
290            
291 13         27 $ret .= "package ${name}__object; ";
292 13         26 $ret .= "use base '$name'; ";
293              
294 13         29 for (@$data) {
295 44 100       203 if ($_->{type} eq 'submethod') {
296 15         66 $ret .= "sub $_->{name} $_->{sig} { " . $scopecode{$_->{scope}}->($_->{name}, $_->{type}) .
297             "goto &{\$_[0]{_submethod_$_->{name}}} }";
298             }
299             }
300              
301 13         27 $ret .= "} \$_ret; } $emptystruct->{ws}";
302              
303 13         174 $ret;
304             }
305              
306             sub extract_class {
307 212 50   212 0 682 local $_ = shift if @_;
308            
309 212         221 my $ret;
310 212 100       1350 if (/\G class (\s+ $identifier)? (\s+ $traits)? (?= \s* \{ )/cgx) {
311 13 100       70 my @inherit = grep { /\S/ && $_ ne 'is' } split /\s+/, $2;
  9         59  
312            
313 13         23 my $anon;
314 13         28 my $name = $1;
315 13 100       34 unless ($name) {
316 3         11 $name = newclass;
317 3         5 $anon = 1;
318             }
319 13         59 $name =~ s/^\s*//;
320 13         49 my $code = (extract_codeblock)[0];
321 13         14394 my $ppos = pos;
322 13         50 my $data = parse($code, $name);
323 13         47 pos = $ppos;
324            
325 13         39 $ret = generate_class($name, $data, \@inherit);
326 13 100       30 if ($anon) {
327 3         26 $ret = "do $ret";
328             }
329             else {
330 10         60 $ret = "$ret;";
331             }
332             }
333 212         1010 $ret;
334             }
335              
336             sub filter {
337 16 100   16 0 61 local $_ = shift if @_;
338             my @parts = extract_multiple(undef, [
339             qr/\s+/,
340 212     212   38789 sub { scalar extract_class },
341             qr/#[^\n]*/,
342 199     199   6206 sub { scalar extract_quotelike },
343 167     167   9214 sub { scalar extract_variable },
344 16         359 qr/.[^\&\%\@\$"'q#c]*/,
345             ]);
346 16         1922 join '', @parts;
347             }
348              
349             FILTER {
350             $_ = filter;
351             }
352              
353             __END__