Mercurial > lbo > hg > arith
changeset 0:e85652916197 draft
Initial commit
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Fri, 17 May 2019 22:28:13 +0200 |
parents | |
children | b41edf239685 |
files | README.md arith/eval.py arith/parse.py arith/tree.py |
diffstat | 4 files changed, 274 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.md Fri May 17 22:28:13 2019 +0200 @@ -0,0 +1,11 @@ +# arith: Toy arithmetic parser in Python + +This is not really useful except for playing around with recursive parsers and +possible applications of computer algebra. + +Ideas list: + +* Vector calculations +* Matrix calculations +* Automatic symbolic differentiation (and integration) +* Graphing (with matplotlib)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/arith/eval.py Fri May 17 22:28:13 2019 +0200 @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri May 17 21:55:20 2019 + +@author: lbo +""" + +from tree import Product, Term, Power, Value, Symbol + +class NotBoundError(Exception): + + def __init__(self, symbol): + super(Exception, self).__init__('Symbol {} not bound!'.format(symbol)) + + +class Bindings: + _bindings = {} + + def __init__(self, bind={}): + self._bindings = bind + + def set(self, sym, val): + self._bindings[sym] = val + + def unset(self, sym): + self._bindings.pop(sym) + + def get(self, sym): + sym = sym.c + if sym in self._bindings: + return self._bindings[sym] + raise NotBoundError(sym) + +def evaluate(node, bindings=None): + bindings = Bindings() if not bindings else bindings + assert isinstance(node, Symbol) or isinstance(node, Value) or (node.left and node.right) + if isinstance(node, Symbol): + return bindings.get(node) + elif isinstance(node, Value): + return node.v + elif isinstance(node, Product): + if node.typ == Product.PROD: + return evaluate(node.left, bindings) * evaluate(node.right, bindings) + elif node.typ == Product.QUOT: + right = evaluate(node.right, bindings) + assert right != 0 + return evaluate(node.left, bindings) / right + elif isinstance(node, Term): + if node.typ == Term.PLUS: + return evaluate(node.left, bindings) + evaluate(node.right, bindings) + elif node.typ == Term.MINUS: + return evaluate(node.left, bindings) - evaluate(node.right, bindings) + elif isinstance(node, Power): + if node.typ == Power.POWER: + return evaluate(node.left, bindings) ** evaluate(node.right, bindings) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/arith/parse.py Fri May 17 22:28:13 2019 +0200 @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Sun May 12 19:23:08 2019 + +@author: lbo +""" + +""" +Parse arithmetic terms. + +Terms are structured like this: + + Sum (consists of) products (consist of) factors +""" + +from tree import Value, Symbol, Product, Term, Power + +class ParseState: + """Encapsulates state as the parser goes through input.""" + + _input = '' + _index = 0 + + def __init__(self, s): + self._input = s + + def repr(self): + return 'ParseState({} < {} > {}'.format( + self._input[0:self._index], self._input[self._index], self._input[self._index:]) + + def next(self): + current = self.peek() + self._index += 1 + while not self.finished() and self.peek().isspace(): + self._index += 1 + return current + + def peek(self): + return self._input[self._index] + + def index(self): + return self._index + + def reset(self, ix): + self._index = ix + + def __iter__(self): + return self + + def __next__(self): + return self.next() + + def finished(self): + return self._index == len(self._input) + +def parse(s): + st = ParseState(s) + return parse_term(st) + +def parse_term(st): + left = parse_product(st) + result = left + if st.finished(): + return result + op = st.peek() + while op in [Term.PLUS, Term.MINUS]: + st.next() + right = parse_product(st) + result = Term(result, right) + result.typ = op + if st.finished(): + break + op = st.peek() + return result + +def parse_product(st): + left = parse_power(st) + result = left + if st.finished(): + return result + op = st.peek() + while not st.finished() and op in [Product.PROD, Product.QUOT]: + st.next() + right = parse_product(st) + result = Product(result, right) + result.typ = op + if st.finished(): + break + op = st.peek() + return result + +def parse_power(st): + left = parse_atom(st) + result = left + if st.finished(): + return result + next = st.peek() + if next == '^': + st.next() + right = parse_atom(st) + result = Power(left, right) + return result + + +def parse_atom(st): + next = st.next() + if next == '(': + term = parse_term(st) + st.next() # Consume closing paren + return term + + full = next + + def is_number(s): + return all([a.isnumeric() or a == '.' for a in s]) + + while not st.finished() and (st.peek().isalpha() or is_number(st.peek())): + full += st.next() + + if is_number(full): + return Value(float(full)) + if full.isalnum(): + return Symbol(full) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/arith/tree.py Fri May 17 22:28:13 2019 +0200 @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri May 17 21:53:30 2019 + +@author: lbo +""" + +class Node: + left = None + right = None + typ = '' + + def __init__(self, left=None, right=None): + self.left = left + self.right = right + + def traverse(self): + if not self: + return '' + return (self.left.traverse() + ' ' if self.left else '') + self.repr() + (' ' + self.right.traverse() if self.right else '') + + def __repr__(self): + return self.repr() + +class Symbol(Node): + SYMBOL = '<symbol>' + typ = SYMBOL + + c = '' + + def __init__(self, sym): + self.c = sym + + def repr(self): + return self.c + +class Value(Node): + VALUE = '<value>' + typ = VALUE + + v = None + + def __init__(self, v): + self.v = v + + def repr(self): + return str(self.v) + +class Term(Node): + MINUS = '-' + PLUS = '+' + + typ = PLUS + + def __init__(self, *args): + Node.__init__(self, *args) + + def repr(self): + return '(' + self.left.repr() + self.typ + self.right.repr() + ')' + +class Product(Node): + PROD = '*' + QUOT = '/' + + typ = PROD + + def __init__(self, *args): + Node.__init__(self, *args) + + def repr(self): + return '(' + self.left.repr() + self.typ + self.right.repr() + ')' + +class Power(Node): + POWER = '^' + + typ = POWER + + def __init__(self, *args): + Node.__init__(self, *args) + + def repr(self): + return '(' + self.left.repr() + '^' + self.right.repr() + ')' \ No newline at end of file