miniexpr DSL Syntax (Canonical Reference)¶
This is the practical reference for the DSL used by @blosc2.dsl_kernel
functions (and checked by blosc2.validate_dsl()).
It focuses on what works today and the most common gotchas.
For usage walkthroughs and end-to-end examples, see the
LazyArray UDF DSL kernels tutorial.
Quick start¶
A valid DSL program is one function:
def kernel(x, y):
temp = sin(x) ** 2
return temp + cos(y) ** 2
Use Python-style indentation and always return a value on the paths you execute.
@blosc2.jit auto-detects this DSL: a decorated function whose body contains an
if/for/while and that compiles under this grammar is dispatched here
automatically, so its branches and loops actually run, once per chunk, instead
of jit’s normal approach of calling the function only once to record a single
expression — which would otherwise capture just whichever branch that one call
happened to take, silently dropping the rest. @blosc2.dsl_kernel remains the
explicit form — it always requires the DSL to compile, equivalent to
jit(strict=True).
Program shape¶
Exactly one top-level
def ...:function is expected.Leading blank lines and header comments are allowed.
Any extra trailing content after the function is a parse error.
Nested
definside the function body is not allowed.
Header pragmas¶
Supported file-header pragmas:
# me:fp=strict|contract|fast# me:compiler=tcc|cc
Notes:
Pragma keys must be unique.
Unknown
me:*pragmas are errors.Malformed pragma values are errors.
Function signature and inputs¶
Parameters are positional names:
def kernel(a, b, c): ...Parameter names must be unique.
At compile time, DSL parameter names must match input variable names by set membership (order may differ, count must match).
Statements¶
Supported statement forms:
Assignment:
a = exprCompound assignment:
+=,-=,*=,/=,//=Expression statement:
exprReturn:
return exprPrint:
print(...)Conditionals:
if/elif/elseWhile loop:
while cond:For loop:
for i in range(...):Loop control:
break,continue
General rules:
Python-style indentation is required.
Empty blocks are invalid.
elif/elsemust belong to a matchingif.Deprecated forms like
break if cond/continue if condare not part of DSL syntax.
if / elif / else example¶
def kernel(x):
if x > 0:
y = x
elif x == 0:
y = 1
else:
y = -x
return y
for example¶
def kernel(n):
acc = 0
for i in range(0, n, 1):
acc += i
return acc
while example¶
def kernel(x):
i = 0
y = x
while i < 3:
y = y * 2
i += 1
return y
Expressions and function calls¶
Expressions are compiled by miniexpr with DSL checks.
Commonly supported:
Names and numeric constants
Unary operators:
+,-, logical not (not/!)Arithmetic and bitwise binary operators
Comparisons:
==,!=,<,<=,>,>=Function calls to supported miniexpr functions
User-registered C functions/closures passed in
me_variable
Cast intrinsics:
int(expr)float(expr)bool(expr)
Cast rules:
Use function-call form only.
Exactly one argument.
Temporary variable type inference¶
Local temporaries get their dtype from the expression assigned to them.
Example:
def kernel(x):
temp = sin(x) ** 2
return temp + cos(x) ** 2
In this example, temp is inferred from sin(x) ** 2 (typically a floating type).
Notes:
You do not need to declare local variable types.
If you assign a value with an incompatible dtype to the same local later, compilation fails.
Loops¶
for ... in range(...)¶
Supported forms:
for i in range(stop):
...
for i in range(start, stop):
...
for i in range(start, stop, step):
...
Rules:
rangetakes 1, 2, or 3 arguments.step == 0raises a runtime evaluation error.
while¶
whilecondition is a regular DSL expression.Runtime iteration cap is enforced by
ME_DSL_WHILE_MAX_ITERS.
Reductions inside control flow¶
sum, max, min and other block reductions collapse the whole chunk being
evaluated to one value, not one value per element. Using one as the
condition of if/while, or assigning one to a local that per-element code
later reads (e.g. y = max(x) followed by if x > 0: y = y + 1), does
not raise a compile-time or runtime error – it compiles and runs, and
produces results that are only correct for element 0 of the block; every
other element sees a stale/zero value where the reduction result should be.
This is a rough edge in the underlying
miniexpr compiler, not something this
Python layer validates today. Write the per-element form instead (drop the
reduction, e.g. if abs(diff) < tol rather than if max(abs(diff)) < tol)
whenever the intent is a per-element, not whole-block, decision.
print(...)¶
print is supported as a DSL statement.
Rules:
At least one argument is required.
First argument may be a format string.
Placeholder count must match provided values.
Printed expressions must be uniform/scalar for the block.
Reserved names¶
Do not use these as user variable/function names in DSL:
print,int,float,bool,def,return_ndim_i<d>and_n<d>(reserved ND symbols)_flat_idx
ND reserved symbols¶
When referenced, these are synthesized by DSL compiler/runtime:
_i0,_i1, … (index per dimension)_n0,_n1, … (shape per dimension)_ndim_flat_idx(global C-order linear index)
Typing and return behavior¶
Reassigning incompatible dtypes to the same local is a compile-time error.
Return dtype must be consistent across all
returnstatements.Non-guaranteed return paths may compile; if execution reaches a missing return path, evaluation fails at runtime.
Compute dtype and integer exactness¶
The kernel’s output dtype determines the compute dtype for the whole expression:
With an integer output dtype, arithmetic is exact int64. Intermediates must fit in int64: products at or above 2^63 overflow and give wrong results.
With a float output dtype, integer inputs and temporaries are evaluated in float64, where integer operations are exact only below 2^53. Keep products under that bound (e.g. a 32-bit value times a multiplier below 2^21); larger products silently lose low bits.
Values outside the output dtype’s range wrap two’s-complement on the final store (e.g. returning a value in
[0, 2^32)into an int32 output yields the full[-2^31, 2^31)range).
Compound assignment desugaring¶
a += b->a = a + ba -= b->a = a - ba *= b->a = a * ba /= b->a = a / ba //= b->a = floor(a / b)
Compile-time vs runtime errors¶
Compile-time error examples:
Invalid program shape or signature
Unsupported statement forms
Invalid
range(...)arityInvalid cast intrinsic arity
Reserved-name misuse
Return dtype mismatch
Runtime error examples:
range(..., step=0)Missing return on executed control path
While-loop iteration cap exceeded
Execution backends¶
A DSL kernel is compiled and run by one of two backends, selected per evaluation
via the jit / jit_backend arguments to compute() / __getitem__:
miniexpr (default on native builds): a runtime JIT (TinyCC,
jit_backend="tcc") with an interpreter fallback (jit=False). Supports the full DSL described here, including integer/complex dtypes and reductions.JavaScript (
jit_backend="js"): transpiles the kernel to JavaScript and runs it through the browser’s JIT. WebAssembly/Pyodide only — requesting it elsewhere raises. Under WebAssembly it is also the default for eligible kernels (setjit=Falseorstrict_miniexpr=Trueto opt out), and silently falls back to miniexpr for anything it cannot handle.
The JavaScript backend computes in float64 and covers floating-point element-wise kernels:
arithmetic, comparisons, where, if/elif/else, for ... in range(...)/while
loops, the index/shape symbols (_i0/_n0/_ndim/_flat_idx), and the standard math
functions. It also accepts integer inputs when the output dtype is floating. It does
not support integer/complex output, reductions, or constructs outside the transpiled
subset; those stay on miniexpr (or, with an explicit jit_backend="js", raise).
Python syntax that is out of DSL scope¶
These Python features are not part of this DSL:
Ternary expression:
a if cond else bfor ... elseandwhile ... elseKeyword-argument calls and other call forms outside the supported subset
Docstrings (or any other bare string-literal statement) inside the kernel body – this is a compile-time parse error at the miniexpr level, not a silently-ignored statement.