Source code for experimental_experiment.xshape.evaluate_expressions
import ast
import operator
from typing import Dict
operators = {
ast.Add: operator.add,
ast.BitXor: lambda x, y: max(x, y),
ast.FloorDiv: operator.floordiv,
ast.Mod: operator.mod,
ast.Mult: operator.mul,
ast.Pow: operator.pow,
ast.Sub: operator.sub,
ast.USub: operator.neg,
}
def _CeilToDiv(n: int, div: int) -> int:
return n // div if n % div == 0 else n // div + 1
def _eval(node, variables, expr):
if isinstance(node, ast.Expression):
return _eval(node.body)
if isinstance(node, ast.Constant):
if isinstance(node.value, int):
return node.value
raise TypeError(
f"Unsupported constant type {node.value!r} "
f"in expression {expr!r} with context={variables!r}"
)
if isinstance(node, ast.BinOp):
left = _eval(node.left, variables, expr)
right = _eval(node.right, variables, expr)
op_type = type(node.op)
if op_type in operators:
return operators[op_type](left, right)
raise TypeError(
f"Unsupported operator: {op_type!r} "
f"in expression {expr!r} with context={variables!r}"
)
if isinstance(node, ast.UnaryOp):
operand = _eval(node.operand, variables, expr)
op_type = type(node.op)
if op_type in operators:
return operators[op_type](operand)
raise TypeError(
f"Unsupported unary operator: {op_type!r} "
f"in expression {expr!r} with context={variables!r}"
)
if isinstance(node, ast.Name): # variable
if node.id in variables:
val = variables[node.id]
if not isinstance(val, (int, float)):
raise TypeError(
f"Variable {node.id!r} must be numeric "
f"in expression {expr!r} with context={variables!r}"
)
return val
raise NameError(
f"Unknown variable: {node.id!r} in expression {expr!r} with context={variables!r}"
)
if isinstance(node, ast.Call):
# Specific function
name = node.func.id
assert name == "CeilToInt", f"Unable to evaluate function {name!r} in {expr!r}"
values = [_eval(a, variables, expr) for a in node.args]
return _CeilToDiv(*values)
raise TypeError(
f"Unsupported AST node: {type(node)} in expression {expr!r} with context={variables!r}"
)
[docs]
def evaluate_expression(expression: str, context: Dict[str, int]) -> int:
"""
Evaluates an expression handling dimensions.
.. runpython::
:showcode:
from experimental_experiment.xshape.evaluate_expressions import (
evaluate_expression,
)
print(evaluate_expression("x+y", dict(x=3, y=5)))
"""
if isinstance(expression, int):
return expression
parsed = ast.parse(expression, mode="eval")
return _eval(parsed.body, variables=context, expr=expression)