Expression builder for runtime-safe logic

The expression builder lets you describe logic as data that the runtime can evaluate safely.

What thorm() is

thorm() is an expression builder for runtime-safe logic. Instead of calculating values in PHP, you describe the logic as a small tree of operations. The runtime evaluates that tree later, so it can keep UI updates reactive and predictable.

This keeps your logic declarative. The tree is serializable, safe to run in the browser, and deterministic in both client and server rendering.

Who this is for

This page is for anyone, but a little PHP knowledge helps because the IR is authored in PHP. If you know basic JavaScript, the runtime model will click faster because the expressions are evaluated in the browser after hydration.

If either of those is new, you can still follow along, but expect to pause and look up a few fundamentals.

Mental model: build a tree, not a value

thorm() does not execute logic in PHP. It builds an expression tree that is stored in the IR. The runtime evaluates that tree when it needs to render or update UI.

This is why thorm() exists: it lets you write logic once, then run it safely in a controlled runtime, with dependency tracking and automatic updates.

Quick start

Here is a small, complete example. The math is authored in thorm(), and the UI reads the expression directly. The runtime evaluates the tree and updates the text automatically.

PHP

<?php
$scrollY 
state(0);
$docH    state(1);
$viewH   state(1);

$percent thorm(['mul',
    [
'div'read($scrollY), ['sub'read($docH), read($viewH)]],
    
100
]);

$app el('div', [cls('container p-3')], [
    
el('h1', [], [ text('Scroll percent') ]),
    
el('p', [], [ text(concat('Progress: '$percent'%')) ]),
]);

Functions used here: state(), read(), thorm(), el(), text(), concat().

You can mix thorm() with standard expression helpers like concat() or cond(). The key is that the actual logic lives in the expression tree, not in PHP.

Full ops list

thorm() accepts a small, explicit set of operations. This whitelist is what keeps the runtime safe and deterministic.

Aliases: +, -, *, /, %, ==, >, <, >=, <=, mulx, divx are accepted and mapped to add/sub/mul/div/mod/eq/gt/lt/gte/lte.

Anything outside this list is intentionally disallowed. That is part of the safety guarantee.

Classic expression style still works

If you already prefer the nested helper style, keep using it. thorm() is optional and designed to make complex trees easier to read, but the classic approach remains valid.

PHP

<?php
$percent 
mul(
    
div(read($scrollY), add(read($docH), mul(read($viewH), val(-1)))),
    
val(100)
);

You can mix both styles in the same project, so pick the one that keeps your code clearest.

Safety and determinism

thorm() is designed to be pure and deterministic. That means no randomness, no time access, no DOM access, and no external I/O. Expressions only transform the inputs you give them.

If you need side effects, use actions or runtime capabilities instead. This separation makes it easier to reason about what can and cannot happen during rendering.

Best practices

  • Keep expressions small and readable; split complex math into smaller trees.
  • Use read($atom) directly in thorm() to keep dependencies explicit.
  • Use cond() or thorm(['cond', ...]) for safe branching instead of PHP conditionals.
  • Prefer derived expressions over storing computed values in state.

Example: split complex math into smaller chunks.

Bad: one long expression

PHP

<?php
$percent 
thorm(['mul',
    [
'div'read($scrollY), ['sub'read($docH), read($viewH)]],
    
100
]);

Good: named steps that explain intent

PHP

<?php
$scrollRange 
thorm(['sub'read($docH), read($viewH)]);
$ratio       thorm(['div'read($scrollY), $scrollRange]);
$percent     thorm(['mul'$ratio100]);

Example: derived expressions vs stored computed values.

Bad: store computed values in state

PHP

<?php
$price 
state(10);
$qty   state(2);

$total state(20);
// You must remember to update $total everywhere $price or $qty change.

Good: derive it once and always correct

PHP

<?php
$price 
state(10);
$qty   state(2);

$total thorm(['mul'read($price), read($qty)]);

These habits keep your UI predictable and make reactivity easier to debug.

SSR note

The same expression tree can be evaluated on the server or in the browser. SSR uses the initial atom values, while the client continues evaluating the same tree after hydration.

If a value is not available on the server, you can still render a fallback and let the client take over after mount.

What is on the horizon

Future work may extend thorm() with list operators (map/filter/reduce) and lambda nodes for algorithmic use cases. These are not implemented yet and are intentionally kept out of the current runtime for now.

When they arrive, they will follow the same safety-first approach: a small whitelist, deterministic evaluation, and full serialization.