The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#!/usr/bin/perl
#Copyright 2009 Arthur S Goldstein
use Parse::Stallion::EBNF;

my $calculator=<<'END';

 start_expression = expression;

 expression = (term {plus_or_minus term})
  S{my $value = shift @$term;
    for my $i (0..$#{$term}) {
      if ($plus_or_minus->[$i] eq '+') {
        $value += $term->[$i];
      }
      else {
        $value -= $term->[$i];
      }
    }
    return $value;}S ;

 term = (factor {times_or_divide_or_modulo factor})
  S{my $value = shift @$factor;
    for my $i (0..$#{$factor}) {
      if ($times_or_divide_or_modulo->[$i] eq '*') {
        $value *= $factor->[$i];
      }
      elsif ($times_or_divide_or_modulo->[$i] eq '%') {
        $value = $value % $factor->[$i];
      }
      else {
#could check for zero
        $value /= $factor->[$i];
      }
    }
    return $value;
   }S ;

 factor = (fin_exp {power_of fin_exp})
    S{
    my $value = pop @$fin_exp;
    while ($#{$fin_exp} > -1) {
      $value = (pop @$fin_exp) ** $value;
    }
    return $value;
   }S ;

 fin_exp = 
   ((left_parenthesis expression right_parenthesis) S{$expression}S)
    | ((number) S{$number}S) ;

 number =
  (qr/\s*[+-]?(\d+(\.\d*)?|\.\d+)\s*/) S{return $_}S;

 left_parenthesis = qr/\s*\(\s*/;

 right_parenthesis = qr/\s*\)\s*/;

 power_of = qr/\s*\*\*\s*/;

 plus_or_minus = qr/\s*([-+])\s*/;

 times_or_divide_or_modulo = qr.\s*([\*/])\s*.;
END

my $calculator_parser = ebnf_new Parse::Stallion::EBNF($calculator);

my $result = $calculator_parser->parse_and_evaluate("7+4");
print "should be 11, result is $result\n";

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

$result = $calculator_parser->parse_and_evaluate("3 + 7*4*2+5");
print "should be 64, result is $result\n";

$result = $calculator_parser->parse_and_evaluate(
 "(6** 3) * 5 + 7*4*2+1 * (9-8)");
print "should be 1137, result is $result\n";

$result = $calculator_parser->parse_and_evaluate("3+-+7*4",{parse_info=>$parse_info = {}});

print "should be 0, Parse succeeded: ".$parse_info->{parse_succeeded}."\n";

print "\nAll done\n";