39 lines
2.0 KiB
Python
39 lines
2.0 KiB
Python
from __future__ import annotations
|
|
|
|
import ast
|
|
import operator
|
|
import re
|
|
from typing import Any
|
|
|
|
SCALE = {"mm": 1.0, "millimeter": 1.0, "millimeters": 1.0, "cm": 10.0, "m": 1000.0,
|
|
"meter": 1000.0, "meters": 1000.0, "in": 25.4, "inch": 25.4, "inches": 25.4,
|
|
"ft": 304.8, "foot": 304.8, "feet": 304.8}
|
|
_NUM_UNIT = re.compile(r"^\s*([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)\s*(?:\*\s*)?([A-Za-z]+)\s*$")
|
|
|
|
|
|
def _eval(node: ast.AST, names: dict[str, float]) -> float:
|
|
if isinstance(node, ast.Expression): return _eval(node.body, names)
|
|
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return float(node.value)
|
|
if isinstance(node, ast.Name) and node.id in names: return names[node.id]
|
|
if isinstance(node, ast.UnaryOp) and isinstance(node.op, (ast.UAdd, ast.USub)):
|
|
value = _eval(node.operand, names); return value if isinstance(node.op, ast.UAdd) else -value
|
|
if isinstance(node, ast.BinOp) and isinstance(node.op, (ast.Add, ast.Sub, ast.Mult, ast.Div)):
|
|
left, right = _eval(node.left, names), _eval(node.right, names)
|
|
return {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv}[type(node.op)](left, right)
|
|
raise ValueError("expression is not a safe constant")
|
|
|
|
|
|
def length_mm(expression: Any, names: dict[str, float] | None = None) -> float:
|
|
if not isinstance(expression, str): raise ValueError("missing length expression")
|
|
text = expression.strip().replace("millimeter", "mm").replace("inch", "in")
|
|
match = _NUM_UNIT.match(text)
|
|
if match:
|
|
unit = match.group(2).lower()
|
|
if unit not in SCALE: raise ValueError(f"unsupported unit {unit!r}")
|
|
return float(match.group(1)) * SCALE[unit]
|
|
env = {"mm": 1.0, "cm": 10.0, "m": 1000.0, "inch": 25.4, "in": 25.4, **(names or {})}
|
|
try:
|
|
return _eval(ast.parse(text, mode="eval"), env)
|
|
except Exception as exc:
|
|
raise ValueError(f"unsupported length expression {expression!r}") from exc
|