import ast
import math
import numpy as np

_ALLOWED_NODES = {
    "Expression","BinOp","UnaryOp","Call","Name","Load","Constant","Num",
    "Add","Sub","Mult","Div","FloorDiv","Mod","Pow",
    "LShift","RShift","BitOr","BitAnd","BitXor",
    "UAdd","USub","Invert"
}

_ALLOWED_NAMES = {
    "t", "sin","cos","tan","floor","ceil","abs","sqrt","log","exp","pi","e",
    "np","int","float"
}

def _validate(node):
    for n in ast.walk(node):
        if type(n).__name__ not in _ALLOWED_NODES:
            raise ValueError(f"Disallowed node: {type(n).__name__}")

def compile_formula(expr: str):
    tree = ast.parse(expr, mode="eval")
    _validate(tree)

    for n in ast.walk(tree):
        if isinstance(n, ast.Name) and n.id not in _ALLOWED_NAMES:
            raise ValueError(f"Unknown name: {n.id}")

    allowed = {
        "np": np,
        "sin": np.sin, "cos": np.cos, "tan": np.tan,
        "floor": np.floor, "ceil": np.ceil,
        "abs": np.abs, "sqrt": np.sqrt,
        "log": np.log, "exp": np.exp,
        "pi": math.pi, "e": math.e,
        "int": np.int64, "float": np.float64,
    }

    return eval(f"lambda t: {expr}", {"__builtins__": None, **allowed})
