Tue, 02 Feb 2016 20:47:47 +0200
Stuff
''' Copyright 2015 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' from __future__ import print_function import re import cmath import math import random import time import operator import string from fraction import Fraction from copy import deepcopy from math import pi as π ε = 1e-10 class Operator (object): def __init__ (self, name, symbol, operands, priority, function, assign): self.name = name self.symbol = symbol self.operands = operands self.priority = priority self.function = function self.assign = assign def __repr__ (self): return '<operator %s>' % (self.name) """return ('Operator(name={name}, symbol={symbol}, operands={operands}, ' 'priority={priority}, function={function})'.format(**self.__dict__))""" class FunctionCall (object): def __init__ (self, funcname, args): assert (type(args) is list) self.funcname = funcname self.args = args def __repr__ (self): return '''FunctionCall(%r, %r)''' % (self.funcname, self.args) class Value (object): def __init__ (self, value): if isinstance (value, Fraction) and value.denominator == 1: value = value.numerator self.value = value def __repr__ (self): return '''Value(%r)''' % (self.value) class Name (object): def __init__ (self, name): self.name = name def __repr__ (self): return '''Name(%r)''' % (self.name) class AssignmentResult (object): def __init__ (self, name, value): self.name, self.value = name, value def __repr__ (self): return '''AssignmentResult(%r, %r)''' % (self.name, self.value) def do_realf (func, *args): for x in args: if x.imag: raise ValueError ('%s called with a complex number' % func.__name__) return func (*[x.real for x in args]) def do_intf (func, *args): for x in args: if x.imag: raise ValueError ('%s called with a complex number' % func.__name__) if x.real - math.floor (x.real): raise ValueError ('%s called with a floating point number' % func.__name__) return func (*[int (x.real) for x in args]) def realf (func): return lambda *args: do_realf (func, *args) def intf (func): return lambda *args: do_intf (func, *args) def dice (numRolls, maxValue): sumValue = 0 for i in range (0, numRolls): sumValue += random.randint (1, maxValue) return sumValue def scientific (mantissa, exp): return mantissa * 10 ** exp def cbrt (x): return x ** (1/3) def factorial (x): return math.gamma (x + 1) def is_int (x): return math.fabs (x - math.floor(x)) < ε def div(a,b): if is_int(a) and is_int(b): return Fraction(a,b) else: return a / b def assignment (name, value, calculator): calculator.variables[name.name] = value.value Operators = {} OperatorData = { 'not': { 'symbol': '!', 'operands': 1, 'priority': 5, 'function': lambda x: not x }, 'compl': { 'symbol': '~', 'operands': 1, 'priority': 5, 'function': intf (operator.inv) }, 'neg': { 'symbol': '-', 'operands': 1, 'priority': 5, 'function': lambda x: -x }, 'sqrt': { 'symbol': '√', 'operands': 1, 'priority': 7, 'function': cmath.sqrt }, 'cbrt': { 'symbol': '∛', 'operands': 1, 'priority': 7, 'function': cbrt }, 'fact': { 'symbol': '!', 'operands': -1, 'priority': 8, 'function': realf(factorial) }, 'pow': { 'symbol': '**', 'operands': 2, 'priority': 10, 'function': lambda x, y: x ** y }, 'mul': { 'symbol': '*', 'operands': 2, 'priority': 50, 'function': lambda x, y: x * y }, 'div': { 'symbol': '/', 'operands': 2, 'priority': 50, 'function': div }, 'mod': { 'symbol': '%', 'operands': 2, 'priority': 50, 'function': realf (math.fmod) }, 'add': { 'symbol': '+', 'operands': 2, 'priority': 100, 'function': lambda x, y: x + y }, 'sub': { 'symbol': '-', 'operands': 2, 'priority': 100, 'function': lambda x, y: x - y }, 'eq': { 'symbol': '==', 'operands': 2, 'priority': 500, 'function': lambda x, y: x == y }, 'neq': { 'symbol': '!=', 'operands': 2, 'priority': 500, 'function': lambda x, y: x != y }, 'lt': { 'symbol': '<', 'operands': 2, 'priority': 500, 'function': lambda x, y: x < y }, 'lteq': { 'symbol': '<=', 'operands': 2, 'priority': 500, 'function': lambda x, y: x <= y }, 'gt': { 'symbol': '>', 'operands': 2, 'priority': 500, 'function': lambda x, y: x > y }, 'gteq': { 'symbol': '>=', 'operands': 2, 'priority': 500, 'function': lambda x, y: x >= y }, 'btand': { 'symbol': '&', 'operands': 2, 'priority': 600, 'function': intf (operator.and_) }, 'btxor': { 'symbol': '^', 'operands': 2, 'priority': 601, 'function': intf (operator.xor) }, 'btor': { 'symbol': '|', 'operands': 2, 'priority': 602, 'function': intf (operator.or_) }, 'and': { 'symbol': '&&', 'operands': 2, 'priority': 603, 'function': lambda x, y: x and y }, 'or': { 'symbol': '||', 'operands': 2, 'priority': 604, 'function': lambda x, y: x or y }, 'assign': { 'symbol': '=', 'operands': 2, 'priority': 999, 'function': assignment, 'assign':True }, } def powerfunction (n): return lambda x: x**n superscripts='⁰¹²³⁴⁵⁶⁷⁸⁹' for i, sup in enumerate(superscripts): func = powerfunction(i) func.__name__ = 'x' + sup OperatorData['sup' + str(i)] = { 'symbol': sup, 'operands': -1, 'priority': 3, 'function': func } for name, data in OperatorData.items(): if 'assign' not in data: data['assign'] = False Operators[name] = Operator (name=name, **data) OperatorSymbols={} for op in Operators.values(): if op.symbol in OperatorSymbols: OperatorSymbols[op.symbol].append (op) else: OperatorSymbols[op.symbol] = [op] def sgn (x): if abs(x) < 1e-10: return 0 else: return x / abs(x) def integerclamp (x, bits, signed): x = x % (2 ** bits) if signed and x >= (2 ** (bits - 1)): x = -(2 ** bits) + x return x Constants = { 'pi': π, 'e': math.ℯ, 'phi': 1.6180339887498948482, 'epsilon': ε, } random.seed (time.time()) Functions = { 'round': { 'function': realf (round) }, 'floor': { 'function': realf (math.floor) }, 'ceil': { 'function': realf (math.ceil) }, 'factorial': { 'function': intf (math.factorial) }, 'abs': { 'function': realf (math.fabs) }, 'degrees': { 'function': realf (math.degrees) }, 'radians': { 'function': realf (math.radians) }, 'erf': { 'function': realf (math.erf) }, 'erfc': { 'function': realf (math.erfc) }, 'gamma': { 'function': realf (math.gamma) }, 'lgamma': { 'function': realf (math.lgamma) }, 'sqrt': { 'function': cmath.sqrt }, 'cbrt': { 'function': cbrt }, 'ln': { 'function': cmath.log }, 'log': { 'function': cmath.log10 }, 'sin': { 'function': cmath.sin }, 'sinh': { 'function': cmath.sinh }, 'asin': { 'function': cmath.asin }, 'asinh': { 'function': cmath.asinh }, 'cos': { 'function': cmath.cos }, 'cosh': { 'function': cmath.cosh }, 'acos': { 'function': cmath.acos }, 'acosh': { 'function': cmath.acosh }, 'tan': { 'function': cmath.tan }, 'tanh': { 'function': cmath.tanh }, 'atan': { 'function': cmath.atan }, 'atanh': { 'function': cmath.atanh }, 'exp': { 'function': cmath.exp }, 'phase': { 'function': cmath.phase }, 'lg': { 'function': lambda x: cmath.log (x, 2) }, 're': { 'function': lambda x: x.real }, 'im': { 'function': lambda x: x.imag }, 'conjugate': { 'function': lambda x: x.conjugate() }, 'rand': { 'function': random.random }, 'sgn': { 'function': realf (sgn) }, 'byte': { 'function': intf (lambda x: integerclamp (x, bits=8, signed=False)) }, 'sbyte': { 'function': intf (lambda x: integerclamp (x, bits=8, signed=True)) }, 'word': { 'function': intf (lambda x: integerclamp (x, bits=16, signed=False)) }, 'sword': { 'function': intf (lambda x: integerclamp (x, bits=16, signed=True)) }, 'dword': { 'function': intf (lambda x: integerclamp (x, bits=32, signed=False)) }, 'sdword': { 'function': intf (lambda x: integerclamp (x, bits=32, signed=True)) }, 'qword': { 'function': intf (lambda x: integerclamp (x, bits=64, signed=False)) }, 'sqword': { 'function': intf (lambda x: integerclamp (x, bits=64, signed=True)) }, 'cdf': { 'function': realf (lambda x: (1 + math.erf (x / math.sqrt(2))) / 2) }, } Tokens = {'(', ')'} # Symbol table class SymbolType: constant = 0 function = 1 operator = 2 token = 3 SymbolTypes = {} Symbols = {'ans'} SymbolTypes['ans'] = SymbolType.constant Aliases = { 'π': 'pi', 'ℯ': 'e', 'φ': 'phi', 'ε': 'epsilon', '∨': '||', '∧': '&&', 'Φ': 'cdf', } for name, value in Constants.items(): Symbols.add (name) SymbolTypes[name] = SymbolType.constant for name, data in Functions.items(): Symbols.add (name) SymbolTypes[name] = SymbolType.function for op in Operators.values(): if op.symbol not in Symbols: Symbols.add (op.symbol) SymbolTypes[op.symbol] = SymbolType.operator for name in Tokens: SymbolTypes[name] = SymbolType.token Symbols |= Tokens Symbols |= set(Aliases.keys()) Symbols = sorted (Symbols, key=lambda x: len (x), reverse=True) PreferredBase = 10 def set_preferred_base(x): global PreferredBase PreferredBase = x def rindex (li, value): return (len(li) - 1) - li[::-1].index(value) Attributes = { 'hex': lambda self: self.set_preferred_base (0x10), 'binary': lambda self: self.set_preferred_base (0b10), 'decimal': lambda self: self.set_preferred_base (10), } Attributes['bin'] = Attributes['binary'] Attributes['dec'] = Attributes['decimal'] AttributeNames = sorted ((key for key in Attributes), key=lambda x:len(x), reverse=True) def is_int (x): return math.fabs (x - math.floor(x)) < ε identifierchars = set(string.ascii_lowercase + string.ascii_uppercase + string.digits + '_') def is_identifier(x): return x and (x[0] not in string.digits) and (set(x) - identifierchars == set()) class Calculator (object): def __init__ (self, verbose=False): self.previous_result = None self.preferred_base = None self.verbose = verbose self.variables = {} self.namepattern = re.compile (r'^([A-Za-z_][A-Za-z0-9_]*)') def set_preferred_base (self, x): self.preferred_base = x def trim_spaces (self, expr): return re.sub ('\s+', '', expr.strip()) def parse_attributes (self, expr): if expr[0] != '<': return expr i = 1 while expr[i] != '>': if expr[i] == '|': i += 1 for name in AttributeNames: if expr[i:i + len(name)] == name: Attributes[name] (self) i += len(name) if expr[i] == '>': break if expr[i] != '|': raise ValueError ('malformed attributes') break else: raise ValueError ('bad attributes: %s' % expr[i:]) return expr[i + 1:] def parse_number (self, expr): """Tries to parse a number from the expression. Returns (value, length) on success.""" i = 0 value = None base = 10 # Possibly it's hexadecimal if expr[0:2].lower() == '0x': base = 0x10 digits = string.hexdigits digitname = 'hexit' i = 2 elif expr[0:2].lower() == '0b': base = 0b10 digits = ['0', '1'] digitname = 'bit' i = 2 if base != 10: if not self.preferred_base: self.preferred_base = base # Skip leading zeroes while i < len (expr) and expr[i] == '0': i += 1 startingIndex = i while i < len (expr) and expr[i] in digits: i += 1 if i < len(expr) and expr[i] == '.': raise ValueError ('non-decimal floating point numbers are not supported') if i == startingIndex: if i < len (expr): raise ValueError ('not a valid %s "%s" in %s' % (digitname, expr[i], expr[0:i+1])) else: raise ValueError ('end of expression encountered while parsing ' 'base-%d literal' % base) return (Value (int (expr[startingIndex:i], base)), i) if expr[0] == 'i' or expr[0] == 'ι': # Special case, we just have 'i' -- need special handling here because otherwise this would # call float('') in the complex number routine, which throws an error. return (Value(1j), 1) # Try parse increasingly long substrings until we are unable to create a float or complex number # from it. try: while i < len (expr): if expr[i] == 'i' or expr[i] == 'ι': value = Value (float (expr[:i]) * 1j) else: value = Value (float (expr [:i + 1])) i += 1 return (value, i) except ValueError: if i != 0: # Got a number (the error happened when we tried to parse past the number) return (value, i) else: # The error happened on the first character. So this is not a number. return None def parse_symbol (self, expr): for sym in Symbols: if len(sym) == 1 and ord(sym) > 256: if expr[0] == sym: return sym else: if expr[:len (sym)].lower() == sym.lower(): return sym.lower() return None def tokenize (self, expr): i=0 tokens = [] while i < len(expr): sym = self.parse_symbol (expr[i:]) if sym: i += len(sym) if sym in Aliases: sym = Aliases[sym] if sym == 'ans': if self.previous_result is None: raise ValueError ('Cannot use "ans" because there is no prior result') tokens.append (Value (self.previous_result)) else: symtype = SymbolTypes[sym] if symtype == SymbolType.constant: tokens.append (Value(Constants[sym])) else: tokens.append (sym) continue result = self.parse_number (expr[i:]) if result: num, length = result tokens.append (num) i += length continue match = self.namepattern.match (expr[i:]) if match: name = match.group(1) i += len(name) tokens.append (Name(name)) continue raise ValueError ("""couldn't parse: %s""" % expr[i:]) return tokens def process_parens (self, expr): """Processes parentheses of expr into sublists in-place. [1.0, '*', '(', 3.5, '+', 1j, ')'] -> [1.0, '*', [3.5, '+', 1j]]""" if '(' not in expr and ')' not in expr: return try: parenStart = rindex (expr, '(') parenEnd = expr.index (')', parenStart) except ValueError: raise ValueError ("""mismatched parentheses in expression: %s""" % expr) subexpr = expr[parenStart + 1:parenEnd] del expr[parenStart + 1:parenEnd + 1] expr[parenStart] = subexpr self.process_parens (expr) def process_functions (self, expr): """Processes functions in-place""" i = 0 while i < len (expr): if type (expr[i]) is list: self.process_functions (expr[i]) if (type(expr[i]) is not str) or (expr[i] not in Functions): i += 1 continue # Ensure that arguments follow if (i + 1 >= len (expr)) or (type (expr[i + 1]) is not list): raise ValueError ("""function %s used without arguments""" % expr[i]) args = expr[i + 1] del expr[i + 1] expr[i] = FunctionCall (expr[i], args) i += 1 def process_variables (self, expr): for i, x in enumerate(expr): if isinstance(x, list): self.process_variables(x) elif isinstance(x, Name): try: expr[i].value = Value(self.variables[x.name]) except KeyError as e: pass def is_operand (self, x): # Operands can be either lists (which also mean parens, thus can be single-expressions) # or complex numbers return type(x) in [list, FunctionCall, Value, Name] def find_fitting_operator (self, sym, numOperands): # Pass 1: exact numOperands match foundByName = False for op in Operators.values(): if op.symbol != sym: continue foundByName = True if op.operands == numOperands: return op if foundByName: raise ValueError ('''Operator %s used incorrectly''' % sym) else: raise ValueError ('''unknown operator %s!''' % sym) def process_operators (self, expr): """Processes operators""" i = 0 # Find all operators in this expression while i < len (expr): if type (expr[i]) is list: self.process_functions (expr[i]) self.process_operators (expr[i]) if type (expr[i]) is FunctionCall: self.process_functions (expr[i].args) self.process_operators (expr[i].args) if (type(expr[i]) is not str) or (expr[i] not in OperatorSymbols): i += 1 continue # Test for suffix operators if i > 0 and self.is_operand (expr[i - 1]): try: op = self.find_fitting_operator (expr[i], -1) except: pass else: expr[i] = op continue args = [] argIndices = [] if i > 0 and self.is_operand (expr[i - 1]): args.append (expr[i - 1]) argIndices.append (i - 1) if i + 1 < len(expr) and self.is_operand (expr[i + 1]): args.append (expr[i + 1]) argIndices.append (i + 1) # Resolve operators with the same symbol based on operand count numOperands = 0 for arg in args: if self.is_operand (arg): numOperands += 1 expr[i] = self.find_fitting_operator (expr[i], numOperands) i += 1 def amend_multiplication (self, expr): i = 0 while i + 1 < len(expr): if self.is_operand (expr[i]) and self.is_operand (expr[i + 1]): expr.insert(i + 1, Operators['mul']) i += 2 else: i += 1 # Also amend multiplication in sub-expressions for x in expr: if isinstance(x, list): self.amend_multiplication(x) def find_priority_operator (self, expr): """Finds the operator with most priority in the expression""" bestOp = None bestOpIndex = -1 for i in range (0, len (expr)): sym = expr[i] if type (sym) is not Operator: continue if not bestOp or sym.priority < bestOp.priority: bestOp = sym bestOpIndex = i return (bestOp, bestOpIndex) def process_values (self, values): result = [] for x in values: if isinstance (x, Name) and not hasattr (x, 'value'): raise ValueError ('%s is unknown' % (x.name)) value = x while hasattr (value, 'value'): value = value.value result.append (value) return result def evaluate (self, expr, verbose=False): printFunc = print if verbose else lambda x:None printFunc (self.tabs + 'Preprocess: %s' % expr) # If there are sub-expressions in here, those need to be evaluated first i = 0 while i < len (expr): sym = expr[i] if type (sym) is list and sym: printFunc (self.tabs + 'Evaluating sub-expression: %s' % (sym)) self.tabs += '\t' expr[i] = self.evaluate (list (sym), verbose) self.tabs = self.tabs[:-1] printFunc (self.tabs + '-> %s' % expr[i]) # If there are function calls, evaluate those if type (sym) is FunctionCall: self.tabs += '\t' if sym.args: sym.args = [self.evaluate (sym.args, verbose)] self.tabs = self.tabs[:-1] printFunc (self.tabs + 'Evaluating function call: %s' % (sym)) expr[i] = Value (Functions[sym.funcname]['function'] (*self.process_values(sym.args))) printFunc (self.tabs + '-> %s' % expr[i]) i += 1 printFunc (self.tabs + 'Evaluate: %s' % expr) runaway = 0 while True: runaway += 1 if runaway > 1000: raise ValueError ('infinite loop detected') op, i = self.find_priority_operator (expr) if not op: break if op.operands == 2: argIndices = [i - 1, i + 1] elif op.operands == -1: argIndices = [i - 1] else: argIndices = [i + 1] args = [expr[x] for x in argIndices] argIndices = sorted (argIndices, reverse=True) printFunc (self.tabs + 'Processing: (%s, %d) with args %s' % (op, i, args)) if op.assign: op.function (*args, calculator=self) expr[i] = AssignmentResult(name=args[0], value=Value(self.variables[args[0].name])) else: expr[i] = Value (op.function (*self.process_values(args))) printFunc (self.tabs + '-> %s' % expr[i]) for i2 in argIndices: del expr[i2] if op.assign and len(expr) != 1: raise ValueError ('expression did not evaluate into an assignment: %s' % expr) printFunc (self.tabs + 'Result: %s' % expr[0]) if len(expr) != 1: printFunc (self.tabs + 'Bad expression detected, tokens: %s' % expr) raise ValueError ('malformed expression') return expr[0] def repr_number (self, x): """Returns a string representation for a real number""" base = self.preferred_base if self.preferred_base else 10 if is_int (x) and base != 10: digits='0123456789abcdef' assert base <= len (digits), '''base %d is too large''' % base divisor = base rep = '' x = int (x) runaway = 0 if not x: return '0x0' if x < 0: return '-' + self.repr_number (abs (x)) while x > 0: runaway += 1 if runaway > 1000: raise ValueError('runaway triggered') i = (x % divisor) / (divisor / base) x -= i * (divisor / base) rep += digits[i] divisor *= base rep += 'x' if base == 16 else 'b' rep += '0' return rep[::-1] rep = '%.10f' % x if '.' in rep: while rep[-1] == '0': rep = rep[:-1] if rep[-1] == '.': rep = rep[:-1] return rep def repr_imaginary (self, x): rep = self.repr_number (x) if rep == '1': return 'i' elif rep == '-1': return '-i' else: return rep + 'i' def represent (self, x): """Returns a string representation of a float or complex number""" if isinstance (x, Value): x = x.value if isinstance (x, Fraction): return '%s / %s' % (x.numerator, x.denominator) elif math.fabs (x.imag) > ε: if math.fabs (x.real) > ε: # Complex number return '%s %s %s' % (self.repr_number (x.real), '+' if x.imag >= 0 else '-', self.repr_imaginary (math.fabs (x.imag))) else: # Pure imaginary number return self.repr_imaginary (x.imag) else: # Real number return self.repr_number (x.real) elif isinstance (x, AssignmentResult): return '%s = %s' % (x.name.name, self.represent (x.value)) elif isinstance (x, Name) and hasattr(x, 'value'): return self.represent(x.value) else: return '<%s %s>' % (type(x).__name__, x) def calc (self, expr, verbose=None): if verbose is None: verbose = self.verbose self.state = {} self.tabs = '' expr = self.trim_spaces (expr) expr = self.parse_attributes (expr) expr = self.tokenize (expr) self.process_parens (expr) self.process_functions (expr) self.process_variables (expr) self.amend_multiplication (expr) self.process_operators (expr) result = self.evaluate (expr, verbose) self.previous_result = result return self.represent (result) def __call__ (self, expr): return self.calc (expr) calc = Calculator() dcalc = Calculator(verbose=True)