Acme-AlgebraicToRPN
view release on metacpan or search on metacpan
lib/Acme/AlgebraicToRPN.pm view on Meta::CPAN
Processes $expr (an algebraic format expression) and return the
stack necessary to process it. The stack consists entirely of
variables, constants and operations. For operations, be
prepared to handle (and others, see B<Math::Symbolic> documentation):
negate
add
subtract
multiply
divide
exponentiate
sin
cos
tan
cot
asin
acos
atan
atan2
acot
sinh
cosh
asinh
acosh
Plus any that you may add in constructor [1].
undef is returned if the parens don't balance. That's all the
checking we do.
[1] If you supply a custom function, you can supply arguments
to it. When you see your function name on the returned stack,
the next thing on the stack is the I<number> of arguments,
and then the arguments themselves. For example, let's say
you registered your function 'foo' (in constructor)
and you gave B<rpn> this equation: 4*foo(a,3)
You'd get back this:
4 a 3 2 foo multiply
=cut
sub rpn {
my ($self, $algebraic) = @_;
$algebraic =~ s/\s+//g;
# ensure parens match
my $open = $algebraic =~ tr/(/(/;
my $close = $algebraic =~ tr/)/)/;
return unless $open == $close;
#my $tree = Math::Symbolic->parse_from_string($algebraic);
my $tree;
my $rpn;
eval q(
$tree = $.parser->parse($algebraic);
$rpn = $tree->to_string('prefix');
);
if ($@) {
print STDERR "$.Class - equation didn't parse; did you forget ",
"to add a userFunc?\n";
return undef;
}
$rpn =~ s/\s//g;
./_Eval($rpn);
my @result = ./_Cleanup();
# reset, ready for next equation
$.stack = [];
return @result;
}
=head2 B<rpn_as_string>
$stack = $al->rpn($expr);
Same as B<rpn>, but returns as a comma-separated list. Split on
commas, and you have your stack to be processed.
=cut
sub rpn_as_string {
my ($self, $algebraic) = @_;
my @result = ./rpn($algebraic);
return join(",", @result);
}
sub _Cleanup {
my ($self) = @_;
my @Stack;
map {
$_ =~ s/^,//;
if ($_ ne '') {
my (@c) = split(',', $_);
if (@c) {
s/\s//g foreach @c;
push(@Stack, @c);
}
else {
push(@Stack, $_);
}
}
} @{$.stack};
return @Stack;
}
sub _Eval {
my ($self, $expr) = @_;
return unless defined $expr;
#print "Evaling $expr\n";
if ($expr =~ /(.+?),(.+)/) {
my $L = $1;
my $R = $2;
if ($L =~ /^\w+$/ && $R =~ /$RE{balanced}{-parens=>'()'}/) {
#print "HERE $L\n";
push(@{$.stack}, $L);
}
}
if ($expr =~ /(\w+)($RE{balanced}{-parens=>'()'})(.*)/) {
my $op = $1;
my $p = $2;
my $r = $3;
my $core = substr($p, 1, length($p)-2);
if (defined $.userFunc && defined $.userFunc{$op}) {
# count # of commas in arg list
my $na = $core =~ tr/,/,/;
# bump by one
$na++;
# add # of aguments on
$core = qq($core,$na);
}
./_Eval($core);
push(@{$.stack}, $core)
unless $core =~ /$RE{balanced}{-parens=>'()'}/;
push(@{$.stack}, $op);
./_Eval($r)
if defined $r && $r =~ /$RE{balanced}{-parens=>'()'}/;
push(@{$.stack}, $r)
if defined $r && !($r =~ /$RE{balanced}{-parens=>'()'}/);
}
}
=head2 B<check>
$ok = $al->check(\@stack, @expected);
Checks result of RPN conversion. @stack is what the B<rpn> function
returned, and @expected is what you expected the result to be. This
is kind of a diagnostic routine for testing.
Returns 1 if both @stack and @expected were the same, 0 if not.
=cut
sub check {
my ($self, $ref, @result) = @_;
my @shouldbe = @$ref;
return 0 unless @shouldbe == @result;
my $same = 1;
map {
my $sb = shift(@shouldbe);
$same = 0 unless $sb eq $_;
} @result;
return $same;
}
1; # End of Acme::AlgebraicToRPN
( run in 1.621 second using v1.01-cache-2.11-cpan-99c4e6809bf )