Skip to content

Commit

Permalink
Added the ability to input in prefix and postfix as well as infix.
Browse files Browse the repository at this point in the history
Expressions are now outputted in infix, prefix, and postfix when evaluated.
  • Loading branch information
nrubin29 committed Apr 22, 2018
1 parent b5f86bf commit 392c91f
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 31 deletions.
50 changes: 47 additions & 3 deletions ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import copy
from typing import Dict

from common import RuleMatch, remove, left_assoc, Token
from common import RuleMatch, remove, left_assoc, Token, precedence
from rules import rule_value_map, rule_value_operation_map
from vartypes import TupleValue

Expand Down Expand Up @@ -84,11 +84,55 @@ def _evaluate(self, node, vrs: Dict[str, RuleMatch]):
return rule_value_operation_map[node.name](values, tokens[0] if len(tokens) > 0 else None) # This extra rule is part of the num hotfix.

def infix(self) -> str:
# TODO: Add parentheses and missing tokens.
return self._infix(self.root)

def _infix(self, node: RuleMatch) -> str:
return ' '.join(map(lambda t: t.value if isinstance(t, Token) else self._infix(t), node.matched))
# TODO: Add missing tokens.
s = ''

if len(node.matched) == 1:
s += node.matched[0].value

else:
for c in [node.matched[1]] + [node.matched[0]] + node.matched[2:]:
if isinstance(c, RuleMatch):
if c.name in precedence and node.name in precedence and precedence.index(c.name) > precedence.index(node.name):
s += '(' + self._infix(c) + ') '

else:
s += self._infix(c) + ' '
else:
s += c.value + ' '

return s.strip()

def prefix(self) -> str:
return self._prefix(self.root)

def _prefix(self, node: RuleMatch) -> str:
s = ''

for c in node.matched:
if isinstance(c, RuleMatch):
s += self._prefix(c) + ' '
else:
s += c.value + ' '

return s.strip()

def postfix(self) -> str:
return self._postfix(self.root)

def _postfix(self, node: RuleMatch) -> str:
s = ''

for c in node.matched[1:] + [node.matched[0]]:
if isinstance(c, RuleMatch):
s += self._postfix(c) + ' '
else:
s += c.value + ' '

return s.strip()

def __str__(self):
return str(self.root) # + '\n>> ' + self.infix()
Expand Down
41 changes: 35 additions & 6 deletions calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,43 @@
import re

from ast import Ast
from common import Token, token_map, rules_map, RuleMatch
from common import Token, token_map, rules_map, RuleMatch, ImmutableIndexedDict
from vartypes import Value


class Calculator:
def __init__(self):
self.vrs = {}

def evaluate(self, eqtn: str, verbose=True) -> Value:
def evaluate(self, eqtn: str, tpe: str, verbose=True) -> Value:
for e in eqtn.split(';'):
root, remaining_tokens = self._match(self._tokenize(e), 'asn')
tokens = self._tokenize(e)

# Because postfix is not conducive to recursive descent, we must convert it to prefix first.
if tpe == 'postfix':
stack = []

for token in tokens:
if token.name == 'NUM':
stack.append(token)

else:
a = stack.pop()

if isinstance(a, Token):
a = a.value

b = stack.pop()

if isinstance(b, Token):
b = b.value

stack.append('{} {} {}'.format(token.value, b, a))

tpe = 'prefix'
tokens = self._tokenize(stack[0])

root, remaining_tokens = self._match(tokens, 'asn', rules_map[tpe])

if remaining_tokens:
raise Exception('Invalid equation (bad format)')
Expand All @@ -29,6 +55,9 @@ def evaluate(self, eqtn: str, verbose=True) -> Value:

if verbose:
print(ast)
print('Infix: ' + ast.infix())
print('Prefix: ' + ast.prefix())
print('Postfix: ' + ast.postfix())

return res

Expand All @@ -48,7 +77,7 @@ def _tokenize(self, eqtn: str) -> List[Token]:

return tokens

def _match(self, tokens: List[Token], target_rule: str):
def _match(self, tokens: List[Token], target_rule: str, rules_map: ImmutableIndexedDict):
# print('match', tokens, target_rule)

if target_rule.isupper(): # This is a token, not a rule.
Expand All @@ -65,7 +94,7 @@ def _match(self, tokens: List[Token], target_rule: str):

