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 32     32   11449 use Perlmazing::Feature;
  32         74  
  32         165  
3 32     32   15376 use Submodules;
  32         157793  
  32         175  
4 32     32   1071 use Carp;
  32         58  
  32         1734  
5 32     32   195 use Scalar::Util qw(set_prototype);
  32         54  
  32         1489  
6 32     32   15455 use Taint::Util 'untaint';
  32         15613  
  32         166  
7 32     32   17746 use Data::Dump 'dump';
  32         169197  
  32         23942  
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 32     32 1 140 my $self = shift;
16 32   33     246 my $package = (shift or caller);
17 32 50       132 return unless exists $found_symbols->{$package};
18 32         58 sort keys %{$found_symbols->{$package}};
  32         2151  
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 32     32 1 112 my $self = shift;
30 32         91 my $package = caller;
31 32         133 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 32     32   82 my $self = shift;
39 32 50       191 carp "If passing arguments to this module, it most be using named arguments" if @_ % 2;
40 32         71 my $package = caller;
41 32         61 my $p = {@_};
42 32 50       386 $parameters->{$package} = {} unless $parameters->{$package};
43 32         55 $parameters->{$package} = {%{$parameters->{$package}}, %$p};
  32         236  
44 32 50       132 return if exists $found_symbols->{$package};
45 32         64 $found_symbols->{$package} = {};
46 32         132 _debug("Importing $self");
47 32         82 $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 12097     12097   15007 my $msg = shift;
64 12097   66     19781 my $caller = (shift or caller);
65 12097 50       22963 print STDERR __PACKAGE__." DEBUG: Package $caller, $msg\n" if $parameters->{$caller}->{debug};
66             }
67              
68             sub _find_symbols {
69 32     32   51 my $self = shift;
70 32         49 my $package = shift;
71 32         49 my @paths;
72             my $seen_paths;
73 32         79 _debug("Looking for symbols", $package);
74 32         166 for my $i (Submodules->find("${package}::Perlmazing")) {
75 10368 100       1753915 next if $i->{Clobber};
76 3456         8906 _debug("Found file $i->{AbsPath} for symbol $i->{Name}", $package);
77 3456         24895 $found_symbols->{$package}->{$i->Name} = {%$i};
78 3456 100       49446 if ($i->Module eq "${package}::Perlmazing::Precompile::$i->{Name}") {
79 1696         22206 $precompile_symbols->{$package}->{$i->Name} = $i;
80             }
81 32     32   262 no strict 'refs';
  32         59  
  32         1270  
82 32     32   195 no warnings; # prevents during-cleanup warnings when Submodules was destroyed and $i is undefined
  32         64  
  32         42324  
83 3456         43725 my $name = $i->Name;
84 3456         15602 *{"${package}::$i->{Name}"} = sub {
85 29     29   7359 unshift @_, $package, $name;
86 29         133 goto &_autoload;
87 3456         42704 };
88             }
89             }
90              
91             sub precompile {
92 32     32 1 85 my $self = shift;
93 32         78 my $package = caller;
94 32 50       126 return unless exists $precompile_symbols->{$package};
95 32         55 for my $name (sort keys %{$precompile_symbols->{$package}}) {
  32         767  
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 1696 50       4217 next if not defined $precompile_symbols->{$package}->{$name};
101 1696         6422 undef $precompile_symbols->{$package}->{$name};
102 1696         41861 _debug("Precompiling symbol $name", $package);
103 1696         2797 _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 29     29   76 my ($package, $symbol) = (shift, shift);
118 29         115 _debug("Autoloading symbol $symbol", $package);
119 29         78 my $code = _load_symbol($package, $symbol);
120 29         99 goto $code;
121             }
122              
123             sub _load_symbol {
124 1725     1725   3094 my ($package, $symbol) = (shift, shift);
125 1725         2123 local $@;
126 1725 100 100     5991 return $loaded_symbols->{$package}->{$symbol} if exists $loaded_symbols->{$package} and exists $loaded_symbols->{$package}->{$symbol};
127 1713 50 33     5825 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 1713         5370 _debug("Reading file $found_symbols->{$package}->{$symbol}", $package);
129 1713         4147 my $code = Submodules::Result::read($found_symbols->{$package}->{$symbol});
130 1713         189387 _debug("Parsing contents of $found_symbols->{$package}->{$symbol}->{AbsPath}", $package);
131 1713         2424 my $stderr = '';
132 1713         5054 my $eval_string = "\n#line 1 $found_symbols->{$package}->{$symbol}->{AbsPath}\npackage ${package}::Perlmazing::$symbol; $code";
133             {
134 1713         2184 local *STDERR;
  1713         3574  
135 32     32   210 open STDERR, '>>', \$stderr;
  32         54  
  32         340  
  1713         11845  
136 1713         30633 untaint $eval_string;
137 1713         546847 eval $eval_string;
138             }
139 1713 50       12326 if (my $e = $@) {
140 0         0 croak "While attempting to load symbol '$symbol': $e";
141             }
142 1713 50       3717 print STDERR $stderr if length $stderr;
143 1713         20300 $loaded_symbols->{$package}->{$symbol} = "${package}::Perlmazing::${symbol}"->can('main');
144 1713 50       4866 die "Unable to find sub 'main' at $found_symbols->{$package}->{$symbol}->{AbsPath} line 1 to EOF\n" unless $loaded_symbols->{$package}->{$symbol};
145 1713         6107 _debug("Replacing skeleton symbol with actual code from $found_symbols->{$package}->{$symbol}->{AbsPath}", $package);
146 1713 50       11816 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 32     32   256 no strict 'refs';
  32         61  
  32         1058  
187 32     32   168 no warnings qw(redefine once);
  32         76  
  32         14267  
188 1713         2592 my $skeleton = *{"${package}::$symbol"}{CODE};
  1713         4782  
189 1713         2809 my ($callers, $offset);
190 1713         5009 while (my $caller = caller($offset++)) {
191 8781         18235 $callers->{$caller}++;
192             }
193 1713         2174 $callers->{$package}++;
194 1713         5322 for my $i (keys %$callers) {
195 5193 100       9138 next if $i eq __PACKAGE__;
196 3480 100       3850 if (my $ref = *{"${i}::$symbol"}{CODE}) {
  3480         11479  
197 1730         1931 my $proto_old = prototype \&{"${i}::$symbol"};
  1730         4059  
198 1730         3611 my $proto_new = prototype $loaded_symbols->{$package}->{$symbol};
199 1730 100 66     13794 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 1472         1920 set_prototype \&{"${i}::$symbol"}, $proto_new;
  1472         7655  
203             }
204 1730 50       5181 *{"${i}::$symbol"} = $loaded_symbols->{$package}->{$symbol} if $ref eq $skeleton;
  1730         7088  
205             }
206             }
207 1713         6019 _debug(__PACKAGE__." no longer has power over symbol &${package}::$symbol (it's now loaded on it's own code)", $package);
208 1713         9516 $loaded_symbols->{$package}->{$symbol};
209             }
210              
211             package Perlmazing::Listable;
212              
213             1;
214              
215             __END__