Skip to content

The compiled dialect

situ compiles a bounded subset of Python to JavaScript and rejects everything else at compile time with a CompileError that names the problem and usually the fix. This is the same design stance as the numeric subset-of-Python compilers (Numba, JAX): a well-understood subset with loud rejection, so a missing feature surfaces as an error during development.

The compiler carries 111 distinct rejection sites; the greatest hits are catalogued in Compile errors.

Expressions

Usable in binder values (:show="…", :text="…") and event actions:

Construct Compiles to Notes
names signal reads (S.get) or row-var reads unknown names are rejected
literals JS literals str/int/float/bool/None
and / or / not && / \|\| / !
== != < <= > >= strict ===-family, numeric-normalized when either side is numeric, operands are wrapped in Number(...) — never JS loose equality
+ - * / % JS arithmetic
x if c else y ternary
x in y / not in Set.has on a Local[set], .includes on strings can't chain with relational ops
f-strings template literals conversions (!r) and format specs (:.2f) are rejected
t.field (row var) a dataset / row-object read only inside a :each scope or on data-id rows
subscripts, slices JS indexing / .slice no slice step
dict / list literals object / array literals dict keys must be string constants

Whitelisted calls: len (→ .length/.size), sum(<generator>) (→ .reduce, single generator, no filter), min/max (≥ 2 args), abs, round, today() (→ the local ISO date), and the string methods strip lower upper startswith endswith.

Statements (local handler bodies)

Accepted Notes
name = expr a local-signal write (S.set) or a plain let — single target only
x += / -= / *= / /= / %= augmented assignment
if / elif / else
return (incl. early)
s.add(x) s.discard(x) l.append(x) l.remove(x) on Local[set] / Local[list] — immutable shim helpers, so subscribers always fire
reset("name", ...) back to declared defaults
global / nonlocal emit nothing — they make bare-name signal writes valid CPython
pass, docstrings

Rejected — always loudly

for / while / with / try / yield / lambda / comprehensions (beyond the one sum generator) / import / nested def or class / unknown calls or attributes — and every seam violation. Each rejection is a one-line message; the catch-all reads statement not in the compiled dialect: <NodeType>.

Semantics worth knowing

  • Numbers stay numbers. A :bind on <input type="number"> stores a JS number, so count == 0 and count > 50 behave like Python. Comparisons are strict equality after explicit numeric coercion.
  • Decimal stays a string (in declui and by convention): a JS float would corrupt money. Equality works; relational comparison on Decimals is rejected where it would be lexical.
  • today() lowers to the local date as ISO YYYY-MM-DD — safe to compare lexically against <input type="date"> values.
  • sum(x.price for x in items) works on a Local[list] — the one comprehension-shaped construct, chosen because rollups are ubiquitous.

When you hit the wall

The wall is the feature. When the dialect rejects your code, the fix is one of:

  1. Move it to the server — add an await (a facade call) and let it become a command; loops over data belong in context() or the facade.
  2. Restructure into dialect — e.g. replace a loop-accumulate with sum(...), or a try with a validity check.
  3. It is runtime user input — then it can't be compiled by anyone; that's what the one sanctioned interpreter boundary looks like (the spreadsheet demo's :cells formula engine).

If a rejection message doesn't point you at the fix, that's a bug in the message — the error texts are maintained as part of the API.