示例#1
0
def parse_to_rpn(expression, token_list, options):
    """Parse an infix math expression to RPN.

    :type expression: str
    :type token_list: list
    :type options: _opt.Option
    :param expression: The infix math expression.
    :param token_list: The tokenized infix math expression.
    :param options: BCE options.
    :rtype : list
    :return: The RPN token list.
    :raise _pe.Error: When a parser error occurred.
    """

    #  Initialize
    token_id = 0
    token_cnt = len(token_list)
    rpn = _RPNProcessor()
    cur_argc = 0
    req_argc = 0
    prev_sep_pos = -1
    p_match_map = {")": "(", "]": "[", "}": "{"}
    p_stack = _adt_stack.Stack()
    p_fn = False

    while token_id < token_cnt:
        #  Get current token.
        token = token_list[token_id]

        #  Get previous token.
        if token_id != 0:
            prev_tok = token_list[token_id - 1]
        else:
            prev_tok = None

        if token.is_operand():
            if not (prev_tok is None):
                if prev_tok.is_right_parenthesis():
                    if token.is_symbol_operand():
                        #  Do completion:
                        #    ([expr])[unknown] => ([expr])*[unknown]
                        #
                        #  For example:
                        #    (3-y)x => (3-y)*x
                        rpn.add_operator(
                            _mexp_tok.create_multiply_operator_token())
                    else:
                        #  Numeric parenthesis suffix was not supported.
                        #
                        #  For example:
                        #    (x-y)3
                        #         ^
                        #         Requires a '*' before this token.
                        err = _pe.Error(
                            _mexp_errors.PE_MEXP_MISSING_OPERATOR,
                            _msg_id.MSG_PE_MEXP_MISSING_OPERATOR_DESCRIPTION,
                            options)

                        err.push_traceback_ex(
                            expression, token.get_position(),
                            token.get_position() + len(token.get_symbol()) - 1,
                            _msg_id.MSG_PE_MEXP_MISSING_OPERATOR_MUL_BEFORE)

                        raise err

                if prev_tok.is_operand():
                    #  Do completion:
                    #    [number][symbol] => [number]*[symbol]
                    #
                    #  For example:
                    #    4x => 4*x
                    rpn.add_operator(
                        _mexp_tok.create_multiply_operator_token())

            #  Process the token.
            rpn.add_operand(token)

            #  Go to next token.
            token_id += 1

            continue
        elif token.is_function():
            #  Raise an error if the function is unsupported.
            if not token.get_symbol() in _mexp_fns.SUPPORTED:
                err = _pe.Error(_mexp_errors.PE_MEXP_FN_UNSUPPORTED,
                                _msg_id.MSG_PE_MEXP_FN_UNSUPPORTED_DESCRIPTION,
                                options)

                err.push_traceback_ex(
                    expression, token.get_position(),
                    token.get_position() + len(token.get_symbol()) - 1,
                    _msg_id.MSG_PE_MEXP_FN_UNSUPPORTED_TB_MESSAGE,
                    {"$1": token.get_symbol()})

                raise err

            if (not (prev_tok is None)) and (prev_tok.is_operand() or
                                             prev_tok.is_right_parenthesis()):
                #  Do completion:
                #    [num][fn] => [num]*[fn]
                #
                #  For example:
                #    4pow(2,3) => 4*pow(2,3)
                rpn.add_operator(_mexp_tok.create_multiply_operator_token())

            #  Process the token.
            rpn.add_function(token)

            #  Go to next token.
            token_id += 1

            continue
        elif token.is_operator():
            #  Get the operator.
            op = _mexp_operators.OPERATORS[token.get_subtype()]

            #  Check operands.
            if op.is_required_left_operand():
                _check_left_operand(expression, token_list, token_id, options)

            if op.is_required_right_operand():
                _check_right_operand(expression, token_list, token_id, options)

            #  Process the token.
            rpn.add_operator(token)

            #  Go to next token.
            token_id += 1

            continue
        elif token.is_left_parenthesis():
            #  Save state.
            p_stack.push(
                _ParenthesisStackItem(token.get_symbol(), token_id, p_fn,
                                      cur_argc, req_argc, prev_sep_pos))

            cur_argc = 0
            prev_sep_pos = token_id

            #  Set function state and get required argument count.
            if (not (prev_tok is None)) and prev_tok.is_function():
                p_fn = True
                req_argc = _mexp_fns.ARGUMENT_COUNT[prev_tok.get_symbol()]
            else:
                p_fn = False
                req_argc = 0

            if (not (prev_tok is None)) and (prev_tok.is_right_parenthesis()
                                             or prev_tok.is_operand()):
                #  Do completion
                #    [lp][expr][rp][lp][expr][rp] => [lp][expr][rp]*[lp][expr][rp]
                #
                #  For example:
                #    (2+3)(4+2) => (2+3)*(4+2)
                rpn.add_operator(_mexp_tok.create_multiply_operator_token())

            #  Process the token.
            rpn.add_left_parenthesis(token)

            #  Go to next token.
            token_id += 1

            continue
        elif token.is_right_parenthesis():
            #  Raise an error if there's no content between two separators.
            if prev_sep_pos + 1 == token_id:
                err = _pe.Error(_mexp_errors.PE_MEXP_NO_CONTENT,
                                _msg_id.MSG_PE_MEXP_NO_CONTENT_DESCRIPTION,
                                options)

                if prev_tok.is_left_parenthesis():
                    err.push_traceback_ex(
                        expression, prev_tok.get_position(),
                        token.get_position(),
                        _msg_id.MSG_PE_MEXP_NO_CONTENT_PARENTHESIS)
                else:
                    err.push_traceback_ex(
                        expression, prev_tok.get_position(),
                        token.get_position(),
                        _msg_id.MSG_PE_MEXP_NO_CONTENT_ARGUMENT)

                raise err

            #  Raise an error if there's no left parenthesis to be matched with.
            if len(p_stack) == 0:
                err = _pe.Error(
                    _mexp_errors.PE_MEXP_PARENTHESIS_MISMATCH,
                    _msg_id.MSG_PE_MEXP_PARENTHESIS_MISMATCH_DESCRIPTION,
                    options)

                err.push_traceback_ex(
                    expression, token.get_position(), token.get_position(),
                    _msg_id.MSG_PE_MEXP_PARENTHESIS_MISMATCH_MISSING_LEFT)

                raise err

            #  Get the top item of the stack.
            p_item = p_stack.pop()

            #  Get the symbol of the parenthesis matches with current token.
            p_matched_sym = p_match_map[token.get_symbol()]

            #  Raise an error if the parenthesis was mismatched.
            if p_matched_sym != p_item.get_symbol():
                err = _pe.Error(
                    _mexp_errors.PE_MEXP_PARENTHESIS_MISMATCH,
                    _msg_id.MSG_PE_MEXP_PARENTHESIS_MISMATCH_DESCRIPTION,
                    options)

                err.push_traceback_ex(
                    expression, token.get_position(), token.get_position(),
                    _msg_id.MSG_PE_MEXP_PARENTHESIS_MISMATCH_INCORRECT,
                    {"$1": p_matched_sym})

                raise err

            if p_fn:
                cur_argc += 1

                #  Raise an error if the argument count was not matched.
                if cur_argc != req_argc:
                    fn_token = token_list[p_item.get_token_id() - 1]

                    err = _pe.Error(
                        _mexp_errors.PE_MEXP_FN_ARGC_MISMATCH,
                        _msg_id.MSG_PE_MEXP_FN_ARGC_MISMATCH_DESCRIPTION,
                        options)

                    err.push_traceback_ex(
                        expression, fn_token.get_position(),
                        fn_token.get_position() + len(fn_token.get_symbol()) -
                        1, _msg_id.MSG_PE_MEXP_FN_ARGC_MISMATCH_TB_MESSAGE, {
                            "$1": str(req_argc),
                            "$2": str(cur_argc)
                        })

                    raise err

            #  Restore state.
            p_fn = p_item.is_in_function()
            cur_argc = p_item.get_current_argument_count()
            req_argc = p_item.get_required_argument_count()
            prev_sep_pos = p_item.get_previous_separator_position()

            #  Process the token.
            rpn.add_right_parenthesis()

            #  Go to next token.
            token_id += 1

            continue
        elif token.is_separator():
            #  Raise an error if we're not in function now.
            if not p_fn:
                err = _pe.Error(
                    _mexp_errors.PE_MEXP_ILLEGAL_ARG_SEPARATOR,
                    _msg_id.MSG_PE_MEXP_ILLEGAL_ARG_SEPARATOR_DESCRIPTION,
                    options)

                err.push_traceback_ex(
                    expression, token.get_position(), token.get_position(),
                    _msg_id.MSG_PE_MEXP_ILLEGAL_ARG_SEPARATOR_TB_MESSAGE)

                raise err

            #  Raise an error if there's no content between two separators.
            if prev_sep_pos + 1 == token_id:
                err = _pe.Error(_mexp_errors.PE_MEXP_NO_CONTENT,
                                _msg_id.MSG_PE_MEXP_NO_CONTENT_DESCRIPTION,
                                options)

                err.push_traceback_ex(expression, prev_tok.get_position(),
                                      token.get_position(),
                                      _msg_id.MSG_PE_MEXP_NO_CONTENT_ARGUMENT)

                raise err

            #  Save separator position.
            prev_sep_pos = token_id

            #  Increase argument counter.
            cur_argc += 1

            #  Process the token.
            rpn.add_separator()

            #  Go to next token.
            token_id += 1

            continue
        else:
            raise RuntimeError("Never reach this condition.")

    #  Raise an error if there are still some left parentheses in the stack.
    if len(p_stack) != 0:
        err = _pe.Error(_mexp_errors.PE_MEXP_PARENTHESIS_MISMATCH,
                        _msg_id.MSG_PE_MEXP_PARENTHESIS_MISMATCH_DESCRIPTION,
                        options)

        while len(p_stack) != 0:
            p_item = p_stack.pop()
            p_token = token_list[p_item.get_token_id()]
            err.push_traceback_ex(
                expression, p_token.get_position(), p_token.get_position(),
                _msg_id.MSG_PE_MEXP_PARENTHESIS_MISMATCH_MISSING_RIGHT)

        raise err

    #  Pop all items off from the stack and push them onto the RPN token list.
    rpn.finalize()

    #  Return the RPN token list.
    return rpn.get_rpn()
