package Math::Expression::Evaluator; @ISA = qw(Exporter); @EXPORT = qw(evaluate); $VERSION = '0.01'; use strict; use warnings; use Carp; my %op_eval = ( '^' => sub {$_[0] ** $_[1]}, '+' => sub {$_[0] + $_[1]}, '-' => sub {$_[0] - $_[1]}, '*' => sub {$_[0] * $_[1]}, '/' => sub {$_[0] / $_[1]}, ); my %func_eval = ( abs => sub { abs $_[0] }, int => sub { int $_[0] }, sqrt => sub { sqrt $_[0] }, ); my ($func_re) = map qr{$_}, join '|', keys %func_eval; my $oper_re = qr{[()/*^+-]}; my $numb_re = qr{[+-]?(?:\d+(?:\.\d*)?|\.\d+)}; my $parser = qr{($func_re|$numb_re|$oper_re)}; sub evaluate { my @stack = @_ == 1 ? parse(@_) : @_; return $_[0] if @stack == 1; 0 while fix_op(\@stack); 0 while reduce_func(\@stack); 0 while reduce_paren(\@stack); for my $op (qw[^ * / + -]) { 0 while reduce_op($op, \@stack); } croak "Unable to reduce to a number: '@stack'" if @stack != 1; return evaluate($stack[0]); } sub parse { my $expr = shift @_; my @part = $expr =~ /$parser/g; parse_error_check($expr, \@part); return @part; } sub parse_error_check { my ($expr, $part) = @_; $expr =~ s/$parser//g; croak "Unparseable parts: '$expr'" if $expr !~ /^\s*$/; croak "Not a number: '$part->[0]'" if @$part == 1 && ! is_num($part->[0]); } sub is_num { return $_[0] =~ /$numb_re/; } sub fix_op { my $stack = shift @_; for (1 .. $#$stack) { my $atom = $stack->[$_]; next if ! is_num($atom); if ($atom =~ s/^([+-])//) { my $op = $1; next if $stack->[$_ - 1] =~ m{[(*/+^-]}; splice(@$stack, $_, 1, $op, $atom); return 1; } } return; } sub reduce_func { my $stack = shift @_; for (0 .. $#$stack) { my $atom = $stack->[$_]; next if ! is_func($atom); croak "Function $atom require parens" if $stack->[$_ + 1] ne '('; reduce_paren($stack, $_ + 1); splice(@$stack, $_, 2, calculate($atom, $stack->[$_ + 1])); return 1; } } sub is_func { return exists $func_eval{$_[0]}; } sub calculate { my ($key, $x, $y, $val) = @_; eval { $val = is_func($key) ? $func_eval{$key}->($x) : $op_eval{$key}->($x, $y) }; croak "Error: $@" if $@; return $val; } sub reduce_paren { my ($stack, $start) = @_; $start ||= 0; my ($beg, $open); for ($start .. $#$stack) { my $atom = $stack->[$_]; next if $atom ne '(' && $atom ne ')'; $open += $atom eq ')' ? -1 : 1; $beg = $_ if ! defined $beg && $atom eq '('; next if $open; my $len = $_ - $beg + 1; splice(@$stack, $beg, $len, evaluate(@{$stack}[$beg + 1 .. $_ - 1])); return 1; } croak "Unbalanced Parens" if $open; } sub reduce_op { my ($op, $stack) = @_; return if @$stack < 3; for (0 .. $#$stack - 2) { my ($prev, $curr, $next) = @{$stack}[$_ .. $_ + 2]; next if $curr ne $op; croak "Error: '$prev $op $next'" if ! is_num($prev) || ! is_num($next); splice(@$stack, $_, 3, calculate($op, $prev, $next)); return 1; } return; } 'This statement is false'; #### #!/usr/bin/perl use strict; use warnings; use Parse::RecDescent; use Data::Dumper; my $grammar = q{ evaluate : ADD_SUB ADD_SUB : MULT_DIV_MOD ADD_SUB_OP ADD_SUB { [@item[1,2,3]] } | MULT_DIV_MOD ADD_SUB_OP : '+' | '-' MULT_DIV_MOD : GROUP MULT_DIV_MOD_OP MULT_DIV_MOD { [@item[1,2,3]] } | GROUP MULT_DIV_MOD_OP : '*' | '/' | '%' GROUP : '(' ADD_SUB ')' { $item[2] } | NUMBER NUMBER : INTEGER | FLOAT | NAN INTEGER : /[+-]?\d+/ FLOAT : /([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?/ NAN : /(Inf(inity)?|NaN)/i }; my $parser = new Parse::RecDescent $grammar; print Dumper $parser->evaluate('42 - 5 + 1'); # Sees the result as 42 - (5 + 1) #### #!/usr/bin/perl use strict; use warnings; use Parse::RecDescent; use Data::Dumper; my $grammar = q{ evaluate : ADD_SUB #ADD_SUB : MULT_DIV_MOD ADD_SUB_OP ADD_SUB # { [@item[1,2,3]] } # | MULT_DIV_MOD # Reverse to ADD_SUB : ADD_SUB ADD_SUB_OP MULT_DIV_MOD | MULT_DIV_MOD # Use following formula to eliminate left recursion # A : A x | y -> A : y R, R : x R | e # Let A = ADD_SUB # Let x = ADD_SUB_OP MULT_DIV_MOD # let y = MULT_DIV_MOD ADD_SUB : MULT_DIV_MOD ADD_SUB_TAIL { [@item[1,2]] } ADD_SUB_TAIL : ADD_SUB_OP MULT_DIV_MOD ADD_SUB_TAIL { [@item[1..3]] } | ADD_SUB_OP : '+' | '-' # Same as above MULT_DIV_MOD : GROUP MULT_DIV_MOD_TAIL { [@item[1,2]] } MULT_DIV_MOD_TAIL : MULT_DIV_MOD_OP GROUP MULT_DIV_MOD_TAIL { [@item[1..3]] } | MULT_DIV_MOD_OP : '*' | '/' | '%' GROUP : '(' ADD_SUB ')' { $item[2] } | NUMBER NUMBER : INTEGER | FLOAT | NAN INTEGER : /[+-]?\d+/ FLOAT : /([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?/ NAN : /(Inf(inity)?|NaN)/i }; my $parser = new Parse::RecDescent $grammar; print Dumper $parser->evaluate('42 - 5 + 1'); #### #!/usr/bin/perl use strict; use warnings; use Parse::RecDescent; my %dispatch = ( '+' => sub { $_[0] + $_[1] }, '-' => sub { $_[0] - $_[1] }, '*' => sub { $_[0] * $_[1] }, '/' => sub { $_[0] / $_[1] }, '^' => sub { $_[0] ** $_[1] }, 'abs' => sub { abs $_[0] }, 'sqrt' => sub { sqrt $_[0] }, ); sub calculate { my $rule = shift @_; if ($rule eq 'FUNCTION') { my ($func, $x) = @_; my $val = eval { $dispatch{$func}->($x); }; die $@ if $@; return $val; } my @atom = @{ shift @_ }; my $val = shift @atom; while (@atom) { my ($op, $num) = splice(@atom, 0, 2); eval { $val = $dispatch{$op}->($val, $num); }; die $@ if $@; } return $val; } my $grammar = <<'__GRAMMAR__'; evaluate : EXPR /\Z/ { $item[1] } EXPR : ADD_SUB { $item[1] } ADD_SUB : { main::calculate( @item ) } ADD_SUB_OP : '+' | '-' MUL_DIV_MOD : { main::calculate( @item ) } MUL_DIV_MOD_OP : '*' | '/' | '%' POW : { main::calculate( @item ) } POW_OP : '^' FUNCTION : FUNC_NAME GROUP { main::calculate( @item ) } | GROUP FUNC_NAME : 'abs' | 'sqrt' GROUP : '(' EXPR ')' { $item[2] } | NUMBER NUMBER : FLOAT | INTEGER | NAN INTEGER : /[+-]?\d+/ FLOAT : /([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?/ NAN : /(Inf(inity)?|NaN)/i __GRAMMAR__ my $parser = Parse::RecDescent->new($grammar) or die("Bad grammar\n"); my $answer = $parser->evaluate('11 - (4 + 4)^3 * sqrt(5 * (6 - 1)) + abs(-3)'); print defined $answer ? $answer : 'Invalid expression'; #### #!/usr/bin/perl use strict; use warnings; use Parse::RecDescent; my %eval = ( disp => \&eval_dispatch, term => sub { $_[0] -> [1] }, '+' => sub { $_[0] + $_[1] }, '-' => sub { $_[0] - $_[1] }, '*' => sub { $_[0] * $_[1] }, '/' => sub { $_[0] / $_[1] }, '%' => sub { $_[0] % $_[1] }, ); sub eval_node { local *_ = \$_[0]; return $eval{disp}->($_->[0], $_); } sub eval_dispatch { my ($op, $node) = @_; return $eval{$op}->($node) if $op eq 'term'; my $x = eval_node($node->[1]); my $y = eval_node($node->[2]); return $eval{$op}->($x, $y); } sub treeify { my $t = shift @_; $t = [ shift @_, $t, shift @_ ] while @_; return $t; } my $grammar = <<'__END_OF_GRAMMAR__'; build : expr /\Z/ { $item[1] } # Just an alias expr : sum # vvv lowest precedence sum : { main::treeify(@{$item[1]}) } prod : { main::treeify(@{$item[1]}) } # ^^^ highest precedence term : '(' expr ')' { $item[3] } | UNSIGN_INT { [ @item ] } # Tokens UNSIGN_INT : /\d+/ SUM : '+' | '-' PROD : '*' | '/' | '%' __END_OF_GRAMMAR__ my $parser = Parse::RecDescent->new($grammar) or die("Bad grammar\n"); my $tree = $parser->build('11 - 6 + 4'); my $eval = eval_node($tree); print "$eval\n"; #### #!/usr/bin/perl use strict; use warnings; use Data::Dumper; use Parse::Yapp; my $grammar = join '', ; my $parser = Parse::Yapp->new(input => $grammar); my $yapptxt = $parser->Output(classname => 'Calc'); eval $yapptxt; # normally written to a file my $calc = Calc->new(); $calc->Ingest("11 - (4 + 4)^3 * sqrt(5 * (6 - 1)) + abs(-3)\n"); my $output = $calc->YYParse(yylex => \&Calc::Lexer); print $output; __DATA__ %left '-' '+' %left '*' '/' '%' %right '^' %nonassoc 'sqrt' 'abs' %% stack : | stack expr '\n' { push @{$_[1]}, $_[2]; $_[1][0] }; expr : add | del | mul | div | mod | pow | grp | sqrt | abs | NUM; add : expr '+' expr { $_[1] + $_[3] }; del : expr '-' expr { $_[1] - $_[3] }; mul : expr '*' expr { $_[1] * $_[3] }; div : expr '/' expr { $_[1] / $_[3] }; mod : expr '%' expr { $_[1] % $_[3] }; pow : expr '^' expr { $_[1] ** $_[3] }; grp : '(' expr ')' { $_[2] }; abs : 'abs' grp { abs($_[2]) }; sqrt : 'sqrt' grp { sqrt($_[2]) }; %% sub Lexer { my $parser = shift @_; local *_ = \$parser->YYData->{INPUT}; s/^[ \t]+//; # leading non-newline whitespace if (s/^(([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?)//) { return ('NUM', $1); # borrowed from Scalar::Util } return ($1, $1) if s/^(sqrt|abs)//; return ($1, $1) if s/^(.)//s; } sub Ingest { my $self = shift @_; $self->YYData->{INPUT} = $_[0]; } #### #!/usr/bin/perl use strict; use warnings; use Data::Dumper; use Parse::Earley; my $parser = Parse::Earley->new(); my $grammar = <<'__GRAMMAR__'; input: expr expr: expr '+' mul_term | expr '-' mul_term | mul_term mul_term: mul_term '*' exp | mul_term '/' exp | exp exp: term '^' exp | term term: '(' expr ')' | /\d+/ { $_ < 256 } __GRAMMAR__ my $str = '1 + 2 - 3'; $parser->grammar($grammar); $parser->start('input'); $parser->advance($str) for 1..6; my ($tree) = $parser->matches_all($str, 'input'); print Dumper($tree);