2"""Calculate mathematical expressions using :py:obj:`ast.parse` (mode="eval")."""
4from __future__
import annotations
15from flask_babel
import gettext
20if typing.TYPE_CHECKING:
27 """Plugin converts strings to different hash digests. The results are
28 displayed in area for the "answers".
33 def __init__(self, plg_cfg:
"PluginCfg") ->
None:
38 name=gettext(
"Basic Calculator"),
39 description=gettext(
"Calculate mathematical expressions via the search bar"),
40 preference_section=
"general",
45 p = mp_fork.Process(target=handler, args=(que, func, args), kwargs=kwargs)
47 p.join(timeout=timeout)
53 self.
log.debug(
"terminate function (%s: %s // %s) after timeout is exceeded", func.__name__, args, kwargs)
59 def post_search(self, request:
"SXNG_Request", search:
"SearchWithPlugins") -> EngineResults:
63 if search.search_query.pageno > 1:
66 query = search.search_query.query
72 query = query.replace(
"x",
"*").replace(
":",
"/")
75 word, constants =
"", set()
87 if constants - set(math_constants):
91 ui_locale = babel.Locale.parse(request.preferences.get_value(
"locale"), sep=
"-")
94 def _decimal(match: re.Match) -> str:
95 val = match.string[match.start() : match.end()]
96 val = babel.numbers.parse_decimal(val, ui_locale, numbering_system=
"latn")
99 decimal = ui_locale.number_symbols[
"latn"][
"decimal"]
100 group = ui_locale.number_symbols[
"latn"][
"group"]
101 query = re.sub(f
"[0-9]+[{decimal}|{group}][0-9]+[{decimal}|{group}]?[0-9]?", _decimal, query)
104 query_py_formatted = query.replace(
"^",
"**")
107 res = self.
timeout_func(0.05, _eval_expr, query_py_formatted)
108 if res
is None or res[0] ==
"":
111 res, is_boolean = res
113 res =
"True" if res != 0
else "False"
115 res = babel.numbers.format_decimal(res, locale=ui_locale)
116 results.add(results.types.Answer(answer=f
"{search.search_query.query} = {res}"))
121def _compare(ops: list[ast.cmpop], values: list[int | float]) -> int:
123 2 < 3 becomes ops=[ast.Lt] and values=[2,3]
124 2 < 3 <= 4 becomes ops=[ast.Lt, ast.LtE] and values=[2,3, 4]
126 for op, a, b
in zip(ops, values, values[1:]):
127 if isinstance(op, ast.Eq)
and a == b:
129 if isinstance(op, ast.NotEq)
and a != b:
131 if isinstance(op, ast.Lt)
and a < b:
133 if isinstance(op, ast.LtE)
and a <= b:
135 if isinstance(op, ast.Gt)
and a > b:
137 if isinstance(op, ast.GtE)
and a >= b:
152operators: dict[type, typing.Callable] = {
153 ast.Add: operator.add,
154 ast.Sub: operator.sub,
155 ast.Mult: operator.mul,
156 ast.Div: operator.truediv,
157 ast.Pow: operator.pow,
158 ast.BitXor: operator.xor,
159 ast.BitOr: operator.or_,
160 ast.BitAnd: operator.and_,
161 ast.USub: operator.neg,
162 ast.RShift: operator.rshift,
163 ast.LShift: operator.lshift,
164 ast.Mod: operator.mod,
165 ast.Compare: _compare,
179mp_fork = multiprocessing.get_context(
"fork")
184 Evaluates the given textual expression.
186 Returns a tuple of (numericResult, isBooleanResult).
188 >>> _eval_expr('2^6')
190 >>> _eval_expr('2**6')
192 >>> _eval_expr('1 + 2*3**(4^5) / (6 + -7)')
194 >>> _eval_expr('1 < 3')
196 >>> _eval_expr('5 < 3')
198 >>> _eval_expr('17 == 11+1+5 == 7+5+5')
202 root_expr = ast.parse(expr, mode=
'eval').body
203 return _eval(root_expr), isinstance(root_expr, ast.Compare)
205 except (SyntaxError, TypeError, ZeroDivisionError):
211 if isinstance(node, ast.Constant)
and isinstance(node.value, (int, float)):
214 if isinstance(node, ast.BinOp):
215 return operators[type(node.op)](
_eval(node.left),
_eval(node.right))
217 if isinstance(node, ast.UnaryOp):
218 return operators[type(node.op)](
_eval(node.operand))
220 if isinstance(node, ast.Compare):
223 if isinstance(node, ast.Name)
and node.id
in math_constants:
224 return math_constants[node.id]
226 raise TypeError(node)
229def handler(q: multiprocessing.Queue, func, args, **kwargs):
231 q.put(func(*args, **kwargs))
None __init__(self, "PluginCfg" plg_cfg)
EngineResults post_search(self, "SXNG_Request" request, "SearchWithPlugins" search)
timeout_func(self, timeout, func, *args, **kwargs)
int _compare(list[ast.cmpop] ops, list[int|float] values)
handler(multiprocessing.Queue q, func, args, **kwargs)