File Coverage

blib/lib/macro/compiler.pm
Criterion Covered Total %
statement 87 91 95.6
branch 38 46 82.6
condition 16 21 76.1
subroutine 8 8 100.0
pod 0 1 0.0
total 149 167 89.2


line stmt bran cond sub pod time code
1             package macro::compiler;
2              
3 7     7   81070 use strict;
  7         16  
  7         346  
4 7     7   40 use warnings;
  7         14  
  7         762  
5              
6             BEGIN{
7 7     7   863 require macro;
8 7         126 our @ISA = qw(macro);
9 7         10054 *VERSION = \$macro::VERSION;
10             }
11              
12             my %compiled;
13              
14             sub import{
15 15     15   730 my $class = shift;
16              
17 15 100       86 return unless @_;
18              
19 13         23 my($pkg, $file) = do{
20 13         24 my $i = 0;
21 13         99 my($pkg, $file) = (caller($i))[0, 1];
22 13         409 while($pkg->isa('macro')){
23 13         123 ($pkg, $file) = (caller(++$i))[0, 1]
24             }
25 13         342 ($pkg, $file);
26             };
27 13         36 my $file_c = $file . 'c';
28              
29 13 50 33     62 if($^C and not $compiled{$file}){
30 0         0 warn "Compiling $file by $class/$macro::VERSION ...\n";
31             }
32              
33 13         74 my $self = $class->new();
34              
35 13         74 $self->defmacro(@_);
36              
37 11         25 my $fh;
38              
39 11 100 66     9797 ($compiled{$file} and open $fh, '<:perlio', $file_c)
      100        
40             or open $fh, '<:perlio', $file
41             or die qq{Cannot open "$file" for reading: $!};
42              
43 6         493 my $src = <$fh>;
44              
45 6 100       25 unless($compiled{$file}){
46 4 50       21 if($src =~ /^#/){
47 0         0 $src .= $self->_sign($file, 2);
48             }
49             else{
50 4         21 $src = $self->_sign($file, 1) . $src;
51             }
52             }
53              
54 6         14 { local $/; $src .= <$fh> };
  6         35  
  6         109  
55 6         167 close $fh;
56              
57 6         50 $src = $self->process($src, [$pkg, $file, -12]);
58              
59 6         26656 chmod 0644, $file_c; # chmod +w
60              
61 6 50       311629 open $fh, '>:perlio', $file_c
62             or die qq{Cannot open "$file_c" for writing: $!};
63              
64 6 50       289 print $fh $src
65             or die qq{Cannot write to "$file_c": $!};
66              
67 6 50       472728 close $fh
68             or die qq{Cannot close "$file_c: $!};
69 6         409 chmod 0444 & (stat $file)[2], $file_c, ; # chmod -w
70              
71 6         32 $compiled{$file}++;
72              
73              
74 6         2851 require macro::filter;
75 6         79 macro::filter->import(@_);
76              
77 6         77 return;
78             }
79              
80             # called from process();
81             sub preprocess{
82 18     18 0 49 my($self, $d) = @_;
83              
84 18         130 my $elem = $d->find_first(\&_want_use_macro);
85 18 50       2584 die $@ if $@;
86              
87 18 100       97 if($elem){
88              
89 6         30 my $stmt = $elem->content;
90 6         883 $stmt =~ s/^/#/msxg;
91 6         14 $stmt .= "\n";
92              
93 6         28 $d = $elem->parent;
94              
95 6         54 $stmt = PPI::Token::Comment->new($stmt); # comment out the statement
96 6         62 $stmt->{enable} = 1;
97 6         17 $d->{skip} = 1;
98              
99 6         54 $elem->__insert_before($stmt);
100 6         299 $elem->remove();
101             }
102              
103 18         361 return $d;
104             }
105              
106             sub _want_use_macro{
107 1679     1679   174166 my(undef, $it) = @_;
108 1679         1892 my $elem;
109              
110 1679   100     11682 return $it->isa('PPI::Statement::Include')
111              
112             && ($elem = $it->schild(0))
113             && ($elem->content eq 'use')
114              
115             && ($elem = $elem->snext_sibling)
116             && ($elem->content eq __PACKAGE__ or $elem->content eq 'macro')
117              
118             && _has_args($it, $elem->snext_sibling);
119             }
120              
121             # Does the use statement have arguments?
122             # It's too complex to understand :-(
123             # See macro/t/07_has_args.t for the subroutine spec.
124             sub _has_args{
125 62     62   5598 my($stmt, $arg) = @_; # 'use macro ...;' statement
126              
127 62 50       207 return 0 unless $arg;
128              
129             # check the most usual case first
130             # case 'use macro foo => ...';
131 62 100       444 return 1 if $arg->isa('PPI::Token::Word');
132              
133             # case 'use macro;'
134 52 100 66     405 return 0 if $arg->isa('PPI::Token::Structure')
135             && $arg->content eq ';';
136              
137             # case 'use macro 0.1'
138 49 100       371 $arg = $arg->snext_sibling if $arg->isa('PPI::Token::Number');
139              
140              
141 49         407 my @queue = ($arg);
142 49         186 ARG: while($arg = shift @queue){
143              
144             # case 'use macro foo => ...'
145 145 100       947 return 1 if $arg->isa('PPI::Token::Word');
146              
147             # case 'use macro "foo" => ...';
148 128 100       962 return 1 if $arg->isa('PPI::Token::Quote');
149              
150              
151             # case 'use macro qw(...);'
152 121 100 100     1041 return 1 if $arg->isa('PPI::Token::QuoteLike::Words')
153             && $arg->content !~ /^qw . \s* . $/msx;
154              
155              
156 115 100 66     968 return 0 if $arg->isa('PPI::Token::Structure')
157             && $arg->content eq ';';
158              
159              
160             # case '(' expr ')'
161 97 100       737 if($arg->isa('PPI::Structure::List')){
162 50 100       394 if(my $expr = $arg->schild(0)){
163 24         357 push @queue, $expr->schildren;
164             }
165             }
166              
167 97 100       707 if(my $sibling = $arg->snext_sibling){
168 81         1919 push @queue, $sibling;
169             }
170             }
171 1         24 return 0;
172             }
173             sub _sign{
174 4     4   12 my($self, $file, $line) = @_;
175             # meta
176 4         13 my $pkg = ref($self);
177 4         84 my $version = $pkg->VERSION;
178              
179 4         14 my $inc_key;
180 4         43 while(my($key, $path) = each %INC){
181 699 100       3036 if($path eq $file){
182 4         16 $inc_key = $key;
183             }
184             }
185              
186             # original file data
187 4         157 my $mtime = (stat $file)[9];
188              
189 4         452 my $mtimestamp = localtime $mtime;
190              
191             # for the correct file path
192 4 50       20 if(defined $inc_key){
193 4         8 $file = $inc_key;
194             }
195             else{
196 0         0 require File::Basename;
197 0         0 $inc_key = File::Basename::basename($file);
198             }
199              
200 4         51 return <<"SIGN";
201             # It was generated by $pkg version $version.
202             # Don't edit this file, edit $inc_key instead.
203             # ANY CHANGES MADE HERE WILL BE LOST!
204             # ============================= freshness check =============================
205             # the original file modified at $mtimestamp
206             BEGIN{my\$o=\$INC{q{$inc_key}}||q{$file};my\$m=(CORE::stat\$o)[9];
207             if(\$m and \$m != $mtime){ my \$f=do{CORE::open my\$in,'<',\$o or
208             die(qq{Cannot open \$o: \$!});local\$/;<\$in>;};require Filter::Util::Call;
209             Filter::Util::Call::filter_add(sub{ Filter::Util::Call::filter_del();
210             1 while Filter::Util::Call::filter_read();\$_=\$f;return 1; });}}
211             # line $line $inc_key
212             SIGN
213             }
214              
215             1;
216              
217             __END__