- store expression and tokens as members of Calculator rather than being passed around as parameters

Sun, 19 Apr 2015 22:33:39 +0300

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Sun, 19 Apr 2015 22:33:39 +0300
changeset 129
8aa03b5c6e47
parent 128
bd949c554dd2
child 130
c82cef747008

- store expression and tokens as members of Calculator rather than being passed around as parameters

calc.py file | annotate | diff | comparison | revisions
--- 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)

mercurial