MAJOR ACHIEVEMENT: Full working reactive framework! ## What's Working ✅ Python → AST → Ritz IR → LLVM IR → WASM pipeline ✅ Reactive signals stored in WASM memory (@signal_name = global i32) ✅ Event handlers exported from WASM and callable from JS ✅ Clean runtime with RitzLoader and debug tools ✅ Two working examples: HelloWorld and HighFive counter ## Core Components ### Parser (ritz/parser.py) - Uses Python's ast module to parse components - Handles @component decorator, signals, event handlers - Supports yield, with statements, f-strings, augmented assignment (+=) - Clean syntax: from ritz.syntax import * ### LLVM IR Code Generator (ritz/llvm_codegen_signals.py) - Generates LLVM IR directly (not C!) - Signals as global variables in WASM memory - Exports getter/setter functions for each signal - Exports event handler functions for JS to call - UTF-8 string handling ### Build System (ritz/build.py) - Python → AST → IR → LLVM IR → clang --target=wasm32 → WASM - Saves intermediate .ll files for inspection - Debug symbols support (-g flag) ### Runtime (runtime/ritz-runtime.js) - RitzRuntime class: DOM bridge for WASM - RitzLoader class: Simplified component loading - Debug bar with Ctrl+` toggle - Signal updates trigger DOM updates ## Examples ### HelloWorld.py Simple static content - renders <p>Hello World</p> ### HighFive.py Reactive counter with: - Signal in WASM memory (high_fives = signal(0)) - Event handler (increment function) - DOM updates on signal change ## Test Coverage - 4 parser tests (all passing) - 4 LLVM codegen tests (all passing) - 4 signal parsing tests (all passing) - Code formatted with black and ruff ## Next Steps - <script type="text/ritz"> in-browser compilation - Better signal-to-DOM binding tracking - More complex examples (TodoMVC, forms, lists) - Playwright browser tests --- This is a genuinely innovative framework: write Python, compile to WASM, run with reactive signals in WASM memory. No virtual DOM, no transpilation, just compiled Python with fine-grained reactivity. 🎉 Python → WASM → Reactive UI is REAL!
112 lines
3.0 KiB
Python
112 lines
3.0 KiB
Python
"""Tests for AST parser that converts Python components to IR."""
|
|
|
|
import textwrap
|
|
|
|
|
|
def test_parse_hello_world_component():
|
|
"""Test parsing the simplest possible component - static p tag with text."""
|
|
from ritz.parser import parse_component
|
|
|
|
source = textwrap.dedent(
|
|
"""
|
|
@component
|
|
def HelloWorld():
|
|
with P():
|
|
yield "Hello World"
|
|
"""
|
|
)
|
|
|
|
ir = parse_component(source)
|
|
|
|
# Verify component structure
|
|
assert ir["type"] == "component"
|
|
assert ir["name"] == "HelloWorld"
|
|
assert ir["args"] == []
|
|
|
|
# Verify body contains a with statement
|
|
assert len(ir["body"]) == 1
|
|
with_stmt = ir["body"][0]
|
|
assert with_stmt["type"] == "with"
|
|
assert with_stmt["element"]["tag"] == "P"
|
|
assert with_stmt["element"]["props"] == {}
|
|
|
|
# Verify yield inside with
|
|
assert len(with_stmt["body"]) == 1
|
|
yield_stmt = with_stmt["body"][0]
|
|
assert yield_stmt["type"] == "yield"
|
|
assert yield_stmt["value"]["type"] == "constant"
|
|
assert yield_stmt["value"]["value"] == "Hello World"
|
|
|
|
|
|
def test_parse_component_with_props():
|
|
"""Test parsing element with props (class, id, etc)."""
|
|
from ritz.parser import parse_component
|
|
|
|
source = textwrap.dedent(
|
|
"""
|
|
@component
|
|
def StyledDiv():
|
|
with Div(class_="container", id="main"):
|
|
yield "Content"
|
|
"""
|
|
)
|
|
|
|
ir = parse_component(source)
|
|
with_stmt = ir["body"][0]
|
|
|
|
assert with_stmt["element"]["tag"] == "Div"
|
|
# Props are transformed to IR nodes
|
|
assert with_stmt["element"]["props"]["class_"] == {"type": "constant", "value": "container"}
|
|
assert with_stmt["element"]["props"]["id"] == {"type": "constant", "value": "main"}
|
|
|
|
|
|
def test_parse_nested_elements():
|
|
"""Test parsing nested with statements."""
|
|
from ritz.parser import parse_component
|
|
|
|
source = textwrap.dedent(
|
|
"""
|
|
@component
|
|
def Nested():
|
|
with Div():
|
|
with P():
|
|
yield "Inner text"
|
|
"""
|
|
)
|
|
|
|
ir = parse_component(source)
|
|
outer_with = ir["body"][0]
|
|
assert outer_with["element"]["tag"] == "Div"
|
|
|
|
inner_with = outer_with["body"][0]
|
|
assert inner_with["type"] == "with"
|
|
assert inner_with["element"]["tag"] == "P"
|
|
|
|
yield_stmt = inner_with["body"][0]
|
|
assert yield_stmt["value"]["value"] == "Inner text"
|
|
|
|
|
|
def test_parse_multiple_yields():
|
|
"""Test parsing multiple yield statements in sequence."""
|
|
from ritz.parser import parse_component
|
|
|
|
source = textwrap.dedent(
|
|
"""
|
|
@component
|
|
def Multiple():
|
|
with Div():
|
|
yield "First"
|
|
yield "Second"
|
|
yield "Third"
|
|
"""
|
|
)
|
|
|
|
ir = parse_component(source)
|
|
with_stmt = ir["body"][0]
|
|
|
|
assert len(with_stmt["body"]) == 3
|
|
assert all(stmt["type"] == "yield" for stmt in with_stmt["body"])
|
|
assert with_stmt["body"][0]["value"]["value"] == "First"
|
|
assert with_stmt["body"][1]["value"]["value"] == "Second"
|
|
assert with_stmt["body"][2]["value"]["value"] == "Third"
|