def test_tokenize_trigonometrics(self): expression = "sin(5) + cos(5) + tan(5) + ctan(5)" token_list = [ tokens.SinFunctionToken(), tokens.OpenParenthesisToken(), tokens.OperandToken(5), tokens.CloseParenthesisToken(), tokens.PlusOperatorToken(), tokens.CosFunctionToken(), tokens.OpenParenthesisToken(), tokens.OperandToken(5), tokens.CloseParenthesisToken(), tokens.PlusOperatorToken(), tokens.TanFunctionToken(), tokens.OpenParenthesisToken(), tokens.OperandToken(5), tokens.CloseParenthesisToken(), tokens.PlusOperatorToken(), tokens.CtanFunctionToken(), tokens.OpenParenthesisToken(), tokens.OperandToken(5), tokens.CloseParenthesisToken() ] computed_token_list = tokenize(expression) self.assertListEqual(computed_token_list, token_list)
def test_tokenize_implicit_product_operand_and_constant(self): expr = "5 pi" token_list = [ tokens.OperandToken(5), tokens.ProductOperatorToken(), tokens.PiConstantToken() ] self.assertListEqual(token_list, tokenize(expr))
def test_tokenize_division(self): expr = "5 / 2" token_list = [ tokens.OperandToken(5), tokens.DivisionOperatorToken(), tokens.OperandToken(2) ] self.assertListEqual(token_list, tokenize(expr))
def test_tokenize_6(self): expression = "sinpi" token_list = [ tokens.SinFunctionToken(), tokens.PiConstantToken() ] computed_token_list = tokenize(expression) self.assertListEqual(computed_token_list, token_list)
def test_tokenize_5(self): expression = "Log10" token_list = [ tokens.LogFunctionToken(), tokens.OperandToken(10), ] computed_token_list = tokenize(expression) self.assertListEqual(computed_token_list, token_list)
def test_simple_function(self): expression = "sin 5" computed_token_list = tokenize(expression) postfix_token_list = infix_to_postfix(computed_token_list) token_list = [ tokens.OperandToken(5), tokens.SinFunctionToken(), ] self.assertListEqual(postfix_token_list, token_list)
def test_tokenize_logarithm_with_custom_base(self): expr = "Log100(10)" token_list = [ tokens.LogFunctionToken(has_custom_base=True), tokens.OperandToken(100), tokens.OpenParenthesisToken(), tokens.OperandToken(10), tokens.CloseParenthesisToken() ] self.assertListEqual(token_list, tokenize(expr))
def test_tokenize_constants(self): expression = "pi * e" token_list = [ tokens.PiConstantToken(), tokens.ProductOperatorToken(), tokens.EulerConstantToken() ] computed_token_list = tokenize(expression) self.assertListEqual(computed_token_list, token_list)
def test_tokenize_4(self): expression = "Log(10)" token_list = [ tokens.LogFunctionToken(), tokens.OpenParenthesisToken(), tokens.OperandToken(10), tokens.CloseParenthesisToken() ] computed_token_list = tokenize(expression) self.assertListEqual(computed_token_list, token_list)
def test_tokenize_negative(self): expression = "-5 + 2" token_list = [ tokens.OperandToken(-5), tokens.PlusOperatorToken(), tokens.OperandToken(2) ] computed_token_list = tokenize(expression) self.assertListEqual(computed_token_list, token_list)
def test_simple_operator(self): expression = "2 + 1" computed_token_list = tokenize(expression) postfix_token_list = infix_to_postfix(computed_token_list) token_list = [ tokens.OperandToken(2), tokens.OperandToken(1), tokens.PlusOperatorToken(), ] self.assertListEqual(postfix_token_list, token_list)
def test_tokenize_implicit_product_variable_and_parentheses(self): expr = "x(2-1)" token_list = [ tokens.VariableToken('x'), tokens.ProductOperatorToken(), tokens.OpenParenthesisToken(), tokens.OperandToken(2), tokens.MinusOperatorToken(), tokens.OperandToken(1), tokens.CloseParenthesisToken() ] self.assertListEqual(token_list, tokenize(expr))
def test_tokenize_negative_within_parenthesis(self): expression = "(-5 + 2)" token_list = [ tokens.OpenParenthesisToken(), tokens.OperandToken(-5), tokens.PlusOperatorToken(), tokens.OperandToken(2), tokens.CloseParenthesisToken() ] computed_token_list = tokenize(expression) self.assertListEqual(computed_token_list, token_list)
def test_tokenize_7(self): expression = "sin(1.5*pi)" token_list = [ tokens.SinFunctionToken(), tokens.OpenParenthesisToken(), tokens.OperandToken(1.5), tokens.ProductOperatorToken(), tokens.PiConstantToken(), tokens.CloseParenthesisToken() ] computed_token_list = tokenize(expression) self.assertListEqual(computed_token_list, token_list)
def test_multiple_operators_reversed(self): expression = "2 * 1 + 5" computed_token_list = tokenize(expression) postfix_token_list = infix_to_postfix(computed_token_list) token_list = [ tokens.OperandToken(2), tokens.OperandToken(1), tokens.ProductOperatorToken(), tokens.OperandToken(5), tokens.PlusOperatorToken(), ] self.assertListEqual(postfix_token_list, token_list)
def test_parenthesis(self): expression = "2 * (1 + 5)" computed_token_list = tokenize(expression) postfix_token_list = infix_to_postfix(computed_token_list) token_list = [ tokens.OperandToken(2), tokens.OperandToken(1), tokens.OperandToken(5), tokens.PlusOperatorToken(), tokens.ProductOperatorToken() ] self.assertListEqual(postfix_token_list, token_list)
def test_tokenize_2(self): expression = "2 * var + 0.5 = 1" token_list = [ tokens.OperandToken(2), tokens.ProductOperatorToken(), tokens.VariableToken('var'), tokens.PlusOperatorToken(), tokens.OperandToken(0.5), tokens.EqualSignToken(), tokens.OperandToken(1) ] computed_token_list = tokenize(expression) self.assertListEqual(computed_token_list, token_list)
def test_tokenize_1(self): expression = "(3 + (4 - 1)) * 5" token_list = [ tokens.OpenParenthesisToken(), tokens.OperandToken(3), tokens.PlusOperatorToken(), tokens.OpenParenthesisToken(), tokens.OperandToken(4), tokens.MinusOperatorToken(), tokens.OperandToken(1), tokens.CloseParenthesisToken(), tokens.CloseParenthesisToken(), tokens.ProductOperatorToken(), tokens.OperandToken(5) ] computed_token_list = tokenize(expression) self.assertListEqual(computed_token_list, token_list)
def test_tokenize_3(self): expression = "2x + 1 = 2(1-x)" token_list = [ tokens.OperandToken(2), tokens.ProductOperatorToken(), tokens.VariableToken('x'), tokens.PlusOperatorToken(), tokens.OperandToken(1), tokens.EqualSignToken(), tokens.OperandToken(2), tokens.ProductOperatorToken(), tokens.OpenParenthesisToken(), tokens.OperandToken(1), tokens.MinusOperatorToken(), tokens.VariableToken('x'), tokens.CloseParenthesisToken() ] computed_token_list = tokenize(expression) self.assertListEqual(computed_token_list, token_list)
def evaluate(input, is_postfix=False): input = input.strip() if not input: raise RuntimeError("Missing expression") logger.debug("Evaluating input '%s'", input) # Tokenize the input token_list = tokenize(input) logger.debug("Tokens:") for tok in token_list: logger.debug(" %r", tok) # In Postfix notation, make sure there are no parenthesis, variables or equal signs if is_postfix: for tok in token_list: if isinstance( tok, (tokens.OpenParenthesisToken, tokens.CloseParenthesisToken)): raise RuntimeError( "Parenthesis are not allowed in postfix expressions") if isinstance(tok, (tokens.EqualSignToken, tokens.VariableToken)): raise RuntimeError( "Equations cannot be written in postfix notation") # Decide whether it's an expression or an equation is_equation = False # For equations, convert the equal sign into a "minus" sign and surround the RHS of the equation in parenthesis for i, tok in enumerate(token_list): if isinstance(tok, tokens.EqualSignToken): logger.debug("Detected equation") is_equation = True token_list.pop(i) token_list.insert(i, tokens.MinusOperatorToken()) token_list.insert(i + 1, tokens.OpenParenthesisToken()) token_list.append(tokens.CloseParenthesisToken()) break # Equation testing if is_equation: # Make sure there are variables if it's an equation for i, tok in enumerate(token_list): if isinstance(tok, tokens.VariableToken): break else: raise RuntimeError("Incorrect equation: missing variable") # Make sure there are no more equal signs if len([x for x in token_list if isinstance(x, tokens.EqualSignToken)]): raise RuntimeError( "Incorrect equation: more than one equal sign found") # Make sure all variables are the same if len( set([ x.value for x in token_list if isinstance(x, tokens.VariableToken) ])) > 1: raise RuntimeError("Only one variable allowed") # Make sure there are no variables if it's not an equation if not is_equation: for i, tok in enumerate(token_list): if isinstance(tok, tokens.VariableToken): raise RuntimeError( "Variable '{}' found in non-equation".format(tok.value)) if not is_postfix: # Even if it's not postfix, run a dumb detection algorithm just to make sure for i, tok in enumerate(token_list[:-1]): if isinstance(tok, tokens.OperandToken) and isinstance( token_list[i + 1], tokens.OperandToken): logger.debug("Expression already in postfix") is_postfix = True break if not is_postfix: logger.debug("Converting to postfix") token_list = infix_to_postfix(token_list) logger.debug("Tokens in postfix:") for tok in token_list: logger.debug(" %r", tok) stack = [] logger.debug("Evaluating token list:") for tok in token_list: logger.debug("") logger.debug("Stack: %r", stack) logger.debug("%r", tok) node = None if tokens.is_operand(tok): node = EquationNode(constant=tok.value) elif tokens.is_variable(tok): node = EquationNode(coefficient=1) elif tokens.is_constant(tok): node = EquationNode(constant=tok.value) else: if tokens.is_operator(tok): try: second_operand = stack.pop() first_operand = stack.pop() except IndexError: raise RuntimeError("Bad expression, missing operands") logger.debug(" Oper A: %r", first_operand) logger.debug(" Oper B: %r", second_operand) if isinstance(tok, tokens.PlusOperatorToken): constant = None coefficient = None if first_operand.constant is not None: constant = first_operand.constant if second_operand.constant is not None: if constant is None: constant = 0 constant += second_operand.constant if first_operand.coefficient is not None: coefficient = first_operand.coefficient if second_operand.coefficient is not None: if coefficient is None: coefficient = 0 coefficient += second_operand.coefficient node = EquationNode(coefficient=coefficient, constant=constant) elif isinstance(tok, tokens.MinusOperatorToken): constant = None coefficient = None if first_operand.constant is not None: constant = first_operand.constant if second_operand.constant is not None: if constant is None: constant = 0 constant -= second_operand.constant if first_operand.coefficient is not None: coefficient = first_operand.coefficient if second_operand.coefficient is not None: if coefficient is None: coefficient = 0 coefficient -= second_operand.coefficient node = EquationNode(coefficient=coefficient, constant=constant) elif isinstance(tok, tokens.ProductOperatorToken): constant = None coefficient = None if first_operand.has_constant(): if second_operand.has_constant(): if constant is None: constant = 0 constant += first_operand.constant * second_operand.constant if second_operand.has_coef(): if coefficient is None: coefficient = 0 coefficient += first_operand.constant * second_operand.coefficient if first_operand.has_coef(): if second_operand.has_constant(): if coefficient is None: coefficient = 0 coefficient += first_operand.coefficient * second_operand.constant if second_operand.has_coef(): raise RuntimeError( "Unsupported expression, can't have exponential variables" ) node = EquationNode(coefficient=coefficient, constant=constant) elif isinstance(tok, tokens.DivisionOperatorToken): constant = None coefficient = None if first_operand.has_constant(): if second_operand.has_constant(): if constant is None: constant = 0 constant += first_operand.constant / second_operand.constant if second_operand.has_coef(): raise RuntimeError( "Unsupported expression, non-linear equations are not supported" ) if first_operand.has_coef(): if second_operand.has_constant(): if coefficient is None: coefficient = 0 coefficient += first_operand.coefficient / second_operand.constant if second_operand.has_coef(): raise RuntimeError( "Unsupported expression, can't have exponential variables" ) node = EquationNode(coefficient=coefficient, constant=constant) elif tokens.is_function(tok): try: operand = stack.pop() except IndexError as e: raise RuntimeError( "Missing value for trigonometric function") # Functions are only allowed in simple expressions, not in equations, so there should be no coeffs. if operand.has_coef(): raise RuntimeError("Function not allowed in equations") # Make sure there's a value to work with if not operand.has_constant(): raise RuntimeError( "Missing value for trigonometric function") if tokens.is_trigonometric(tok): node = EquationNode(constant=tok.oper(operand.constant)) elif isinstance(tok, tokens.LnFunctionToken): node = EquationNode( constant=math.log(operand.constant, math.e)) elif isinstance(tok, tokens.LogFunctionToken): base = 10 if tok.has_custom_base: base = stack.pop().constant node = EquationNode( constant=math.log(operand.constant, base)) if node is not None: logger.debug("Pushing to stack: %r", node) stack.append(node) logger.debug("Final stack %r", stack) arred = lambda x, n: x * (10**n) // 1 / (10**n) logger.info("Input: {}".format(input)) if is_equation: value = -stack[-1].constant / stack[-1].coefficient value = arred(value, 10) logger.info("x = {}".format(value)) else: value = stack[-1].constant value = arred(value, 10) logger.info(value) return value
def test_missing_right_parenthesis(self): expression = "2 * (1 + 5" computed_token_list = tokenize(expression) with self.assertRaises(RuntimeError): postfix_token_list = infix_to_postfix(computed_token_list)