mirror of
https://github.com/searxng/searxng.git
synced 2025-07-12 15:59:21 +02:00
[feat] calculator: add support for comparation operators (<, <=, ==, ...)
This commit is contained in:
parent
a0fca8c21b
commit
27466faadb
1 changed files with 58 additions and 7 deletions
|
@ -76,15 +76,50 @@ class SXNGPlugin(Plugin):
|
||||||
|
|
||||||
# Prevent the runtime from being longer than 50 ms
|
# Prevent the runtime from being longer than 50 ms
|
||||||
res = timeout_func(0.05, _eval_expr, query_py_formatted)
|
res = timeout_func(0.05, _eval_expr, query_py_formatted)
|
||||||
if res is None or res == "":
|
if res is None or res[0] == "":
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
res, is_boolean = res
|
||||||
|
if is_boolean:
|
||||||
|
res = "True" if res != 0 else "False"
|
||||||
|
else:
|
||||||
res = babel.numbers.format_decimal(res, locale=ui_locale)
|
res = babel.numbers.format_decimal(res, locale=ui_locale)
|
||||||
results.add(results.types.Answer(answer=f"{search.search_query.query} = {res}"))
|
results.add(results.types.Answer(answer=f"{search.search_query.query} = {res}"))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def _compare(ops: list[ast.cmpop], values: list[int | float]) -> int:
|
||||||
|
"""
|
||||||
|
2 < 3 becomes ops=[ast.Lt] and values=[2,3]
|
||||||
|
2 < 3 <= 4 becomes ops=[ast.Lt, ast.LtE] and values=[2,3, 4]
|
||||||
|
"""
|
||||||
|
for op, a, b in zip(ops, values, values[1:]): # pylint: disable=invalid-name
|
||||||
|
if isinstance(op, ast.Eq) and a == b:
|
||||||
|
continue
|
||||||
|
if isinstance(op, ast.NotEq) and a != b:
|
||||||
|
continue
|
||||||
|
if isinstance(op, ast.Lt) and a < b:
|
||||||
|
continue
|
||||||
|
if isinstance(op, ast.LtE) and a <= b:
|
||||||
|
continue
|
||||||
|
if isinstance(op, ast.Gt) and a > b:
|
||||||
|
continue
|
||||||
|
if isinstance(op, ast.GtE) and a >= b:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Ignore impossible ops:
|
||||||
|
# * ast.Is
|
||||||
|
# * ast.IsNot
|
||||||
|
# * ast.In
|
||||||
|
# * ast.NotIn
|
||||||
|
|
||||||
|
# the result is False for a and b and operation op
|
||||||
|
return 0
|
||||||
|
# the results for all the ops are True
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
operators: dict[type, typing.Callable] = {
|
operators: dict[type, typing.Callable] = {
|
||||||
ast.Add: operator.add,
|
ast.Add: operator.add,
|
||||||
ast.Sub: operator.sub,
|
ast.Sub: operator.sub,
|
||||||
|
@ -98,6 +133,7 @@ operators: dict[type, typing.Callable] = {
|
||||||
ast.RShift: operator.rshift,
|
ast.RShift: operator.rshift,
|
||||||
ast.LShift: operator.lshift,
|
ast.LShift: operator.lshift,
|
||||||
ast.Mod: operator.mod,
|
ast.Mod: operator.mod,
|
||||||
|
ast.Compare: _compare,
|
||||||
}
|
}
|
||||||
|
|
||||||
# with multiprocessing.get_context("fork") we are ready for Py3.14 (by emulating
|
# with multiprocessing.get_context("fork") we are ready for Py3.14 (by emulating
|
||||||
|
@ -109,18 +145,30 @@ mp_fork = multiprocessing.get_context("fork")
|
||||||
|
|
||||||
def _eval_expr(expr):
|
def _eval_expr(expr):
|
||||||
"""
|
"""
|
||||||
|
Evaluates the given textual expression.
|
||||||
|
|
||||||
|
Returns a tuple of (numericResult, isBooleanResult).
|
||||||
|
|
||||||
>>> _eval_expr('2^6')
|
>>> _eval_expr('2^6')
|
||||||
64
|
64, False
|
||||||
>>> _eval_expr('2**6')
|
>>> _eval_expr('2**6')
|
||||||
64
|
64, False
|
||||||
>>> _eval_expr('1 + 2*3**(4^5) / (6 + -7)')
|
>>> _eval_expr('1 + 2*3**(4^5) / (6 + -7)')
|
||||||
-5.0
|
-5.0, False
|
||||||
|
>>> _eval_expr('1 < 3')
|
||||||
|
1, True
|
||||||
|
>>> _eval_expr('5 < 3')
|
||||||
|
0, True
|
||||||
|
>>> _eval_expr('17 == 11+1+5 == 7+5+5')
|
||||||
|
1, True
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return _eval(ast.parse(expr, mode='eval').body)
|
root_expr = ast.parse(expr, mode='eval').body
|
||||||
|
return _eval(root_expr), isinstance(root_expr, ast.Compare)
|
||||||
|
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
# This is undefined
|
# This is undefined
|
||||||
return ""
|
return "", False
|
||||||
|
|
||||||
|
|
||||||
def _eval(node):
|
def _eval(node):
|
||||||
|
@ -133,6 +181,9 @@ def _eval(node):
|
||||||
if isinstance(node, ast.UnaryOp):
|
if isinstance(node, ast.UnaryOp):
|
||||||
return operators[type(node.op)](_eval(node.operand))
|
return operators[type(node.op)](_eval(node.operand))
|
||||||
|
|
||||||
|
if isinstance(node, ast.Compare):
|
||||||
|
return _compare(node.ops, [_eval(node.left)] + [_eval(c) for c in node.comparators])
|
||||||
|
|
||||||
raise TypeError(node)
|
raise TypeError(node)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue