# HG changeset patch # User Teemu Piippo # Date 1429472019 -10800 # Node ID 8aa03b5c6e47b293d3c36ab32d974e2837e7270f # Parent bd949c554dd2176b6d8886b4e8d2e5aae7d1a51a - store expression and tokens as members of Calculator rather than being passed around as parameters diff -r bd949c554dd2 -r 8aa03b5c6e47 calc.py --- a/calc.py Sun Apr 19 22:02:02 2015 +0300 +++ b/calc.py Sun Apr 19 22:33:39 2015 +0300 @@ -233,47 +233,47 @@ def set_preferred_base (self, x): self.preferred_base = x - def trim_spaces (self, expr): - return re.sub ('\s+', '', expr.strip()) + def trim_spaces (self): + self.expr = re.sub ('\s+', '', self.expr.strip()) - def parse_attributes (self, expr): - if expr[0] != '<': - return expr + def parse_attributes (self): + if self.expr[0] != '<': + return i = 1 - while expr[i] != '>': - if expr[i] == '|': + while self.expr[i] != '>': + if self.expr[i] == '|': i += 1 for name in AttributeNames: - if expr[i:i + len(name)] == name: + if self.expr[i:i + len(name)] == name: Attributes[name] (self) i += len(name) - if expr[i] == '>': + if self.expr[i] == '>': break - if expr[i] != '|': + if self.expr[i] != '|': raise ValueError ('malformed attributes') break else: - raise ValueError ('bad attributes: %s' % expr[i:]) + raise ValueError ('bad attributes: %s' % self.expr[i:]) - return expr[i + 1:] + return self.expr[i + 1:] - def parse_number (self, expr): - """Tries to parse a number from the expression. Returns (value, length) on success.""" + def parse_number (self): + """Tries to parse a number from the expression. Returns value on success.""" i = 0 value = None base = 10 # Possibly it's hexadecimal - if expr[0:2].lower() == '0x': + if self.expr[0:2].lower() == '0x': base = 0x10 digits = string.hexdigits digitname = 'hexit' i = 2 - elif expr[0:2].lower() == '0b': + elif self.expr[0:2].lower() == '0b': base = 0b10 digits = ['0', '1'] digitname = 'bit' @@ -284,121 +284,118 @@ self.preferred_base = base # Skip leading zeroes - while i < len (expr) and expr[i] == '0': + while i < len (self.expr) and self.expr[i] == '0': i += 1 startingIndex = i - while i < len (expr) and expr[i] in digits: + while i < len (self.expr) and self.expr[i] in digits: i += 1 - if i < len(expr) and expr[i] == '.': + if i < len(self.expr) and self.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])) + if i < len (self.expr): + raise ValueError ('not a valid %s "%s" in %s' % (digitname, self.expr[i], self.expr[0:i+1])) else: raise ValueError ('end of expression encountered while parsing ' 'base-%d literal' % base) - return (complex (int (expr[startingIndex:i], base), 0), i) + return (complex (int (self.expr[startingIndex:i], base), 0), i) - if expr[0] == 'i': + if self.expr[0] == 'i': # 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. # TODO: maybe 'i' can be a symbol instead? - return (1j, 1) + self.expr = self.expr[1:] + return 1j # 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': - value = complex (0.0, float (expr[:i])) + while i < len (self.expr): + if self.expr[i] == 'i': + value = complex (0.0, float (self.expr[:i])) else: - value = complex (float (expr [:i + 1]), 0.0) + value = complex (float (self.expr [:i + 1]), 0.0) i += 1 - return (value, i) + self.expr = self.expr[i:] + return value except ValueError: if i != 0: # Got a number (the error happened when we tried to parse past the number) - return (value, i) + self.expr = self.expr[i:] + return value else: # The error happened on the first character. So this is not a number. return None - def parse_symbol (self, expr): + def parse_symbol (self): for sym in Symbols: - if expr[:len (sym)] == sym: + if self.expr[:len (sym)] == sym: + self.expr = self.expr[len (sym):] return sym return None - def tokenize (self, expr): - i=0 - tokens = [] + def tokenize (self): + self.tokens = [] - while i < len(expr): - sym = self.parse_symbol (expr[i:]) + while self.expr: + sym = self.parse_symbol() if sym: symtype = SymbolTypes[sym] if symtype == SymbolType.CONSTANT: - tokens.append (Constants[sym]) + self.tokens.append (Constants[sym]) else: - tokens.append (sym) - - i += len(sym) + self.tokens.append (sym) continue - result = self.parse_number (expr[i:]) + result = self.parse_number() if result: - num, length = result - tokens.append (num) - i += length + self.tokens.append (result) continue - raise ValueError ("""bad expression, couldn't parse: %s""" % expr[i:]) + raise ValueError ("""bad expression, couldn't parse: %s""" % self.expr[i:]) - return tokens - - def process_parens (self, expr): - """Processes parentheses of expr into sublists in-place. + def process_parens (self): + """Processes parentheses of self.tokens into sublists in-place. [1.0, '*', '(', 3.5, '+', 1j, ')'] -> [1.0, '*', [3.5, '+', 1j]]""" - if '(' not in expr and ')' not in expr: + if '(' not in self.tokens and ')' not in self.tokens: return try: - parenStart = rindex (expr, '(') - parenEnd = expr.index (')', parenStart) + parenStart = rindex (self.tokens, '(') + parenEnd = self.tokens.index (')', parenStart) except ValueError: - raise ValueError ("""mismatched parentheses in expression: %s""" % expr) + raise ValueError ("""mismatched parentheses in expression: %s""" % self.tokens) - subexpr = expr[parenStart + 1:parenEnd] - del expr[parenStart + 1:parenEnd + 1] - expr[parenStart] = subexpr - self.process_parens (expr) + subexpr = self.tokens[parenStart + 1:parenEnd] + del self.tokens[parenStart + 1:parenEnd + 1] + self.tokens[parenStart] = subexpr + self.process_parens (self.tokens) - def process_functions (self, expr): + def process_functions (self): """Processes functions in-place""" i = 0 - while i < len (expr): - if type (expr[i]) is list: - self.process_functions (expr[i]) + while i < len (self.tokens): + if type (self.tokens[i]) is list: + self.process_functions (self.tokens[i]) - if (type(expr[i]) is not str) or (expr[i] not in Functions): + if (type(self.tokens[i]) is not str) or (self.tokens[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]) + if (i + 1 >= len (self.tokens)) or (type (self.tokens[i + 1]) is not list): + raise ValueError ("""function %s used without arguments""" % self.tokens[i]) - args = expr[i + 1] - del expr[i + 1] - expr[i] = FunctionCall (expr[i], args) + args = self.tokens[i + 1] + del self.tokens[i + 1] + self.tokens[i] = FunctionCall (self.tokens[i], args) i += 1 def is_operand (self, x): @@ -422,32 +419,32 @@ raise ValueError ('''unknown operator %s!''' % sym) - def process_operators (self, expr): + def process_operators (self): """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]) + while i < len (self.tokens): + if type (self.tokens[i]) is list: + self.process_functions (self.tokens[i]) + self.process_operators (self.tokens[i]) - if type (expr[i]) is FunctionCall: - self.process_functions (expr[i].args) - self.process_operators (expr[i].args) + if type (self.tokens[i]) is FunctionCall: + self.process_functions (self.tokens[i].args) + self.process_operators (self.tokens[i].args) - if (type(expr[i]) is not str) or (expr[i] not in OperatorSymbols): + if (type(self.tokens[i]) is not str) or (self.tokens[i] not in OperatorSymbols): i += 1 continue args = [] argIndices = [] - if i > 0 and self.is_operand (expr[i - 1]): - args.append (expr[i - 1]) + if i > 0 and self.is_operand (self.tokens[i - 1]): + args.append (self.tokens[i - 1]) argIndices.append (i - 1) - if i - 1 < len(expr) and self.is_operand (expr[i + 1]): - args.append (expr[i + 1]) + if i - 1 < len(self.tokens) and self.is_operand (self.tokens[i + 1]): + args.append (self.tokens[i + 1]) argIndices.append (i + 1) # Resolve operators with the same symbol based on operand count @@ -456,16 +453,16 @@ if self.is_operand (arg): numOperands += 1 - expr[i] = self.find_fitting_operator (expr[i], numOperands) + self.tokens[i] = self.find_fitting_operator (self.tokens[i], numOperands) i += 1 - def find_priority_operator (self, expr): + def find_priority_operator (self): """Finds the operator with most priority in the expression""" bestOp = None bestOpIndex = -1 - for i in range (0, len (expr)): - sym = expr[i] + for i in range (0, len (self.tokens)): + sym = self.tokens[i] if type (sym) is not Operator: continue @@ -476,21 +473,21 @@ return (bestOp, bestOpIndex) - def evaluate (self, expr, verbose=False): + def evaluate (self, verbose=False): printFunc = realPrint if verbose else lambda x:None - printFunc (self.tabs + 'Preprocess: %s' % expr) + printFunc (self.tabs + 'Preprocess: %s' % self.tokens) # If there are sub-expressions in here, those need to be evaluated first i = 0 - while i < len (expr): - sym = expr[i] + while i < len (self.tokens): + sym = self.tokens[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.tokens[i] = self.evaluate (list (sym), verbose) self.tabs = self.tabs[:-1] - printFunc (self.tabs + '-> %s' % expr[i]) + printFunc (self.tabs + '-> %s' % self.tokens[i]) # If there are function calls, evaluate those if type (sym) is FunctionCall: @@ -500,19 +497,19 @@ self.tabs = self.tabs[:-1] printFunc (self.tabs + 'Evaluating function call: %s' % (sym)) - expr[i] = Functions[sym.funcname]['function'] (*sym.args) - printFunc (self.tabs + '-> %s' % expr[i]) + self.tokens[i] = Functions[sym.funcname]['function'] (*sym.args) + printFunc (self.tabs + '-> %s' % self.tokens[i]) i += 1 - printFunc (self.tabs + 'Evaluate: %s' % expr) + printFunc (self.tabs + 'Evaluate: %s' % self.tokens) runaway = 0 while True: runaway += 1 if runaway > 1000: raise ValueError ('infinite loop detected') - op, i = self.find_priority_operator (expr) + op, i = self.find_priority_operator() if not op: break @@ -521,22 +518,22 @@ else: argIndices = [i + 1] - args = [expr[x] for x in argIndices] + args = [self.tokens[x] for x in argIndices] argIndices = sorted (argIndices, reverse=True) printFunc (self.tabs + 'Processing: (%s, %d) with args %s' % (op, i, args)) - expr[i] = op.function (*args) - printFunc (self.tabs + '-> %s' % expr[i]) + self.tokens[i] = op.function (*args) + printFunc (self.tabs + '-> %s' % self.tokens[i]) for i2 in argIndices: - del expr[i2] + del self.tokens[i2] - printFunc (self.tabs + 'Result: %s' % expr[0]) + printFunc (self.tabs + 'Result: %s' % self.tokens[0]) - if len(expr) != 1: - printFunc (self.tabs + 'Bad expression detected, tokens: %s' % expr) + if len (self.tokens) != 1: + printFunc (self.tabs + 'Bad expression detected, tokens: %s' % self.tokens) raise ValueError ('malformed expression') - return expr[0] + return self.tokens[0] def repr_number (self, x): """Returns a string representation for a real number""" @@ -603,12 +600,14 @@ def calc (self, expr, verbose=False): self.state = {} + self.expr = expr 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_operators (expr) - result = self.evaluate (expr, verbose) + + self.trim_spaces() + self.parse_attributes() + self.tokenize() + self.process_parens() + self.process_functions() + self.process_operators() + result = self.evaluate (verbose) return self.represent (result)