예제 #1
0
    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)
예제 #2
0
    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)
예제 #3
0
    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))
예제 #4
0
    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)
예제 #5
0
    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))
예제 #6
0
    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)
예제 #7
0
    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)
예제 #8
0
    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)
예제 #9
0
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
예제 #10
0
def tokenize(input: str):
    input = input.lower()

    token_list = []

    i = 0
    while i < len(input):
        char = input[i]

        if char == '(':
            token_list.append(tokens.OpenParenthesisToken())

        elif char == ')':
            token_list.append(tokens.CloseParenthesisToken())

        elif char == '+':
            token_list.append(tokens.PlusOperatorToken())

        # Only compute the '-' char as the minus operator if it's not part of a negative operand
        elif char == '-' and token_list and not isinstance(token_list[-1], tokens.OpenParenthesisToken):
            token_list.append(tokens.MinusOperatorToken())

        elif char == '*':
            token_list.append(tokens.ProductOperatorToken())

        elif char == '/':
            token_list.append(tokens.DivisionOperatorToken())

        elif char == '=':
            token_list.append(tokens.EqualSignToken())

        elif char.isalpha():

            token_strings = {
                'sin': tokens.SinFunctionToken,
                'cos': tokens.CosFunctionToken,
                'tan': tokens.TanFunctionToken,
                'ctan': tokens.CtanFunctionToken,
                'pi': tokens.PiConstantToken,
                'e': tokens.EulerConstantToken,
                'log': tokens.LogFunctionToken,
                'ln': tokens.LnFunctionToken
            }

            for key in token_strings:
                if input[i:i + len(key)] == key:
                    token_list.append(token_strings[key]())
                    i += len(key) - 1
                    break

            # The character was not part of any special token
            else:
                token_components = [char]

                j = i + 1
                while j < len(input) and input[j].isalpha():
                    token_components.append(input[j])
                    j += 1

                i = j - 1
                token_list.append(tokens.VariableToken(''.join(token_components)))

        elif char.isdecimal() or char == '-':
            token_components = [char]

            # Keep consuming decimal characters
            j = i + 1
            while j < len(input) and (input[j].isdecimal() or input[j] == '.'):
                token_components.append(input[j])
                j += 1

            i = j - 1
            token_list.append(tokens.OperandToken(float(''.join(token_components))))

        i += 1

    # Token List postprocessing, in particular:
    # - Add product operator between operand and variable
    # - Add product operator between operand and constant
    # - Add product operator between operand and open parenthesis (except for the Log function)
    # - Add product operator between variable and open parenthesis
    # - Mark LogFunctionToken to have a custom base if followed by two ConstantTokens

    processed_token_list = []

    for i, tok in enumerate(token_list):
        processed_token_list.append(tok)

        if i == len(token_list) - 1:
            break

        if is_operand(tok):

            # - Add product operator between operand and variable
            if is_variable(token_list[i + 1]):
                logger.debug("Adding implicit product operator between operand and variable")
                processed_token_list.append(tokens.ProductOperatorToken())

            elif is_constant(token_list[i + 1]):
                logger.debug("Adding implicit product operator between operand and constant")
                processed_token_list.append(tokens.ProductOperatorToken())

            # - Add product operator between operand and open parenthesis (except for the Log function)
            elif is_left_paren(token_list[i + 1]) and not isinstance(token_list[i - 1], tokens.LogFunctionToken):
                logger.debug("Adding implicit product operator between operand and open parenthesis")
                processed_token_list.append(tokens.ProductOperatorToken())

        elif is_variable(tok):

            # - Add product operator between variable and open parenthesis
            if is_left_paren(token_list[i + 1]):
                logger.debug("Adding implicit product operator between variable and open parenthesis")
                processed_token_list.append(tokens.ProductOperatorToken())

        # - Mark LogFunctionToken to have a custom base if followed by two ConstantTokens or a ConstantToken and open parenthesis
        elif isinstance(tok, tokens.LogFunctionToken) and i < len(token_list) - 2 and is_operand(
                token_list[i + 1]) and (is_constant(token_list[i + 2]) or is_left_paren(token_list[i + 2])):
            tok.has_custom_base = True

    return processed_token_list