.. _api-parser: toful_parser.py — Input Normalisation ======================================= The ``toful_parser`` module implements a two-pass transformation pipeline for user-supplied mathematical expressions: * **Pass 1** — ``normalise_for_eval()`` transforms raw input into valid Python that can be safely passed to ``eval()``. * **Pass 2** — ``normalise_for_display()`` produces Unicode-rich output for rendering in the UI. Both passes are pure functions with no side-effects and return a :class:`~toful_parser.NormalisationResult` object that includes the transformed string and a human-readable list of changes made. .. contents:: On this page :local: :depth: 2 ---- Data Classes ------------ .. autoclass:: toful_parser.NormalisationResult :members: :undoc-members: ---- Public API ---------- .. autofunction:: toful_parser.normalise_for_eval .. autofunction:: toful_parser.normalise_for_display .. autofunction:: toful_parser.normalise_range_input .. autofunction:: toful_parser.build_safe_dict .. autofunction:: toful_parser.prepare_expression ---- Transformation Pipeline — Pass 1 (eval) ----------------------------------------- The following transformations are applied in this fixed order by ``normalise_for_eval()``. Order matters: later rules depend on earlier ones having already run. 1. ``_apply_operator_aliases`` Replaces Unicode operators with ASCII Python equivalents. ``≤ → <=``, ``≥ → >=``, ``≠ → !=``, ``× → *``, ``÷ → /``, ``− → -`` (Unicode minus), ``∞ → inf``. 2. ``_apply_greek_to_eval`` Replaces Unicode Greek letters with eval-context variable names. ``π → pi``, ``λ → lam``, ``μ → mu``, ``σ → sigma``. 3. ``_apply_subscript_normalisation`` Converts Unicode subscript digits to plain ASCII. ``x₁ → x1``, ``y₂ → y2``. 4. ``_apply_inline_subscript`` Removes underscore between letter and digits. ``x_1 → x1``. 5. ``_apply_superscript_to_power`` Converts Unicode superscript sequences to ``**n`` notation. ``x² → x**2``, ``(x+1)³ → (x+1)**3``. 6. ``_apply_e_caret`` Converts ``e^x`` and ``e^(expr)`` to ``exp(x)`` / ``exp(expr)``. Must run before the generic caret rule. 7. ``_apply_caret_power`` Converts remaining ``^`` to ``**``. ``2^n → 2**n``. 8. ``_apply_absolute_value`` Converts ``|expr|`` to ``abs(expr)`` for single-level expressions. 9. ``_apply_function_aliases`` Applies the function alias table: ``ln( → log(``, ``arcsin( → asin(``, ``√( → sqrt(``, etc. 10. ``_apply_implicit_multiplication`` Inserts ``*`` where multiplication is implied: digit × letter (``2x → 2*x``), ``)(`` → ``)*(``, ``)letter`` → ``)*letter`` (excluding known function names). 11. ``_apply_condition_sugar`` Reserved for future piecewise shorthand. Currently a no-op. ---- Transformation Pipeline — Pass 2 (display) -------------------------------------------- Applied by ``normalise_for_display()``: 1. ``_apply_display_powers`` — ``**2 → ²``, ``**10 → ¹⁰`` 2. ``_apply_display_subscripts`` — ``x1 → x₁``, ``y_2 → y₂`` 3. ``_apply_display_operators`` — ``<= → ≤``, ``>= → ≥``, ``* → ·`` 4. ``_apply_display_greek`` — ``lambda → λ``, ``pi → π`` (whole words only) 5. ``_apply_display_functions`` — ``exp( → e^(``, ``sqrt( → √(`` ---- Safe Evaluation Namespace -------------------------- The ``build_safe_dict()`` function returns the namespace passed as ``locals=`` to every ``eval()`` call, with ``{"__builtins__": {}}`` as globals to block access to Python builtins. .. code-block:: python from toful_parser import build_safe_dict ns = build_safe_dict(x_val=1.5, extra={"lam": 2.0}) code = compile("lam * exp(-lam * x)", "", "eval") result = eval(code, {"__builtins__": {}}, ns) # result ≈ 0.5413 Available names in the namespace: +----------------------+-------------------------------------------+ | Name | Value | +======================+===========================================+ | ``pi`` | numpy.pi | +----------------------+-------------------------------------------+ | ``e`` | numpy.e | +----------------------+-------------------------------------------+ | ``inf`` | numpy.inf | +----------------------+-------------------------------------------+ | ``sqrt`` | numpy.sqrt | +----------------------+-------------------------------------------+ | ``exp`` | numpy.exp | +----------------------+-------------------------------------------+ | ``log`` | numpy.log (natural) | +----------------------+-------------------------------------------+ | ``log2`` | numpy.log2 | +----------------------+-------------------------------------------+ | ``log10`` | numpy.log10 | +----------------------+-------------------------------------------+ | ``sin/cos/tan`` | numpy trigonometric functions | +----------------------+-------------------------------------------+ | ``asin/acos/atan`` | numpy inverse trig | +----------------------+-------------------------------------------+ | ``sinh/cosh/tanh`` | numpy hyperbolic | +----------------------+-------------------------------------------+ | ``ceil/floor`` | numpy.ceil / numpy.floor | +----------------------+-------------------------------------------+ | ``abs`` | Python built-in abs | +----------------------+-------------------------------------------+ | ``factorial`` | math.factorial (integers only) | +----------------------+-------------------------------------------+ | ``gamma`` | scipy.special.gamma | +----------------------+-------------------------------------------+ | ``erf/erfc`` | math.erf / math.erfc | +----------------------+-------------------------------------------+ | ``lam`` | 1.0 (default; override via extra_params) | +----------------------+-------------------------------------------+ | ``mu`` | 0.0 (default; override via extra_params) | +----------------------+-------------------------------------------+ | ``sigma`` | 1.0 (default; override via extra_params) | +----------------------+-------------------------------------------+ | ``x`` | Set per evaluation | +----------------------+-------------------------------------------+ ---- Usage Examples -------------- Basic normalisation ~~~~~~~~~~~~~~~~~~~ .. code-block:: python from toful_parser import normalise_for_eval, normalise_for_display r = normalise_for_eval("0.3 * 0.7^x if x >= 0 else 0") print(r.result) # "0.3 * 0.7**x if x >= 0 else 0" print(r.changes) # ["Caret exponent (^) → Python power (**)"] r2 = normalise_for_eval("e^(-2*x) if x ≥ 0 else 0") print(r2.result) # "exp(-2*x) if x >= 0 else 0" r3 = normalise_for_display("x1**2 + mu") print(r3.result) # "x₁² + μ" Full pipeline ~~~~~~~~~~~~~ .. code-block:: python from toful_parser import prepare_expression eval_expr, display_expr, changes, error = prepare_expression( "2x if 0 <= x <= 1 else 0" ) print(eval_expr) # "2*x if 0 <= x <= 1 else 0" print(display_expr) # "2·x if 0 ≤ x ≤ 1 else 0" print(changes) # ["Implicit multiplication: digit × letter (e.g. 2x → 2*x)"] print(error) # None Range input ~~~~~~~~~~~ .. code-block:: python from toful_parser import normalise_range_input r = normalise_range_input("0,1,2,3,…") print(r.result) # "0,1,2,3,..." print(r.changes) # ["Unicode ellipsis '…' → '...'"] See also -------- * :doc:`core` — the computation engine that consumes parser output * :doc:`/getting-started/input-syntax` — user-facing syntax documentation