File Coverage

blib/lib/Math/Symbolic/Compiler.pm
Criterion Covered Total %
statement 65 71 91.5
branch 13 20 65.0
condition 3 5 60.0
subroutine 10 10 100.0
pod 3 3 100.0
total 94 109 86.2


line stmt bran cond sub pod time code
1             =encoding utf8
2              
3             =head1 NAME
4              
5             Math::Symbolic::Compiler - Compile Math::Symbolic trees to Perl code
6              
7             =head1 SYNOPSIS
8              
9             use Math::Symbolic::Compiler;
10            
11             # A tree to compile
12             my $tree = Math::Symbolic->parse_from_string('a^2 + b * c * 2');
13            
14             # The Math::Symbolic::Variable 'a' will be evaluated to $_[1], etc.
15             my $vars = [qw(b a c)];
16            
17             my ($closure, $code, $trees) =
18             Math::Symbolic::Compiler->compile($tree, $vars);
19            
20             print $closure->(2, 3, 5); # (b, a, c)
21             # prints 29 (= 3^2 + 2 * 5 * 2)
22            
23             # or:
24             ($closure, $trees) =
25             Math::Symbolic::Compiler->compile_to_sub($tree, $vars);
26            
27             ($code, $trees) = Math::Symbolic::Compiler->compile_to_code($tree, $vars);
28              
29             =head1 DESCRIPTION
30              
31             This module allows one to compile Math::Symbolic trees to Perl code and/or
32             anonymous subroutines whose arguments will be positionally mapped to the
33             variables of the compiled Math::Symbolic tree.
34              
35             The reason you'd want to do this is that evaluating a Math::Symbolic tree to
36             its numeric value is extremely slow. So is compiling, but once you've done all
37             necessary symbolic calculations, you can take advantage of the speed gain
38             of invoking a closure instead of evaluating a tree.
39              
40             =head2 UNCOMPILED LEFTOVER TREES
41              
42             Not all, however, is well in the land of compiled Math::Symbolic trees.
43             There may occasionally be trees that cannot be compiled (such as a derivative)
44             which need to be included into the code as trees. These trees will be
45             returned in a referenced array by the compile*() methods. The closures
46             will have access to
47             the required trees as a special variable '@_TREES inside the closure's scope,
48             so you need not worry about them in that case. But if you plan to use the
49             generated code itself, you need to supply an array named @_TREES that
50             contains the trees as returned by the compile*() methods in the scope of
51             the eval() you evaluate the code with.
52              
53             Note that you give away all performance benefits compiling the tree might have
54             if the closure contains uncompiled trees. You can tell there are any by
55             checking the length of the referenced array that contains the trees. If it's
56             0, then there are no trees left to worry about.
57              
58             =head2 AVOIDING LEFTOVER TREES
59              
60             In most cases, this is pretty simple. Just apply all derivatives in the tree
61             to make sure that there are none left in the tree. As of version 0.130, there
62             is no operator except derivatives that cannot be compiled. There may, however,
63             be some operators you cannot get rid of this easily some time in the future.
64             If you have problems getting a tree to compile, try using the means of
65             simplification provided by Math::Symbolic::* to get a simpler tree for
66             compilation.
67              
68             =head2 EXPORT
69              
70             None by default, but you may choose to import the compile(), compile_to_sub(),
71             and compile_to_code() subroutines to your namespace using the standard
72             Exporter semantics including the ':all' tag.
73              
74             =head1 SUBROUTINES
75              
76             =cut
77              
78             package Math::Symbolic::Compiler;
79              
80 23     23   408 use 5.006;
  23         87  
  23         968  
81 23     23   161 use strict;
  23         52  
  23         710  
82 23     23   212 use warnings;
  23         45  
  23         653  
83              
84 23     23   117 use Math::Symbolic::ExportConstants qw/:all/;
  23         49  
  23         16730  