for pattern_token in pattern.split():
# print('checking pattern_token', pattern_token)
m, remaining_tokens = self._match(remaining_tokens, pattern_token)
m, remaining_tokens = self._match(remaining_tokens, pattern_token, rules_map)

if not m:
# print('failed pattern match')
Expand All @@ -78,6 +107,6 @@ def _match(self, tokens: List[Token], target_rule: str):

idx = rules_map.index(target_rule)
if idx is not None and idx + 1 < len(rules_map):
return self._match(tokens, rules_map.key_at(idx + 1))
return self._match(tokens, rules_map.key_at(idx + 1), rules_map)

return None, None
52 changes: 36 additions & 16 deletions common.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,29 +98,49 @@ def key_at(self, i):
return self._keys[i]


rules_map = ImmutableIndexedDict((
('asn', ('asb EQL add',)),
('^asb', ('IDT CMA asb', 'IDT')),
('add', ('mul ADD add', 'mui ADD add',)),
('mui', ('pow mul',)),
('mul', ('pow MUL mul',)),
('pow', ('opr POW pow',)),
('opr', ('OPR LPA opb RPA',)),
('^opb', ('add CMA opb', 'add')),
('neg', ('ADD num', 'ADD opr')),
('var', ('IDT',)),
('num', ('NUM', 'LPA add RPA')),
('mat', ('LBR mbd RBR',)),
('^mbd', ('mrw PPE mbd', 'mrw')),
('^mrw', ('add CMA mrw', 'add')),
))
rules_map = {
'infix': ImmutableIndexedDict((
('asn', ('asb EQL add',)),
('^asb', ('IDT CMA asb', 'IDT')),
('add', ('mul ADD add', 'mui ADD add',)),
('mui', ('pow mul',)),
('mul', ('pow MUL mul',)),
('pow', ('opr POW pow',)),
('opr', ('OPR LPA opb RPA',)),
('^opb', ('add CMA opb', 'add')),
('neg', ('ADD num', 'ADD opr')),
('var', ('IDT',)),
('num', ('NUM', 'LPA add RPA')),
('mat', ('LBR mbd RBR',)),
('^mbd', ('mrw PPE mbd', 'mrw')),
('^mrw', ('add CMA mrw', 'add')),
)),
'prefix': ImmutableIndexedDict((
('asn', ('EQL asb add',)),
('^asb', ('CMA IDT asb', 'IDT')),
('opr', ('OPR opb',)),
('^opb', ('CMA pow opb', 'pow')),
('pow', ('POW mul pow',)),
('mul', ('MUL add mul',)),
('add', ('ADD opr add',)),
('neg', ('ADD num', 'ADD opr')),
('var', ('IDT',)),
('num', ('NUM', 'pow')),
)),
}


left_assoc = {
'ADD': 'add',
'MUL': 'mul',
}

precedence = [
'pow',
'mul',
'add'
]

operations = ('pos', 'neg', 'add', 'sub', 'mul', 'div', 'mod', 'pow', 'sqrt', 'exp', 'identity', 'det', 'trans', 'cof', 'adj', 'inv', 'rref', 'trnsform', 'solve', 'ls', 'eval')


Expand Down
12 changes: 8 additions & 4 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
if __name__ == '__main__':
calc = Calculator()

if len(sys.argv) > 1:
for line in ' '.join(sys.argv[1:]).split(';'):
print(calc.evaluate(line))
if len(sys.argv) > 2:
tpe = sys.argv[1]

for line in ' '.join(sys.argv[2:]).split(';'):
print(calc.evaluate(line, tpe))

else:
tpe = input('What type of expressions will you be inputting? Enter prefix, postfix, or infix: ')

while True:
print(calc.evaluate(input('>> ')))
print(calc.evaluate(input('>> '), tpe))
4 changes: 2 additions & 2 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
from common import EvaluationException


def evaluate(eqtn: str, verbose=True):
def evaluate(eqtn: str, tpe='infix', verbose=True):
if verbose:
print(eqtn)

calc = Calculator()
res = calc.evaluate(eqtn, verbose)
res = calc.evaluate(eqtn, tpe, verbose)

if verbose:
print(res, '\n' + '-' * 50)
Expand Down

0 comments on commit 392c91f

Please sign in to comment.