Skip to content

Instantly share code, notes, and snippets.

@ascv
Last active March 17, 2022 04:06
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save ascv/5022712 to your computer and use it in GitHub Desktop.
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.
"""
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()
@cdpm
Copy link

cdpm commented May 30, 2014

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

@felix-d
Copy link

felix-d commented Jun 22, 2016

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

@dannytranit
Copy link

It cannot handle with things like 12 x 21 / 13

@theoden-dd
Copy link

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
Copy link
Author

ascv commented Mar 4, 2020

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment