$ cat expression_evaluator.pl #!/usr/bin/perl use warnings; use strict; use Data::Dumper; use Scalar::Util qw( looks_like_number ); use Text::Balanced qw( extract_bracketed ); $Data::Dumper::Terse=1; my $precedence_perlop=[ qr/(?:\/|\*|\%|x)/, qr/(?:\+|-|\.)/, qr/(?:<=|>=|<|>lt|gt|le|ge)/, qr/&/, qr/(?:\||\^)/, qr/&&/, qr/(?:\|\||\/\/)/, qr/(not)/, qr/(and)/, qr/(or|xor)/, qr/(?::=)/, ]; my %vars; my %temps; my $last_temp="TEMP000"; sub parse { # Map all variable names to lower case, so temp values in upper case # won't collide my ($regex,$input)=@_; $input = lc($input); # Reset temporaries $last_temp="TEMP000"; return parse_helper($regex, $input); } sub parse_helper{ my ($regex,$input)=@_; $input=~s/\s//g; # parse subexpressions into temps if ($input=~m/(.*?)(\(.*)/) { my $cur_temp = $last_temp++; # get next temp # Split into "$before ($subexpr) $after" my ($before, $nested, $node) = ($1, $2); my ($subexpr, $after) = Text::Balanced::extract_bracketed($nested,"()"); $subexpr = substr($subexpr,1,length($subexpr)-2); # clip parens # Parse & store temporary variable $temps{$cur_temp} = parse_helper($regex, $subexpr); # Rewrite expression with temporary variable name $input = $before . $cur_temp . $after; } # parse as usual for(reverse @$regex){ if($input=~m/(.+)($_)(.+)/){ my ($before,$op,$after,$node)=($1,$2,$3); $node->{$op}=[parse_helper($regex,$before),parse_helper($regex,$after)]; return $node; } } # Return temporary expression tree or scalar value if DNE return exists $temps{$input} ? $temps{$input} : $input; } sub to_RPN { my $tree = shift; return $tree unless ref $tree; foreach my $op ( keys %{$tree} ) { my @terms = map { to_RPN($_) } @{ $tree->{$op} }; return join(" ", @terms, $op); } } sub evaluate_RPN { my $RPN = shift; my @stack; my $pop = sub { die "EMPTY STACK! (malformed expression...)" unless @stack; my $t = pop @stack; if ($t =~ /^[a-z][a-z0-9]*$/) { $t = $vars{$t} // 0; } return $t; }; for (split /\s+/, $RPN) { if ($_ =~ /^[a-z0-9.]*$/) { # Value or variable name push @stack, $_; } elsif ($_ eq ':=') { # Variable assignment my $value = $pop->(); my $varname = pop @stack; $vars{$varname} //= $value; print " ($varname set to <$value>)"; return $value; } else { my $R = $pop->(); #pop @stack; my $L = $pop->(); #pop @stack; push @stack, eval "$L $_ $R"; } } die "EXTRA JUNK ON STACK! (malformed expression)" unless @stack == 1; return $stack[-1]; } while(<>){ s/^\s+//; s/\s+$//; my $tree = parse($precedence_perlop,$_); my $RPN = to_RPN($tree); print "RPN: $RPN"; print " ====> ", evaluate_RPN($RPN), "\n"; print "Variables: ", join(", ", map { "$_:$vars{$_}" } sort keys %vars), "\n"; } #### $ ./expression_evaluator.pl a:=1*(2+(3/5+2)) RPN: a 1 2 3 5 / 2 + + * := (a set to <4.6>) ====> 4.6 Variables: a:4.6 b:=a+15 RPN: b a 15 + := (b set to <19.6>) ====> 19.6 Variables: a:4.6, b:19.6 c:=(b-a)*(5+3+(9-6)*3) RPN: c b a - 5 3 + 9 6 - 3 * + * := (c set to <255>) ====> 255 Variables: a:4.6, b:19.6, c:255