File Coverage

lib/Perlmazing/Engine.pm
Criterion Covered Total %
statement 123 166 74.1
branch 25 54 46.3
condition 16 33 48.4
subroutine 20 25 80.0
pod 4 4 100.0
total 188 282 66.6


line stmt bran cond sub pod time code
1             package Perlmazing::Engine;
2 31     31   11742 use Perlmazing::Feature;
  31         76  
  31         173  
3 31     31   15897 use Submodules;
  31         156133  
  31         199  
4 31     31   1120 use Carp;
  31         70  
  31         1763  
5 31     31   200 use Scalar::Util qw(set_prototype);
  31         53  
  31         1381  
6 31     31   17085 use Taint::Util 'untaint';
  31         15423  
  31         162  
7 31     31   17660 use Data::Dump 'dump';
  31         163827  
  31         24068  
8             our $VERSION = '1.2812';
9             my $found_symbols;
10             my $loaded_symbols;
11             my $precompile_symbols;
12             my $parameters;
13            
14             sub found_symbols {
15 31     31 1 90 my $self = shift;
16 31   33     304 my $package = (shift or caller);
17 31 50       167 return unless exists $found_symbols->{$package};
18 31         64 sort keys %{$found_symbols->{$package}};
  31         2312  
19             }
20            
21             sub loaded_symbols {
22 0     0 1 0 my $self = shift;
23 0   0     0 my $package = (shift or caller);
24 0 0       0 return unless exists $loaded_symbols->{$package};
25 0         0 keys %{$loaded_symbols->{$package}};
  0         0  
26             }
27            
28             sub preload {
29 31     31 1 82 my $self = shift;
30 31         87 my $package = caller;
31 31         115 for my $i (@_) {
32 0         0 _debug("Preloading symbol $i", $package);
33 0         0 _load_symbol($package, $i);
34             }
35             }
36            
37             sub import {
38 31     31   81 my $self = shift;
39 31 50       223 carp "If passing arguments to this module, it most be using named arguments" if @_ % 2;
40 31         79 my $package = caller;
41 31         70 my $p = {@_};
42 31 50       251 $parameters->{$package} = {} unless $parameters->{$package};
43 31         55 $parameters->{$package} = {%{$parameters->{$package}}, %$p};
  31         107  
44 31 50       112 return if exists $found_symbols->{$package};
45 31         353 $found_symbols->{$package} = {};
46 31         142 _debug("Importing $self");
47 31         99 $self->_find_symbols($package);
48             }
49            
50             sub _is_compile_phase {
51 0     0   0 my $code = q[
52             BEGIN {
53             use warnings 'FATAL' => 'all';
54             eval 'INIT{} 1' or die;
55             }
56             ];
57 0         0 eval $code;
58 0 0       0 return 0 if $@;
59 0         0 1;
60             }
61            
62             sub _debug {
63 11686     11686   15246 my $msg = shift;
64 11686   66     19310 my $caller = (shift or caller);
65 11686 50       23059 print STDERR __PACKAGE__." DEBUG: Package $caller, $msg\n" if $parameters->{$caller}->{debug};
66             }
67            
68             sub _find_symbols {
69 31     31   47 my $self = shift;
70 31         49 my $package = shift;
71 31         45 my @paths;
72             my $seen_paths;
73 31         99 _debug("Looking for symbols", $package);
74 31         200 for my $i (Submodules->find("${package}::Perlmazing")) {
75 9951 100       1734446 next if $i->{Clobber};
76 3317         8685 _debug("Found file $i->{AbsPath} for symbol $i->{Name}", $package);
77 3317         24421 $found_symbols->{$package}->{$i->Name} = {%$i};
78 3317 100       47406 if ($i->Module eq "${package}::Perlmazing::Precompile::$i->{Name}") {
79 1643         21538 $precompile_symbols->{$package}->{$i->Name} = $i;
80             }
81 31     31   306 no strict 'refs';
  31         63  
  31         1534  
82 31     31   194 no warnings; # prevents during-cleanup warnings when Submodules was destroyed and $i is undefined
  31         57  
  31         40309  
83 3317         41630 my $name = $i->Name;
84 3317         14710 *{"${package}::$i->{Name}"} = sub {
85 28     28   4556 unshift @_, $package, $name;
86 28         123 goto &_autoload;
87 3317         40958 };
88             }
89             }
90            
91             sub precompile {
92 31     31 1 71 my $self = shift;
93 31         67 my $package = caller;
94 31 50       128 return unless exists $precompile_symbols->{$package};
95 31         63 for my $name (sort keys %{$precompile_symbols->{$package}}) {
  31         725  
96             # We detect already precompiled symbols by undefining this variable
97             # Note that symbols can in some cases (by internal recursion) be called
98             # more than once, not allowing _load_symbol to complete and mark it as loaded
99             # before being called again, so this part comes handy in those cases.
100 1643 50       4234 next if not defined $precompile_symbols->{$package}->{$name};
101 1643         7480 undef $precompile_symbols->{$package}->{$name};
102 1643         41795 _debug("Precompiling symbol $name", $package);
103 1643         3110 _load_symbol($package, $name);
104             }
105             }
106            
107             sub _preload {
108 0     0   0 my $self = shift;
109 0         0 my $package = shift;
110 0         0 for my $i (@_) {
111 0         0 _debug("Preloading symbol $i", $package);
112 0         0 _load_symbol($package, $i);
113             }
114             }
115            
116             sub _autoload {
117 28     28   75 my ($package, $symbol) = (shift, shift);
118 28         117 _debug("Autoloading symbol $symbol", $package);
119 28         77 my $code = _load_symbol($package, $symbol);
120 28         106 goto $code;
121             }
122            
123             sub _load_symbol {
124 1671     1671   3246 my ($package, $symbol) = (shift, shift);
125 1671         2371 local $@;
126 1671 100 100     6523 return $loaded_symbols->{$package}->{$symbol} if exists $loaded_symbols->{$package} and exists $loaded_symbols->{$package}->{$symbol};
127 1659 50 33     6014 croak "File $package/Perlmazing/$symbol.pm cannot be found in \@INC for symbol \&${package}::$symbol - \@INC contains: @INC" unless exists $found_symbols->{$package} and exists $found_symbols->{$package}->{$symbol};
128 1659         5198 _debug("Reading file $found_symbols->{$package}->{$symbol}", $package);
129 1659         4352 my $code = Submodules::Result::read($found_symbols->{$package}->{$symbol});
130 1659         199587 _debug("Parsing contents of $found_symbols->{$package}->{$symbol}->{AbsPath}", $package);
131 1659         2636 my $stderr = '';
132 1659         5515 my $eval_string = "\n#line 1 $found_symbols->{$package}->{$symbol}->{AbsPath}\npackage ${package}::Perlmazing::$symbol; $code";
133             {
134 1659         2382 local *STDERR;
  1659         3594  
135 31     31   206 open STDERR, '>>', \$stderr;
  31         53  
  31         246  
  1659         12538  
136 1659         32909 untaint $eval_string;
137 1659         603803 eval $eval_string;
138             }
139 1659 50       13768 if (my $e = $@) {
140 0         0 croak "While attempting to load symbol '$symbol': $e";
141             }
142 1659 50       4098 print STDERR $stderr if length $stderr;
143 1659         22516 $loaded_symbols->{$package}->{$symbol} = "${package}::Perlmazing::${symbol}"->can('main');
144 1659 50       5259 die "Unable to find sub 'main' at $found_symbols->{$package}->{$symbol}->{AbsPath} line 1 to EOF\n" unless $loaded_symbols->{$package}->{$symbol};
145 1659         7419 _debug("Replacing skeleton symbol with actual code from $found_symbols->{$package}->{$symbol}->{AbsPath}", $package);
146 1659 50       13041 if ("${package}::Perlmazing::${symbol}"->isa('Perlmazing::Listable')) {
147 0         0 _debug("Symbol &${package}::$symbol isa Perlmazing::Listable, creating wrapper sub around it", $package);
148 0         0 my $sub_main = $loaded_symbols->{$package}->{$symbol};
149             my $sub_pre = sub {
150 0     0   0 for my $i (@_) {
151 0         0 $sub_main->($i);
152             }
153 0         0 };
154             $loaded_symbols->{$package}->{$symbol} = sub {
155 0     0   0 my $wantarray = wantarray;
156 0         0 my @call = caller(1);
157 0         0 my @res = eval {
158 0 0       0 (@_) = ($_) if not @_;
159 0 0       0 if ($wantarray) {
    0          
160 0         0 my @res = @_;
161 0         0 foreach my $i (@res) {
162 0         0 $sub_pre->($i);
163             }
164 0         0 return @res;
165             } elsif (defined $wantarray) {
166 0         0 my $i = $_[0];
167 0         0 $sub_pre->($i);
168 0         0 return $i;
169             } else {
170 0         0 foreach my $i (@_) {
171 0         0 $sub_pre->($i);
172             }
173             }
174             };
175 0 0       0 if (my $e = $@) {
176 0 0       0 if ($e =~ /^Modification of a read\-only value attempted/) {
177 0         0 die "Modification of a read-only value attempted at $call[1] line $call[2]\n";
178             } else {
179 0         0 die "Unhandled error in listable function ${package}::$symbol: $e\n";
180             }
181             }
182 0 0       0 return @res if $wantarray;
183 0         0 $res[0];
184 0         0 };
185             }
186 31     31   268 no strict 'refs';
  31         54  
  31         1049  
187 31     31   161 no warnings qw(redefine once);
  31         51  
  31         13949  
188 1659         2746 my $skeleton = *{"${package}::$symbol"}{CODE};
  1659         5477  
189 1659         2848 my ($callers, $offset);
190 1659         5651 while (my $caller = caller($offset++)) {
191 8514         18413 $callers->{$caller}++;
192             }
193 1659         2448 $callers->{$package}++;
194 1659         5850 for my $i (keys %$callers) {
195 5031 100       9193 next if $i eq __PACKAGE__;
196 3372 100       3568 if (my $ref = *{"${i}::$symbol"}{CODE}) {
  3372         12185  
197 1676         1989 my $proto_old = prototype \&{"${i}::$symbol"};
  1676         4339  
198 1676         3781 my $proto_new = prototype $loaded_symbols->{$package}->{$symbol};
199 1676 100 66     15124 if ((defined $proto_new and defined $proto_old and $proto_old ne $proto_new) or (defined $proto_old and not defined $proto_new) or (defined $proto_new and not defined $proto_old)) {
      33        
      33        
      33        
      66        
      66        
200             # Disabling this as it is noisy and seems to be somewhat unimportant for general use. Leaving it commented in case I change my mind in a future version.
201             #carp "Warning: Too late to apply prototype ($proto_new) to symbol &${i}::$symbol - perl compilation phase has passed already" unless _is_compile_phase();
202 1426         1933 set_prototype \&{"${i}::$symbol"}, $proto_new;
  1426         8507  
203             }
204 1676 50       5422 *{"${i}::$symbol"} = $loaded_symbols->{$package}->{$symbol} if $ref eq $skeleton;
  1676         7576  
205             }
206             }
207 1659         6354 _debug(__PACKAGE__." no longer has power over symbol &${package}::$symbol (it's now loaded on it's own code)", $package);
208 1659         5967 $loaded_symbols->{$package}->{$symbol};
209             }
210            
211             package Perlmazing::Listable;
212            
213             1;
214            
215             __END__