The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl
#Copyright 2007-10 Arthur S Goldstein
use Parse::Stallion;

my %calculator_rules = (
 start_expression => A(
   'expression', 'end_of_string',
   E(sub {return $_[0]->{expression}})),
,
 expression => A(
   'term', 
    M(A('plus_or_minus', 'term')),
   E(sub {my $to_combine = $_[0]->{term};
    my $plus_or_minus = $_[0]->{plus_or_minus};
    my $value = shift @$to_combine;
    for my $i (0..$#{$to_combine}) {
      if ($plus_or_minus->[$i] eq '+') {
        $value += $to_combine->[$i];
      }
      else {
        $value -= $to_combine->[$i];
      }
    }
    return $value;
   },
  )),
,
 term => A
   ('factor', 
    M(A('times_or_divide_or_modulo', 'factor')),
    E(sub {my $to_combine = $_[0]->{factor};
    my $times_or_divide_or_modulo = $_[0]->{times_or_divide_or_modulo};
    my $value = shift @$to_combine;
    for my $i (0..$#{$to_combine}) {
      if ($times_or_divide_or_modulo->[$i] eq '*') {
        $value *= $to_combine->[$i];
      }
      elsif ($times_or_divide_or_modulo->[$i] eq '%') {
        $value = $value % $to_combine->[$i];
      }
      else {
#could check for zero
        $value /= $to_combine->[$i];
      }
    }
    return $value;
   },
  )),
,
 factor =>
   AND('fin_exp', 
    M(A('power_of', 'fin_exp')),
   E(sub {
#use Data::Dumper;print STDERR "params f are ".Dumper(\@_)."\n";
    my $to_combine = $_[0]->{fin_exp};
    my $value = pop @$to_combine;
    while ($#{$to_combine} > -1) {
      $value = (pop @$to_combine) ** $value;
    }
    return $value;
   },
  )),
,
fin_exp => OR(
    AND('left_parenthesis', 'expression', 'right_parenthesis',
     EVALUATION(sub {
       #use Data::Dumper;print STDERR "Params are ".Dumper(\@_);
       return $_[0]->{expression} }),
    ),
    (AND('number',
     EVALUATION(sub {
       #use Data::Dumper;print STDERR "params are ".Dumper(\@_);
        return $_[0]->{number} }),
    ),
   ),
  ),
,
end_of_string => LEAF({
  nsl_regex_match => qr/\z/,
 }),
,
number => LEAF({
  nsl_regex_match => qr/\s*[+-]?(\d+(\.\d*)?|\.\d+)\s*/,
 },
EVALUATION(
  sub{
   return 0 + $_[0];
  })),
left_parenthesis => LEAF({
  nsl_regex_match => qr/\s*\(\s*/,
 }),
,
right_parenthesis => LEAF({
  nsl_regex_match => qr/\s*\)\s*/,
 }),
,
power_of => LEAF({
  nsl_regex_match => qr/\s*\*\*\s*/,
 }),
,
plus_or_minus => OR(
  'plus', 'minus',
 ),
,
plus => LEAF({
  nsl_regex_match => qr/\s*\+\s*/,
 }),
,
minus => LEAF({
  nsl_regex_match => qr/\s*\-\s*/,
 }),
,
times_or_divide_or_modulo => OR(
  'times', 'divided_by', 'modulo'
 ),
,
modulo => LEAF({
  nsl_regex_match => qr/\s*\%\s*/,
 }),
,
times => LEAF({
  nsl_regex_match => qr/\s*\*\s*/,
 }),
,
divided_by => LEAF({
  nsl_regex_match => qr/\s*\/\s*/,
 }),
,
);

my $pf_count = 0;
my $pb_count = 0;
my $calculator_stallion = new Parse::Stallion(
  \%calculator_rules,
  {start_rule => 'start_expression',
  parse_forward =>
   sub {
    my $parameters = shift;
    my $input_string_ref = $parameters->{parse_this_ref};
    my $rule_definition = $parameters->{rule_info}->{$parameters->{rule_name}};
    $pf_count=1;
    my $match_rule = $rule_definition->{nsl_regex_match} ||
     $rule_definition->{leaf} ||
     $rule_definition->{l};
    if ($$input_string_ref =~ /\A($match_rule)/) {
      my $matched = $1;
      my $not_match_rule = $rule_definition->{regex_not_match};
      if ($not_match_rule) {
        if (!($$input_string_ref =~ /\A$not_match_rule/)) {
          return (0, undef);
        }
      }
      $$input_string_ref =~ s/\A($match_rule)//;
      return (1, $matched, length($matched));
    }
    return 0;
   },
  parse_backtrack =>
   sub {
    my $parameters = shift;
    my $input_string_ref = $parameters->{parse_this_ref};
    my $stored_value = $parameters->{parse_match};
    $pb_count=1;
    if (defined $stored_value) {
      $$input_string_ref = $stored_value.$$input_string_ref;
    }
   },
  length_routine => sub {
    return length(${$_[0]});
  }
});


my $x = '7+4';
my $result =
 $calculator_stallion->parse_and_evaluate($x);
print "Result is $result should be 11\n";

$x = '7*4';
$result =
 $calculator_stallion->parse_and_evaluate($x);
print "Result is $result should be 28\n";

$x = '3+7*4';
$result =
 $calculator_stallion->parse_and_evaluate($x);
print "Result is $result should be 31\n";

$result = {};
my $y = '3+-+7*4';
$x = 
 $calculator_stallion->parse_and_evaluate($y, {parse_info=>$result,
  trace => 1});

print "Result is $x should be undef\n";
print "Parse succeeded is ".$result->{parse_succeeded}." should be 0\n";

print "\nAll done\n";