PCPlus 265: Evaluating expressions

I write a monthly column for PCPlus, a computer news-views-n-reviews magazine in the UK (actually there are 13 issues a year — there's an Xmas issue as well — so it's a bit more than monthly). The column is called Theory Workshop and appears in the back of every issue. When I signed up, my editor and the magazine were gracious enough to allow me to reprint the articles here after say a year or so. After all, the PDFs do appear on each issue's DVD after a couple of months. When I buy the current issue, I'll publish the article from the issue a year ago.

PCPlus logo Obviously I was on a parsing kick when I wrote this, given that the previous article was on Regular Expressions and this is on expression evaluation. And, equally obviously, I hadn't learned my lessons from a few articles back as again I tried to show some code. Anyway, this article was all about evaluating arithmetic expressions: taking a a string representing an expression like (1+2)×3, converting it into some form that would be more easily evaluated and then evaluating it. The intermediary form is RPN (Reverse Polish Notation) and evaluating an RPN expression is most easily done with a stack.

There's nothing particularly difficult about the article; I think I do a good job in describing how to parse the expression (and how to note errors) to produce an RPN "string" and then how to evaluate the RPN expression.

I thought I'd nailed the images some time ago, but for some reason the illustration showing how to evaluate an RPN expression using a stack "lost" the multiplication sign. Since it was just an asterisk (I checked), it's not like it was some bizarre font issue.

Here's the full code, including a quick test program. It includes the code printed in the article. Note that this code treats numbers only as single digits.

using System;
using System.Collections.Generic;
using System.Text;

namespace ExpressionEvaluator {

  public class ExpressionParser {

    internal interface IParseResult {
      bool Succeeded { get; }
      bool Failed { get; }
      string RpnExpression { get; }
    }

    internal class SuccessfulParse : IParseResult {
      private string rpnExpression;
      public SuccessfulParse(string rpnExpression) {
        this.rpnExpression = rpnExpression;
      }
      public bool Succeeded {
        get { return true; }
      }
      public bool Failed {
        get { return !Succeeded; }
      }
      public string RpnExpression {
        get { return rpnExpression; }
      }
    }

    internal class FailedParse : IParseResult {
      public bool Succeeded {
        get { return false; }
      }

      public bool Failed {
        get { return !Succeeded; }
      }

      public string RpnExpression {
        get { return null; }
      }
    }

    internal class ParserState {
      private string expression;
      private int position;
      private char current;
      public char Current {
        get { return current; }
      }
      public void Advance() {
        position++;
        if (position >= expression.Length)
          current = '\0';
        else
          current = expression[position];
      }
      public ParserState(string expression) {
        this.expression = expression;
        position = -1;
        Advance();
      }
    }

    public static string ParseExpression(string expression) {
      ParserState state = new ParserState(expression);
      IParseResult result = ParseExpression(state);
      return result.RpnExpression;
    }

    private delegate IParseResult ParseDelegate(ParserState state);

    private static IParseResult ParseBinaryExpression(ParserState state,
                                                      ParseDelegate parseOperand,
                                                      ParseDelegate parseOperator) {
      IParseResult operandParse = parseOperand(state);
      if (operandParse.Failed)
        return operandParse;
      string rpn = operandParse.RpnExpression;

      IParseResult operatorParse = parseOperator(state);
      while (operatorParse.Succeeded) {
        operandParse = parseOperand(state);
        if (operandParse.Failed)
          return operandParse;
        rpn += operandParse.RpnExpression + operatorParse.RpnExpression;
        operatorParse = parseOperator(state);
      }
      return new SuccessfulParse(rpn);
    }

    private static IParseResult ParseExpression(ParserState state) {
      return ParseBinaryExpression(state, ParseTerm, ParseAdd);
    }

    private static IParseResult ParseTerm(ParserState state) {
      return ParseBinaryExpression(state, ParseFactor, ParseMultiply);
    }

    private static IParseResult ParseFactor(ParserState state) {
      IParseResult result = ParseNumber(state);
      if (result.Failed) {
        result = ParseParenthesizedExpression(state);
      }
      return result;
    }

