Compiler
wybthon.compiler¶
compiler
¶
The Wybthon compiler: Solid's compile step, done on Python's syntax tree.
Solid's compiler does two things with JSX. It turns static markup into
templates, and it turns every dynamic expression in the markup into a
getter, so {count() * 2} stays reactive without being wrapped. This
module does both for html(t"..."):
@component
def Counter(props: CounterProps):
count, set_count = create_signal(0)
return html(t"<p>{count()} doubled is {count() * 2}, step {props.step}</p>")
compiles to (roughly):
return html(
t"<p>{getter(lambda: count())} doubled is {getter(lambda: count() * 2)}, step {getter(lambda: props.step)}</p>",
__wybthon_t0__, # the literal's parse tree and mount function, built at compile time
)
The rule. An interpolation is dynamic when its expression contains
a call, an attribute access, or a subscript (not counting the bodies of
lambdas inside it). A dynamic interpolation becomes a getter: a hole in a
child position, a binding in an attribute, a getter prop on a component
tag. Event handlers, refs, spreads, the component in a component tag, and
nested html(t"...") markup are never wrapped. Anything that isn't
dynamic (a name, a constant, a lambda) is passed as is.
Calls in child position are dispatched when the template is created
(see wybthon._compiled.call): a component (Card(title=user().name),
Show(...), For(...)) is built with each dynamic argument as a getter,
the way a JSX component receives its props; any other callable
(badge(user()), str(n())) becomes one reactive expression.
Captured names. A getter runs later, so the names its expression reads from enclosing function scopes are captured by value when the template is created (as keyword-only defaults of the getter's lambda). A template made in a loop sees that iteration's values.
Precompiled templates. Each literal is parsed and validated here, so
a malformed template is a SyntaxError with its file and line. Its parse
tree and the mount function for each of its elements are written into
the module, so html() never parses a template or generates code at run
time.
The compiler runs from the import hook that import wybthon installs,
from wyb build and wyb dev, from the pytest plugin, and from wyb run.
Functions:
| Name | Description |
|---|---|
install |
Compile templates in modules imported from now on (idempotent). |
is_candidate |
Whether a module's source may contain |
uninstall |
Remove the import hook. |
is_dynamic |
Whether an expression may read reactive state: it contains a call, attribute access, or subscript. |
transform |
Compile the |
compile_source |
Compile a module's source with its templates compiled. |
defers |
Whether the compiler would defer an interpolation's expression (dev-mode checks). |
install
¶
Compile templates in modules imported from now on (idempotent).
import wybthon calls this. The hook goes right before the regular
path finder, so finders installed earlier (pytest's assertion
rewriting, for one) keep handling the modules they claim.
is_candidate
¶
Whether a module's source may contain html(t"...") calls for the compiler.
is_dynamic
¶
Whether an expression may read reactive state: it contains a call, attribute access, or subscript.
The bodies of lambdas inside the expression don't count; they run whenever their caller decides.
transform
¶
Compile the html(t"...") calls in a module's syntax tree, in place.
Returns the tree. A module that doesn't import Wybthon's html is
returned unchanged. Raises SyntaxError for a malformed template.
compile_source
¶
Compile a module's source with its templates compiled.
What's in this module¶
compiler is Wybthon's compile step, the counterpart of Solid's JSX
compiler, done on Python's syntax tree. It rewrites every
html(t"...") call whose argument is a t-string literal: each dynamic
interpolation (one containing a call, an attribute access, or a
subscript) becomes a getter, a call in a child position is dispatched
to a component or made a reactive hole, and the literal's parse tree and
mount functions are emitted as ordinary module code. Nothing else in the
module changes. See Concepts: Compiler for
the rules and how to inspect the output.
You rarely call it directly. It runs in wyb build and wyb dev, in
the import hook that import wybthon installs, in the pytest plugin,
and in wyb run script.py.
| Name | Description |
|---|---|
install |
Install the import hook (idempotent); import wybthon calls it. |
uninstall |
Remove the import hook. |
compile_source |
compile_source(source, filename): a module's code object with its templates compiled, ready for exec. |
transform |
transform(tree, filename="<unknown>", source=""): rewrite a module's ast in place and return it; raises SyntaxError for a malformed template. |
is_dynamic |
Whether an expression node contains a call, attribute access, or subscript (outside the lambdas inside it). |
is_candidate |
Whether a module's source bytes might hold html(t"...") calls: it mentions wybthon and html and contains a t-string. |
defers |
Whether the compiler would defer an interpolation's source text in a given position; html() uses it for the dev-mode check of uncompiled templates. |
COMPILER_TAG |
The optimization tag of cached compiled bytecode (*.opt-wyb1.pyc); it changes when the compiler's output does. |
from wybthon.compiler import compile_source
source = """
from wybthon import create_signal, html
count, set_count = create_signal(2)
view = html(t"<p>{count() * 2}</p>")
"""
namespace = {}
exec(compile_source(source, "generated.py"), namespace) # {count() * 2} is a reactive hole
htmlis resolved through the module's imports (from wybthon import html,from wybthon.templates import html, an alias of either, orwybthon.html), so a module that defines its ownhtmlis left alone.- A malformed template is a
SyntaxErrorwith its file and line when the module is compiled, not aTemplateErrorat run time. - The generated names (
__wybthon__,__wybthon_t0__,__wybthon_m0__) are dunder names, which Python never mangles inside classes.
See also¶
- Concepts: Compiler
- Templates: what
html()does with a compiled literal - Dev server (CLI):
wyb build,wyb dev, andwyb run - Guides: Testing: the pytest plugin
- RFC 0004: Engine v4