File Coverage

blib/lib/Acme/AlgebraicToRPN.pm
Criterion Covered Total %
statement 103 108 95.3
branch 22 30 73.3
condition 7 12 58.3
subroutine 14 15 93.3
pod 4 4 100.0
total 150 169 88.7


line stmt bran cond sub pod time code
1             package Acme::AlgebraicToRPN;
2              
3 3     3   68193 use warnings;
  3         8  
  3         112  
4 3     3   19 use strict;
  3         7  
  3         227  
5              
6             our $VERSION = '0.02';
7              
8             =head1 NAME
9              
10             Acme::AlgebraicToRPN - convert algebraic notation to sane RPN
11              
12             =head1 VERSION
13              
14             Version 0.02
15              
16             =head1 SYNOPSIS
17              
18             $rpn = Acme::AlgebraicToRPN->new;
19             @RPN = $rpn->eval($equation);
20              
21             =head1 DESCRIPTION
22              
23             Given a string with algebraic notation, convert to RPN, which is
24             what any crappy dime store calculator needs to do anyway.
25              
26             Doesn't really process anything, that's up to you. You will get an
27             array back with all of the variables and operations in RPN format.
28             So that 3+4 will come back as
29              
30             3
31             4
32             add
33              
34             Possible future extensions will be to allow you to actually process
35             this via hooks that allow specifications of how to handle foreign
36             functions. But for my purposes, the array is good enough, as I am
37             passing this on to a C program to do some serious number crunching.
38              
39             Additionally, you can specify (via the constructor) the names of
40             your own functions. See below.
41              
42             =head1 ACKNOWLEDGEMENT
43              
44             The Hewlett Packard Company and the HP 35, my first real calculator,
45             and Steffen Mueller for the Math::Symbolic code.
46              
47             =head1 AUTHOR
48              
49             X Cramps, C<< >>
50              
51             =head1 BUGS
52              
53             Please report any bugs or feature requests to
54             C, or through the web interface at
55             L.
56             I will be notified, and then you'll automatically be notified of progress on
57             your bug as I make changes.
58              
59             =head1 SUPPORT
60              
61             You can find documentation for this module with the perldoc command.
62              
63             perldoc Acme::AlgebraicToRPN
64              
65             You can also look for information at:
66              
67             =over 4
68              
69             =item * AnnoCPAN: Annotated CPAN documentation
70              
71             L
72              
73             =item * CPAN Ratings
74              
75             L
76              
77             =item * RT: CPAN's request tracker
78              
79             L
80              
81             =item * Search CPAN
82              
83             L
84              
85             =back
86              
87             =head1 ACKNOWLEDGEMENTS
88              
89             =head1 COPYRIGHT & LICENSE
90              
91             Copyright 2009 X Cramps, all rights reserved.
92              
93             This program is free software; you can redistribute it and/or modify it
94             under the same terms as Perl itself.
95              
96             =cut
97              
98             package Acme::AlgebraicToRPN;
99              
100 3     3   16 use strict;
  3         14  
  3         91  
101 3     3   15 use warnings;
  3         4  
  3         91  
102 3     3   2743 use Regexp::Common;
  3         10868  
  3         18  
103 3     3   212194 use Perl6::Attributes;
  3         97396  
  3         23  
104 3     3   7585 use Math::Symbolic;
  3         395318  
  3         220  
105 3     3   3097 use Math::SymbolicX::ParserExtensionFactory;
  3         3152  
  3         27  
106              
107             =head2 B
108              
109             $al = Acme::AlgebraicToRPN->new(%opts);
110              
111             %opts (optional) can be:
112              
113             userFunc - user functions, as array reference
114              
115             If you had a user function box and fft, you'd need to
116             specify them like this:
117              
118             $al = Acme::AlgebraicToRPN->new(userFunc =>
119             [qw(box fft)]);
120              
121             =cut
122              
123             sub new {
124 1     1 1 328 my ($class, %opts) = @_;
125 1         3 my $self = \%opts;
126 1         3 bless $self, $class;
127 1         7 $.stack = [];
128 1         18 $.parser = Math::Symbolic::Parser->new;
129 1         907 $.Class = $class;
130 1 50       5 if (defined $.userFunc) {
131 1         2 my @uf = @{$.userFunc};
  1         5  
132 1         2 my %uf;
133 1         2 map { $uf{$_} = 1 } @uf;
  3         9  
134 1         3 $.userFunc = \%uf;
135 1         1 my %x;
136 3         4 map {
137 1         2 my $proc = $_;
138             $x{$_} = sub {
139 10     10   23542 my $argumentstring = shift;
140 10         76 return Math::Symbolic::Constant->new(
141             qq($proc($argumentstring))
142             );
143 3         17 };
144             } @uf;
145 1         13 Math::SymbolicX::ParserExtensionFactory->add_private_functions(
146             $.parser,
147             %x
148             );
149             }
150 1         112 return $self;
151             }
152              
153             =head2 B
154              
155             @stack = $al->rpn($expr);
156              
157             Processes $expr (an algebraic format expression) and return the
158             stack necessary to process it. The stack consists entirely of
159             variables, constants and operations. For operations, be
160             prepared to handle (and others, see B documentation):
161              
162             negate
163             add
164             subtract
165             multiply
166             divide
167             exponentiate
168             sin
169             cos
170             tan
171             cot
172             asin
173             acos
174             atan
175             atan2
176             acot
177             sinh
178             cosh
179             asinh
180             acosh
181              
182             Plus any that you may add in constructor [1].
183              
184             undef is returned if the parens don't balance. That's all the
185             checking we do.
186              
187             [1] If you supply a custom function, you can supply arguments
188             to it. When you see your function name on the returned stack,
189             the next thing on the stack is the I of arguments,
190             and then the arguments themselves. For example, let's say
191             you registered your function 'foo' (in constructor)
192             and you gave B this equation: 4*foo(a,3)
193              
194             You'd get back this:
195             4 a 3 2 foo multiply
196              
197             =cut
198              
199             sub rpn {
200 20     20 1 26563 my ($self, $algebraic) = @_;
201 20         81 $algebraic =~ s/\s+//g;
202             # ensure parens match
203 20         64 my $open = $algebraic =~ tr/(/(/;
204 20         53 my $close = $algebraic =~ tr/)/)/;
205 20 50       74 return unless $open == $close;
206             #my $tree = Math::Symbolic->parse_from_string($algebraic);
207 20         33 my $tree;
208             my $rpn;
209              
210 20         2476 eval q(
211             $tree = $.parser->parse($algebraic);
212             $rpn = $tree->to_string('prefix');
213             );
214              
215 20 100       1860 if ($@) {
216 1         171 print STDERR "$.Class - equation didn't parse; did you forget ",
217             "to add a userFunc?\n";
218 1         15 return undef;
219             }
220              
221 19         106 $rpn =~ s/\s//g;
222 19         90 ./_Eval($rpn);
223 19         2613 my @result = ./_Cleanup();
224             # reset, ready for next equation
225 19         58 $.stack = [];
226 19         244 return @result;
227             }
228              
229             =head2 B
230              
231             $stack = $al->rpn($expr);
232              
233             Same as B, but returns as a comma-separated list. Split on
234             commas, and you have your stack to be processed.
235              
236             =cut
237              
238             sub rpn_as_string {
239 0     0 1 0 my ($self, $algebraic) = @_;
240 0         0 my @result = ./rpn($algebraic);
241 0         0 return join(",", @result);
242             }
243              
244             sub _Cleanup {
245 19     19   59 my ($self) = @_;
246 19         32 my @Stack;
247 127         191 map {
248 19         58 $_ =~ s/^,//;
249 127 50       290 if ($_ ne '') {
250 127         321 my (@c) = split(',', $_);
251 127 50       297 if (@c) {
252 127         400 s/\s//g foreach @c;
253 127         516 push(@Stack, @c);
254             }
255             else {
256 0         0 push(@Stack, $_);
257             }
258             }
259 19         27 } @{$.stack};
260 19         114 return @Stack;
261             }
262              
263             sub _Eval {
264 165     165   8888 my ($self, $expr) = @_;
265 165 50       384 return unless defined $expr;
266             #print "Evaling $expr\n";
267 165 100       719 if ($expr =~ /(.+?),(.+)/) {
268 145         276 my $L = $1;
269 145         232 my $R = $2;
270 145 100 100     757 if ($L =~ /^\w+$/ && $R =~ /$RE{balanced}{-parens=>'()'}/) {
271             #print "HERE $L\n";
272 8         967 push(@{$.stack}, $L);
  8         28  
273             }
274             }
275              
276 165 100       5536 if ($expr =~ /(\w+)($RE{balanced}{-parens=>'()'})(.*)/) {
277 73         10156 my $op = $1;
278 73         130 my $p = $2;
279 73         128 my $r = $3;
280 73         185 my $core = substr($p, 1, length($p)-2);
281 73 100 66     437 if (defined $.userFunc && defined $.userFunc{$op}) {
282             # count # of commas in arg list
283 9         17 my $na = $core =~ tr/,/,/;
284             # bump by one
285 9         13 $na++;
286             # add # of aguments on
287 9         25 $core = qq($core,$na);
288             }
289 73         180 ./_Eval($core);
290 73 100       9469 push(@{$.stack}, $core)
  46         5487  
291             unless $core =~ /$RE{balanced}{-parens=>'()'}/;
292 73         3776 push(@{$.stack}, $op);
  73         195  
293 73 50 33     437 ./_Eval($r)
294             if defined $r && $r =~ /$RE{balanced}{-parens=>'()'}/;
295 73 50 33     9765 push(@{$.stack}, $r)
  0         0  
296             if defined $r && !($r =~ /$RE{balanced}{-parens=>'()'}/);
297             }
298             }
299              
300             =head2 B
301              
302             $ok = $al->check(\@stack, @expected);
303              
304             Checks result of RPN conversion. @stack is what the B function
305             returned, and @expected is what you expected the result to be. This
306             is kind of a diagnostic routine for testing.
307              
308             Returns 1 if both @stack and @expected were the same, 0 if not.
309              
310             =cut
311              
312             sub check {
313 19     19 1 168 my ($self, $ref, @result) = @_;
314 19         61 my @shouldbe = @$ref;
315 19 100       86 return 0 unless @shouldbe == @result;
316 8         15 my $same = 1;
317 22         32 map {
318 8         12 my $sb = shift(@shouldbe);
319 22 50       72 $same = 0 unless $sb eq $_;
320             } @result;
321 8         22 return $same;
322             }
323              
324             1; # End of Acme::AlgebraicToRPN