expr := value ( op value )* #### sub parse_expr { my($value, @stack); while(1) { $value = parse_value or die "Parse error"; my $op = parse_operator or last; $value = process(\@stack, $value, $op); # intermediate processing push @stack, { value => $value, op => $op }; # store for the next round } $value = process(\@stack, $value); # final processing return $value; } #### value := '(' expr ')' #### #!perl -w use strict; my %var; my %op = ( '+' => { prec => 10, assoc => 'L', exec => sub { $_[0] + $_[1] }}, '-' => { prec => 10, assoc => 'L', exec => sub { $_[0] - $_[1] }}, '*' => { prec => 20, assoc => 'L', exec => sub { $_[0] * $_[1] }}, '/' => { prec => 20, assoc => 'L', exec => sub { $_[0] / $_[1] }}, '%' => { prec => 20, assoc => 'L', exec => sub { $_[0] % $_[1] }}, '**' => { prec => 30, assoc => 'R', exec => sub { $_[0] ** $_[1] }}, ); # fields use constant VALUE => 0; use constant OP => 1; sub parse_expr { my @stack; while (1) { trace(); my($value) = parse_value() or die "Parse error at " . where(); trace("value=$value"); if(/\G\s*(\*\*|[+\-*\/%\\])/gc) { # operator my $op = $1; trace("op=$op"); while (@stack and (($op{$op}{prec} < $op{$stack[-1][OP]}{prec}) or ($op{$op}{prec} == $op{$stack[-1][OP]}{prec}) and $op{$stack[-1][OP]}{assoc} eq "L")) { my($lhs, $op) = @{pop @stack}; trace("popping $lhs $op"); $value = $op{$op}{exec}->($lhs, $value); trace("result = $value"); } trace("pushing $value $op"); push @stack, [ $value, $op ]; } else { # no more while(@stack) { my($lhs, $op) = @{pop @stack}; trace("popping $lhs $op"); $value = $op{$op}{exec}->($lhs, $value); trace("result = $value"); } trace("returning $value"); return $value; } } } sub parse_value { /\G\s+/gc; if(/\G\+/gc) { # '+' value trace("Unary plus"); return parse_value(); } if(/\G-/gc) { # '-' value trace("Unary minus"); return -parse_value(); } if(/\G((?:\d+\.?\d*|\.\d+)(?i:E[+-]?\d+)?)/gc) { # number return $1; } if(/\G((?i:[a-z]\w*))/gc) { # variable return $var{$1}; } if(/\G\(/gc) { # '(' expr ')' my $value = parse_expr(); /\G\s*/gc; /\G\)/gc or die "Parse error: ')' expected at: " . where() ; return $value; } return; } sub evaluate { local $_ = shift; my $value = parse_expr(); /\G\s+/gc; /\G$/gc or die sprintf "Junk characters at end: \"%s\"", where(); return $value; } sub where { # debugging info my $s = $_; substr($s, pos || 0, 0) = "\267"; return $s; } sub trace { my($pkg, $file, $line) = caller; printf STDERR "Line %d \"%s\" %s\n", $line, where(), @_ ? shift : ""; } %var = ( a => 101, b => 7 ); $_ = "20+3*a+10*-b-5*(3 +2)*5"; $\ = "\n"; print evaluate($_); #### #!perl -w use strict; my %var; my %op = ( '+' => { prec => 10, assoc => 'L', exec => sub { $_[0] + $_[1] }}, '-' => { prec => 10, assoc => 'L', exec => sub { $_[0] - $_[1] }}, '*' => { prec => 20, assoc => 'L', exec => sub { $_[0] * $_[1] }}, '/' => { prec => 20, assoc => 'L', exec => sub { $_[0] / $_[1] }}, '%' => { prec => 20, assoc => 'L', exec => sub { $_[0] % $_[1] }}, '**' => { prec => 30, assoc => 'R', exec => sub { $_[0] ** $_[1] }}, ); # fields use constant VALUE => 0; use constant OP => 1; sub parse_expr { my @stack; while (1) { trace(); my($value) = parse_value() or die "Parse error at " . where(); trace("value=$value"); if(/\G\s*(\*\*|[+\-*\/%\\])/gc) { # operator my $op = $1; trace("op=$op"); while (@stack and (($op{$op}{prec} < $op{$stack[-1][OP]}{prec}) or ($op{$op}{prec} == $op{$stack[-1][OP]}{prec}) and $op{$stack[-1][OP]}{assoc} eq "L")) { my($lhs, $op) = @{pop @stack}; trace("popping $lhs $op"); $value = $op{$op}{exec}->($lhs, $value); trace("result = $value"); } trace("pushing $value $op"); push @stack, [ $value, $op ]; } else { # no more while(@stack) { my($lhs, $op) = @{pop @stack}; trace("popping $lhs $op"); $value = $op{$op}{exec}->($lhs, $value); trace("result = $value"); } trace("returning $value"); return $value; } } } sub parse_value { /\G\s+/gc; if(/\G\+/gc) { # '+' value trace("Unary plus"); return parse_value(); } if(/\G-/gc) { # '-' value trace("Unary minus"); return -parse_value(); } if(/\G((?:\d+\.?\d*|\.\d+)(?i:E[+-]?\d+)?)/gc) { # number return $1; } if(/\G((?i:[a-z]\w*))/gc) { # variable return $var{$1}; } if(/\G\(/gc) { # '(' expr ')' my $value = parse_expr(); /\G\s*/gc; /\G\)/gc or die "Parse error: ')' expected at: " . where() ; return $value; } return; } sub evaluate { local $_ = shift; my $value = parse_expr(); /\G\s+/gc; /\G$/gc or die sprintf "Junk characters at end: \"%s\"", where(); return $value; } sub where { # debugging info my $s = $_; substr($s, pos || 0, 0) = "\267"; return $s; } sub trace { my($pkg, $file, $line) = caller; printf STDERR "Line %d \"%s\" %s\n", $line, where(), @_ ? shift : ""; } %var = ( a => 101, b => 7 ); $_ = "20+3*a+10*-b-5*(3 +2)*5"; $\ = "\n"; print evaluate($_); #### value := name '(' ( expr ( ',' expr ) * )? ')'