Files
ritz/tests/test_parser.py
T
Aaron Sinclair 635d466874 Implement Python → LLVM IR → WASM reactive framework
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!
2025-11-15 01:50:40 -08:00

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"