    private static IParseResult ParseParenthesizedExpression(ParserState state) {
      if (state.Current != '(')
        return new FailedParse();
      state.Advance();

      IParseResult result = ParseExpression(state);
      if (result.Failed)
        return result;

      if (state.Current != ')')
        return new FailedParse();
      state.Advance();

      return result;
    }

    private static IParseResult ParseNumber(ParserState state) {
      char current = state.Current;
      if (char.IsDigit(current)) {
        state.Advance();
        return new SuccessfulParse(current.ToString());
      }
      return new FailedParse();
    }

    private static IParseResult ParseAdd(ParserState state) {
      char current = state.Current;
      switch (current) {
        case '+':
        case '-':
          state.Advance();
          return new SuccessfulParse(current.ToString());
        default:
          return new FailedParse();
      }
    }

    private static IParseResult ParseMultiply(ParserState state) {
      char current = state.Current;
      switch (current) {
        case '*':
        case '/':
          state.Advance();
          return new SuccessfulParse(current.ToString());
        default:
          return new FailedParse();
      }
    }
  }

  class RpnEvaluator {
    private static double Convert(char token) {
      return (int)token - (int)'0';
    }

    private static double Evaluate(char op, double rhs, double lhs) {
      switch (op) {
        case '+':
          return lhs + rhs;
        case '-':
          return lhs - rhs;
        case '*':
          return lhs * rhs;
        case '/':
          return lhs / rhs;
        default:
          throw new ArgumentException("unknown arithmetic operator", "op");
      }
    }

    public static double Evaluate(string rpn) {
      Stack<double> stack = new Stack<double>();
      foreach (char token in rpn) {
        if (char.IsDigit(token))
          stack.Push(Convert(token));
        else
          stack.Push(Evaluate(token, stack.Pop(), stack.Pop()));
      }
      return stack.Pop();
    }
  }

  class Program {
    static void Evaluate(string expr) {
      string rpn = ExpressionParser.ParseExpression(expr);
      Console.WriteLine(String.Format("{0} => {1} = {2}", expr, rpn, RpnEvaluator.Evaluate(rpn)));
    }

    static void Main(string[] args) {
      Evaluate("(1+2)*3");
      Evaluate("1+2*3");
      Evaluate("1+2/3");
      Evaluate("1/2-3");
      Evaluate("1+2*3-4");
      Evaluate("(1+2)*(3-4)");
      Evaluate("((1+2)*(3-4))-5");
      Evaluate("1+2+3+4");
      Evaluate("1*2*3*4");

      Console.ReadLine();
    }
  }
}

This article first appeared in issue 265, February 2008.

You can download the PDF here.

Now playing:
Dido - Worthless
(from Café del Mar, Vol. 8)

Loading similar posts...   Loading links to posts on similar topics...

2 Responses

#1 Dew Drop – February 16, 2009 | Alvin Ashcraft's Morning Dew said...
16-Feb-09 7:17 AM

Pingback from Dew Drop – February 16, 2009 | Alvin Ashcraft's Morning Dew

#2 JavaScript for C# programmers: object inheritance (part 1) said...
02-Mar-09 11:22 PM

More in the series for C# programmers learning JavaScript, with Firebug as our scratchpad. In this episode, we let go of class inheritance. Bye! I know, it's taken you years to understand class inheritance until you could do it in your sleep. It took

Leave a response

Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.

  •  Emphasize with italics: surround word with underscores _emphasis_
  •  Emphasize strongly: surround word with double-asterisks **strong**
  •  Link: surround text with square brackets, url with parentheses [text](url)
  •  Inline code: surround text with backticks `IEnumerable`
  •  Unordered list: start each line with an asterisk, space * an item
  •  Ordered list: start each line with a digit, period, space 1. an item
  •  Insert code block: start each line with four spaces
  •  Insert blockquote: start each line with right-angle-bracket, space > Now is the time...
Preview of response