示例#2
0
    def __init__(self):
        """Initialize the processor with empty operator stack and RPN token list."""

        self.__op_stack = _adt_stack.Stack()
        self.__rpn = []
示例#3
0
    def clear(self):
        """Clear the operator stack and RPN token list."""

        self.__op_stack = _adt_stack.Stack()
        self.__rpn = []
示例#4
0
def tokenize(expression, options):
    """Tokenize a chemical equation.

    :type expression: str
    :type options: _opt.Option
    :param expression: The chemical equation.
    :param options: The BCE options.
    :rtype : list[Token]
    :return: The token list.
    """

    #  Initialize the result container.
    ret = []

    #  Initialize the cursor.
    cursor = 0

    while cursor < len(expression):
        #  Get current character.
        cur_ch = expression[cursor]
        if cur_ch == "+":
            #  Add a plus token.
            ret.append(create_operator_plus_token(len(ret), cursor))

            #  Next position.
            cursor += 1
        elif cur_ch == "-":
            #  Add a minus token.
            ret.append(create_operator_minus_token(len(ret), cursor))

            #  Next position.
            cursor += 1
        elif cur_ch == ";":
            #  Add a separator token.
            ret.append(create_operator_separator_token(len(ret), cursor))

            #  Next position.
            cursor += 1
        elif cur_ch == "=":
            #  Add an equal sign token.
            ret.append(create_equal_token(len(ret), cursor))

            #  Next position.
            cursor += 1
        else:
            #  Initialize the stack.
            pm = _adt_stack.Stack()

            #  Initialize the searching cursor.
            search_pos = cursor

            #  Initialize the molecule symbol.
            molecule_symbol = ""

            while search_pos < len(expression):
                #  Get current character.
                search_ch = expression[search_pos]

                if search_ch in ["(", "[", "{", "<"]:
                    #  Emulate pushing operation.
                    pm.push(search_pos)

                    #  Add the character.
                    molecule_symbol += search_ch
                elif search_ch in [")", "]", "}", ">"]:
                    #  Raise an error if there is no left parenthesis in the stack.
                    if len(pm) == 0:
                        err = _pe.Error(
                            _ce_error.PE_CE_PARENTHESIS_MISMATCH,
                            _msg_id.MSG_PE_CE_PARENTHESIS_MISMATCH_DESCRIPTION,
                            options)

                        err.push_traceback_ex(
                            expression, search_pos, search_pos, _msg_id.
                            MSG_PE_CE_PARENTHESIS_MISMATCH_MISSING_LEFT)

                        raise err

                    #  Emulate popping operation.
                    pm.pop()

                    #  Add the character.
                    molecule_symbol += search_ch
                elif search_ch in ["+", "-", ";", "="] and len(pm) == 0:
                    break
                else:
                    #  Add the character.
                    molecule_symbol += search_ch

                #  Move the searching cursor.
                search_pos += 1

            #  Raise an error if there are still some parentheses in the stack.
            if len(pm) != 0:
                err = _pe.Error(
                    _ce_error.PE_CE_PARENTHESIS_MISMATCH,
                    _msg_id.MSG_PE_CE_PARENTHESIS_MISMATCH_DESCRIPTION,
                    options)

                while len(pm) != 0:
                    mismatched_pos = pm.pop()
                    err.push_traceback_ex(
                        expression, mismatched_pos, mismatched_pos,
                        _msg_id.MSG_PE_CE_PARENTHESIS_MISMATCH_MISSING_RIGHT)

                raise err

            #  Add a molecule token.
            ret.append(create_molecule_token(molecule_symbol, len(ret),
                                             cursor))

            #  Set the cursor.
            cursor = search_pos

    #  Add an end token.
    ret.append(create_end_token(len(ret), len(expression)))

    return ret
