# -*- coding: utf-8 -*-
""" 算式處理函式庫 / Formula processing functions """
import re
import collections
import logging
_log = logging.getLogger(__name__)
[docs]class BaseOperator(object):
""" 表現運算子的基底物件 / Base class of operator """
[docs] def get_priority(self):
# type: () -> int
"""
取得運算子的優先順序,愈高的優先順序應傳回愈大的值
Get the priority of operator. Bigger value means higher priority.
Returns:
運算子的優先權 / Priority of operator
"""
return -1
[docs] def is_left_associative(self):
# type: () -> bool
"""
運算子是否為左結合
Return a boolean value to indicate associative of operator.
Returns:
傳回 True 時表示運算子為左結合,否則為右結合 / Return True for left-associative operator; False for right-associative operator
"""
return True
[docs] def get_operand_count(self):
# type: () -> int
"""
取得運算元的數量
Get the number of operand
Return:
運算元的數量 / Number of operand(s)
"""
raise NotImplementedError("not implemented get_operand_count() yet")
[docs] def evaluate(self, *args):
# type: (*Any) -> Any
"""
進行運算,參數數量為 get_operand_count() 方法所傳回的數值
Perform the computation. The number of parameter will be the value returned by get_operand_count() method.
Returns:
運算結果 / Result of evaluation
"""
raise NotImplementedError("not implemented evaluate() yet")
[docs]class BaseFunction(object):
""" 表現函數的基底物件 / Base class of function """
[docs] def get_argument_count(self):
# type: () -> int
"""
取得參數的數量
Get the number of arguments
Return:
參數的數量 / Number of arguments
"""
raise NotImplementedError("not implemented get_argument_count() yet")
[docs] def evaluate(self, *args):
# type: (*Any) -> Any
"""
進行運算,參數數量為 get_argument_count() 方法所傳回的數值
Perform the computation. The number of parameter will be the value returned by get_argument_count() method.
Returns:
運算結果 / Result of evaluation
"""
raise NotImplementedError("not implemented evaluate() yet")
[docs]class ParenthesisL(object):
""" 表現左括弧的物件 / Class of left parenthesis """
def __init__(self, v=None, *args, **kwds): # pylint: disable=unused-argument
super(ParenthesisL, self).__init__(*args, **kwds)
[docs]class ParenthesisR(object):
""" 表現右括弧的物件 / Class of right parenthesis """
def __init__(self, v=None, *args, **kwds): # pylint: disable=unused-argument
super(ParenthesisR, self).__init__(*args, **kwds)
[docs]class ArgumentSeparator(object):
""" 表現參數分隔字元的物件 / Class of argument separator token """
def __init__(self, v=None, *args, **kwds): # pylint: disable=unused-argument
super(ArgumentSeparator, self).__init__(*args, **kwds)
[docs]class Plus(BaseOperator):
def __init__(self, v=None, *args, **kwds): # pylint: disable=unused-argument
super(Plus, self).__init__(*args, **kwds)
[docs] def get_priority(self):
return 2
[docs] def get_operand_count(self):
return 2
[docs] def evaluate(self, a, b, *args): # pylint: disable=arguments-differ
return (a + b)
[docs]class Minus(BaseOperator):
def __init__(self, v=None, *args, **kwds): # pylint: disable=unused-argument
super(Minus, self).__init__(*args, **kwds)
[docs] def get_priority(self):
return 2
[docs] def get_operand_count(self):
return 2
[docs] def evaluate(self, a, b, *args): # pylint: disable=arguments-differ
return (a - b)
[docs]class Multiply(BaseOperator):
def __init__(self, v=None, *args, **kwds): # pylint: disable=unused-argument
super(Multiply, self).__init__(*args, **kwds)
[docs] def get_priority(self):
return 3
[docs] def get_operand_count(self):
return 2
[docs] def evaluate(self, a, b, *args): # pylint: disable=arguments-differ
return (a * b)
[docs]class Divide(BaseOperator):
def __init__(self, v=None, *args, **kwds): # pylint: disable=unused-argument
super(Divide, self).__init__(*args, **kwds)
[docs] def get_priority(self):
return 3
[docs] def get_operand_count(self):
return 2
[docs] def evaluate(self, a, b, *args): # pylint: disable=arguments-differ
return (a / b)
def math_operator(v):
# type: (str) -> BaseOperator
if v == "+":
return Plus()
if v == "-":
return Minus()
if v == "*":
return Multiply()
if v == "/":
return Divide()
return None
RegexParsePlan = collections.namedtuple("RegexParsePlan", (
"regex_object",
"element_mapper",
))
LITERAL_MATH = RegexParsePlan(re.compile("([0-9]+)|([\\*\\/+-])|(\\()|(\\))|\s+"), (
None,
int,
math_operator,
ParenthesisL,
ParenthesisR,
))
[docs]def parse_token_w_regex(v, parse_plan):
# type: (str, RegexParsePlan) -> List[Any]
"""
將給定的陳述式進行句元分割並轉換為後波蘭表示式
Split given statement into tokens and transform into reverse polish (postfix) notation form
Args:
v: 要解析的陳述式字串
parse_plan: 解析規則 (RegexParsePlan 物件)
Returns:
轉換為後波蘭表示式的句元所構成的串列 / Resulted token list in reverse polish notation
"""
s_pos = 0
l = len(parse_plan.element_mapper)
q = []
result = []
for m in parse_plan.regex_object.finditer(v):
m_s, m_e, = m.span()
if m_s != s_pos:
raise ValueError("unknown token for %r, location %r ~ %r" % (
v,
s_pos,
m_s,
))
s_pos = m_e
for i in xrange(1, l):
e = m.group(i)
g = parse_plan.element_mapper[i]
if e is not None:
if g is not None:
node = g(e)
# https://en.wikipedia.org/wiki/Shunting-yard_algorithm
if isinstance(node, BaseOperator):
try:
aux = q.pop()
n_left_assoc = node.is_left_associative()
n_priority = node.get_priority()
while isinstance(aux, BaseOperator) and ((n_left_assoc and (aux.get_priority() >= n_priority)) or
((not n_left_assoc) and (aux.get_priority() > n_priority))):
result.append(aux)
aux = q.pop()
q.append(aux)
except IndexError:
pass
q.append(node)
elif isinstance(node, BaseFunction):
q.append(node)
elif isinstance(node, ParenthesisL):
q.append(node)
elif isinstance(node, ParenthesisR):
try:
aux = q.pop()
while not isinstance(aux, ParenthesisL):
result.append(aux)
aux = q.pop()
except IndexError:
pass
elif isinstance(node, ArgumentSeparator):
try:
aux = q.pop()
while not isinstance(aux, ParenthesisL):
result.append(aux)
aux = q.pop()
q.append(aux)
except IndexError:
raise ValueError("caught argument separator without parenthesis")
else:
result.append(node)
break
try:
aux = q.pop()
while isinstance(aux, (
BaseOperator,
BaseFunction,
)):
result.append(aux)
aux = q.pop()
except IndexError:
pass
return result
[docs]def parse_literal_math(v):
# type: (str) -> List[Any]
"""
將給定的數學算式陳述式進行句元分割並轉換為後波蘭表示式
Convert given math formula to reverse polish notation
Args:
v: 要解析的算式字串 / String to be parse
Returns:
轉換為後波蘭表示式的句元所構成的串列 / Resulted token list in reverse polish notation
"""
return parse_token_w_regex(v, LITERAL_MATH)
def _pop_arguments(d, c):
""" (internal) 由串列 d 尾端取出 c 個元素
"""
a = [d.pop() for _i in xrange(c)]
a.reverse()
return a
[docs]def evaluate(fm):
# type: (List[Any]) -> Any
"""
計算由 parse_*() 函數所解析出的算式結果
Evaluate the result of parse_*() function.
Args:
fm: 透過 parse_*() 函數所解析出的算式 / Formula object generated by parse_*() function
Returns:
計算結果 / Result of evaluation
"""
d = []
for e in fm:
if isinstance(e, BaseOperator):
a = _pop_arguments(d, e.get_operand_count())
r = e.evaluate(*a)
d.append(r)
elif isinstance(e, BaseFunction):
a = _pop_arguments(d, e.get_argument_count())
r = e.evaluate(*a)
d.append(r)
else:
d.append(e)
if len(d) > 1:
_log.warning("there are more than 1 values in operand queue: %r", len(d))
return d.pop()
# vim: ts=4 sw=4 ai nowrap