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!
This commit is contained in:
Aaron Sinclair committed 2025-11-15 01:50:40 -08:00
1 parent 316c090778
commit 635d466874
54 files changed
+4393

No files matched your search

+132
View File
@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ritz Framework Demo</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
background: #f5f5f5;
}
#app {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header {
border-bottom: 2px solid #333;
padding-bottom: 10px;
margin-bottom: 20px;
}
.status {
margin: 10px 0;
padding: 10px;
background: #f0f0f0;
border-radius: 4px;
font-family: monospace;
font-size: 12px;
}
.error {
background: #fee;
color: #c00;
}
</style>
</head>
<body>
<div class="header">
<h1>🎭 Ritz Framework</h1>
<p>Python components running in WebAssembly</p>
</div>
<div class="status" id="status">Loading...</div>
<div id="app"></div>
<script src="ritz.js"></script>
<script>
// Configuration
const COMPONENT_NAME = 'HelloWorld'; // Change this to your component name
const WASM_FILE = '../examples/HelloWorld.wasm';
async function loadComponent() {
const status = document.getElementById('status');
const appRoot = document.getElementById('app');
try {
// Initialize Ritz runtime
status.textContent = '⏳ Initializing Ritz runtime...';
const runtime = new RitzRuntime(appRoot);
// Load WASM module
status.textContent = `⏳ Loading ${WASM_FILE}...`;
const response = await fetch(WASM_FILE);
if (!response.ok) {
throw new Error(`Failed to fetch ${WASM_FILE}: ${response.status}`);
}
const wasmBytes = await response.arrayBuffer();
// Set up Module for Emscripten
window.Module = {
wasmBinary: wasmBytes,
ritz: runtime, // Provide runtime to WASM
onRuntimeInitialized: function() {
status.textContent = '⏳ Rendering component...';
try {
// Call the render function
const renderFn = Module[`_render_${COMPONENT_NAME}`];
if (!renderFn) {
throw new Error(`Component render_${COMPONENT_NAME} not found in WASM exports`);
}
// Render the component
Module.ccall(`render_${COMPONENT_NAME}`, null, [], []);
status.textContent = `✅ ${COMPONENT_NAME} rendered successfully!`;
status.className = 'status';
} catch (error) {
status.textContent = `❌ Render error: ${error.message}`;
status.className = 'status error';
console.error('Render error:', error);
}
},
print: function(text) {
console.log('[WASM]', text);
},
printErr: function(text) {
console.error('[WASM]', text);
}
};
// Load the Emscripten JS glue code
status.textContent = '⏳ Loading WASM runtime...';
const script = document.createElement('script');
script.src = WASM_FILE.replace('.wasm', '.js');
script.onerror = () => {
throw new Error('Failed to load WASM JS glue code');
};
document.body.appendChild(script);
} catch (error) {
status.textContent = `❌ Error: ${error.message}`;
status.className = 'status error';
console.error('Load error:', error);
}
}
// Start loading when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadComponent);
} else {
loadComponent();
}
</script>
</body>
</html>