示例#5
0
def calculate_rpn(origin_token_list, rpn_token_list, options):
    """Calculate the value of a RPN token list.

    :type origin_token_list: list of _mexp_token.Token
    :type rpn_token_list: list of _mexp_token.Token
    :type options: _opt.Option
    :param origin_token_list: The origin token list.
    :param rpn_token_list: The RPN token list.
    :return: The calculated value.
    :raise RuntimeError: When a bug appears.
    """

    #  This routine implements the postfix algorithm.

    #  Initialize the operand stack.
    calc_stack = _adt_stack.Stack()

    for token in rpn_token_list:
        if token.is_integer_operand():
            #  Convert the symbol to integer and push it onto the stack.
            calc_stack.push(_mexp_utils.convert_int_string_to_rational(token.get_symbol()))
        elif token.is_float_operand():
            #  Convert the symbol to float and push it onto the stack.
            calc_stack.push(_mexp_utils.convert_float_string_to_rational(token.get_symbol()))
        elif token.is_symbol_operand():
            #  Create a math symbol and push it onto the stack.
            calc_stack.push(_sympy.Symbol(token.get_symbol()))
        elif token.is_plus_operator():
            #  Get two operands.
            num2 = calc_stack.pop()
            num1 = calc_stack.pop()

            #  Do plus and push the result onto the stack.
            calc_stack.push(num1 + num2)
        elif token.is_minus_operator():
            #  Get two operands.
            num2 = calc_stack.pop()
            num1 = calc_stack.pop()

            #  Do minus and push the result onto the stack.
            calc_stack.push(num1 - num2)
        elif token.is_multiply_operator():
            #  Get two operands.
            num2 = calc_stack.pop()
            num1 = calc_stack.pop()

            #  Do multiplication and push the result onto the stack.
            calc_stack.push(num1 * num2)
        elif token.is_divide_operator():
            #  Get two operands.
            num2 = calc_stack.pop()
            num1 = calc_stack.pop()

            #  Raise an error if the rhs equals to zero.
            if num2.is_zero:
                err = _pe.Error(_mexp_errors.PE_MEXP_RPNEV_DIVIDE_ZERO,
                                _msg_id.MSG_PE_MEXP_RPNEV_DIVIDE_ZERO_DESCRIPTION,
                                options)
                err.push_traceback_ex(_base_token.untokenize(origin_token_list),
                                      token.get_position(),
                                      token.get_position(),
                                      _msg_id.MSG_PE_MEXP_RPNEV_DIVIDE_ZERO_OPERATOR)
                raise err

            #  Do division and push the result onto the stack.
            calc_stack.push(num1 / num2)
        elif token.is_pow_operator():
            #  Get two operands.
            num2 = calc_stack.pop()
            num1 = calc_stack.pop()

            #  For a ^ b, when b < 0, a != 0.
            if num2.is_negative and num1.is_zero:
                err = _pe.Error(_mexp_errors.PE_MEXP_RPNEV_DIVIDE_ZERO,
                                _msg_id.MSG_PE_MEXP_RPNEV_DIVIDE_ZERO_DESCRIPTION,
                                options)

                err.push_traceback_ex(_base_token.untokenize(origin_token_list),
                                      token.get_position(),
                                      token.get_position(),
                                      _msg_id.MSG_PE_MEXP_RPNEV_DIVIDE_ZERO_OPERATOR)

                raise err

            #  Do power and push the result onto the stack.
            calc_stack.push(num1 ** num2)
        elif token.is_negative_operator():
            num1 = calc_stack.pop()
            calc_stack.push(-num1)
        elif token.is_function():
            if token.get_symbol() == "pow":
                #  Get two operands.
                num2 = calc_stack.pop()
                num1 = calc_stack.pop()

                #  For pow(a, b), when b < 0, a != 0.
                if num2.is_negative and num1.is_zero:
                    err = _pe.Error(_mexp_errors.PE_MEXP_RPNEV_DIVIDE_ZERO,
                                    _msg_id.MSG_PE_MEXP_RPNEV_DIVIDE_ZERO_DESCRIPTION,
                                    options)

                    err.push_traceback_ex(_base_token.untokenize(origin_token_list),
                                          token.get_position(),
                                          token.get_position() + len(token.get_symbol()) - 1,
                                          _msg_id.MSG_PE_MEXP_RPNEV_DIVIDE_ZERO_POW)

                    raise err

                #  Do power and push the result onto the stack.
                calc_stack.push(num1 ** num2)
            elif token.get_symbol() == "sqrt":
                #  Get one operand.
                num1 = calc_stack.pop()

                #  (For a^b, when b < 0, a != 0.
                if num1.is_negative:
                    err = _pe.Error(_mexp_errors.PE_MEXP_RPNEV_SQRT_NEG_ARG,
                                    _msg_id.MSG_PE_MEXP_RPNEV_SQRT_NEG_ARG_DESCRIPTION,
                                    options)

                    err.push_traceback_ex(_base_token.untokenize(origin_token_list),
                                          token.get_position(),
                                          token.get_position() + len(token.get_symbol()) - 1,
                                          _msg_id.MSG_PE_MEXP_RPNEV_SQRT_NEG_ARG_TB_MESSAGE)

                    raise err

                #  Do sqrt and push the result onto the stack.
                calc_stack.push(_mexp_fns.do_sqrt(num1))
            else:
                raise RuntimeError("Unreachable condition (Invalid function name).")
        else:
            raise RuntimeError("Unreachable condition (Invalid token type).")

    #  If there are more than one operands in the stack, raise a runtime error. But generally,
    #  we shouldn't get this error because we have checked the whole expression when tokenizing.
    if len(calc_stack) > 1:
        raise RuntimeError("Unreachable condition (Too many items in the stack after calculation).")

    return calc_stack.top()
