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()
def __init__(self): """Initialize the processor with empty operator stack and RPN token list.""" self.__op_stack = _adt_stack.Stack() self.__rpn = []
def clear(self): """Clear the operator stack and RPN token list.""" self.__op_stack = _adt_stack.Stack() self.__rpn = []
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
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()
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
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()
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)