85              
86             our @ISA = qw(Exporter);
87              
88             our %EXPORT_TAGS = (
89             'all' => [
90             qw(
91             compile
92             compile_to_sub
93             compile_to_code
94             )
95             ]
96             );
97              
98             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
99              
100             our @EXPORT = qw();
101              
102             our $VERSION = '0.612';
103              
104             =head2 ($code, $trees) = compile_to_code($tree, $vars)
105              
106             The compile_to_code() class method takes one mandatory argument which is
107             the Math::Symbolic tree to be compiled. Second argument is optional
108             and an array reference to an array of variable mappings.
109             See L for details on how this works.
110              
111             compile_to_code() returns a string and an array reference. The string
112             contains the compiled Perl code that uses the values stored in @_ as described
113             in the section on positional variable passing. It also accesses a special
114             variable @_TREES if there were any sub-trees (inside the tree that has been
115             compiled) that were impossible to compile. The array reference returned by this
116             method contains any of the aforementioned trees that failed to compile.
117              
118             If there are any such trees that did not compile, you may put them into the
119             @_TREES variable in scope of the eval() that evaluates the compiled code
120             in the same order that they were returned by this method. If you do that, the
121             code will run and determine the value of the tree at run-time. Needless to say,
122             that is slow.
123              
124             =cut
125              
126             sub compile_to_code {
127 21     21 1 3131 my $tree = shift;
128 21 50 33     186 $tree = shift if not ref $tree and $tree eq __PACKAGE__;
129              
130 21   100     65 my $order = shift || [];
131 21         32 my %order;
132 21 50       87 if (ref($order) eq 'HASH') {
    50          
133 0         0 %order = %$order;
134             }
135             elsif (ref($order) eq 'ARRAY') {
136 21         28 my $count = 0;
137 21         56 %order = map { ( $_, $count++ ) } @$order;
  9         28  
138             }
139              
140 23     23   138 no warnings 'recursion';
  23         62  
  23         19308  
141              
142 21         89 my $vars = [ $tree->explicit_signature() ];
143              
144 21         44 my %vars;
145             my @not_placed;
146 21         47 foreach (@$vars) {
147 51         69 my $pos = $order{$_};
148 51 100       93 if ( defined $pos ) {
149 9         19 $vars{$_} = $pos;
150             }
151             else {
152 42         159 push @not_placed, $_;
153             }
154             }
155              
156 21         38 my $count = 0;
157 21         54 foreach ( sort @not_placed ) {
158 42         188 $vars{$_} = @$vars - @not_placed + $count++;
159             }
160              
161             # The user is to do that himself. Left in to show that it would be
162             # a sensible (if slow) thing to do.
163             # $tree = $tree->simplify();
164             # $tree = $tree->apply_derivatives();
165             # $tree = $tree->simplify();
166              
167 21         49 my @trees;
168              
169 21         68 my $code = _rec_ms_to_sub( $tree, \%vars, \@trees );
170              
171 21         153 return ( $code, \@trees );
172             }
173              
174             =head2 ($sub, $trees) = compile_to_sub($tree, $vars)
175              
176             The compile_to_sub() class method takes one mandatory argument which is
177             the Math::Symbolic tree to be compiled. Second argument is optional
178             and an array reference to an array of variable mappings.
179             See L for details on how this works.
180              
181             compile_to_sub() returns a list of two elements, the first being the compiled
182             anonymous subroutine. For details on the second element, please refer to
183             the docs on the compile_to_code() subroutine.
184              
185             =cut
186              
187             sub compile_to_sub {
188 10     10 1 173 my ( $code, $trees ) = Math::Symbolic::Compiler::compile_to_code(@_);
189 10         193 my $sub = _compile_sub( 'sub {' . $code . '}', @$trees );
190 10         55 return ( $sub, $trees );
191             }
192              
193             =head2 ($sub, $code, $trees) = compile($tree, $vars)
194              
195             The compile() class method takes one mandatory argument which is
196             the Math::Symbolic tree to be compiled. Second argument is optional
197             and an array reference to an array of variable mappings.
198             See L for details on how this works.
199              
200             compile() returns a list of three elements, the first being the compiled
201             anonymous subroutine, the second being the compiled code. For details on the
202             second and third elements, please refer to the docs on the compile_to_code()
203             subroutine.
204              
205             =cut
206              
207             sub compile {
208 1     1 1 1539 my ( $code, $trees ) = Math::Symbolic::Compiler::compile_to_code(@_);
209 1         6 my $sub = _compile_sub( 'sub {' . $code . '}', @$trees );
210 1         10 return ( $sub, $code, $trees );
211             }
212              
213             sub _compile_sub {
214 11     11   19 my @_TREES;
215 11 50       240 @_TREES = @_[ 1 .. $#_ ] if @_ > 1;
216 11         1884 my $sub = eval $_[0];
217 11 50       38 die "$@" if $@;
218 11         28 return $sub;
219             }
220              
221             sub _rec_ms_to_sub {
222 349     349   398 my $tree = shift;
223 349         362 my $vars = shift;
224 349         468 my $trees = shift;
225              
226 349         1079 my $code = '';
227 349         1052 my $ttype = $tree->term_type();
228              
229 349 100       884 if ( $ttype == T_CONSTANT ) {
    100          
230 83         215 $code .= $tree->value();
231             }
232             elsif ( $ttype == T_VARIABLE ) {
233 95         254 $code .= '$_[' . $vars->{ $tree->name() } . ']';
234             }
235             else {
236 171         551 my $type = $tree->type();
237 171         322 my $otype = $Math::Symbolic::Operator::Op_Types[$type];
238 171         261 my $app = $otype->{application};
239 171 50       349 if ( ref($app) eq 'CODE' ) {
240 0         0 push @$trees, $tree->new();
241 0         0 my $arg_str = join( ', ',
242 0         0 map { "'$_' => \$_[" . $vars->{$_} . ']' } keys %$vars );
243 0         0 my $index = $#$trees;
244 0         0 $code .= <
245             (\$_TREES[$index]->value($arg_str))
246             HERE
247             }
248             else {
249 171         1235 my @app = split /\$_\[(\d+)\]/, $app;
250 171 50       699 if ( @app > 1 ) {
251 171         578 for ( my $i = 1 ; $i < @app ; $i += 2 ) {
252 328         1336 $app[$i] = '('
253             . _rec_ms_to_sub( $tree->{operands}[ $app[$i] ],
254             $vars, $trees )
255             . ')';
256             }
257             }
258 171         771 $code .= join '', @app;
259             }
260             }
261 349         1555 return $code;
262             }
263              
264             1;
265             __END__