File Coverage

blib/lib/Math/Expression/Evaluator/Optimizer.pm
Criterion Covered Total %
statement 50 50 100.0
branch 20 20 100.0
condition 6 6 100.0
subroutine 5 5 100.0
pod n/a
total 81 81 100.0


line stmt bran cond sub pod time code
1             package Math::Expression::Evaluator::Optimizer;
2 7     7   40 use strict;
  7         13  
  7         317  
3 7     7   39 use warnings;
  7         14  
  7         4658  
4              
5             =head1 NAME
6              
7             Math::Expression::Evaluator::Optimizer -
8             Optimize Math::Expression::Evaluator ASTs
9              
10             =head1 SYNOPSIS
11              
12             use Math::Expression::Evaluator;
13             my $m = Math::Expression::Evaluator->new("2 + 4*f");
14             $m->optimize();
15             for (0..100){
16             print $m->val({f => $_}), "\n";
17             }
18              
19             =head1 DESCRIPTION
20              
21             Math::Expression::Evaluator::Optimizer performs simple optimizations on the
22             abstract syntax tree from Math::Expression::Evaluator.
23              
24             You should not use this module directly, but interface it via
25             L.
26              
27             The following optimizations are implemented:
28              
29             =over
30              
31             =item *
32              
33             Constant sub expressions: C is simplfied to
34             C.
35              
36             =item *
37              
38             Joining of constants in mixed constant/variable expressions: C<2 + var + 3>
39             is simplified to C. Works only with sums and products (but internally
40             a C<2 - 3 + x> is represented as C<2 + (-3) + x>, so it actually works with
41             differences and divisions as well).
42              
43             =item *
44              
45             Flattening of nested sub expression: C is flattened into
46             C. Currently this is done before any other optimization and not
47             repeated.
48              
49             =back
50              
51             =head1 PERFORMANCE CONSIDERATIONS
52              
53             C currently takes two full loops through the AST, copying and
54             recreating it. If you execute C only once, calling C
55             is in fact a performance loss.
56              
57             If the expression is optimizable, and you execute it C<$n> times, you
58             usually have a net gain over unoptimized execution if C<< $n > 15 >>.
59              
60             Of course that value depends on the complexity of the expression, and how
61             well it can be reduced by the implemented optimizations.
62              
63             Your best is to always benchmark what you do. Most of the time the compiled
64             version returned by C<< ->compiled >> is much faster than the optimized
65             (and not compiled) form.
66             =cut
67              
68             my %is_commutative = (
69             '+' => 1,
70             '*' => 1,
71             );
72              
73             sub _optimize {
74 373     373   540 my ($expr, $ast) = @_;
75 373         662 return _partial_execute($expr, _flatten($ast));
76             }
77              
78             # Note: if you ever want to introduce some kind of scoping that is
79             # tied to blocks, remove the '{' here.
80             my %flattable = map { $_ => 1 } ('{', '+', '*');
81              
82             sub _flatten {
83 890     890   1110 my ($ast) = @_;
84 890 100       2533 return $ast unless ref $ast;
85              
86 322         438 my $type = shift @$ast;
87 322         557 my @new_nodes = ($type);
88 322 100       614 if ($flattable{$type}){
89             # interpolate AST nodes with the same type
90             # e.g. ['+', 2, ['+', 3, 4]] into ['+', 2, 3, 4]
91 144         241 for (map { _flatten($_) } @$ast){
  291         508  
92 291 100 100     966 if (ref $_ and $_->[0] eq $type){
93 12         34 my @inner_nodes = @$_;
94 12         16 shift @inner_nodes;
95 12         41 push @new_nodes, @inner_nodes;
96             } else {
97 279         566 push @new_nodes, $_;
98             }
99             }
100             } else {
101 178         278 push @new_nodes, map { _flatten($_) } @$ast;
  226         386  
102             }
103 322         893 return \@new_nodes;
104             }
105              
106              
107             # _parital_execute reduces constant subexpressions to a minimal form.
108             sub _partial_execute {
109 373     373   503 my ($expr, $ast) = @_;
110 373 100       675 if (ref $ast){
111 178         394 my @nodes = @$ast;
112 178         243 my $type = shift @nodes;
113 178 100 100     756 if ($type eq '=' || $type eq '$'){
114             # XXX what to do about assignments? more thoughts needed
115 34         96 return $ast;
116             }
117 144         228 my @new_nodes = ($type);
118 144         192 my $tainted = 0;
119 144         206 for my $n (@nodes){
120 289         524 push @new_nodes, _optimize($expr, $n);
121 289 100       1009 if (ref $new_nodes[-1]){
122 36         59 $tainted = 1;
123             }
124             }
125 144 100       291 if ($tainted){
126             # try to optimize things like '2 + a +3' into 'a + 5'
127             # is only allowed for commutative ops
128 29 100       74 if ($is_commutative{$type}){
129             # print STDERR "Trying commutative optimization\n";
130 27         47 my @untainted = ($type);
131 27         42 my @tainted = ($type);
132 27         72 for (1..$#new_nodes) {
133 80 100       141 if (ref $new_nodes[$_]){
134 33         59 push @tainted, $new_nodes[$_];
135             } else {
136 47         89 push @untainted, $new_nodes[$_];
137             }
138             }
139              
140 27 100       65 if (@untainted > 2) {
141             # there is something to optimize
142 17         76 push @tainted, $expr->_execute(\@untainted);
143 17         106 return \@tainted;
144             } else {
145             # 'twas all in vain
146 10         50 return \@new_nodes;
147             }
148             } else {
149 2         11 return \@new_nodes;
150             }
151             } else {
152 115         389 return $expr->_execute(\@new_nodes);
153             }
154             } else {
155 195         495 return $ast;
156             }
157              
158             }
159              
160             1;
161              
162             # vim: sw=4 ts=4 expandtab