
How To Write A Calculator in 70 Python Lines - gklein
http://blog.erezsh.com/how-to-write-a-calculator-in-70-python-lines-by-writing-a-recursive-descent-parser/
======
philfreo
How to Write a Calculator in 1 Python Line

    
    
        print input()
    
        > 25*4-50
        > 50

~~~
edwintorok
It accepts more than just numbers though:

    
    
      >>> print input()
      "a"+"b"
      ab

~~~
readme
Mathematically, this is wrong.. a + b is not ab.

~~~
xxbondsxx
It's not symbolic...

~~~
readme
Of course. I understand that "a" + "b" when taken to mean "concatenate the
strings" is "ab" but OP's post was a calculator. It would not have yielded
this result.

------
ivan_ah
Very cool.

Thanks for the cool post. I am not sure I got the difference between the LL
and LR parser. Is what you have above an LR parser?

Also, why did you choose to represent both + and - as "ADD" tokens (and * and
/ as "MUL") is this to enforce evaluation priority? It would be interesting to
see if you can add __or ^ as an exponent for this calculator.

Maybe you intended this post strictly as as an educational post, but I think
doing parsing right (and from first principles) is a really cool thing to
have. Check out how khan-exercises framework uses to parse math expressions
into an AST: [https://github.com/Khan/khan-
exercises/blob/master/utils/mat...](https://github.com/Khan/khan-
exercises/blob/master/utils/math-model.js)

~~~
erezsh
An LR-parser tries to reduce the input over and over again into rules,
eventually ending with the 'start' rule. So a+b+c+d becomes [add]+c+d ->
[add]+d -> [add] -> [start]

An LL-parser tries to expand the initial rule into a more complex rule
structure, until it matches the input. So to match a+b+c+d it will do [start]
-> [add] -> [add] + [num] -> [add] + [num] + [num] -> etc.

What I wrote is an LL-parser, simply because it's much much simpler to write
and to understand.

Yes, both ADD and MUL are used for precedence. Since any list of +- or of */
will evaluate correctly if reduced from left to right, I didn't mind grouping
them together and making my life easier (and shorter).

It was strictly educational, and also a shtick; a short code hack. If I was to
write an actual parser (and I don't think I would ever try to), it would look
very different!

------
abecedarius
Very nicely done!

I think a precedence parser could be even shorter, though I don't seem to have
one handy in Python. Pratt parsing is similar, though:
[https://github.com/darius/sketchbook/blob/master/parsing/pra...](https://github.com/darius/sketchbook/blob/master/parsing/pratt.py)
And here's precedence parsing in C: <http://wry.me/~darius/hacks/dcalc.c> (I
learned this method from Dave Gillespie, author of Gnu Calc.)

Finally, using a small parsing library very similar to the scheme in this
post:
[https://github.com/darius/peglet/blob/master/examples/infix....](https://github.com/darius/peglet/blob/master/examples/infix.py)

------
coolSCV
The calculator seems to miss the point of recursive descent parsing. The
parser should follow naturally from the grammar. As an example, here is a 52
line python calculator:

<https://gist.github.com/ascv/5022712>

The idea isn't to 1-up the OP but rather to showcase the technique of
recursive descent parsing. The wikipedia entry for recursive descent parsing
also has a very nice example.

~~~
erezsh
Well, to be fair, your calculator seems to miss the point of being a
calculator...

    
    
        > 10/2*2
        2.5

~~~
coolSCV
Clearly a bug. Thanks when I have more time I will fix it. I didn't give much
thought to operator precedence, I assumed the user would specify it using
parentheses.

The idea is what is important though. Recursive descent parsing uses the
functions that correspond to types in the grammar to mutually call each other
thus parsing the expression recursively in a descending fashion.

------
andrewcooke
this reminded me of something similar i did (i'm afraid my recursive decent
parser is probably more opaque) - differentiating numerical expressions in
python. <http://www.acooke.org/cute/Differenti0.html>

------
vladoh
Here is a similar calculator implemented in 58 languages including in 16 lines
of Python code :)

<http://www.math.bas.bg/bantchev/place/rpn/rpn.impl.html>

~~~
konstruktor
This is an RPN calculator, which means you can basically execute the input
using the token stream directly. TFA shows executing a context free language,
which requires parsing, explaining the program being an order of magnitude
larger.

------
tiziano88
FYI this <http://www.imgur.com/5Ge93Xc.png> is what it looks like on my Nexus
4, perhaps it's worth considering a more mobile-friendly layout ;)

------
piqufoh
This article seems rather un-Zen

>>> import this

... though rules are made to be broken

~~~
erezsh
Why is it un-zen?

~~~
gbog
I think it is not too unzen in its aim and method but if you ask there many
little tricks that I'd say are unzen. First, give all the code a pass of
autopep8 comb. Second, avoid things like "lambda (op,num): (num, -num)[...],
just use "x if y else z"

Then you wouldn't be too far from Norvig's little educational gems.

~~~
erezsh
I suppose I got carried away with the lambdas :)

Thank you, you gave me a great compliment. Norvig's short spellchecker was
very inspirational for me.

------
gusgordon
I wonder if there's a way to add algebraic equation solving to this, or
something like it, easily.

~~~
lutusp
Not easily, and not in 70 lines. But there's the Python library "sympy" which
can do symbolic algebraic equation solving (and many other things):

    
    
        from sympy import *
        var('x,b,c')
        print solve(a*x**2+b*x+c,x)
    

Result:

    
    
        [(-b + (-4*a*c + b**2)**(1/2))/(2*a), -(b + (-4*a*c + b**2)**(1/2))/(2*a)]

