Tags:
create new tag
view all tags
Link to YaccHowTo

#!/usr/local/bin/perl4
@rem = '
@echo off
perl -S %0.bat %1 %2 %3 %4 %5 %6 %7 %8 %9
goto endofperl
';
#
# Another way to evaluate infix expressions...(with precedence)
#   Tokenize input, parse expressions, use priority table.
#   Expect input to basically alternate between numbers and operators.
#   Use $last_was_op flag to handle ()'s and unary minus, which
#   will violate the alternation rule, such as 2*-3 and (1+2)*3.
#   Use two stacks, one for data (@d) and one for operators (@o).
#   Simply push numbers onto data stack.
#   Push operators onto operator stack, but check that no higher
#   priority operations are "pending", i.e. on the stack.  If they
#   are, pop them off and use the data on the top of the data stack.
#   Do some trickery with ()'s to make them evaluate innards first.
#   Make = the lowest priority to make sure whole expression is evaluated.
# (C) Copyright 12Dec96 Robert Dowling www.uman.com
#
$DOS = -e '/autoexec.bat';

$D = 0;      # Set this to 1 to get debugging info

$OPS = "^[-+*\\/^=()]\$";   # Regular expression for operators

# Each operation and it's precedence, bigger means tighter
%priority = (
   '=',   0,
   ')',   5,
   '+',   10,   '-',   10,
   '*',   20,   '/',   20,
   '^',   30,   
   'U-',   40,
   '(',   50,
);

# What to do when we find an operation
%code = (
   '+','$b=pop(@d); $a=pop(@d); push(@d, $a+$b);',
   '-','$b=pop(@d); $a=pop(@d); push(@d, $a-$b);',
   '*','$b=pop(@d); $a=pop(@d); push(@d, $a*$b);',
   '/','$b=pop(@d); $a=pop(@d); push(@d, $a/$b);',
   '^','$b=pop(@d); $a=pop(@d); push(@d, $a**$b);',
   ')','',
   'U-','push (@d, -pop(@d));'
);

while ($i=<>)
{
   # Concatenate input until line ending in =
   chop ($i);
   $_ .= $i;
   next unless /=$/;

   # Split on operations [+-*/^=()] and prune null elements
   undef (@i);
   for (split (/([-+*\/^=()])/))   { push (@i, $_) if $_; }

   #print join(',  ', @i),"\n";

   # Join scientific 1e + 3 notation back together into 1e+3
   undef (@_);
   while ($_ = shift (@i))
   {
      if (/[Ee]$/ && $i[0]=~/^[-+]$/) {
         $_ .= shift (@i);
         $_ .= shift (@i);
      }
      push (@_, $_);
   }
   #print join(',  ', @_),"\n";

   # Process
   #print join(',  ', @_),"\n";
   print "Exp=", &addition (@_), "\n";

   $_='';
}

# Just return 1 if token is an operator
sub is_op
{
   local ($_) = @_;
   /$OPS/o;
}

# Why I called this addition, I don't know.
# It does all the work.  Given an array of tokens, like '2','+','3','='
# compute the expression and return it.
sub addition
{
   local (@i, @o, @d, $_, $a, $b, $d, $o, $last_was_op) = @_;

   # Pretend the last token input was an operation
   $last_was_op=1;

   # Get a new token
   while ($_ = shift(@i))
   {
      if ($D) {
       print "Input=$_=$priority{$_}\t";
       print "i=", join(",", @i), "\t";
       print "o=", join(",", @o), "\t";
       print "d=", join(",", @d), "\n"; }
      if (&is_op ($_))
      {
         # About to push on another operator, so check if
         # we should pop last one off and execute it.
         # Execute higher precedence operations.
         # The last_was_op stuff if to handle parenthesis,
         # like (2+2)*2, where ) and * are next to each other
         while (scalar(@o) &&
            $priority{$_} <= $priority{$o[$#o]} &&
            !$last_was_op)
         {
            # Fetch a pending operation on stack
            $o = pop (@o);

            # Execute using @d stack and leaving
            # answer back on @d stack
            $d = $code{$o};
            eval $d;

            # Just debug
            if ($D) {
             print "...eval $d;";
             print "--$@--";
             print "...o=", join(",", @o), "\t";
             print "d=", join(",", @d), "\n";
            }

            # The precedence of ) is low to force
            # everything inside ()'s to execute,
            last if $o =~ /\)/;
         }

         # Special magic to make ()'s work.  Don't push '('.
         # Push a ')' instead because it has low precedence
         # and will force everything inside to execute first.
         # But note that ( has highest precedence.
         if (/\(/)
         {
            $last_was_op = 1;
            push (@o, ')');
            # Get out of here.  Read another token
            next;
         }
         if (/\)/)
         {
            # Make (#+#) look like #, so last was not op.
            $last_was_op = 0;
            # Read another token
            next;
         }

         # Special case unary - by renaming it
         # Unary minus always appears just after an operator
         if ($last_was_op)
         {
            $_ = "U$_";
         }

         # If we got here, we have finished processing other
         # operators (and parenthesis)
         # Just push onto operator stack
         # And remember we just read an operator
         $last_was_op = 1;
         push (@o, $_);
      }
      else   # Else token is a number
      {
         # Push number onto data stack
         $last_was_op = 0;
         push (@d, $_);
      }
   }

   # Return top of data stack as answer
   0+pop (@d);
}

#
#
#
__END__
:endofperl

-- JohnCoe - 04 Apr 2004

This is almost identical to the infix expression parser in the FormQueryPlugin, and like that parser duplicates the function of the "eval it and pray" parser in the SpreadSheetPlugin.

-- CrawfordCurrie - 04 Apr 2004

Well, what about the performance of the regular expression based "eval it and pray" parser compared to a YACC?

-- PeterThoeny - 06 Apr 2004

I'd bet my money on a YACC based parser every time. Wanna race? wink But neither the parser presented above, nor the one in the FQP, are YACC parsers - YACC parsers are table-driven parsers generated by a parser generator and coded in C. Comparative performance of these three parsers? Dunno. At a guess, I would think the eval-and-pray would be several times faster, because it delegates the parsing function to perl i.e. it doesn't actually parse anything. On the other hand, it isn't possible to suicide the twiki installation using the coded parser! -- CC

Actually it isn't possible to kill the sevrer using a $EVAL - I just tried smile However (every cloud has to have a silver lining!) $EVAL only supports simple arithmetic operations on numbers, while the parser approach can support many more infix operators and even variables and functions, which can never be done in the eval-and-pray approach because of the security risk. Oh, and the eval-and-pray approach is about 10X faster. -- CC

I've used CPAN:Parse::RecDescent with good results, but it's quite slow so it wouldn't really work for TWiki. Of course, using a high level but efficiently compiled language like OCaml would help smile (See Donkin:OCamlLanguage for more about this). Recovery from errors would need to be quite good - the current TWiki parser has the merit of producing some output even if the input is quite wrong.

-- RichardDonkin - 06 Apr 2004

Edit | Attach | Watch | Print version | History: r5 < r4 < r3 < r2 < r1 | Backlinks | Raw View | Raw edit | More topic actions
Topic revision: r5 - 2004-04-06 - RichardDonkin
 
  • Learn about TWiki  
  • Download TWiki
This site is powered by the TWiki collaboration platform Powered by Perl Hosted by OICcam.com Ideas, requests, problems regarding TWiki? Send feedback. Ask community in the support forum.
Copyright © 1999-2026 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.