Sun, 19 Apr 2015 22:46:39 +0300
- reverted previous commit (now that was a bad idea)
calc.py | file | annotate | diff | comparison | revisions |
--- a/calc.py Sun Apr 19 22:33:39 2015 +0300 +++ b/calc.py Sun Apr 19 22:46:39 2015 +0300 @@ -233,47 +233,47 @@ def set_preferred_base (self, x): self.preferred_base = x - def trim_spaces (self): - self.expr = re.sub ('\s+', '', self.expr.strip()) + def trim_spaces (self, expr): + return re.sub ('\s+', '', expr.strip()) - def parse_attributes (self): - if self.expr[0] != '<': - return + def parse_attributes (self, expr): + if expr[0] != '<': + return expr i = 1 - while self.expr[i] != '>': - if self.expr[i] == '|': + while expr[i] != '>': + if expr[i] == '|': i += 1 for name in AttributeNames: - if self.expr[i:i + len(name)] == name: + if expr[i:i + len(name)] == name: Attributes[name] (self) i += len(name) - if self.expr[i] == '>': + if expr[i] == '>': break - if self.expr[i] != '|': + if expr[i] != '|': raise ValueError ('malformed attributes') break else: - raise ValueError ('bad attributes: %s' % self.expr[i:]) + raise ValueError ('bad attributes: %s' % expr[i:]) - return self.expr[i + 1:] + return expr[i + 1:] - def parse_number (self): - """Tries to parse a number from the expression. Returns value on success.""" + 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 self.expr[0:2].lower() == '0x': + if expr[0:2].lower() == '0x': base = 0x10 digits = string.hexdigits digitname = 'hexit' i = 2 - elif self.expr[0:2].lower() == '0b': + elif expr[0:2].lower() == '0b': base = 0b10 digits = ['0', '1'] digitname = 'bit' @@ -284,118 +284,121 @@ self.preferred_base = base # Skip leading zeroes - while i < len (self.expr) and self.expr[i] == '0': + while i < len (expr) and expr[i] == '0': i += 1 startingIndex = i - while i < len (self.expr) and self.expr[i] in digits: + while i < len (expr) and expr[i] in digits: i += 1 - if i < len(self.expr) and self.expr[i] == '.': + if i < len(expr) and expr[i] == '.': raise ValueError ('non-decimal floating point numbers are not supported') if i == startingIndex: - if i < len (self.expr): - raise ValueError ('not a valid %s "%s" in %s' % (digitname, self.expr[i], self.expr[0:i+1])) + 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 (complex (int (self.expr[startingIndex:i], base), 0), i) + return (complex (int (expr[startingIndex:i], base), 0), i) - if self.expr[0] == 'i': + if 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? - self.expr = self.expr[1:] - return 1j + return (1j, 1) # Try parse increasingly long substrings until we are unable to create a float or complex number # from it. try: - while i < len (self.expr): - if self.expr[i] == 'i': - value = complex (0.0, float (self.expr[:i])) + while i < len (expr): + if expr[i] == 'i': + value = complex (0.0, float (expr[:i])) else: - value = complex (float (self.expr [:i + 1]), 0.0) + value = complex (float (expr [:i + 1]), 0.0) i += 1 - self.expr = self.expr[i:] - return value + return (value, i) except ValueError: if i != 0: # Got a number (the error happened when we tried to parse past the number) - self.expr = self.expr[i:] - return value + return (value, i) else: # The error happened on the first character. So this is not a number. return None - def parse_symbol (self): + def parse_symbol (self, expr): for sym in Symbols: - if self.expr[:len (sym)] == sym: - self.expr = self.expr[len (sym):] + if expr[:len (sym)] == sym: return sym return None - def tokenize (self): - self.tokens = [] + def tokenize (self, expr): + i=0 + tokens = [] - while self.expr: - sym = self.parse_symbol() + while i < len(expr): + sym = self.parse_symbol (expr[i:]) if sym: symtype = SymbolTypes[sym] if symtype == SymbolType.CONSTANT: - self.tokens.append (Constants[sym]) + tokens.append (Constants[sym]) else: - self.tokens.append (sym) + tokens.append (sym) + + i += len(sym) continue - result = self.parse_number() + result = self.parse_number (expr[i:]) if result: - self.tokens.append (result) + num, length = result + tokens.append (num) + i += length continue - raise ValueError ("""bad expression, couldn't parse: %s""" % self.expr[i:]) + raise ValueError ("""bad expression, couldn't parse: %s""" % expr[i:]) - def process_parens (self): - """Processes parentheses of self.tokens into sublists in-place. + 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 self.tokens and ')' not in self.tokens: + if '(' not in expr and ')' not in expr: return try: - parenStart = rindex (self.tokens, '(') - parenEnd = self.tokens.index (')', parenStart) + parenStart = rindex (expr, '(') + parenEnd = expr.index (')', parenStart) except ValueError: - raise ValueError ("""mismatched parentheses in expression: %s""" % self.tokens) + raise ValueError ("""mismatched parentheses in expression: %s""" % expr) - subexpr = self.tokens[parenStart + 1:parenEnd] - del self.tokens[parenStart + 1:parenEnd + 1] - self.tokens[parenStart] = subexpr - self.process_parens (self.tokens) + subexpr = expr[parenStart + 1:parenEnd] + del expr[parenStart + 1:parenEnd + 1] + expr[parenStart] = subexpr + self.process_parens (expr) - def process_functions (self): + def process_functions (self, expr): """Processes functions in-place""" i = 0 - while i < len (self.tokens): - if type (self.tokens[i]) is list: - self.process_functions (self.tokens[i]) + while i < len (expr): + if type (expr[i]) is list: + self.process_functions (expr[i]) - if (type(self.tokens[i]) is not str) or (self.tokens[i] not in Functions): + if (type(expr[i]) is not str) or (expr[i] not in Functions): i += 1 continue # Ensure that arguments follow - 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]) + if (i + 1 >= len (expr)) or (type (expr[i + 1]) is not list): + raise ValueError ("""function %s used without arguments""" % expr[i]) - args = self.tokens[i + 1] - del self.tokens[i + 1] - self.tokens[i] = FunctionCall (self.tokens[i], args) + args = expr[i + 1] + del expr[i + 1] + expr[i] = FunctionCall (expr[i], args) i += 1 def is_operand (self, x): @@ -419,32 +422,32 @@ raise ValueError ('''unknown operator %s!''' % sym) - def process_operators (self): + def process_operators (self, expr): """Processes operators""" i = 0 # Find all operators in this expression - while i < len (self.tokens): - if type (self.tokens[i]) is list: - self.process_functions (self.tokens[i]) - self.process_operators (self.tokens[i]) + while i < len (expr): + if type (expr[i]) is list: + self.process_functions (expr[i]) + self.process_operators (expr[i]) - 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 FunctionCall: + self.process_functions (expr[i].args) + self.process_operators (expr[i].args) - if (type(self.tokens[i]) is not str) or (self.tokens[i] not in OperatorSymbols): + if (type(expr[i]) is not str) or (expr[i] not in OperatorSymbols): i += 1 continue args = [] argIndices = [] - if i > 0 and self.is_operand (self.tokens[i - 1]): - args.append (self.tokens[i - 1]) + if i > 0 and self.is_operand (expr[i - 1]): + args.append (expr[i - 1]) argIndices.append (i - 1) - if i - 1 < len(self.tokens) and self.is_operand (self.tokens[i + 1]): - args.append (self.tokens[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 @@ -453,16 +456,16 @@ if self.is_operand (arg): numOperands += 1 - self.tokens[i] = self.find_fitting_operator (self.tokens[i], numOperands) + expr[i] = self.find_fitting_operator (expr[i], numOperands) i += 1 - def find_priority_operator (self): + 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 (self.tokens)): - sym = self.tokens[i] + for i in range (0, len (expr)): + sym = expr[i] if type (sym) is not Operator: continue @@ -473,21 +476,21 @@ return (bestOp, bestOpIndex) - def evaluate (self, verbose=False): + def evaluate (self, expr, verbose=False): printFunc = realPrint if verbose else lambda x:None - printFunc (self.tabs + 'Preprocess: %s' % self.tokens) + printFunc (self.tabs + 'Preprocess: %s' % expr) # If there are sub-expressions in here, those need to be evaluated first i = 0 - while i < len (self.tokens): - sym = self.tokens[i] + 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' - self.tokens[i] = self.evaluate (list (sym), verbose) + expr[i] = self.evaluate (list (sym), verbose) self.tabs = self.tabs[:-1] - printFunc (self.tabs + '-> %s' % self.tokens[i]) + printFunc (self.tabs + '-> %s' % expr[i]) # If there are function calls, evaluate those if type (sym) is FunctionCall: @@ -497,19 +500,19 @@ self.tabs = self.tabs[:-1] printFunc (self.tabs + 'Evaluating function call: %s' % (sym)) - self.tokens[i] = Functions[sym.funcname]['function'] (*sym.args) - printFunc (self.tabs + '-> %s' % self.tokens[i]) + expr[i] = Functions[sym.funcname]['function'] (*sym.args) + printFunc (self.tabs + '-> %s' % expr[i]) i += 1 - printFunc (self.tabs + 'Evaluate: %s' % self.tokens) + 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() + op, i = self.find_priority_operator (expr) if not op: break @@ -518,22 +521,22 @@ else: argIndices = [i + 1] - args = [self.tokens[x] for x in argIndices] + args = [expr[x] for x in argIndices] argIndices = sorted (argIndices, reverse=True) printFunc (self.tabs + 'Processing: (%s, %d) with args %s' % (op, i, args)) - self.tokens[i] = op.function (*args) - printFunc (self.tabs + '-> %s' % self.tokens[i]) + expr[i] = op.function (*args) + printFunc (self.tabs + '-> %s' % expr[i]) for i2 in argIndices: - del self.tokens[i2] + del expr[i2] - printFunc (self.tabs + 'Result: %s' % self.tokens[0]) + printFunc (self.tabs + 'Result: %s' % expr[0]) - if len (self.tokens) != 1: - printFunc (self.tabs + 'Bad expression detected, tokens: %s' % self.tokens) + if len(expr) != 1: + printFunc (self.tabs + 'Bad expression detected, tokens: %s' % expr) raise ValueError ('malformed expression') - return self.tokens[0] + return expr[0] def repr_number (self, x): """Returns a string representation for a real number""" @@ -600,14 +603,12 @@ def calc (self, expr, verbose=False): self.state = {} - self.expr = expr self.tabs = '' - - self.trim_spaces() - self.parse_attributes() - self.tokenize() - self.process_parens() - self.process_functions() - self.process_operators() - result = self.evaluate (verbose) + 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) return self.represent (result)