示例#6
0
def tokenize(expression, options):
    """Tokenize a chemical equation.

    :type expression: str
    :type options: bce.option.Option
    :param expression: The chemical equation.
    :param options: The options.
    :rtype : list[Token]
    :return: The token list.
    """

    #  Get the language ID.
    lang_id = _l10n_opt.OptionWrapper(options).get_language_id()

    #  Initialize the result container.
    result = []

    #  Initialize the cursor.
    cursor = 0

    while cursor < len(expression):
        #  Get current character.
        cur_ch = expression[cursor]
        if cur_ch == "+":
            #  Add a plus token.
            result.append(create_operator_plus_token(len(result), cursor))

            #  Next position.
            cursor += 1
        elif cur_ch == "-":
            #  Add a minus token.
            result.append(create_operator_minus_token(len(result), cursor))

            #  Next position.
            cursor += 1
        elif cur_ch == ";":
            #  Add a separator token.
            result.append(create_operator_separator_token(len(result), cursor))

            #  Next position.
            cursor += 1
        elif cur_ch == "=":
            #  Add an equal sign token.
            result.append(create_equal_token(len(result), cursor))

            #  Next position.
            cursor += 1
        else:
            #  Initialize the stack.
            pm = _adt_stack.Stack()

            #  Initialize the searching cursor.
            search_pos = cursor

            #  Initialize the molecule symbol.
            molecule_symbol = ""

            while search_pos < len(expression):
                #  Get current character.
                search_ch = expression[search_pos]

                if search_ch in ["(", "[", "{", "<"]:
                    #  Emulate pushing operation.
                    pm.push(search_pos)

                    #  Add the character.
                    molecule_symbol += search_ch
                elif search_ch in [")", "]", "}", ">"]:
                    #  Raise an error if there is no left parenthesis in the stack.
                    if len(pm) == 0:
                        err = _cm_error.Error(
                            _ce_error.CEXP_PARENTHESIS_MISMATCH,
                            _l10n_reg.get_message(
                                lang_id,
                                "parser.cexp.error.parenthesis_mismatch.description"
                            ), options)
                        err.push_traceback(
                            expression, search_pos, search_pos,
                            _l10n_reg.get_message(
                                lang_id,
                                "parser.cexp.error.parenthesis_mismatch.left"))
                        raise err

                    #  Emulate popping operation.
                    pm.pop()

                    #  Add the character.
                    molecule_symbol += search_ch
                elif search_ch in ["+", "-", ";", "="] and len(pm) == 0:
                    break
                else:
                    #  Add the character.
                    molecule_symbol += search_ch

                #  Move the searching cursor.
                search_pos += 1

            #  Raise an error if there are still some parentheses in the stack.
            if len(pm) != 0:
                err = _cm_error.Error(
                    _ce_error.CEXP_PARENTHESIS_MISMATCH,
                    _l10n_reg.get_message(
                        lang_id,
                        "parser.cexp.error.parenthesis_mismatch.description"),
                    options)

                while len(pm) != 0:
                    mismatched_pos = pm.pop()
                    err.push_traceback(
                        expression, mismatched_pos, mismatched_pos,
                        _l10n_reg.get_message(
                            lang_id,
                            "parser.cexp.error.parenthesis_mismatch.right"))

                raise err

            #  Add a molecule token.
            result.append(
                create_molecule_token(molecule_symbol, len(result), cursor))

            #  Set the cursor.
            cursor = search_pos

    #  Add an end token.
    result.append(create_end_token(len(result), len(expression)))

    return result
