Instantly share code, notes, and snippets.

# ascv/calc.py

Last active March 17, 2022 04:06
Show Gist options
• Save ascv/5022712 to your computer and use it in GitHub Desktop.
A simple python calculator to demo recursive descent parsing. Execute the script to use the calculator. It accepts only well formed input. Use parentheses to specify operator precedence.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
 """ exp ::= term | exp + term | exp - term term ::= factor | factor * term | factor / term factor ::= number | ( exp ) """ class Calculator(): def __init__(self, tokens): self._tokens = tokens self._current = tokens[0] def exp(self): result = self.term() while self._current in ('+', '-'): if self._current == '+': self.next() result += self.term() if self._current == '-': self.next() result -= self.term() return result def factor(self): result = None if self._current[0].isdigit() or self._current[-1].isdigit(): result = float(self._current) self.next() elif self._current is '(': self.next() result = self.exp() self.next() return result def next(self): self._tokens = self._tokens[1:] self._current = self._tokens[0] if len(self._tokens) > 0 else None def term(self): result = self.factor() while self._current in ('*', '/'): if self._current == '*': self.next() result *= self.term() if self._current == '/': self.next() result /= self.term() return result if __name__ == '__main__': while True: lst = list(raw_input('> ').replace(' ', '')) tokens = [] for i in range(len(lst)): if lst[i].isdigit() and i > 0 and (tokens[-1].isdigit() or tokens[-1][-1] is '.'): tokens[-1] += lst[i] elif lst[i] is '.' and i > 0 and tokens[-1].isdigit(): tokens[-1] += lst[i] else: tokens.append(lst[i]) print Calculator(tokens).exp()

### 6502 commented Jul 14, 2013

I didn't try it, but looking at the code I'd say that it cannot handle things like "1+2-3+4-5" because expects a sequences of zero or more "+" followed by a sequence of zero or more "-". Ditto for "*" and "/".

Instead of two consecutive while loops should do one while loop accepting all operators of the same precedence.

Fixed.

### cdpm commented May 30, 2014

Should it not be
"exp ::= term | term + term | term - term"
"exp ::= term | exp + term | exp - term" in the docstring?

### felix-d commented Jun 22, 2016 • edited

``` ...
while True:
tokens = re.findall(r'[\d.]+|[+-/*()]', input('> ').replace(' ', ''))
print Calculator(tokens).exp() ```

### dannytranit commented Mar 25, 2018

It cannot handle with things like 12 x 21 / 13

### theoden-dd commented Mar 4, 2020

This code failed with expressions like `(2 / (2 + 3.33) * 4) - -6`. Had to merge with https://effbot.org/zone/simple-top-down-parsing.htm to make it working.

### ascv commented Mar 4, 2020 • edited

It cannot handle with things like 12 x 21 / 13

Not in the grammar.

This code failed with expressions like `(2 / (2 + 3.33) * 4) - -6`. Had to merge with https://effbot.org/zone/simple-top-down-parsing.htm to make it working.

Not in the grammar.

To clarify, this is not a general purpose calculator, it's just a simple demo to show how to do recursive descent parsing. The grammar is intentionally limited. If I recall correctly, at the time I wrote it, I was trying to keep the number of lines to <= 60. Modify to support your needs :)