Recently I’ve resumed work on a GTK# project I started three years ago: SnazzyCalculator, hosted on Github. I originally started the project out of curiosity about GTK#, and it’s been fun to develop – I know no one needs another calculator tool. I do intend to develop it into what I’ve been calling a wordulator: type in something like “one plus one equals” and it’ll respond “two”. Feel free to laugh, Jon keeps teasing me about it certainly. :P Anyway, what’s been really fun since I’ve resumed development is getting the parser and equation solver to work. I found this awesome tutorial by Eric White on writing a recursive descent parser in C#, and that gave me the bulk of my parser code. I did discover two bugs in his code that I fixed; I’ll share my fixes with you.


ParserException from white space generation

In his Expression.Produce, he has the following:

1
2
3
4
return new Expression(
    Enumerable.Repeat(new WhiteSpace(), whiteSpaceBefore),
    n,
    Enumerable.Repeat(new WhiteSpace(), whiteSpaceAfter));

This caused a ParserException for me because of the checks in the Symbol(params Object[] symbols) constructor. Basically, Enumerable.Repeat returned an Enumerable/<CreateRepeatIterator> instead of either a Symbol or IEnumerable<Symbol> like the Symbol constructor expected, so Symbol wigged out and threw a ParserException. I ended up adding the following to my WhiteSpace class:

1
2
3
4
5
6
7
8
9
public static IEnumerable<Symbol> CreateWhiteSpace(int amount)
{
    List<Symbol> ws = new List<Symbol>();
    for (int i=0; i<amount; i++)
    {
        ws.Add(new WhiteSpace());
    }
    return (IEnumerable<Symbol>)ws;
}

Then in Expression.Produce, instead of Eric’s code, I had:

1
2
return new Expression(WhiteSpace.CreateWhiteSpace(whiteSpaceBefore),
    n, WhiteSpace.CreateWhiteSpace(whiteSpaceAfter));

Mismatched parentheses

Parsing an expression like (1+2)\*(2+3) resulted in an exception because of the order in which his NospaceExpression.Produce method constructs new NospaceExpression instances. His code was looking for an opening parenthesis and a closing parenthesis first; then, if that didn’t match, checking for an Expression followed by an InfixOperator followed by another Expression. That resulted in it extracting 1+2)\*(2+3, i.e. ditching the first and last parentheses, and trying to treat that as an Expression, which it isn’t. I just moved the following chunk of code down a bit, so it came after return new NospaceExpression(e1, io, e2);:

1
2
3
4
5
6
7
8
9
10
if (symbols.First() is OpenParenthesis &&
    symbols.Last() is CloseParenthesis)
{
    Expression e = Expression.Produce(symbols.Skip(1).SkipLast(1));
    if (e != null)
    {
        return new NospaceExpression(new OpenParenthesis(), e,
            new CloseParenthesis());
    }
}

This results in first trying to find an expression, then some operator that works on two expressions, then a final expression. And since an Expression consists of another NospaceExpression, which can then consist of parentheses surrounding another Expression, you wind up with (1+2) being seen as its own Expression, then \* as an InfixOperator, then finally (2+3) as the last Expression.


After I got the parser working as expected, I had to make it actually do something with its parse – that is, solve the equation. I did that by adding a Solve method to several classes that returned a double representing its value. That was easiest for the DecimalDigit class, since it just returns its own numeric value. I worked up from there, determining what the symbols could be in each class, so I could determine how to take those symbols and produce some double result. A fun part was adding the following method to InfixOperator that returns a function that knows how to solve an equation involving two terms, depending on which operator the InfixOperator contains:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public Func<double, double, double> GetSolver()
{
    Symbol sym = ConstituentSymbols[0];
    if (sym is Plus)
    {
        return (ad1, ad2) => ad1 + ad2;
    }
    if (sym is Minus)
    {
        return (t1, t2) => t1 - t2;
    }
    if (sym is Asterisk)
    {
        return (f1, f2) => f1 * f2;
    }
    if (sym is ForwardSlash)
    {
        return (dividend, divisor) => {
            if (0 == divisor) {
                throw new DivideByZeroException();
            }
            return dividend / divisor;
        };
    }
    if (sym is Caret)
    {
        return (t1, t2) => Math.Pow(t1, t2);
    }
    return (t1, t2) => {
        throw new ParserException("Don't know how to solve " +
                                  "expression with InfixOperator " +
                                  sym.GetType().ToString());
    };
}

So now my SnazzyCalculator calculates, and it seems to do so correctly, following PEMDAS. It correctly reports “ERROR” when you give it a crap equation like *1.32.34)), or you divide by zero. The next step is to write some tests for it, to ensure I don’t accidentally break the parser or solver while tinkering with other bits. I’ve been fiddling with the GUI, and while it looks all right, I don’t like how I’m styling it. I’m applying the fonts and colors programmatically, instead of in the gui.stetic XML file where all the widgets are laid out. It seems like I ought to be able to describe the appearance of each button in the XML somewhere, but I haven’t found any example showing how.

SnazzyCalculator with formula