示例#7
0
def parse_to_rpn(expression,
                 token_list,
                 options,
                 protected_header_enabled=False,
                 protected_header_prefix="X"):
    """Parse an infix math expression to RPN.

    :type expression: str
    :type token_list: list[bce.parser.mexp.token.Token]
    :type options: bce.option.Option
    :type protected_header_enabled: bool
    :type protected_header_prefix: str
    :param expression: The infix math expression.
    :param token_list: The tokenized infix math expression.
    :param options: The options.
    :param protected_header_enabled: Whether the protected headers are enabled.
    :param protected_header_prefix: The prefix of the protected headers.
    :rtype : list[bce.parser.mexp.token.Token]
    :return: The RPN token list.
    :raise bce.parser.common.error.Error: Raise when a parser error occurred.
    """

    #  Initialize
    lang_id = _l10n_opt.OptionWrapper(options).get_language_id()
    token_id = 0
    token_count = len(token_list)
    rpn = _RPNProcessor()
    current_argc = 0
    required_argc = 0
    prev_separator_position = -1
    parenthesis_mapping = {")": "(", "]": "[", "}": "{"}
    parenthesis_stack = _adt_stack.Stack()
    in_function = False

    while token_id < token_count:
        #  Get current token.
        token = token_list[token_id]

        #  Get previous token.
        if token_id != 0:
            prev_tok = token_list[token_id - 1]
        else:
            prev_tok = None

        if token.is_operand():
            if token.is_symbol_operand():
                #  Check the protected header.
                if protected_header_enabled and token.get_symbol().startswith(
                        protected_header_prefix):
                    err = _cm_error.Error(
                        _mexp_errors.MEXP_USE_PROTECTED_HEADER,
                        _l10n_reg.get_message(
                            lang_id,
                            "parser.mexp.error.protected_header.description"),
                        options)
                    err.push_traceback(
                        expression,
                        token.get_position(),
                        token.get_position() + len(token.get_symbol()) - 1,
                        _l10n_reg.get_message(
                            lang_id,
                            "parser.mexp.error.protected_header.message"),
                        replace_map={"$1": protected_header_prefix})
                    raise err

            if prev_tok is not None:
                if prev_tok.is_right_parenthesis():
                    if token.is_symbol_operand():
                        #  Do completion:
                        #    ([expr])[unknown] => ([expr])*[unknown]
                        #
                        #  For example:
                        #    (3-y)x => (3-y)*x
                        rpn.add_operator(
                            _mexp_token.create_multiply_operator_token())
                    else:
                        #  Numeric parenthesis suffix was not supported.
                        #
                        #  For example:
                        #    (x-y)3
                        #         ^
                        #         Requires a '*' before this token.
                        err = _cm_error.Error(
                            _mexp_errors.MEXP_MISSING_OPERATOR,
                            _l10n_reg.get_message(
                                lang_id,
                                "parser.mexp.error.missing_operator.description"
                            ), options)
                        err.push_traceback(
                            expression, token.get_position(),
                            token.get_position() + len(token.get_symbol()) - 1,
                            _l10n_reg.get_message(
                                lang_id,
                                "parser.mexp.error.missing_operator.multiply_before"
                            ))
                        raise err

                if prev_tok.is_operand():
                    #  Do completion:
                    #    [number][symbol] => [number]*[symbol]
                    #
                    #  For example:
                    #    4x => 4*x
                    rpn.add_operator(
                        _mexp_token.create_multiply_operator_token())

            #  Process the token.
            rpn.add_operand(token)

            #  Go to next token.
            token_id += 1

            continue
        elif token.is_function():
            #  Raise an error if the function is unsupported.
            if _mexp_functions.find_function(token.get_symbol()) is None:
                err = _cm_error.Error(
                    _mexp_errors.MEXP_FUNCTION_UNSUPPORTED,
                    _l10n_reg.get_message(
                        lang_id,
                        "parser.mexp.error.unsupported_function.description"),
                    options)
                err.push_traceback(
                    expression,
                    token.get_position(),
                    token.get_position() + len(token.get_symbol()) - 1,
                    _l10n_reg.get_message(
                        lang_id,
                        "parser.mexp.error.unsupported_function.message"),
                    replace_map={"$1": token.get_symbol()})
                raise err

            if prev_tok is not None and (prev_tok.is_operand()
                                         or prev_tok.is_right_parenthesis()):
                #  Do completion:
                #    [num][fn] => [num]*[fn]
                #
                #  For example:
                #    4pow(2,3) => 4*pow(2,3)
                rpn.add_operator(_mexp_token.create_multiply_operator_token())

            #  Process the token.
            rpn.add_function(token)

            #  Go to next token.
            token_id += 1

            continue
        elif token.is_operator():
            #  Get the operator.
            op = _mexp_operators.OPERATORS[token.get_subtype()]

            #  Check operands.
            if op.is_required_left_operand():
                _check_left_operand(expression, token_list, token_id, options)

            if op.is_required_right_operand():
                _check_right_operand(expression, token_list, token_id, options)

            #  Process the token.
            rpn.add_operator(token)

            #  Go to next token.
            token_id += 1

            continue
        elif token.is_left_parenthesis():
            #  Save state.
            parenthesis_stack.push(
                _ParenthesisStackItem(token.get_symbol(), token_id,
                                      in_function, current_argc, required_argc,
                                      prev_separator_position))

            current_argc = 0
            prev_separator_position = token_id

            #  Set function state and get required argument count.
            if prev_tok is not None and prev_tok.is_function():
                #  Mark the flag.
                in_function = True

                #  Get the function object.
                fn_object = _mexp_functions.find_function(
                    prev_tok.get_symbol())
                if fn_object is None:
                    raise RuntimeError("BUG: Function object is None.")

                #  Get the required argument count.
                required_argc = fn_object.get_argument_count()
            else:
                #  Clear the flag.
                in_function = False
                required_argc = 0

            if prev_tok is not None and (prev_tok.is_right_parenthesis()
                                         or prev_tok.is_operand()):
                #  Do completion
                #    [lp][expr][rp][lp][expr][rp] => [lp][expr][rp]*[lp][expr][rp]
                #
                #  For example:
                #    (2+3)(4+2) => (2+3)*(4+2)
                rpn.add_operator(_mexp_token.create_multiply_operator_token())

            #  Process the token.
            rpn.add_left_parenthesis(token)

            #  Go to next token.
            token_id += 1

            continue
        elif token.is_right_parenthesis():
            #  Raise an error if there's no content between two separators.
            if prev_separator_position + 1 == token_id:
                err = _cm_error.Error(
                    _mexp_errors.MEXP_NO_CONTENT,
                    _l10n_reg.get_message(
                        lang_id, "parser.mexp.error.no_content.description"),
                    options)
                if prev_tok.is_left_parenthesis():
                    err.push_traceback(
                        expression, prev_tok.get_position(),
                        token.get_position(),
                        _l10n_reg.get_message(
                            lang_id,
                            "parser.mexp.error.no_content.in_parentheses"))
                else:
                    err.push_traceback(
                        expression, prev_tok.get_position(),
                        token.get_position(),
                        _l10n_reg.get_message(
                            lang_id,
                            "parser.mexp.error.no_content.in_argument"))

                raise err

            #  Raise an error if there's no left parenthesis to be matched with.
            if len(parenthesis_stack) == 0:
                err = _cm_error.Error(
                    _mexp_errors.MEXP_PARENTHESIS_MISMATCH,
                    _l10n_reg.get_message(
                        lang_id,
                        "parser.mexp.error.parenthesis_mismatch.description"),
                    options)
                err.push_traceback(
                    expression, token.get_position(), token.get_position(),
                    _l10n_reg.get_message(
                        lang_id,
                        "parser.mexp.error.parenthesis_mismatch.left"))
                raise err

            #  Get the top item of the stack.
            p_item = parenthesis_stack.pop()

            #  Get the symbol of the parenthesis matches with current token.
            p_matched_sym = parenthesis_mapping[token.get_symbol()]

            #  Raise an error if the parenthesis was mismatched.
            if p_matched_sym != p_item.get_symbol():
                err = _cm_error.Error(
                    _mexp_errors.MEXP_PARENTHESIS_MISMATCH,
                    _l10n_reg.get_message(
                        lang_id,
                        "parser.mexp.error.parenthesis_mismatch.description"),
                    options)
                err.push_traceback(
                    expression,
                    token.get_position(),
                    token.get_position(),
                    _l10n_reg.get_message(
                        lang_id,
                        "parser.mexp.error.parenthesis_mismatch.incorrect"),
                    replace_map={"$1": p_matched_sym})
                raise err

            if in_function:
                current_argc += 1

                #  Raise an error if the argument count was not matched.
                if current_argc != required_argc:
                    fn_token = token_list[p_item.get_token_id() - 1]

                    err = _cm_error.Error(
                        _mexp_errors.MEXP_FUNCTION_ARGUMENT_COUNT_MISMATCH,
                        _l10n_reg.get_message(
                            lang_id,
                            "parser.mexp.error.argument_count_mismatch.description"
                        ), options)
                    err.push_traceback(
                        expression, fn_token.get_position(),
                        fn_token.get_position() + len(fn_token.get_symbol()) -
                        1,
                        _l10n_reg.get_message(
                            lang_id,
                            "parser.mexp.error.argument_count_mismatch.message"
                        ), {
                            "$1": str(required_argc),
                            "$2": str(current_argc)
                        })
                    raise err

            #  Restore state.
            in_function = p_item.is_in_function()
            current_argc = p_item.get_current_argument_count()
            required_argc = p_item.get_required_argument_count()
            prev_separator_position = p_item.get_previous_separator_position()

            #  Process the token.
            rpn.add_right_parenthesis()

            #  Go to next token.
            token_id += 1

            continue
        elif token.is_separator():
            #  Raise an error if we're not in function now.
            if not in_function:
                err = _cm_error.Error(
                    _mexp_errors.MEXP_ILLEGAL_ARGUMENT_SEPARATOR,
                    _l10n_reg.get_message(
                        lang_id,
                        "parser.mexp.error.illegal_separator.description"),
                    options)
                err.push_traceback(
                    expression, token.get_position(), token.get_position(),
                    _l10n_reg.get_message(
                        lang_id,
                        "parser.mexp.error.illegal_separator.message"))
                raise err

            #  Raise an error if there's no content between two separators.
            if prev_separator_position + 1 == token_id:
                err = _cm_error.Error(
                    _mexp_errors.MEXP_NO_CONTENT,
                    _l10n_reg.get_message(
                        lang_id, "parser.mexp.error.no_content.description"),
                    options)
                err.push_traceback(
                    expression, prev_tok.get_position(), token.get_position(),
                    _l10n_reg.get_message(
                        lang_id, "parser.mexp.error.no_content.in_argument"))
                raise err

            #  Save separator position.
            prev_separator_position = token_id

            #  Increase argument counter.
            current_argc += 1

            #  Process the token.
            rpn.add_separator()

            #  Go to next token.
            token_id += 1

            continue
        else:
            raise RuntimeError("Never reach this condition.")

    #  Raise an error if there are still some left parentheses in the stack.
    if len(parenthesis_stack) != 0:
        err = _cm_error.Error(
            _mexp_errors.MEXP_PARENTHESIS_MISMATCH,
            _l10n_reg.get_message(
                lang_id, "parser.mexp.error.parenthesis_mismatch.description"),
            options)
        while len(parenthesis_stack) != 0:
            p_item = parenthesis_stack.pop()
            p_token = token_list[p_item.get_token_id()]
            err.push_traceback(
                expression, p_token.get_position(), p_token.get_position(),
                _l10n_reg.get_message(
                    lang_id, "parser.mexp.error.parenthesis_mismatch.right"))
        raise err

    #  Pop all items off from the stack and push them onto the RPN token list.
    rpn.finalize()

    #  Return the RPN token list.
    return rpn.get_rpn()
