Files
ritz/tests/test_signal_codegen.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

142 lines
3.6 KiB
Python

"""Tests for Signal codegen to LLVM IR."""
import textwrap
def test_signal_llvm_ir_structure():
"""Test that signal generates proper LLVM IR with memory allocation."""
from ritz.llvm_codegen import generate_llvm_ir
from ritz.parser import parse_component
source = textwrap.dedent(
"""
@component
def Counter():
count = signal(0)
with P():
yield "test"
"""
)
ir = parse_component(source)
llvm_ir = generate_llvm_ir(ir)
# Should have global for signal value
assert "@signal_count" in llvm_ir or "signal" in llvm_ir.lower()
# Should have signal getter function
assert "get_signal" in llvm_ir or "@get_count" in llvm_ir
# Should have signal setter function
assert "set_signal" in llvm_ir or "@set_count" in llvm_ir
def test_signal_with_fstring():
"""Test signal reference in f-string generates proper IR."""
from ritz.llvm_codegen import generate_llvm_ir
from ritz.parser import parse_component
source = textwrap.dedent(
"""
@component
def Counter():
count = signal(0)
with P():
yield f"Count: {count}"
"""
)
ir = parse_component(source)
llvm_ir = generate_llvm_ir(ir)
# Should call signal getter
assert "get_count" in llvm_ir or "load" in llvm_ir # Loading signal value
# Should have string formatting/concatenation
assert "Count:" in llvm_ir
def test_event_handler_export():
"""Test that event handler functions are exported for JS to call."""
from ritz.llvm_codegen import generate_llvm_ir
from ritz.parser import parse_component
source = textwrap.dedent(
"""
@component
def Counter():
count = signal(0)
def increment():
count += 1
with Button(onclick=increment):
yield "Click"
"""
)
ir = parse_component(source)
llvm_ir = generate_llvm_ir(ir)
# Event handler should be exported as separate function
assert "define" in llvm_ir and "increment" in llvm_ir
# Should update the signal
assert "store" in llvm_ir or "set_count" in llvm_ir
def test_signal_update_in_handler():
"""Test that count += 1 generates proper signal update code."""
from ritz.llvm_codegen import generate_llvm_ir
from ritz.parser import parse_component
source = textwrap.dedent(
"""
@component
def Counter():
count = signal(0)
def increment():
count += 1
"""
)
ir = parse_component(source)
llvm_ir = generate_llvm_ir(ir)
# Should load current value, add 1, store back
# This is the signal update pattern
found_load = "load" in llvm_ir or "get_count" in llvm_ir
found_add = "add" in llvm_ir
found_store = "store" in llvm_ir or "set_count" in llvm_ir
assert found_load, "Should load signal value"
assert found_add, "Should add to value"
assert found_store, "Should store updated value"
def test_onclick_binding():
"""Test that onclick prop generates event binding code."""
from ritz.llvm_codegen import generate_llvm_ir
from ritz.parser import parse_component
source = textwrap.dedent(
"""
@component
def Counter():
def handleClick():
pass
with Button(onclick=handleClick):
yield "Click"
"""
)
ir = parse_component(source)
llvm_ir = generate_llvm_ir(ir)
# Should call js_bind_event or similar to register handler
assert "js_bind_event" in llvm_ir or "onclick" in llvm_ir