File Coverage

blib/lib/Lexical/Attributes.pm
Criterion Covered Total %
statement 59 62 95.1
branch 23 26 88.4
condition 3 3 100.0
subroutine 10 10 100.0
pod 0 4 0.0
total 95 105 90.4


line stmt bran cond sub pod time code
1             package Lexical::Attributes;
2              
3 9     9   288628 use 5.008001;
  9         37  
  9         504  
4              
5 9     9   70 use strict;
  9         16  
  9         251  
6 9     9   43 use warnings;
  9         21  
  9         250  
7 9     9   42 no warnings 'syntax';
  9         17  
  9         315  
8 9     9   11327 use Filter::Simple;
  9         415081  
  9         73  
9 9     9   705 use Scalar::Util;
  9         20  
  9         14451  
10              
11             our $VERSION = '2009121601';
12              
13             my $sigil = '[$@%]';
14             my $sec_sigil = '[.]';
15             my $trait = '(?:r[ow]|pr(?:iv)?)'; # read-only, read-write, private.
16             my $name = qr /[a-zA-Z_][a-zA-Z0-9_]*/; # Starts with alpha or _, followed
17             # by one or more alphanumunders.
18             my $has_attribute = qr /(?>$sigil$sec_sigil$name)/;
19             my $use_attribute = qr /(?>(?:\$#?|[%\@])$sec_sigil$name)/;
20             my $int_attribute = qr /(?>(?:\$#?|\@)$sec_sigil$name)/; # No hash.
21              
22             my %attributes;
23              
24             sub declare_attribute {
25 52     52 0 163 my ($attributes, $trait) = @_;
26              
27 52 100 100     294 $trait = "pr" if !$trait || $trait eq "priv";
28              
29 52         80 my $text = "";
30              
31 52         417 foreach my $attribute (split /\s*,\s*/ => $attributes) {
32              
33 64         316 my ($sigil, $sec_sigil, $name) = unpack "A A A*" => $attribute;
34 64         104 my $str = "";
35              
36 64 100       163 if ($attributes {$name}) {
37 1         29 warn "Duplicate attribute '$attribute' ignored\n";
38 1         11 next;
39             }
40              
41 63         229 $attributes {$name} = [$sigil, $trait];
42              
43 63         154 $str .= "my %$name;";
44 63 100       155 unless ($trait eq "pr") {
45 41 100       218 if ($sigil eq '$') {
    100          
    50          
46 27         49 $str .= <<' --';
47             sub _NAME {
48             my $_key = Scalar::Util::refaddr shift;
49             $_NAME {$_key}
50             }
51             --
52             }
53             # @_ ? @{$_NAME {$_key}} [@_] : @{$_NAME {$_key}};
54             elsif ($sigil eq '@') {
55 7         13 $str .= <<' --';
56             sub _NAME {
57             my $_key = Scalar::Util::refaddr shift;
58             @_ ? @{$_NAME {$_key}} [@_] : @{$_NAME {$_key} || []};
59             }
60             --
61             }
62             elsif ($sigil eq '%') {
63 7         14 $str .= <<' --';
64             sub _NAME {
65             my $_key = Scalar::Util::refaddr shift;
66             @_ ? @{$_NAME {$_key}} {@_} :
67             wantarray ? %{$_NAME {$_key} || {}} :
68             keys %{$_NAME {$_key} || {}};
69             }
70             --
71             }
72             else {
73 0         0 die "'$attribute' not implemented\n";
74             }
75              
76 41 100       115 if ($trait eq "rw") {
77 29 100       83 if ($sigil eq '$') {
    100          
    50          
78 17         38 $str .= <<' --';
79             sub set__NAME {
80             my $self = shift;
81             my $_key = Scalar::Util::refaddr $self;
82             $_NAME {$_key} = shift;
83             $self;
84             }
85             --
86             }
87             elsif ($sigil eq '@') {
88 6         23 $str .= <<' --';
89             sub set__NAME {
90             my $self = shift;
91             my $_key = Scalar::Util::refaddr $self;
92             if (@_ == 0) {delete $_NAME {$_key}}
93             elsif (@_ == 1) {
94             if (ref $_ [0] eq 'ARRAY') {$_NAME {$_key} = $_ [0]}
95             else {delete $_NAME {$_key} [$_ [0]]}
96             }
97             else {
98             while (@_ >= 2) {
99             my ($index, $value) = splice @_ => 0, 2;
100             $_NAME {$_key} [$index] = $value;
101             }
102             }
103             $self;
104             }
105             --
106             }
107             elsif ($sigil eq '%') {
108 6         98 $str .= <<' --';
109             sub set__NAME {
110             my $self = shift;
111             my $_key = Scalar::Util::refaddr $self;
112             if (@_ == 0) {delete $_NAME {$_key}}
113             elsif (@_ == 1) {
114             if (ref $_ [0] eq 'HASH') {$_NAME {$_key} = $_ [0]}
115             else {delete $_NAME {$_key} {$_ [0]}}
116             }
117             else {
118             while (@_ >= 2) {
119             my ($key, $value) = splice @_ => 0, 2;
120             $_NAME {$_key} {$key} = $value;
121             }
122             }
123             $self;
124             }
125             --
126             }
127             }
128             }
129 63         647 $str =~ s/\n\s*/ /g;
130 63         362 $str =~ s/_NAME/$name/g;
131              
132 63         187 $text .= $str;
133             }
134            
135 52         1197 return $text;
136             }
137              
138             sub destroy_attributes {
139 13     13 0 40 my $str = "";
140 13         121 while (my ($key) = each %attributes) {
141 63         274 $str .= "delete \$$key {Scalar::Util::refaddr \$self};\n";
142             }
143 13         55 $str;
144             }
145              
146             sub use_attribute {
147 95     95 0 221 my ($attribute) = @_;
148              
149 95         423 my ($sigil, $name) = split /[.]/ => $attribute, 2;
150              
151 95 50       247 if (!$attributes {$name}) {
152 0         0 die $_;
153 0         0 die qq !Attribute "$attribute" requires declaration!;
154             }
155              
156 95         112 my $str;
157 95 100       167 if ($sigil eq '$') {
158 62         184 $str = "\$$name\{Scalar::Util::refaddr \$self}";
159             }
160             else {
161 33         70 $str = "$sigil\{\$$name\{Scalar::Util::refaddr \$self}}";
162             }
163 95         1356 $str;
164             }
165              
166             sub interpolate {
167 24     24 0 47 local $_ = shift;
168              
169             # The regex below finds attribute names. We cannot simply use a
170             # regex for finding them, we need to parse the entire string, to
171             # be able to deal with backslashes.
172             #
173             # We use loop unrolling for efficiency.
174             #
175 24         482 s {( # Capture non attributes in $1.
176             [^\$\@\\]* # Anything that isn't $, @ or \ is ok.
177             (?: # Group (1)
178             (?: # Group (2) ("Special things")
179             \\. # Escape followed by any character
180             | # or
181             (?: # Group (3)
182             (?:>\$\#?) # Scalar sigil, or array count
183             # The (?> ) is vital here.
184             | # Or
185             \@ # Array sigil
186             ) # End group (3)
187 122 100       447 (?!\.$name) # not followed by dot attribute name
188             ) # End group (2)
189             [^\$\@\\]* # Anything that isn't $, @ or \
190             )* # End group (1), repeated zero or more times.
191             ) # End capture $1
192             | # Or
193             ( # In $2, capture an attribute
194             (?:(?>\$\#?)|\@) # Primary sigil
195             \.$name # dot attribute name
196             ) # End $2
197             }
198             {defined $1 ? $1 : use_attribute ($2)}sexg;
199              
200 24         123 $_
201             }
202              
203             FILTER_ONLY
204             #
205             # Initialize variables.
206             #
207             all => sub {%attributes = ()},
208              
209             #
210             # Save all attributes found in comments. *Very* simple heuristics
211             # to determine comments - note that quote like constructs have been
212             # moved out of the way.
213             #
214             # Moving away the attributes found in comments prevents subsequent
215             # passes to modify them. In particular, outcommented attribute
216             # declarations shouldn't create methods or hashes.
217             #
218             code => sub {
219             1 while s/( # Save
220             (?
221             \# # Start of a comment.
222             [^\n]* # Not a newline, not an attribute,
223             (?: $sigil (?!$sec_sigil) [^\n]*)*
224             # using standard unrolling.
225             ) ($sigil) ($sec_sigil) ($name)
226             /$1$2<$3>$4/xg;
227             },
228              
229             #
230             # Find the attribute declarions and uses. Foreach declararion, the sub
231             # 'attribute' is called, which will create an attribute hash,
232             # and, for non-private attributes, a constructor (which maybe
233             # an lvalue method if the rw trait is given).
234             #
235             # We recognize:
236             # "has" [$@%].attribute ( ("is")? "pr(iv)?|ro|rw")? ";"
237             # "has" "(" [$@%].attribute ("," [$@%].attribute)* ")" \
238             # ( ("is")? "pr(iv)?|ro|rw")? ";"
239             #
240             # Other attribute usages are just:
241             # ([$@%]|$#).attribute
242             #
243             # Attribute uses are handled by calling 'use_attribute'.
244             #
245             code => sub {
246             s{(?: # Declaration using 'has',
247             \bhas \s* # Must start with "has"
248             (?: # Either
249             ($has_attribute) # a single ttribute, stored in $1.
250             | # or
251             [(] \s* ($has_attribute (?: \s* , \s* $has_attribute)*) \s* [)]
252             # an attribute list, stored in $2.
253             )
254             (?: \s* (?:is \s+)? ($trait))? # Optional trait - stored in $3.
255             \s* ; # Terminated by semi-colon.
256             ) | # or actual usage.
257             ($use_attribute) # It's in $4.
258             }
259             {$4 ? use_attribute ($4) : declare_attribute ($1 || $2, $3)}egx;
260             },
261              
262             #
263             # Interpolation. Double quoted strings, backticks, slashes and q[qrx] {},
264             # m// and s/// constructs.
265             #
266             # Note that '', qw{}, tr///, m'' and s''' don't interpolate.
267             #
268             # How to test qx?
269             #
270             quotelike => sub {
271             if (m {^(["`/]|q[qrx]\s*\S|[sm]\s*[^\s'])(.*)(\S)$}s) {
272             $_ = $1 . interpolate ($2) . $3
273             }
274             },
275              
276             #
277             # If a subroutine uses the keyword 'method' (at the beginning of
278             # a line), add an assignment to '$self'.
279             #
280              
281             code => sub {
282             s<^(\s*) method (\s+ [a-zA-Z_]\w* \s* # sub name
283             (?:\([^)]*\) \s*)? # Optional prototype
284             \{) # Opening of block
285             ><$1 sub $2 my \$self = shift;>mgx;
286             },
287              
288             #
289             # Add a DESTROY function
290             #
291             code => sub {
292             my $destroy = <<' --';
293              
294             sub DESTROY {
295             our @ISA;
296             my $self = shift;
297             my $DESTRUCT = __PACKAGE__ . "::DESTRUCT";
298             $self -> $DESTRUCT if do {no strict 'refs'; exists &$DESTRUCT};
299             DESTROY_ATTRIBUTES;
300             foreach my $class (@ISA) {
301             my $destroy = $class . "::DESTROY";
302             $self -> $destroy if $self -> can ($destroy);
303             }
304             }
305             --
306              
307             my $destroy_attributes = destroy_attributes;
308              
309             $destroy =~ s/DESTROY_ATTRIBUTES/$destroy_attributes/;
310             $destroy =~ s/^ {8}//gm;
311              
312             $_ .= $destroy;
313             },
314              
315             #
316             # Restore tucked away, outcommented, attributes.
317             #
318             code => sub {
319             1 while s/( # Save
320             (?
321             \# # Start of a comment.
322             [^\n]* # Not a newline, not an attribute,
323             (?: $sigil (?!<$sec_sigil>) [^\n]*)*
324             # using standard unrolling.
325             ) ($sigil) <($sec_sigil)> ($name)
326             /$1$2$3$4/xg;
327             },
328              
329             #
330             # For debugging purposes; to be removed.
331             #
332             # all => sub {print "<<$_>>\n" if $::DEBUG || $ENV {DEBUG}},
333              
334             ;
335              
336             __END__