示例#8
0
def solve_equation(matrix, symbol_header="X"):
    """Solve linear equations.

    :type matrix: _mat.Matrix
    :type symbol_header: str
    :param matrix: The matrix that contains the linear equations.
    :param symbol_header: The symbol header of created symbols.
    :rtype : SolvedEquation
    :return: The solutions (presents with SolvedEquation class).
    :raise RuntimeError: When a bug appears.
    """

    #  Get matrix property.
    cur_unknown = 0
    row_c = matrix.get_row_count()
    col_c = matrix.get_column_count()

    #  Initialize
    ans = [None] * (col_c - 1)
    process_stack = _base_stack.Stack()

    #  Push initial matrix onto the stack.
    process_stack.push(_EqSolverStackItem(0, 0, 0))

    while len(process_stack) != 0:
        #  Pop off the process from process stack.
        cur_process = process_stack.pop()

        #  Get the row and column offset.
        #  In comments below, the 'first' column means the column whose offset is |offset_col| and the
        #  first row means the row whose offset is |offset_row|. 'Current matrix' means the sub-matrix
        #  of |matrix| range from [|offset_row|][|offset_column|] to [|row_c|][|col_c|].
        offset_row = cur_process.get_offset_row()
        offset_col = cur_process.get_offset_column()

        #  Get the process status(break point).
        break_point = cur_process.get_break_point()

        if break_point == 0:
            found_nz_row = False

            #  Simplify current column.
            for row_id in range(offset_row, row_c):
                tmp_offset = matrix.get_item_offset(row_id, offset_col)
                tmp_value = matrix.get_item_by_offset(tmp_offset)
                if len(tmp_value.free_symbols) != 0:
                    tmp_value = tmp_value.simplify()
                    matrix.write_item_by_offset(tmp_offset, tmp_value)

            #  Determine the row(in current matrix) whose first item is non-zero and exchange
            #  the row with the first row. If there's no such row, keep variable |found_nz_row|
            #  unchanged.
            for row_id in range(offset_row, row_c):
                if not matrix.get_item_by_position(row_id, offset_col).is_zero:
                    #  Exchange the row with the first row only if it's not the first row.
                    if row_id != offset_row:
                        matrix.exchange_row(row_id, offset_row)

                    #  Mark that we have found such row.
                    found_nz_row = True

                    break

            #  If all items in the first column are zero, set the value of unknown corresponding to
            #  this column to a new created unknown.
            if not found_nz_row:
                #  Set the value of unknown corresponding to the first column.
                ans[offset_col] = _sympy.Symbol(unknown_id_to_symbol(cur_unknown, symbol_header))
                cur_unknown += 1

                #  If there are still some unknown, solve them.
                if offset_col + 2 != col_c:
                    process_stack.push(_EqSolverStackItem(offset_row, offset_col + 1, 0))

                continue

            #  If there's only one unknown in current matrix, get the value of the unknown.
            if offset_col + 2 == col_c:
                tmp_offset = matrix.get_item_offset(offset_row, offset_col)
                ans[offset_col] = matrix.get_item_by_offset(tmp_offset + 1) / matrix.get_item_by_offset(tmp_offset)

                continue

            #  Get the offset of the first row.
            first_row_ofx = matrix.get_row_offset(offset_row)

            if offset_row + 1 == row_c:
                #  Initialize the value of the unknown corresponding to the first column.
                tmp_ans = matrix.get_item_by_offset(first_row_ofx + col_c - 1)

                #  Initialize the offset of coefficients.
                coeff_ofx = first_row_ofx + offset_col + 1

                #  Create new unknowns for the columns other than the first column and
                #  use these unknowns to represent the value of the unknown corresponding to
                #  the first column.
                for col_id in range(offset_col + 1, col_c - 1):
                    #  Set the value of unknown corresponding to this column.
                    new_sym = _sympy.Symbol(unknown_id_to_symbol(cur_unknown, symbol_header))
                    ans[col_id] = new_sym
                    cur_unknown += 1

                    #  Get the coefficient.
                    coeff = matrix.get_item_by_offset(coeff_ofx)
                    coeff_ofx += 1

                    #  Calculate the value.
                    tmp_ans -= coeff * new_sym

                #  Save the calculated value.
                ans[offset_col] = tmp_ans / matrix.get_item_by_offset(first_row_ofx + offset_col)

                continue

            #  Save the value of the first item of the first row and set its value to 1.
            tmp_offset = matrix.get_item_offset(offset_row, offset_col)
            first_value = matrix.get_item_by_offset(tmp_offset)
            matrix.write_item_by_offset(tmp_offset, _sympy.Integer(1))

            #  Move to next item.
            tmp_offset += 1

            #  Let all other items of the first row divide by the first item of the first row.
            for col_id in range(offset_col + 1, col_c):
                #  Get the value at specific position.
                tmp_value = matrix.get_item_by_offset(tmp_offset)

                #  Do division and change the value if it's not zero. (Just an optimization).
                if not tmp_value.is_zero:
                    tmp_value /= first_value
                    matrix.write_item_by_offset(tmp_offset, tmp_value)

                #  Move to next item.
                tmp_offset += 1

            #  Do elimination for rows other than the first row.
            for row_id in range(offset_row + 1, row_c):
                #  Get the value of the first item of the row whose offset is |row_id|.
                tmp_offset = matrix.get_item_offset(row_id, offset_col)
                first_value = matrix.get_item_by_offset(tmp_offset)

                #  Ignore this row if the first value of it is zero.
                if first_value.is_zero:
                    continue

                #  Set the value of the first value to zero.
                matrix.write_item_by_offset(tmp_offset, _sympy.Integer(0))

                #  Move to next item.
                tmp_offset += 1

                #  Do elimination with the first row.
                for col_id in range(offset_col + 1, col_c):
                    #  Get the value at specific position.
                    tmp_value = matrix.get_item_by_offset(tmp_offset)

                    #  Do elimination
                    tmp_value = tmp_value / first_value - matrix.get_item_by_offset(first_row_ofx + col_id)

                    #  Write the value back.
                    matrix.write_item_by_offset(tmp_offset, tmp_value)

                    #  Move to next item.
                    tmp_offset += 1

            #  Save current process. We will do back substitution next time.
            process_stack.push(_EqSolverStackItem(offset_row, offset_col, 1))

            #  Solve the order-reduced matrix.
            process_stack.push(_EqSolverStackItem(offset_row + 1, offset_col + 1, 0))

            continue

        if break_point == 1:
            #  Get the offset of the first row.
            first_row_ofx = matrix.get_row_offset(offset_row)

            #  Initialize the value of the unknown corresponding to the first column.
            tmp_ans = matrix.get_item_by_offset(first_row_ofx + col_c - 1)

            #  Initialize the offset of coefficients.
            coeff_ofx = first_row_ofx + offset_col + 1

            #  Use the calculated values to get the value of the unknown corresponding to the first column.
            for col_id in range(offset_col + 1, col_c - 1):
                #  Get the coefficient.
                coeff = matrix.get_item_by_offset(coeff_ofx)
                coeff_ofx += 1

                #  Calculate the value.
                tmp_ans -= coeff * ans[col_id]

            #  Save the calculated value.
            ans[offset_col] = tmp_ans

            continue

        raise RuntimeError("Invalid break point.")

    return SolvedEquation(ans, cur_unknown)