File Coverage

blib/lib/Math/SymbolicX/Inline.pm
Criterion Covered Total %
statement 199 218 91.2
branch 59 84 70.2
condition 8 21 38.1
subroutine 13 13 100.0
pod n/a
total 279 336 83.0


line stmt bran cond sub pod time code
1             package Math::SymbolicX::Inline;
2              
3 3     3   10983 use 5.006001;
  3         10  
  3         107  
4 3     3   14 use strict;
  3         6  
  3         91  
5 3     3   14 use warnings;
  3         9  
  3         109  
6 3     3   13 use Carp qw/cluck confess/;
  3         4  
  3         218  
7 3     3   2700 use Math::Symbolic qw/parse_from_string U_P_DERIVATIVE U_T_DERIVATIVE/;
  3         478026  
  3         525  
8 3     3   3361 use Math::Symbolic::Custom::Contains;
  3         8496  
  3         31  
9 3     3   143 use Math::Symbolic::Compiler qw/compile_to_code/;
  3         6  
  3         5911  
10              
11             our $VERSION = '1.11';
12              
13             sub import {
14              
15             # Just using the module shouldn't throw a fatal error.
16 14 100   14   2519 if ( @_ != 2 ) {
17 2         25 return ();
18             }
19              
20 12         27 my ( $class, $code ) = @_;
21 12         42 my ( $pkg, undef ) = caller;
22              
23 12         22 my %definitions;
24              
25 12 50       36 if ( not defined $code ) {
26 0         0 confess "undef passed to Math::SymbolicX::Inline as source\n"
27             . "code. Can't compile undef to something reasonable, can you?";
28             }
29              
30 12         92 my @lines = split /\n+/, $code;
31 12         21 my $lastsymbol = undef;
32 12         28 foreach my $line (@lines) {
33              
34             # prepare the line, skip empty lines, strip comments...
35 42         209 chomp $line;
36 42         104 $line =~ s/\#.*$//;
37 42 100       170 next if $line =~ /^\s*$/;
38 33         70 $line =~ s/^\s+//;
39 33         103 $line =~ s/\s+$//;
40              
41             # new definitions
42 33 100       187 if ( $line =~ /^([A-Za-z_][A-Za-z0-9_]*)\s*(\(:?=\)|:?=)(.*)$/ ) {
43 29         125 my ( $symbol, $type, $codestart ) = ( $1, $2, $3 );
44 29 50       97 if ( exists $definitions{$1} ) {
45 0         0 confess "(Math::SymbolicX::Inline:) Symbol "
46             . "'$symbol' redefined in package '$pkg'.";
47             }
48              
49 29         149 $definitions{$symbol} = {
50             code => $codestart,
51             type => $type,
52             };
53              
54             # Check syntax of the finished piece of code here
55 29         83 _check_syntax( \%definitions, $lastsymbol );
56              
57 29         90 $lastsymbol = $symbol;
58             }
59             else {
60 4 100       16 if ( not defined $lastsymbol ) {
61 1         277 confess "Math::SymbolicX::Inline code must "
62             . "start with a symbol.";
63             }
64 3         13 $definitions{$lastsymbol}{code} .= ' ' . $line;
65             }
66             }
67              
68             # Check the syntax of the last piece of code
69 11         33 _check_syntax( \%definitions, $lastsymbol );
70              
71             # Now we start distinguishing between the different operator types.
72              
73 11         25 my %early;
74             my %late;
75              
76 11         50 foreach my $s ( keys %definitions ) {
77 29 100       144 if ( $definitions{$s}{type} eq '=' ) {
    50          
    100          
    50          
78 13         45 $early{$s} = $definitions{$s};
79             }
80             elsif ( $definitions{$s}{type} eq ':=' ) {
81 0         0 $late{$s} = $definitions{$s};
82             }
83             elsif ( $definitions{$s}{type} eq '(=)' ) {
84 4         14 $early{$s} = $definitions{$s};
85             }
86             elsif ( $definitions{$s}{type} eq '(:=)' ) {
87 12         79 $late{$s} = $definitions{$s};
88             }
89             else {
90 0         0 confess "Something went wrong: We parsed an invalid "
91             . "operator type '"
92             . $definitions{$s}{type}
93             . "' for "
94             . "symbol '$s'";
95             }
96             }
97              
98             # Implement early replace dependencies
99 11         62 my @pairs;
100 11         37 foreach my $s ( keys %early ) {
101 17         95 my @sig = $early{$s}{parsed}->explicit_signature();
102 17         1764 foreach (@sig) {
103              
104             # Exclude the late and external dependencies
105 33 100       116 next if not exists $early{$_};
106 5         17 push @pairs, [ $_, $s ];
107             }
108             }
109 11         43 my @sort = _topo_sort( \@pairs );
110              
111             # Detect cycles
112 11 50 33     43 if ( @sort == 1 and not defined $sort[0] ) {
113 0         0 confess "Detected cycle in definitions. Cannot do topological "
114             . "sort";
115             }
116              
117             # actually implement symbolic dependencies of early replaces
118 11         24 foreach my $sym (@sort) {
119 10         23 my $f = $early{$sym}{parsed};
120 10         27 my @sig = $f->explicit_signature();
121 5         20 $f->implement(
122 19         62 map { ( $_ => $early{$_}{parsed}->new() ) }
123 10         675 grep { exists $early{$_} } @sig
124             );
125              
126 10         3307 $early{$sym}{parsed} = $f;
127             }
128              
129             # apply derivatives
130 11         33 foreach my $sym ( keys %early ) {
131 17         39 my $f = $early{$sym}{parsed};
132 17         67 $f = $f->simplify()->apply_derivatives()->simplify();
133 17 50 33     98596 if ( $f->contains_operator(U_P_DERIVATIVE)
134             or $f->contains_operator(U_T_DERIVATIVE) )
135             {
136 0         0 confess "Could not apply all derivatives in function '$sym'.";
137             }
138              
139 17         5937 $early{$sym}{parsed} = $f;
140             }
141              
142             # Implement late replace dependencies
143 11         32 @pairs = ();
144 11         40 foreach my $s ( keys %late ) {
145 12         47 my @sig = $late{$s}{parsed}->explicit_signature();
146 12         256 foreach (@sig) {
147              
148             # Die on dependencies on early replaced functions
149 16 50       39 confess "Dependency on outer scope function '$_' "
150             . "found in function '$s'."
151             if exists $early{$_};
152              
153             # Exclude the external dependencies
154 16 100       53 next if not exists $late{$_};
155 2         9 push @pairs, [ $_, $s ];
156             }
157             }
158              
159 11         42 @sort = _topo_sort( \@pairs );
160              
161             # Detect cycles
162 11 50 33     48 if ( @sort == 1 and not defined $sort[0] ) {
163 0         0 confess "Detected cycle in definitions. Cannot do topological "
164             . "sort";
165             }
166              
167             # actually implement symbolic dependencies of late replaces
168 11         27 foreach my $sym (@sort) {
169 3         11 my $f = $late{$sym}{parsed};
170 3         11 my @sig = $f->explicit_signature();
171 2         8 $f->implement(
172 7         18 map { ( $_ => $late{$_}{parsed}->new() ) }
173 3         182 grep { exists $late{$_} } @sig
174             );
175 3         1083 $f = $f->simplify()->apply_derivatives()->simplify();
176 3         20988 $late{$sym}{parsed} = $f;
177             }
178              
179             # apply derivatives
180 11         32 foreach my $sym ( keys %late ) {
181 12         24 my $f = $late{$sym}{parsed};
182 12         34 $f = $f->simplify()->apply_derivatives()->simplify();
183 12 50 33     25076 if ( $f->contains_operator(U_P_DERIVATIVE)
184             or $f->contains_operator(U_T_DERIVATIVE) )
185             {
186 0         0 confess "Could not apply all derivatives in function '$sym'.";
187             }
188              
189 12         1843 $late{$sym}{parsed} = $f;
190             }
191              
192             # implement symbolic dependencies of early replaces on late replaces
193 11         42 foreach my $s ( keys %early ) {
194 21         820 $early{$s}{parsed}->implement(
195 17         4142 map { ( $_ => $late{$_}{parsed}->new() ) }
196             keys %late
197             );
198             }
199              
200             # external dependencies, compilation and subs
201 11         5397 foreach my $obj (
  17         75  
202 12         32 ( map { [ $_ => $early{$_} ] } keys %early ),
203             ( map { [ $_ => $late{$_} ] } keys %late )
204             )
205             {
206 29         56 my ( $sym, $h ) = @$obj;
207              
208             # don't compile anything with parens in the operator.
209 29 100       180 next if $h->{type} =~ /^\(:?=\)$/;
210              
211             # external dependencies
212 13         65 my @external = $h->{parsed}->explicit_signature();
213              
214             # actual arguments
215 18         52 my @args =
216 7         22 map { "arg$_" }
217 18         49 sort { $a <=> $b }
218 18         70 map { /^arg(\d+)$/; $1 }
  25         89  
219 13         5399 grep { /^arg\d+$/ } @external;
220 13         29 my $highest = $args[-1];
221 13 100 66     104 if ( not defined $highest or $highest eq '' ) {
222 1         2 $highest = 0;
223             }
224             else {
225 12         60 $highest =~ s/^arg(\d+)$/$1/;
226             }
227              
228             # number of arguments.
229 13 100       49 my $num_args = @args==0 ? 0 : $highest+1;
230              
231             # external sub calls
232 13         23 my @real_external = sort grep { $_ !~ /^arg\d+$/ } @external;
  25         84  
233 13         23 my $num_real_external = @real_external;
234              
235             # This is where it gets really fancy!
236             # ... and This is not the Right Way To Do It! FIXME!!!
237 13         21 my $final_code = "sub {\n";
238 13 100       34 $final_code .= "my \@args = \@_;\n" if $num_real_external;
239              
240 13 100       39 if (@args) {
241 12         73 $final_code .= <
242             if (\@_ < $highest+1) {
243             cluck(
244             "Warning: Math::SymbolicX::Inline compiled sub "
245             ."'${pkg}::${sym}'\nrequires $num_args argument(s) "
246             ."but received only " . scalar(\@_)
247             );
248             }
249             if (grep {!defined} \@_[0..$highest]) {
250             cluck(
251             "Warning: Undefined value passed to '${pkg}::${sym}'"
252             );
253             }
254             HERE
255             }
256              
257 13         24 my $num_argsm1 = $num_args - 1;
258              
259 4         11 $final_code .= "local \@_[0..$num_argsm1+$num_real_external] = ("
260             . join( ', ',
261 7         25 @args ? ( map { "\$_[$_]" } 0..$highest ) : (),
262 13 100       50 ( map { "${pkg}::$_(\@args)" } @real_external ) )
    100          
263             . ");\n"
264             if $num_real_external;
265              
266 13 100       48 my $vars = [ @args?(map {"arg$_"} 0..$highest):(), @real_external ];
  19         60  
267              
268 13         29 my ( $mcode, $trees );
269 13         19 eval { ( $mcode, $trees ) = compile_to_code( $h->{parsed}, $vars ); };
  13         72  
270 13 50 33     16110 if ( $@ or not defined $mcode ) {
271 0         0 confess "Could not compile Perl code for function " . "'$sym'.";
272             }
273 13 50 33     84 if ( defined $trees and @$trees ) {
274 0         0 confess <
275             Could not resolve all trees in Math::Symbolic expression. That means, the
276             compiler encountered operators that could not be compiled to Perl code.
277             These include derivatives, but those should usually be applied before
278             compilation. Details can be found in the Math::Symbolic::Compiler man-page.
279             The expression that should have been compiled is:
280             ---
281             $code
282             ---
283             HERE
284             }
285 13         42 $final_code .= $mcode . "\n};\n";
286              
287             # DEBUG OUTPUT
288             # warn "$sym = $final_code\n\n";
289              
290 13         35 my $anon_sub = _make_sub($final_code);
291 13 50       45 if ($@) {
292 0         0 confess <
293             Something went wrong compiling the code for '${pkg}::$sym'.
294             This was the source:
295             ---
296             $code
297             ---
298             And the cluttery generated code is:
299             ---
300             $final_code
301             ---
302             HERE
303             }
304              
305 13         17 do {
306 3     3   25 no strict;
  3         7  
  3         1960  
307 13         20 *{"${pkg}::${sym}"} = \&$anon_sub;
  13         159  
308             };
309             }
310             }
311              
312             # create an anonymous sub in a clean environment.
313             sub _make_sub {
314 13 50   13   3738 return eval $_[0];
  4 50   4   876  
  2 50       8  
  2 50       6  
  6 50       20  
  4 50       11  
  2 50       62  
  3 50       836  
  2         32  
  3         640  
  3         14  
  0         0  
  3         11  
  4         889  
  0         0  
  4         57  
  8         48  
  1         664  
  3         116  
  2         9  
  4         894  
  5         17  
  1         4  
  4         10  
  5         20  
  2         707  
  4         117  
  6         98  
  12         380  
  0         0  
  10         42  
  10         781  
  0         0  
  10         195  
315             }
316              
317             # Takes array of pairs as argument (1 pair: ['x', 'y'])
318             # returns topological sort
319             # returns undef in case of cycles
320             sub _topo_sort {
321 22     22   37 my $pairs = shift;
322              
323 22         33 my %pairs; # all pairs ($l, $r)
324             my %npred; # number of predecessors
325 0         0 my %succ; # list of successors
326              
327 22         63 foreach my $p (@$pairs) {
328 7         16 my ( $l, $r ) = @$p;
329 7 50       30 next if defined $pairs{$l}{$r};
330 7         15 $pairs{$l}{$r}++;
331 7         14 $npred{$l} += 0;
332 7         13 ++$npred{$r};
333 7         10 push @{ $succ{$l} }, $r;
  7         24  
334             }
335              
336             # create a list of nodes without predecessors
337 22         52 my @list = grep { !$npred{$_} } keys %npred;
  13         33  
338              
339 22         35 my @return;
340 22         62 while (@list) {
341 13         27 $_ = pop @list;
342 13         21 push @return, $_;
343 13         13 foreach my $child ( @{ $succ{$_} } ) {
  13         40  
344 7 50       36 unshift @list, $child unless --$npred{$child};
345             }
346             }
347              
348 22 50       63 return (undef) if grep { $npred{$_} } keys %npred;
  13         38  
349 22         82 return @return;
350             }
351              
352             # Check the syntax of a definition
353             sub _check_syntax {
354 40     40   66 my ( $definitions, $lastsymbol ) = @_;
355 40 100       101 if ( defined $lastsymbol ) {
356 29         40 my $parsed;
357 29         37 eval {
358 29         157 $parsed = parse_from_string( $definitions->{$lastsymbol}{code} );
359             };
360 29 50       330659 if ($@) {
    50          
361 0         0 confess "Parsing of Math::SymbolicX::Inline "
362             . "section failed. Error:\n$@";
363             }
364             elsif ( not defined $parsed ) {
365 0         0 my $t = $definitions->{$lastsymbol}{code};
366 0         0 confess <
367             Parsing of Math::SymbolicX::Inline section failed due to an unknown error.
368             The offending expression (for symbol '$lastsymbol') is:
369             ---
370             $t
371             ---
372             HERE
373             }
374 29         127 $definitions->{$lastsymbol}{parsed} = $parsed;
375             }
376             }
377              
378             1;
379             __END__