Compare commits
5
Commits
0b77c2ff3c
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea7dadc5ae | ||
|
|
ddd2c03a9d | ||
|
|
999327172e | ||
|
|
a43b325755 | ||
|
|
7efa7786cf |
No files matched your search
@@ -0,0 +1,291 @@
|
||||
# Phase 4: Cleanup & Polish - Make It Sexy
|
||||
|
||||
## Critical Issues to Fix
|
||||
|
||||
### 1. Debug Console UI Jank
|
||||
|
||||
**Problem**: Page content shifts RIGHT when debug console opens (counter-intuitive)
|
||||
- Expected: Content shifts LEFT to make room
|
||||
- Current: Content margin-right increases, pushing content right
|
||||
|
||||
**Fix**:
|
||||
```css
|
||||
/* BEFORE (wrong) */
|
||||
body.debug-open #app {
|
||||
margin-right: 420px; /* Pushes content right - bad! */
|
||||
}
|
||||
|
||||
/* AFTER (correct) */
|
||||
body.debug-open #app {
|
||||
margin-right: 0; /* Keep in place or... */
|
||||
/* OR shift left to center remaining space */
|
||||
transform: translateX(-200px);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Un-Ritzy Toggle Implementation (CRITICAL)
|
||||
|
||||
**Current (WRONG)**: JS manipulates DOM classes, WASM calls JS bridge function
|
||||
```python
|
||||
# ANTI-PATTERN - calling JS to manipulate DOM!
|
||||
@external
|
||||
def js_toggle_debug_console():
|
||||
pass
|
||||
|
||||
def toggle():
|
||||
js_toggle_debug_console() # ❌ JS does the work
|
||||
```
|
||||
|
||||
**Correct (RITZY)**: Signal drives reactive class, declarative event binding
|
||||
```python
|
||||
# THE RITZ WAY - declarative and reactive!
|
||||
console_open = signal(False)
|
||||
|
||||
@event('keydown') # Declarative event registration
|
||||
def handle_keydown(event):
|
||||
if event.key == '`' and event.ctrlKey:
|
||||
console_open.value = not console_open.value
|
||||
|
||||
def toggle():
|
||||
"""Button click handler"""
|
||||
console_open.value = not console_open.value
|
||||
|
||||
# Reactive class binding (syntax TBD)
|
||||
with Div(class_=lambda: 'ritz-debug' if console_open else 'ritz-debug hidden'):
|
||||
# ...or maybe:
|
||||
# class_={'ritz-debug': True, 'hidden': not console_open}
|
||||
```
|
||||
|
||||
**What this requires**:
|
||||
|
||||
#### A. Reactive Class Binding
|
||||
- [ ] Parser support for lambda/function in class attribute
|
||||
- [ ] Codegen to track class dependencies on signals
|
||||
- [ ] Runtime bridge: `js_set_class(elem_id, class_string)`
|
||||
- [ ] Signal updates trigger class re-evaluation
|
||||
|
||||
#### B. Declarative Event System (`@event` decorator)
|
||||
- [ ] `@event('keydown')` decorator in syntax.py
|
||||
- [ ] Parser recognizes @event and extracts event type
|
||||
- [ ] Codegen emits global event registration (not element-specific)
|
||||
- [ ] Runtime: `js_register_global_event(event_type, handler_name)`
|
||||
- [ ] JS passes event object to WASM with properties
|
||||
- [ ] WASM can access `event.key`, `event.ctrlKey`, etc.
|
||||
|
||||
#### C. Event Object Bridge
|
||||
- [ ] Define event object structure (dict/struct in WASM)
|
||||
- [ ] JS serializes event properties to WASM memory
|
||||
- [ ] WASM handler receives event pointer
|
||||
- [ ] Helper functions: `event_get_key()`, `event_get_ctrl_key()`, etc.
|
||||
|
||||
## Framework Improvements Needed
|
||||
|
||||
### 3. Reactive Attribute System (Beyond Classes)
|
||||
|
||||
Make ALL attributes reactive:
|
||||
```python
|
||||
disabled = signal(False)
|
||||
with Button(disabled=disabled):
|
||||
yield "Click me"
|
||||
|
||||
# When disabled changes, button.disabled updates automatically
|
||||
```
|
||||
|
||||
**Implementation**:
|
||||
- [ ] Track attribute dependencies on signals
|
||||
- [ ] Generate update functions for each reactive attribute
|
||||
- [ ] Runtime bridge: `js_set_attribute_bool()`, `js_set_attribute_int()`, etc.
|
||||
|
||||
### 4. Testing Infrastructure
|
||||
|
||||
**Problem**: No way to test Ritz code without manual browser testing
|
||||
|
||||
**Solution A: Python Interpreter Mode**
|
||||
```bash
|
||||
# Run Ritz components in pure Python for testing
|
||||
python3 -m ritz.test examples/highfive/HighFive.py
|
||||
|
||||
# Output:
|
||||
# ✓ HighFive renders correctly
|
||||
# ✓ increment() updates signal
|
||||
# ✓ Button has correct onclick handler
|
||||
```
|
||||
|
||||
Implementation:
|
||||
- [ ] Create `ritz/test_runtime.py` - Python VNode interpreter
|
||||
- [ ] Execute component functions, track signal changes
|
||||
- [ ] Assert on rendered VNode tree structure
|
||||
- [ ] Mock event handlers and verify signal updates
|
||||
|
||||
**Solution B: LLVM Interpreter**
|
||||
```bash
|
||||
# Execute LLVM IR directly (no WASM step)
|
||||
python3 -m ritz.test --llvm examples/highfive/HighFive.py
|
||||
|
||||
# Uses: lli (LLVM interpreter) or llvm-python bindings
|
||||
```
|
||||
|
||||
Implementation:
|
||||
- [ ] Mock JS bridge functions in C/LLVM
|
||||
- [ ] Execute LLVM IR with test assertions
|
||||
- [ ] Verify correct function calls and signal state
|
||||
|
||||
**Solution C: Headless Browser Tests**
|
||||
```bash
|
||||
# Full integration tests with Playwright/Puppeteer
|
||||
python3 -m ritz.test --browser examples/highfive/HighFive.py
|
||||
```
|
||||
|
||||
Implementation:
|
||||
- [ ] Playwright/Puppeteer test harness
|
||||
- [ ] Load WASM in headless browser
|
||||
- [ ] Verify DOM structure and interactions
|
||||
- [ ] Snapshot testing for visual regression
|
||||
|
||||
### 5. Test Coverage Requirements
|
||||
|
||||
**Unit Tests Needed**:
|
||||
- [ ] Parser: All AST node types
|
||||
- [ ] Parser: @component, @external, @event decorators
|
||||
- [ ] Codegen: Signal generation
|
||||
- [ ] Codegen: Event handler generation
|
||||
- [ ] Codegen: Reactive attributes
|
||||
- [ ] Codegen: Conditional rendering
|
||||
- [ ] Runtime: Signal updates
|
||||
- [ ] Runtime: Event dispatch
|
||||
- [ ] Runtime: DOM mutations
|
||||
|
||||
**Integration Tests Needed**:
|
||||
- [ ] Counter component (signals + events)
|
||||
- [ ] Tabs component (match/case conditionals)
|
||||
- [ ] Forms (input binding, validation)
|
||||
- [ ] Debug console (global events, reactive classes)
|
||||
|
||||
**Snapshot Tests**:
|
||||
- [ ] Generated LLVM IR (detect codegen regressions)
|
||||
- [ ] Generated WASM size (detect bloat)
|
||||
- [ ] Rendered HTML structure
|
||||
|
||||
## Code Quality & Consistency
|
||||
|
||||
### 6. Example App Consistency
|
||||
|
||||
Update all examples to use:
|
||||
- [ ] RitzMain() convention
|
||||
- [ ] Main.wasm output
|
||||
- [ ] template.html (no custom HTML)
|
||||
- [ ] Shared styles.css
|
||||
- [ ] Component-specific styles in <style> tag only
|
||||
- [ ] @external for any JS bridge functions
|
||||
- [ ] Consistent naming conventions
|
||||
|
||||
Examples to update:
|
||||
- [ ] `examples/tabs/Tabs.py` - Add RitzMain
|
||||
- [ ] `examples/textinput/TextInput.py` - Add RitzMain
|
||||
- [ ] `examples/toggle/Toggle.py` - Add RitzMain
|
||||
- [ ] All: Copy template.html
|
||||
- [ ] All: Remove duplicate styles
|
||||
|
||||
### 7. Documentation
|
||||
|
||||
- [ ] Update CLAUDE.md with @external decorator
|
||||
- [ ] Update CLAUDE.md with @event decorator (when implemented)
|
||||
- [ ] Document reactive attribute syntax
|
||||
- [ ] Testing guide (how to run tests)
|
||||
- [ ] Examples README with descriptions
|
||||
|
||||
## Architecture Questions to Resolve
|
||||
|
||||
### 8. Reactive Class Syntax
|
||||
|
||||
**Option 1: Lambda (Python-like)**
|
||||
```python
|
||||
with Div(class_=lambda: 'active' if is_active else 'inactive'):
|
||||
```
|
||||
Pros: Pythonic, clear dependency
|
||||
Cons: Lambdas need special handling in parser
|
||||
|
||||
**Option 2: Template strings (f-string-like)**
|
||||
```python
|
||||
with Div(class_=f"menu {('active' if is_open else '')}"):
|
||||
```
|
||||
Pros: Familiar syntax
|
||||
Cons: Hard to track dependencies, less clear
|
||||
|
||||
**Option 3: Dict with signal values**
|
||||
```python
|
||||
with Div(class_={'menu': True, 'active': is_open, 'disabled': is_disabled}):
|
||||
```
|
||||
Pros: Very clear dependencies, React-like
|
||||
Cons: Different from HTML class syntax
|
||||
|
||||
**Decision needed**: Which syntax to use?
|
||||
|
||||
### 9. Event Object Design
|
||||
|
||||
How should event handlers access event properties?
|
||||
|
||||
**Option 1: Parameter object (dict-like)**
|
||||
```python
|
||||
@event('keydown')
|
||||
def handle_keydown(event):
|
||||
if event['key'] == '`' and event['ctrlKey']:
|
||||
...
|
||||
```
|
||||
|
||||
**Option 2: External functions**
|
||||
```python
|
||||
@external
|
||||
def event_get_key() -> str:
|
||||
pass
|
||||
|
||||
@event('keydown')
|
||||
def handle_keydown():
|
||||
if event_get_key() == '`' and event_get_ctrl_key():
|
||||
...
|
||||
```
|
||||
|
||||
**Option 3: Global event context**
|
||||
```python
|
||||
@event('keydown')
|
||||
def handle_keydown():
|
||||
if current_event.key == '`' and current_event.ctrlKey:
|
||||
...
|
||||
```
|
||||
|
||||
**Decision needed**: Which approach?
|
||||
|
||||
## Success Criteria
|
||||
|
||||
Before moving to Phase 5 (was Phase 4), we must have:
|
||||
|
||||
- ✅ Debug console opens/closes with proper CSS transitions
|
||||
- ✅ Page content shifts LEFT (or stays in place) when console opens
|
||||
- ✅ Console toggle is 100% Ritz (signal + reactive class)
|
||||
- ✅ Ctrl+` registered declaratively with @event decorator
|
||||
- ✅ Event object accessible in WASM handlers
|
||||
- ✅ Test infrastructure in place (at least Python mode)
|
||||
- ✅ All examples updated to use RitzMain convention
|
||||
- ✅ Test coverage >70% for parser and codegen
|
||||
- ✅ Documentation updated
|
||||
- ✅ No JS DOM manipulation (except initial setup)
|
||||
|
||||
## Timeline Estimate
|
||||
|
||||
- **Reactive classes**: 2-3 hours (parser + codegen + runtime)
|
||||
- **@event decorator**: 3-4 hours (parser + codegen + runtime + event bridge)
|
||||
- **Event object bridge**: 2-3 hours (memory layout + JS serialization)
|
||||
- **Python test mode**: 4-5 hours (VNode interpreter + test harness)
|
||||
- **Unit tests**: 3-4 hours (parser, codegen tests)
|
||||
- **Example cleanup**: 1-2 hours (update 4 examples)
|
||||
- **Debug console refactor**: 1-2 hours (use reactive classes + @event)
|
||||
|
||||
**Total**: ~20-25 hours of focused work
|
||||
|
||||
## Notes
|
||||
|
||||
- The @external decorator is **beautiful** and should be the template
|
||||
- Everything should feel declarative and Pythonic
|
||||
- WASM should handle logic, JS should only bridge
|
||||
- Tests are critical - no more manual browser testing for every change
|
||||
- Keep examples simple and pedagogical
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 315 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 327 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
@@ -1,96 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>HighFive Counter - Ritz Framework</title>
|
||||
<link rel="stylesheet" href="../runtime/ritz-debug.css">
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
#app {
|
||||
max-width: 800px;
|
||||
margin: 50px auto;
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
.high-five-app {
|
||||
text-align: center;
|
||||
}
|
||||
.high-five-app p {
|
||||
font-size: 18px;
|
||||
margin: 15px 0;
|
||||
color: #333;
|
||||
}
|
||||
.high-five-app .count {
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
color: #0066cc;
|
||||
margin: 30px 0;
|
||||
}
|
||||
.high-five-app button {
|
||||
font-size: 18px;
|
||||
padding: 15px 40px;
|
||||
background: #0066cc;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.high-five-app button:hover {
|
||||
background: #0052a3;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.high-five-app button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<div id="ritz-debug" class="ritz-debug hidden">
|
||||
<div class="debug-header">
|
||||
<h3>🎭 Ritz Debug</h3>
|
||||
<button onclick="ritzDebug.toggle()">×</button>
|
||||
</div>
|
||||
<div id="status" class="status">Loading...</div>
|
||||
<div class="debug-content">
|
||||
<div class="debug-section">
|
||||
<h4>Component</h4>
|
||||
<pre>HighFive.py (Reactive Signals)</pre>
|
||||
</div>
|
||||
<div class="debug-section">
|
||||
<h4>Features</h4>
|
||||
<pre>✓ Signal in WASM memory
|
||||
✓ Event handlers
|
||||
✓ Reactive updates</pre>
|
||||
</div>
|
||||
<div class="debug-section">
|
||||
<h4>Console Commands</h4>
|
||||
<pre>ritzRuntime.getLog()
|
||||
ritzInstance.exports.get_high_fives()
|
||||
ritzInstance.exports.increment()</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../runtime/ritz-runtime.js"></script>
|
||||
<script src="../runtime/ritz-debug.js"></script>
|
||||
<script>
|
||||
// Wait for DOM to be ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new RitzLoader().load('HighFive.wasm', 'HighFive');
|
||||
});
|
||||
} else {
|
||||
new RitzLoader().load('HighFive.wasm', 'HighFive');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,75 +0,0 @@
|
||||
; Generated by Ritz Framework with Signals
|
||||
; Component: HighFive
|
||||
|
||||
; String constants
|
||||
@.str.0 = private unnamed_addr constant [4 x i8] c"div\00"
|
||||
@.str.1 = private unnamed_addr constant [6 x i8] c"class\00"
|
||||
@.str.2 = private unnamed_addr constant [14 x i8] c"high-five-app\00"
|
||||
@.str.3 = private unnamed_addr constant [2 x i8] c"p\00"
|
||||
@.str.4 = private unnamed_addr constant [25 x i8] c"Give me a high five! ✋\00"
|
||||
@.str.5 = private unnamed_addr constant [6 x i8] c"count\00"
|
||||
@.str.6 = private unnamed_addr constant [2 x i8] c"0\00"
|
||||
@.str.7 = private unnamed_addr constant [7 x i8] c"button\00"
|
||||
@.str.8 = private unnamed_addr constant [6 x i8] c"click\00"
|
||||
@.str.9 = private unnamed_addr constant [10 x i8] c"increment\00"
|
||||
@.str.10 = private unnamed_addr constant [11 x i8] c"High Five!\00"
|
||||
|
||||
; Signal storage (reactive state in WASM memory)
|
||||
@signal_high_fives = global i32 0
|
||||
|
||||
; JavaScript bridge function declarations
|
||||
declare i32 @js_create_element(i8*)
|
||||
declare i32 @js_create_text(i8*)
|
||||
declare void @js_append_child(i32, i32)
|
||||
declare void @js_set_attribute(i32, i8*, i8*)
|
||||
declare void @js_set_root(i32)
|
||||
declare void @js_bind_event(i32, i8*, i8*)
|
||||
declare void @js_update_text_content(i32, i8*)
|
||||
|
||||
; Signal helper functions
|
||||
define i32 @get_high_fives() {
|
||||
entry:
|
||||
%val = load i32, i32* @signal_high_fives
|
||||
ret i32 %val
|
||||
}
|
||||
|
||||
define void @set_high_fives(i32 %newval) {
|
||||
entry:
|
||||
store i32 %newval, i32* @signal_high_fives
|
||||
; TODO: Trigger DOM update callback
|
||||
ret void
|
||||
}
|
||||
|
||||
; Event handler: increment
|
||||
define void @increment() {
|
||||
entry:
|
||||
%var0 = load i32, i32* @signal_high_fives
|
||||
%var1 = add i32 %var0, 1
|
||||
store i32 %var1, i32* @signal_high_fives
|
||||
; TODO: Update DOM nodes bound to high_fives
|
||||
ret void
|
||||
}
|
||||
|
||||
; Component render function
|
||||
define void @render_HighFive() {
|
||||
entry:
|
||||
%var2 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.0, i32 0, i32 0))
|
||||
call void @js_set_attribute(i32 %var2, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str.2, i32 0, i32 0))
|
||||
%var3 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.3, i32 0, i32 0))
|
||||
%var4 = call i32 @js_create_text(i8* getelementptr inbounds ([25 x i8], [25 x i8]* @.str.4, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var3, i32 %var4)
|
||||
call void @js_append_child(i32 %var2, i32 %var3)
|
||||
%var5 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.3, i32 0, i32 0))
|
||||
call void @js_set_attribute(i32 %var5, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.5, i32 0, i32 0))
|
||||
; TODO: Format f-string with signal high_fives
|
||||
%var6 = call i32 @js_create_text(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.6, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var5, i32 %var6)
|
||||
call void @js_append_child(i32 %var2, i32 %var5)
|
||||
%var7 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.7, i32 0, i32 0))
|
||||
call void @js_bind_event(i32 %var7, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.8, i32 0, i32 0), i8* getelementptr inbounds ([10 x i8], [10 x i8]* @.str.9, i32 0, i32 0))
|
||||
%var8 = call i32 @js_create_text(i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.10, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var7, i32 %var8)
|
||||
call void @js_append_child(i32 %var2, i32 %var7)
|
||||
call void @js_set_root(i32 %var2)
|
||||
ret void
|
||||
}
|
||||
Binary file not shown.
@@ -1,314 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>HighFive Counter - Reactive Signals in WASM</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.header {
|
||||
border-bottom: 2px solid #333;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.pipeline {
|
||||
margin: 20px 0;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.8;
|
||||
background: #fff;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.status {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background: #e8f4f8;
|
||||
border-left: 4px solid #0066cc;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
.success {
|
||||
background: #efe;
|
||||
border-left-color: #0a0;
|
||||
color: #060;
|
||||
}
|
||||
.error {
|
||||
background: #fee;
|
||||
border-left-color: #c00;
|
||||
color: #c00;
|
||||
}
|
||||
#app {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
min-height: 150px;
|
||||
}
|
||||
/* Component styles */
|
||||
.high-five-app {
|
||||
text-align: center;
|
||||
}
|
||||
.high-five-app p {
|
||||
font-size: 18px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
.high-five-app .count {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: #0066cc;
|
||||
margin: 25px 0;
|
||||
}
|
||||
.high-five-app button {
|
||||
font-size: 18px;
|
||||
padding: 15px 40px;
|
||||
background: #0066cc;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.high-five-app button:hover {
|
||||
background: #0052a3;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.high-five-app button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>🎭 Ritz Framework - HighFive Counter</h1>
|
||||
<p><strong>Reactive Signals in WebAssembly</strong></p>
|
||||
<div class="pipeline">
|
||||
✨ Signal stored in WASM memory<br>
|
||||
✨ Event handler exported from WASM<br>
|
||||
✨ Click → WASM → Signal update → DOM update<br>
|
||||
<br>
|
||||
Pipeline: HighFive.py → LLVM IR → clang → WASM
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status" id="status">⏳ Initializing...</div>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<script>
|
||||
// Enhanced Ritz Runtime with Signal and Event support
|
||||
class RitzRuntime {
|
||||
constructor(rootElement) {
|
||||
this.rootElement = rootElement;
|
||||
this.nodes = new Map();
|
||||
this.nextNodeId = 1;
|
||||
this.log = [];
|
||||
this.wasmInstance = null;
|
||||
this.eventHandlers = new Map(); // handler_name -> wasm function
|
||||
}
|
||||
|
||||
js_create_element(tagPtr) {
|
||||
const tag = this.readString(tagPtr);
|
||||
const element = document.createElement(tag);
|
||||
const id = this.nextNodeId++;
|
||||
this.nodes.set(id, element);
|
||||
this.log.push(`createElement("${tag}") = ${id}`);
|
||||
return id;
|
||||
}
|
||||
|
||||
js_create_text(textPtr) {
|
||||
const text = this.readString(textPtr);
|
||||
const textNode = document.createTextNode(text);
|
||||
const id = this.nextNodeId++;
|
||||
this.nodes.set(id, textNode);
|
||||
this.log.push(`createTextNode("${text}") = ${id}`);
|
||||
return id;
|
||||
}
|
||||
|
||||
js_append_child(parentId, childId) {
|
||||
const parent = this.nodes.get(parentId);
|
||||
const child = this.nodes.get(childId);
|
||||
if (parent && child) {
|
||||
parent.appendChild(child);
|
||||
this.log.push(`appendChild(${parentId}, ${childId})`);
|
||||
}
|
||||
}
|
||||
|
||||
js_set_attribute(elemId, namePtr, valuePtr) {
|
||||
const element = this.nodes.get(elemId);
|
||||
const name = this.readString(namePtr);
|
||||
const value = this.readString(valuePtr);
|
||||
if (element) {
|
||||
if (name === 'class') {
|
||||
element.className = value;
|
||||
} else {
|
||||
element.setAttribute(name, value);
|
||||
}
|
||||
this.log.push(`setAttribute(${elemId}, "${name}", "${value}")`);
|
||||
}
|
||||
}
|
||||
|
||||
js_bind_event(elemId, eventNamePtr, handlerNamePtr) {
|
||||
const element = this.nodes.get(elemId);
|
||||
const eventName = this.readString(eventNamePtr);
|
||||
const handlerName = this.readString(handlerNamePtr);
|
||||
|
||||
if (element && this.wasmInstance) {
|
||||
const handler = this.wasmInstance.exports[handlerName];
|
||||
if (handler) {
|
||||
element.addEventListener(eventName, () => {
|
||||
console.log(`[Event] ${eventName} → calling WASM ${handlerName}()`);
|
||||
handler();
|
||||
// After handler runs, read signal and update DOM
|
||||
this.updateSignalDisplay();
|
||||
});
|
||||
this.log.push(`bindEvent(${elemId}, "${eventName}", "${handlerName}")`);
|
||||
} else {
|
||||
console.error(`Handler ${handlerName} not found in WASM exports`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
js_set_root(elemId) {
|
||||
const element = this.nodes.get(elemId);
|
||||
if (element) {
|
||||
this.rootElement.innerHTML = '';
|
||||
this.rootElement.appendChild(element);
|
||||
this.log.push(`setRoot(${elemId})`);
|
||||
}
|
||||
}
|
||||
|
||||
js_update_text_content(nodeId, textPtr) {
|
||||
const node = this.nodes.get(nodeId);
|
||||
const text = this.readString(textPtr);
|
||||
if (node) {
|
||||
node.textContent = text;
|
||||
this.log.push(`updateTextContent(${nodeId}, "${text}")`);
|
||||
}
|
||||
}
|
||||
|
||||
updateSignalDisplay() {
|
||||
// Call WASM getter function to read signal value
|
||||
if (!this.wasmInstance || !this.wasmInstance.exports.get_high_fives) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Call the exported getter function
|
||||
const signalValue = this.wasmInstance.exports.get_high_fives();
|
||||
|
||||
console.log(`[Signal] high_fives = ${signalValue}`);
|
||||
|
||||
// Update DOM elements bound to this signal
|
||||
// TODO: Track signal-to-DOM bindings during render
|
||||
const countElements = document.querySelectorAll('.count');
|
||||
countElements.forEach(el => {
|
||||
el.textContent = `High fives: ${signalValue}`;
|
||||
});
|
||||
}
|
||||
|
||||
readString(ptr) {
|
||||
if (!this.memory) return '';
|
||||
const bytes = new Uint8Array(this.memory.buffer, ptr);
|
||||
let length = 0;
|
||||
while (bytes[length] !== 0) length++;
|
||||
return new TextDecoder().decode(bytes.slice(0, length));
|
||||
}
|
||||
|
||||
setMemory(memory) {
|
||||
this.memory = memory;
|
||||
}
|
||||
|
||||
setWasmInstance(instance) {
|
||||
this.wasmInstance = instance;
|
||||
}
|
||||
|
||||
getLog() {
|
||||
return this.log;
|
||||
}
|
||||
}
|
||||
|
||||
// Global runtime for debugging
|
||||
let runtime = null;
|
||||
let wasmInstance = null;
|
||||
|
||||
// Load and run WASM
|
||||
async function main() {
|
||||
const status = document.getElementById('status');
|
||||
const appRoot = document.getElementById('app');
|
||||
|
||||
try {
|
||||
status.textContent = '⏳ Creating runtime...';
|
||||
runtime = new RitzRuntime(appRoot);
|
||||
|
||||
status.textContent = '⏳ Loading HighFive.wasm...';
|
||||
const response = await fetch('HighFive.wasm');
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load WASM: ${response.status}`);
|
||||
}
|
||||
|
||||
const wasmBytes = await response.arrayBuffer();
|
||||
|
||||
status.textContent = '⏳ Instantiating WASM module...';
|
||||
|
||||
// Create imports for WASM
|
||||
const imports = {
|
||||
env: {
|
||||
js_create_element: (ptr) => runtime.js_create_element(ptr),
|
||||
js_create_text: (ptr) => runtime.js_create_text(ptr),
|
||||
js_append_child: (p, c) => runtime.js_append_child(p, c),
|
||||
js_set_attribute: (e, n, v) => runtime.js_set_attribute(e, n, v),
|
||||
js_set_root: (e) => runtime.js_set_root(e),
|
||||
js_bind_event: (e, n, h) => runtime.js_bind_event(e, n, h),
|
||||
js_update_text_content: (n, t) => runtime.js_update_text_content(n, t),
|
||||
}
|
||||
};
|
||||
|
||||
const { instance } = await WebAssembly.instantiate(wasmBytes, imports);
|
||||
wasmInstance = instance;
|
||||
|
||||
// Give runtime access to WASM
|
||||
runtime.setMemory(instance.exports.memory);
|
||||
runtime.setWasmInstance(instance);
|
||||
|
||||
status.textContent = '⏳ Rendering component...';
|
||||
|
||||
// Call the render function
|
||||
instance.exports.render_HighFive();
|
||||
|
||||
// Initial signal display update
|
||||
runtime.updateSignalDisplay();
|
||||
|
||||
status.textContent = '✅ HighFive rendered! Click the button to test reactivity.';
|
||||
status.className = 'status success';
|
||||
|
||||
// Log the operations
|
||||
console.log('Runtime operations:', runtime.getLog());
|
||||
console.log('🎉 Reactive signals working in WASM!');
|
||||
console.log('Debug commands:');
|
||||
console.log(' runtime.getLog() - View operations');
|
||||
console.log(' wasmInstance.exports - View WASM exports');
|
||||
console.log(' wasmInstance.exports.signal_high_fives.value - Read signal value');
|
||||
|
||||
} catch (error) {
|
||||
status.textContent = `❌ Error: ${error.message}`;
|
||||
status.className = 'status error';
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Run when page loads
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', main);
|
||||
} else {
|
||||
main();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>HighFive Counter - Ritz Framework</title>
|
||||
<link rel="stylesheet" href="../runtime/styles.css">
|
||||
<style>
|
||||
/* Component-specific styles */
|
||||
.high-five-app {
|
||||
text-align: center;
|
||||
}
|
||||
.high-five-app p {
|
||||
font-size: 18px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
.high-five-app .count {
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
color: #0066cc;
|
||||
margin: 30px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="status"></div>
|
||||
|
||||
<script src="../runtime/ritz-runtime.js"></script>
|
||||
<script>
|
||||
// Load Main.wasm with debug console enabled
|
||||
// The debug console is itself written in Ritz!
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new RitzLoader({ debug: true }).load('Main.wasm');
|
||||
});
|
||||
} else {
|
||||
new RitzLoader({ debug: true }).load('Main.wasm');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,104 @@
|
||||
; Generated by Ritz Framework with Signals
|
||||
; Components: HighFive
|
||||
|
||||
; String constants
|
||||
@.str.0 = private unnamed_addr constant [13 x i8] c"High fives: \00"
|
||||
@.str.1 = private unnamed_addr constant [4 x i8] c"div\00"
|
||||
@.str.2 = private unnamed_addr constant [6 x i8] c"class\00"
|
||||
@.str.3 = private unnamed_addr constant [14 x i8] c"high-five-app\00"
|
||||
@.str.4 = private unnamed_addr constant [2 x i8] c"p\00"
|
||||
@.str.5 = private unnamed_addr constant [25 x i8] c"Give me a high five! ✋\00"
|
||||
@.str.6 = private unnamed_addr constant [6 x i8] c"count\00"
|
||||
@.str.7 = private unnamed_addr constant [7 x i8] c"button\00"
|
||||
@.str.8 = private unnamed_addr constant [6 x i8] c"click\00"
|
||||
@.str.9 = private unnamed_addr constant [10 x i8] c"increment\00"
|
||||
@.str.10 = private unnamed_addr constant [11 x i8] c"High Five!\00"
|
||||
|
||||
; JavaScript bridge function declarations
|
||||
declare i32 @js_create_element(i8*)
|
||||
declare i32 @js_create_text(i8*)
|
||||
declare i32 @js_create_text_with_int(i8*, i32)
|
||||
declare void @js_append_child(i32, i32)
|
||||
declare void @js_set_attribute(i32, i8*, i8*)
|
||||
declare void @js_set_root(i32)
|
||||
declare void @js_bind_event(i32, i8*, i8*)
|
||||
declare void @js_update_text_content(i32, i8*)
|
||||
declare void @js_update_text_with_int(i32, i8*, i32)
|
||||
declare i32 @js_create_marker()
|
||||
declare void @js_replace_at_marker(i32, i32, i32)
|
||||
|
||||
; Signal storage for HighFive (reactive state in WASM memory)
|
||||
@signal_HighFive_high_fives = global i32 0
|
||||
|
||||
; Reactive text node storage
|
||||
@reactive_text_0_node = global i32 0
|
||||
|
||||
; Signal helper functions
|
||||
define i32 @get_high_fives() {
|
||||
entry:
|
||||
%val = load i32, i32* @signal_HighFive_high_fives
|
||||
ret i32 %val
|
||||
}
|
||||
|
||||
define void @set_high_fives(i32 %newval) {
|
||||
entry:
|
||||
store i32 %newval, i32* @signal_HighFive_high_fives
|
||||
; TODO: Trigger DOM update callback
|
||||
ret void
|
||||
}
|
||||
|
||||
; Event handler: increment
|
||||
define void @increment() {
|
||||
entry:
|
||||
%var0 = load i32, i32* @signal_HighFive_high_fives
|
||||
%var1 = add i32 %var0, 1
|
||||
store i32 %var1, i32* @signal_HighFive_high_fives
|
||||
%var2 = load i32, i32* @reactive_text_0_node
|
||||
%var3 = getelementptr inbounds [13 x i8], [13 x i8]* @.str.0, i32 0, i32 0
|
||||
%var4 = load i32, i32* @signal_HighFive_high_fives
|
||||
call void @js_update_text_with_int(i32 %var2, i8* %var3, i32 %var4)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Component render function
|
||||
define i32 @render_HighFive() {
|
||||
entry:
|
||||
%var6 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.1, i32 0, i32 0
|
||||
%var5 = call i32 @js_create_element(i8* %var6)
|
||||
%var7 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.2, i32 0, i32 0
|
||||
%var8 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.3, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var5, i8* %var7, i8* %var8)
|
||||
%var10 = getelementptr inbounds [2 x i8], [2 x i8]* @.str.4, i32 0, i32 0
|
||||
%var9 = call i32 @js_create_element(i8* %var10)
|
||||
%var11 = call i32 @js_create_text(i8* getelementptr inbounds ([25 x i8], [25 x i8]* @.str.5, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var9, i32 %var11)
|
||||
call void @js_append_child(i32 %var5, i32 %var9)
|
||||
%var13 = getelementptr inbounds [2 x i8], [2 x i8]* @.str.4, i32 0, i32 0
|
||||
%var12 = call i32 @js_create_element(i8* %var13)
|
||||
%var14 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.2, i32 0, i32 0
|
||||
%var15 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.6, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var12, i8* %var14, i8* %var15)
|
||||
%var16 = getelementptr inbounds [13 x i8], [13 x i8]* @.str.0, i32 0, i32 0
|
||||
%var17 = load i32, i32* @signal_HighFive_high_fives
|
||||
%var18 = call i32 @js_create_text_with_int(i8* %var16, i32 %var17)
|
||||
store i32 %var18, i32* @reactive_text_0_node
|
||||
call void @js_append_child(i32 %var12, i32 %var18)
|
||||
call void @js_append_child(i32 %var5, i32 %var12)
|
||||
%var20 = getelementptr inbounds [7 x i8], [7 x i8]* @.str.7, i32 0, i32 0
|
||||
%var19 = call i32 @js_create_element(i8* %var20)
|
||||
%var21 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.8, i32 0, i32 0
|
||||
%var22 = getelementptr inbounds [10 x i8], [10 x i8]* @.str.9, i32 0, i32 0
|
||||
call void @js_bind_event(i32 %var19, i8* %var21, i8* %var22)
|
||||
%var23 = call i32 @js_create_text(i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.10, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var19, i32 %var23)
|
||||
call void @js_append_child(i32 %var5, i32 %var19)
|
||||
ret i32 %var5
|
||||
}
|
||||
|
||||
; Main entry point
|
||||
define void @main() {
|
||||
entry:
|
||||
%root = call i32 @render_HighFive()
|
||||
call void @js_set_root(i32 %root)
|
||||
ret void
|
||||
}
|
||||
@@ -24,3 +24,9 @@ def HighFive():
|
||||
|
||||
with Button(onclick=increment):
|
||||
yield "High Five!"
|
||||
|
||||
|
||||
@component
|
||||
def RitzMain():
|
||||
"""Entry point - required for all Ritz applications."""
|
||||
yield HighFive()
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,114 @@
|
||||
; Generated by Ritz Framework with Signals
|
||||
; Components: HighFive, RitzMain
|
||||
|
||||
; String constants
|
||||
@.str.0 = private unnamed_addr constant [13 x i8] c"High fives: \00"
|
||||
@.str.1 = private unnamed_addr constant [4 x i8] c"div\00"
|
||||
@.str.2 = private unnamed_addr constant [6 x i8] c"class\00"
|
||||
@.str.3 = private unnamed_addr constant [14 x i8] c"high-five-app\00"
|
||||
@.str.4 = private unnamed_addr constant [2 x i8] c"p\00"
|
||||
@.str.5 = private unnamed_addr constant [25 x i8] c"Give me a high five! ✋\00"
|
||||
@.str.6 = private unnamed_addr constant [6 x i8] c"count\00"
|
||||
@.str.7 = private unnamed_addr constant [7 x i8] c"button\00"
|
||||
@.str.8 = private unnamed_addr constant [6 x i8] c"click\00"
|
||||
@.str.9 = private unnamed_addr constant [10 x i8] c"increment\00"
|
||||
@.str.10 = private unnamed_addr constant [11 x i8] c"High Five!\00"
|
||||
|
||||
; JavaScript bridge function declarations
|
||||
declare i32 @js_create_element(i8*)
|
||||
declare i32 @js_create_text(i8*)
|
||||
declare i32 @js_create_text_with_int(i8*, i32)
|
||||
declare void @js_append_child(i32, i32)
|
||||
declare void @js_set_attribute(i32, i8*, i8*)
|
||||
declare void @js_set_root(i32)
|
||||
declare void @js_bind_event(i32, i8*, i8*)
|
||||
declare void @js_update_text_content(i32, i8*)
|
||||
declare void @js_update_text_with_int(i32, i8*, i32)
|
||||
declare i32 @js_create_marker()
|
||||
declare void @js_replace_at_marker(i32, i32, i32)
|
||||
|
||||
; Signal storage for HighFive (reactive state in WASM memory)
|
||||
@signal_HighFive_high_fives = global i32 0
|
||||
|
||||
; Reactive text node storage for HighFive
|
||||
@reactive_text_HighFive_0_node = global i32 0
|
||||
|
||||
; Signal helper functions
|
||||
define i32 @get_high_fives() {
|
||||
entry:
|
||||
%val = load i32, i32* @signal_HighFive_high_fives
|
||||
ret i32 %val
|
||||
}
|
||||
|
||||
define void @set_high_fives(i32 %newval) {
|
||||
entry:
|
||||
store i32 %newval, i32* @signal_HighFive_high_fives
|
||||
; TODO: Trigger DOM update callback
|
||||
ret void
|
||||
}
|
||||
|
||||
; Event handler: increment
|
||||
define void @increment() {
|
||||
entry:
|
||||
%var0 = load i32, i32* @signal_HighFive_high_fives
|
||||
%var1 = add i32 %var0, 1
|
||||
store i32 %var1, i32* @signal_HighFive_high_fives
|
||||
%var2 = load i32, i32* @reactive_text_HighFive_0_node
|
||||
%var3 = getelementptr inbounds [13 x i8], [13 x i8]* @.str.0, i32 0, i32 0
|
||||
%var4 = load i32, i32* @signal_HighFive_high_fives
|
||||
call void @js_update_text_with_int(i32 %var2, i8* %var3, i32 %var4)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Component render function
|
||||
define i32 @render_HighFive() {
|
||||
entry:
|
||||
%var6 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.1, i32 0, i32 0
|
||||
%var5 = call i32 @js_create_element(i8* %var6)
|
||||
%var7 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.2, i32 0, i32 0
|
||||
%var8 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.3, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var5, i8* %var7, i8* %var8)
|
||||
%var10 = getelementptr inbounds [2 x i8], [2 x i8]* @.str.4, i32 0, i32 0
|
||||
%var9 = call i32 @js_create_element(i8* %var10)
|
||||
%var11 = call i32 @js_create_text(i8* getelementptr inbounds ([25 x i8], [25 x i8]* @.str.5, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var9, i32 %var11)
|
||||
call void @js_append_child(i32 %var5, i32 %var9)
|
||||
%var13 = getelementptr inbounds [2 x i8], [2 x i8]* @.str.4, i32 0, i32 0
|
||||
%var12 = call i32 @js_create_element(i8* %var13)
|
||||
%var14 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.2, i32 0, i32 0
|
||||
%var15 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.6, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var12, i8* %var14, i8* %var15)
|
||||
%var16 = getelementptr inbounds [13 x i8], [13 x i8]* @.str.0, i32 0, i32 0
|
||||
%var17 = load i32, i32* @signal_HighFive_high_fives
|
||||
%var18 = call i32 @js_create_text_with_int(i8* %var16, i32 %var17)
|
||||
store i32 %var18, i32* @reactive_text_HighFive_0_node
|
||||
call void @js_append_child(i32 %var12, i32 %var18)
|
||||
call void @js_append_child(i32 %var5, i32 %var12)
|
||||
%var20 = getelementptr inbounds [7 x i8], [7 x i8]* @.str.7, i32 0, i32 0
|
||||
%var19 = call i32 @js_create_element(i8* %var20)
|
||||
%var21 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.8, i32 0, i32 0
|
||||
%var22 = getelementptr inbounds [10 x i8], [10 x i8]* @.str.9, i32 0, i32 0
|
||||
call void @js_bind_event(i32 %var19, i8* %var21, i8* %var22)
|
||||
%var23 = call i32 @js_create_text(i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.10, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var19, i32 %var23)
|
||||
call void @js_append_child(i32 %var5, i32 %var19)
|
||||
ret i32 %var5
|
||||
}
|
||||
|
||||
; Reactive text node storage for RitzMain
|
||||
@reactive_text_RitzMain_0_node = global i32 0
|
||||
|
||||
; Component render function
|
||||
define i32 @render_RitzMain() {
|
||||
entry:
|
||||
%var24 = call i32 @render_HighFive()
|
||||
ret i32 %var24
|
||||
}
|
||||
|
||||
; Main entry point
|
||||
define void @main() {
|
||||
entry:
|
||||
%root = call i32 @render_RitzMain()
|
||||
call void @js_set_root(i32 %root)
|
||||
ret void
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,227 @@
|
||||
; Generated by Ritz Framework with Signals
|
||||
; Components: DebugConsole, RitzMain
|
||||
|
||||
; String constants
|
||||
@.str.0 = private unnamed_addr constant [4 x i8] c"div\00"
|
||||
@.str.1 = private unnamed_addr constant [6 x i8] c"class\00"
|
||||
@.str.2 = private unnamed_addr constant [18 x i8] c"ritz-debug hidden\00"
|
||||
@.str.3 = private unnamed_addr constant [3 x i8] c"id\00"
|
||||
@.str.4 = private unnamed_addr constant [11 x i8] c"ritz-debug\00"
|
||||
@.str.5 = private unnamed_addr constant [13 x i8] c"debug-header\00"
|
||||
@.str.6 = private unnamed_addr constant [3 x i8] c"h3\00"
|
||||
@.str.7 = private unnamed_addr constant [16 x i8] c"🎭 Ritz Debug\00"
|
||||
@.str.8 = private unnamed_addr constant [7 x i8] c"button\00"
|
||||
@.str.9 = private unnamed_addr constant [6 x i8] c"click\00"
|
||||
@.str.10 = private unnamed_addr constant [7 x i8] c"toggle\00"
|
||||
@.str.11 = private unnamed_addr constant [3 x i8] c"×\00"
|
||||
@.str.12 = private unnamed_addr constant [15 x i8] c"status success\00"
|
||||
@.str.13 = private unnamed_addr constant [7 x i8] c"status\00"
|
||||
@.str.14 = private unnamed_addr constant [17 x i8] c"✅ Main loaded!\00"
|
||||
@.str.15 = private unnamed_addr constant [14 x i8] c"debug-content\00"
|
||||
@.str.16 = private unnamed_addr constant [14 x i8] c"debug-section\00"
|
||||
@.str.17 = private unnamed_addr constant [3 x i8] c"h4\00"
|
||||
@.str.18 = private unnamed_addr constant [10 x i8] c"COMPONENT\00"
|
||||
@.str.19 = private unnamed_addr constant [4 x i8] c"pre\00"
|
||||
@.str.20 = private unnamed_addr constant [32 x i8] c"Main.wasm (Multi-Part Switches)\00"
|
||||
@.str.21 = private unnamed_addr constant [9 x i8] c"FEATURES\00"
|
||||
@.str.22 = private unnamed_addr constant [27 x i8] c"✓ Python source to WASM
|
||||
\00"
|
||||
@.str.23 = private unnamed_addr constant [32 x i8] c"✓ Reactive signals (no vDOM)
|
||||
\00"
|
||||
@.str.24 = private unnamed_addr constant [34 x i8] c"✓ Conditional CSR (match/case)
|
||||
\00"
|
||||
@.str.25 = private unnamed_addr constant [20 x i8] c"✓ Event handlers
|
||||
\00"
|
||||
@.str.26 = private unnamed_addr constant [32 x i8] c"✓ Self-hosted debug console!
|
||||
\00"
|
||||
@.str.27 = private unnamed_addr constant [14 x i8] c"RUNTIME STATS\00"
|
||||
@.str.28 = private unnamed_addr constant [32 x i8] c"Memory: JavaScript will update
|
||||
\00"
|
||||
@.str.29 = private unnamed_addr constant [33 x i8] c"Signals: JavaScript will update
|
||||
\00"
|
||||
@.str.30 = private unnamed_addr constant [35 x i8] c"DOM nodes: JavaScript will update
|
||||
\00"
|
||||
@.str.31 = private unnamed_addr constant [17 x i8] c"CONSOLE COMMANDS\00"
|
||||
@.str.32 = private unnamed_addr constant [22 x i8] c"ritzRuntime.getLog()
|
||||
\00"
|
||||
@.str.33 = private unnamed_addr constant [24 x i8] c"ritzRuntime.nodes.size
|
||||
\00"
|
||||
@.str.34 = private unnamed_addr constant [22 x i8] c"ritzInstance.exports
|
||||
\00"
|
||||
@.str.35 = private unnamed_addr constant [18 x i8] c"debug-toggle-hint\00"
|
||||
@.str.36 = private unnamed_addr constant [29 x i8] c"Press Ctrl+` to toggle debug\00"
|
||||
|
||||
; JavaScript bridge function declarations
|
||||
declare i32 @js_create_element(i8*)
|
||||
declare i32 @js_create_text(i8*)
|
||||
declare i32 @js_create_text_with_int(i8*, i32)
|
||||
declare void @js_append_child(i32, i32)
|
||||
declare void @js_set_attribute(i32, i8*, i8*)
|
||||
declare void @js_set_root(i32)
|
||||
declare void @js_bind_event(i32, i8*, i8*)
|
||||
declare void @js_update_text_content(i32, i8*)
|
||||
declare void @js_update_text_with_int(i32, i8*, i32)
|
||||
declare i32 @js_create_marker()
|
||||
declare void @js_replace_at_marker(i32, i32, i32)
|
||||
|
||||
; External functions (@external decorator)
|
||||
declare void @js_toggle_debug_console()
|
||||
|
||||
; Event handler: toggle
|
||||
define void @toggle() {
|
||||
entry:
|
||||
call void @js_toggle_debug_console()
|
||||
ret void
|
||||
}
|
||||
|
||||
; Component render function
|
||||
define i32 @render_DebugConsole() {
|
||||
entry:
|
||||
%var1 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var0 = call i32 @js_create_element(i8* %var1)
|
||||
%var2 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var3 = getelementptr inbounds [18 x i8], [18 x i8]* @.str.2, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var0, i8* %var2, i8* %var3)
|
||||
%var4 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.3, i32 0, i32 0
|
||||
%var5 = getelementptr inbounds [11 x i8], [11 x i8]* @.str.4, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var0, i8* %var4, i8* %var5)
|
||||
%var7 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var6 = call i32 @js_create_element(i8* %var7)
|
||||
%var8 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var9 = getelementptr inbounds [13 x i8], [13 x i8]* @.str.5, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var6, i8* %var8, i8* %var9)
|
||||
%var11 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.6, i32 0, i32 0
|
||||
%var10 = call i32 @js_create_element(i8* %var11)
|
||||
%var12 = call i32 @js_create_text(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str.7, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var10, i32 %var12)
|
||||
call void @js_append_child(i32 %var6, i32 %var10)
|
||||
%var14 = getelementptr inbounds [7 x i8], [7 x i8]* @.str.8, i32 0, i32 0
|
||||
%var13 = call i32 @js_create_element(i8* %var14)
|
||||
%var15 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.9, i32 0, i32 0
|
||||
%var16 = getelementptr inbounds [7 x i8], [7 x i8]* @.str.10, i32 0, i32 0
|
||||
call void @js_bind_event(i32 %var13, i8* %var15, i8* %var16)
|
||||
%var17 = call i32 @js_create_text(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.11, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var13, i32 %var17)
|
||||
call void @js_append_child(i32 %var6, i32 %var13)
|
||||
call void @js_append_child(i32 %var0, i32 %var6)
|
||||
%var19 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var18 = call i32 @js_create_element(i8* %var19)
|
||||
%var20 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var21 = getelementptr inbounds [15 x i8], [15 x i8]* @.str.12, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var18, i8* %var20, i8* %var21)
|
||||
%var22 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.3, i32 0, i32 0
|
||||
%var23 = getelementptr inbounds [7 x i8], [7 x i8]* @.str.13, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var18, i8* %var22, i8* %var23)
|
||||
%var24 = call i32 @js_create_text(i8* getelementptr inbounds ([17 x i8], [17 x i8]* @.str.14, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var18, i32 %var24)
|
||||
call void @js_append_child(i32 %var0, i32 %var18)
|
||||
%var26 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var25 = call i32 @js_create_element(i8* %var26)
|
||||
%var27 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var28 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.15, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var25, i8* %var27, i8* %var28)
|
||||
%var30 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var29 = call i32 @js_create_element(i8* %var30)
|
||||
%var31 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var32 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.16, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var29, i8* %var31, i8* %var32)
|
||||
%var34 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.17, i32 0, i32 0
|
||||
%var33 = call i32 @js_create_element(i8* %var34)
|
||||
%var35 = call i32 @js_create_text(i8* getelementptr inbounds ([10 x i8], [10 x i8]* @.str.18, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var33, i32 %var35)
|
||||
call void @js_append_child(i32 %var29, i32 %var33)
|
||||
%var37 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.19, i32 0, i32 0
|
||||
%var36 = call i32 @js_create_element(i8* %var37)
|
||||
%var38 = call i32 @js_create_text(i8* getelementptr inbounds ([32 x i8], [32 x i8]* @.str.20, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var36, i32 %var38)
|
||||
call void @js_append_child(i32 %var29, i32 %var36)
|
||||
call void @js_append_child(i32 %var25, i32 %var29)
|
||||
%var40 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var39 = call i32 @js_create_element(i8* %var40)
|
||||
%var41 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var42 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.16, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var39, i8* %var41, i8* %var42)
|
||||
%var44 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.17, i32 0, i32 0
|
||||
%var43 = call i32 @js_create_element(i8* %var44)
|
||||
%var45 = call i32 @js_create_text(i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str.21, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var43, i32 %var45)
|
||||
call void @js_append_child(i32 %var39, i32 %var43)
|
||||
%var47 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.19, i32 0, i32 0
|
||||
%var46 = call i32 @js_create_element(i8* %var47)
|
||||
%var48 = call i32 @js_create_text(i8* getelementptr inbounds ([27 x i8], [27 x i8]* @.str.22, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var46, i32 %var48)
|
||||
%var49 = call i32 @js_create_text(i8* getelementptr inbounds ([32 x i8], [32 x i8]* @.str.23, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var46, i32 %var49)
|
||||
%var50 = call i32 @js_create_text(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @.str.24, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var46, i32 %var50)
|
||||
%var51 = call i32 @js_create_text(i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.25, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var46, i32 %var51)
|
||||
%var52 = call i32 @js_create_text(i8* getelementptr inbounds ([32 x i8], [32 x i8]* @.str.26, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var46, i32 %var52)
|
||||
call void @js_append_child(i32 %var39, i32 %var46)
|
||||
call void @js_append_child(i32 %var25, i32 %var39)
|
||||
%var54 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var53 = call i32 @js_create_element(i8* %var54)
|
||||
%var55 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var56 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.16, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var53, i8* %var55, i8* %var56)
|
||||
%var58 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.17, i32 0, i32 0
|
||||
%var57 = call i32 @js_create_element(i8* %var58)
|
||||
%var59 = call i32 @js_create_text(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str.27, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var57, i32 %var59)
|
||||
call void @js_append_child(i32 %var53, i32 %var57)
|
||||
%var61 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.19, i32 0, i32 0
|
||||
%var60 = call i32 @js_create_element(i8* %var61)
|
||||
%var62 = call i32 @js_create_text(i8* getelementptr inbounds ([32 x i8], [32 x i8]* @.str.28, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var60, i32 %var62)
|
||||
%var63 = call i32 @js_create_text(i8* getelementptr inbounds ([33 x i8], [33 x i8]* @.str.29, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var60, i32 %var63)
|
||||
%var64 = call i32 @js_create_text(i8* getelementptr inbounds ([35 x i8], [35 x i8]* @.str.30, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var60, i32 %var64)
|
||||
call void @js_append_child(i32 %var53, i32 %var60)
|
||||
call void @js_append_child(i32 %var25, i32 %var53)
|
||||
%var66 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var65 = call i32 @js_create_element(i8* %var66)
|
||||
%var67 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var68 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.16, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var65, i8* %var67, i8* %var68)
|
||||
%var70 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.17, i32 0, i32 0
|
||||
%var69 = call i32 @js_create_element(i8* %var70)
|
||||
%var71 = call i32 @js_create_text(i8* getelementptr inbounds ([17 x i8], [17 x i8]* @.str.31, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var69, i32 %var71)
|
||||
call void @js_append_child(i32 %var65, i32 %var69)
|
||||
%var73 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.19, i32 0, i32 0
|
||||
%var72 = call i32 @js_create_element(i8* %var73)
|
||||
%var74 = call i32 @js_create_text(i8* getelementptr inbounds ([22 x i8], [22 x i8]* @.str.32, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var72, i32 %var74)
|
||||
%var75 = call i32 @js_create_text(i8* getelementptr inbounds ([24 x i8], [24 x i8]* @.str.33, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var72, i32 %var75)
|
||||
%var76 = call i32 @js_create_text(i8* getelementptr inbounds ([22 x i8], [22 x i8]* @.str.34, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var72, i32 %var76)
|
||||
call void @js_append_child(i32 %var65, i32 %var72)
|
||||
call void @js_append_child(i32 %var25, i32 %var65)
|
||||
call void @js_append_child(i32 %var0, i32 %var25)
|
||||
%var78 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var77 = call i32 @js_create_element(i8* %var78)
|
||||
%var79 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var80 = getelementptr inbounds [18 x i8], [18 x i8]* @.str.35, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var77, i8* %var79, i8* %var80)
|
||||
%var81 = call i32 @js_create_text(i8* getelementptr inbounds ([29 x i8], [29 x i8]* @.str.36, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var77, i32 %var81)
|
||||
ret i32 %var0
|
||||
}
|
||||
|
||||
; Component render function
|
||||
define i32 @render_RitzMain() {
|
||||
entry:
|
||||
%var82 = call i32 @render_DebugConsole()
|
||||
ret i32 %var82
|
||||
}
|
||||
|
||||
; Main entry point
|
||||
define void @main() {
|
||||
entry:
|
||||
%root = call i32 @render_RitzMain()
|
||||
call void @js_set_root(i32 %root)
|
||||
ret void
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Ritz Debug Console - Self-hosted debug interface written in Ritz itself!
|
||||
|
||||
This demonstrates Ritz's capability to build complex interactive UIs with:
|
||||
- Signal-based state management
|
||||
- Event handling
|
||||
- Conditional rendering
|
||||
- Dynamic content updates
|
||||
|
||||
The entire debug console - state, UI, and interactions - is written in Ritz!
|
||||
"""
|
||||
|
||||
from ritz.syntax import *
|
||||
|
||||
|
||||
@external
|
||||
def js_toggle_debug_console():
|
||||
"""External JavaScript function to toggle debug console visibility."""
|
||||
pass # Implementation provided by JavaScript runtime
|
||||
|
||||
|
||||
@component
|
||||
def DebugConsole():
|
||||
"""
|
||||
Self-hosted debug console for Ritz applications.
|
||||
|
||||
Shows runtime information, memory stats, and console commands.
|
||||
"""
|
||||
def toggle():
|
||||
"""Toggle debug console visibility by calling JavaScript bridge."""
|
||||
js_toggle_debug_console()
|
||||
|
||||
# Start hidden - JavaScript will handle toggling via class manipulation
|
||||
with Div(class_='ritz-debug hidden', id='ritz-debug'):
|
||||
# Header with toggle button
|
||||
with Div(class_='debug-header'):
|
||||
with H3():
|
||||
yield '🎭 Ritz Debug'
|
||||
with Button(onclick=toggle):
|
||||
yield '×'
|
||||
|
||||
# Status indicator
|
||||
with Div(class_='status success', id='status'):
|
||||
yield '✅ Main loaded!'
|
||||
|
||||
# Debug content sections
|
||||
with Div(class_='debug-content'):
|
||||
# Component info
|
||||
with Div(class_='debug-section'):
|
||||
with H4():
|
||||
yield 'COMPONENT'
|
||||
with Pre():
|
||||
yield 'Main.wasm (Multi-Part Switches)'
|
||||
|
||||
# Features section
|
||||
with Div(class_='debug-section'):
|
||||
with H4():
|
||||
yield 'FEATURES'
|
||||
with Pre():
|
||||
yield '✓ Python source to WASM\n'
|
||||
yield '✓ Reactive signals (no vDOM)\n'
|
||||
yield '✓ Conditional CSR (match/case)\n'
|
||||
yield '✓ Event handlers\n'
|
||||
yield '✓ Self-hosted debug console!\n'
|
||||
|
||||
# Runtime stats section
|
||||
with Div(class_='debug-section'):
|
||||
with H4():
|
||||
yield 'RUNTIME STATS'
|
||||
with Pre():
|
||||
yield 'Memory: JavaScript will update\n'
|
||||
yield 'Signals: JavaScript will update\n'
|
||||
yield 'DOM nodes: JavaScript will update\n'
|
||||
|
||||
# Console commands section
|
||||
with Div(class_='debug-section'):
|
||||
with H4():
|
||||
yield 'CONSOLE COMMANDS'
|
||||
with Pre():
|
||||
yield 'ritzRuntime.getLog()\n'
|
||||
yield 'ritzRuntime.nodes.size\n'
|
||||
yield 'ritzInstance.exports\n'
|
||||
|
||||
# Toggle hint (appears on page load)
|
||||
with Div(class_='debug-toggle-hint'):
|
||||
yield 'Press Ctrl+` to toggle debug'
|
||||
|
||||
|
||||
@component
|
||||
def RitzMain():
|
||||
"""Entry point for debug console component."""
|
||||
yield DebugConsole()
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,302 @@
|
||||
; Generated by Ritz Framework with Signals
|
||||
; Components: DebugConsole, RitzMain
|
||||
|
||||
; String constants
|
||||
@.str.0 = private unnamed_addr constant [4 x i8] c"div\00"
|
||||
@.str.1 = private unnamed_addr constant [6 x i8] c"class\00"
|
||||
@.str.2 = private unnamed_addr constant [14 x i8] c"debug-section\00"
|
||||
@.str.3 = private unnamed_addr constant [3 x i8] c"h4\00"
|
||||
@.str.4 = private unnamed_addr constant [9 x i8] c"FEATURES\00"
|
||||
@.str.5 = private unnamed_addr constant [4 x i8] c"pre\00"
|
||||
@.str.6 = private unnamed_addr constant [17 x i8] c"CONSOLE COMMANDS\00"
|
||||
@.str.7 = private unnamed_addr constant [3 x i8] c"id\00"
|
||||
@.str.8 = private unnamed_addr constant [11 x i8] c"ritz-debug\00"
|
||||
@.str.9 = private unnamed_addr constant [13 x i8] c"debug-header\00"
|
||||
@.str.10 = private unnamed_addr constant [7 x i8] c"status\00"
|
||||
@.str.11 = private unnamed_addr constant [6 x i8] c"Ready\00"
|
||||
@.str.12 = private unnamed_addr constant [14 x i8] c"debug-content\00"
|
||||
@.str.13 = private unnamed_addr constant [10 x i8] c"COMPONENT\00"
|
||||
@.str.14 = private unnamed_addr constant [1 x i8] c"\00"
|
||||
@.str.15 = private unnamed_addr constant [18 x i8] c"debug-toggle-hint\00"
|
||||
@.str.16 = private unnamed_addr constant [29 x i8] c"Press Ctrl+` to toggle debug\00"
|
||||
|
||||
; JavaScript bridge function declarations
|
||||
declare i32 @js_create_element(i8*)
|
||||
declare i32 @js_create_text(i8*)
|
||||
declare i32 @js_create_text_with_int(i8*, i32)
|
||||
declare void @js_append_child(i32, i32)
|
||||
declare void @js_set_attribute(i32, i8*, i8*)
|
||||
declare void @js_set_root(i32)
|
||||
declare void @js_bind_event(i32, i8*, i8*)
|
||||
declare void @js_update_text_content(i32, i8*)
|
||||
declare void @js_update_text_with_int(i32, i8*, i32)
|
||||
declare i32 @js_create_marker()
|
||||
declare void @js_replace_at_marker(i32, i32, i32)
|
||||
|
||||
; Signal storage for DebugConsole (reactive state in WASM memory)
|
||||
@signal_DebugConsole_is_open = global i32 0
|
||||
|
||||
; Conditional rendering state
|
||||
@cond_0_marker = global i32 0
|
||||
@cond_0_current = global i32 0
|
||||
@cond_0_then_root = global i32 0
|
||||
@cond_0_else_root = global i32 0
|
||||
@cond_1_marker = global i32 0
|
||||
@cond_1_current = global i32 0
|
||||
@cond_1_then_root = global i32 0
|
||||
@cond_1_else_root = global i32 0
|
||||
|
||||
; Signal helper functions
|
||||
define i32 @get_is_open() {
|
||||
entry:
|
||||
%val = load i32, i32* @signal_DebugConsole_is_open
|
||||
ret i32 %val
|
||||
}
|
||||
|
||||
define void @set_is_open(i32 %newval) {
|
||||
entry:
|
||||
store i32 %newval, i32* @signal_DebugConsole_is_open
|
||||
; TODO: Trigger DOM update callback
|
||||
ret void
|
||||
}
|
||||
|
||||
; Event handler: toggle
|
||||
define void @toggle() {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Initialize conditional 0
|
||||
define void @_init_conditional_0() {
|
||||
entry:
|
||||
; Render both branches upfront
|
||||
%var0 = call i32 @_render_cond_0_then()
|
||||
%var1 = call i32 @_render_cond_0_else()
|
||||
store i32 %var0, i32* @cond_0_then_root
|
||||
store i32 %var1, i32* @cond_0_else_root
|
||||
%var2 = icmp ne i32 0, 0
|
||||
%var3 = load i32, i32* @cond_0_marker
|
||||
br i1 %var2, label %insert_then, label %insert_else
|
||||
insert_then:
|
||||
call void @js_replace_at_marker(i32 %var3, i32 0, i32 %var0)
|
||||
store i32 1, i32* @cond_0_current
|
||||
ret void
|
||||
insert_else:
|
||||
call void @js_replace_at_marker(i32 %var3, i32 0, i32 %var1)
|
||||
store i32 0, i32* @cond_0_current
|
||||
ret void
|
||||
}
|
||||
|
||||
; Re-evaluate conditional 0
|
||||
define void @reevaluate_conditional_0() {
|
||||
entry:
|
||||
%var4 = icmp ne i32 0, 0
|
||||
%var5 = zext i1 %var4 to i32
|
||||
%var6 = load i32, i32* @cond_0_current
|
||||
%var7 = icmp ne i32 %var6, %var5
|
||||
br i1 %var7, label %swap, label %done
|
||||
swap:
|
||||
br i1 %var4, label %swap_to_then, label %swap_to_else
|
||||
swap_to_then:
|
||||
%var8 = load i32, i32* @cond_0_else_root
|
||||
%var9 = load i32, i32* @cond_0_then_root
|
||||
%var10 = load i32, i32* @cond_0_marker
|
||||
call void @js_replace_at_marker(i32 %var10, i32 %var8, i32 %var9)
|
||||
store i32 1, i32* @cond_0_current
|
||||
br label %done
|
||||
swap_to_else:
|
||||
%var11 = load i32, i32* @cond_0_then_root
|
||||
%var12 = load i32, i32* @cond_0_else_root
|
||||
%var13 = load i32, i32* @cond_0_marker
|
||||
call void @js_replace_at_marker(i32 %var13, i32 %var11, i32 %var12)
|
||||
store i32 0, i32* @cond_0_current
|
||||
br label %done
|
||||
done:
|
||||
ret void
|
||||
}
|
||||
|
||||
define i32 @_render_cond_0_then() {
|
||||
entry:
|
||||
%var15 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var14 = call i32 @js_create_element(i8* %var15)
|
||||
%var17 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var16 = call i32 @js_create_element(i8* %var17)
|
||||
%var18 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var19 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.2, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var16, i8* %var18, i8* %var19)
|
||||
%var21 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.3, i32 0, i32 0
|
||||
%var20 = call i32 @js_create_element(i8* %var21)
|
||||
%var22 = call i32 @js_create_text(i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str.4, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var20, i32 %var22)
|
||||
call void @js_append_child(i32 %var16, i32 %var20)
|
||||
%var24 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.5, i32 0, i32 0
|
||||
%var23 = call i32 @js_create_element(i8* %var24)
|
||||
call void @js_append_child(i32 %var16, i32 %var23)
|
||||
call void @js_append_child(i32 %var14, i32 %var16)
|
||||
ret i32 %var14
|
||||
}
|
||||
|
||||
define i32 @_render_cond_0_else() {
|
||||
entry:
|
||||
%var26 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var25 = call i32 @js_create_element(i8* %var26)
|
||||
ret i32 %var25
|
||||
}
|
||||
|
||||
; Initialize conditional 1
|
||||
define void @_init_conditional_1() {
|
||||
entry:
|
||||
; Render both branches upfront
|
||||
%var27 = call i32 @_render_cond_1_then()
|
||||
%var28 = call i32 @_render_cond_1_else()
|
||||
store i32 %var27, i32* @cond_1_then_root
|
||||
store i32 %var28, i32* @cond_1_else_root
|
||||
%var29 = icmp ne i32 0, 0
|
||||
%var30 = load i32, i32* @cond_1_marker
|
||||
br i1 %var29, label %insert_then, label %insert_else
|
||||
insert_then:
|
||||
call void @js_replace_at_marker(i32 %var30, i32 0, i32 %var27)
|
||||
store i32 1, i32* @cond_1_current
|
||||
ret void
|
||||
insert_else:
|
||||
call void @js_replace_at_marker(i32 %var30, i32 0, i32 %var28)
|
||||
store i32 0, i32* @cond_1_current
|
||||
ret void
|
||||
}
|
||||
|
||||
; Re-evaluate conditional 1
|
||||
define void @reevaluate_conditional_1() {
|
||||
entry:
|
||||
%var31 = icmp ne i32 0, 0
|
||||
%var32 = zext i1 %var31 to i32
|
||||
%var33 = load i32, i32* @cond_1_current
|
||||
%var34 = icmp ne i32 %var33, %var32
|
||||
br i1 %var34, label %swap, label %done
|
||||
swap:
|
||||
br i1 %var31, label %swap_to_then, label %swap_to_else
|
||||
swap_to_then:
|
||||
%var35 = load i32, i32* @cond_1_else_root
|
||||
%var36 = load i32, i32* @cond_1_then_root
|
||||
%var37 = load i32, i32* @cond_1_marker
|
||||
call void @js_replace_at_marker(i32 %var37, i32 %var35, i32 %var36)
|
||||
store i32 1, i32* @cond_1_current
|
||||
br label %done
|
||||
swap_to_else:
|
||||
%var38 = load i32, i32* @cond_1_then_root
|
||||
%var39 = load i32, i32* @cond_1_else_root
|
||||
%var40 = load i32, i32* @cond_1_marker
|
||||
call void @js_replace_at_marker(i32 %var40, i32 %var38, i32 %var39)
|
||||
store i32 0, i32* @cond_1_current
|
||||
br label %done
|
||||
done:
|
||||
ret void
|
||||
}
|
||||
|
||||
define i32 @_render_cond_1_then() {
|
||||
entry:
|
||||
%var42 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var41 = call i32 @js_create_element(i8* %var42)
|
||||
%var44 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var43 = call i32 @js_create_element(i8* %var44)
|
||||
%var45 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var46 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.2, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var43, i8* %var45, i8* %var46)
|
||||
%var48 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.3, i32 0, i32 0
|
||||
%var47 = call i32 @js_create_element(i8* %var48)
|
||||
%var49 = call i32 @js_create_text(i8* getelementptr inbounds ([17 x i8], [17 x i8]* @.str.6, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var47, i32 %var49)
|
||||
call void @js_append_child(i32 %var43, i32 %var47)
|
||||
%var51 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.5, i32 0, i32 0
|
||||
%var50 = call i32 @js_create_element(i8* %var51)
|
||||
call void @js_append_child(i32 %var43, i32 %var50)
|
||||
call void @js_append_child(i32 %var41, i32 %var43)
|
||||
ret i32 %var41
|
||||
}
|
||||
|
||||
define i32 @_render_cond_1_else() {
|
||||
entry:
|
||||
%var53 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var52 = call i32 @js_create_element(i8* %var53)
|
||||
ret i32 %var52
|
||||
}
|
||||
|
||||
; Component render function
|
||||
define i32 @render_DebugConsole(i32 %component_name, i32 %features, i32 %console_commands) {
|
||||
entry:
|
||||
%var55 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var54 = call i32 @js_create_element(i8* %var55)
|
||||
%var56 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.7, i32 0, i32 0
|
||||
%var57 = getelementptr inbounds [11 x i8], [11 x i8]* @.str.8, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var54, i8* %var56, i8* %var57)
|
||||
%var59 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var58 = call i32 @js_create_element(i8* %var59)
|
||||
%var60 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var61 = getelementptr inbounds [13 x i8], [13 x i8]* @.str.9, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var58, i8* %var60, i8* %var61)
|
||||
call void @js_append_child(i32 %var54, i32 %var58)
|
||||
%var63 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var62 = call i32 @js_create_element(i8* %var63)
|
||||
%var64 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var65 = getelementptr inbounds [7 x i8], [7 x i8]* @.str.10, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var62, i8* %var64, i8* %var65)
|
||||
%var66 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.7, i32 0, i32 0
|
||||
%var67 = getelementptr inbounds [7 x i8], [7 x i8]* @.str.10, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var62, i8* %var66, i8* %var67)
|
||||
%var68 = call i32 @js_create_text(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.11, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var62, i32 %var68)
|
||||
call void @js_append_child(i32 %var54, i32 %var62)
|
||||
%var70 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var69 = call i32 @js_create_element(i8* %var70)
|
||||
%var71 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var72 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.12, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var69, i8* %var71, i8* %var72)
|
||||
%var74 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var73 = call i32 @js_create_element(i8* %var74)
|
||||
%var75 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var76 = getelementptr inbounds [14 x i8], [14 x i8]* @.str.2, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var73, i8* %var75, i8* %var76)
|
||||
%var78 = getelementptr inbounds [3 x i8], [3 x i8]* @.str.3, i32 0, i32 0
|
||||
%var77 = call i32 @js_create_element(i8* %var78)
|
||||
%var79 = call i32 @js_create_text(i8* getelementptr inbounds ([10 x i8], [10 x i8]* @.str.13, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var77, i32 %var79)
|
||||
call void @js_append_child(i32 %var73, i32 %var77)
|
||||
%var81 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.5, i32 0, i32 0
|
||||
%var80 = call i32 @js_create_element(i8* %var81)
|
||||
%var83 = getelementptr inbounds [1 x i8], [1 x i8]* @.str.14, i32 0, i32 0
|
||||
%var82 = call i32 @js_create_text(i8* %var83)
|
||||
call void @js_append_child(i32 %var80, i32 %var82)
|
||||
call void @js_append_child(i32 %var73, i32 %var80)
|
||||
call void @js_append_child(i32 %var69, i32 %var73)
|
||||
%var84 = call i32 @js_create_marker()
|
||||
call void @js_append_child(i32 %var69, i32 %var84)
|
||||
store i32 %var84, i32* @cond_0_marker
|
||||
call void @_init_conditional_0()
|
||||
%var85 = call i32 @js_create_marker()
|
||||
call void @js_append_child(i32 %var69, i32 %var85)
|
||||
store i32 %var85, i32* @cond_1_marker
|
||||
call void @_init_conditional_1()
|
||||
call void @js_append_child(i32 %var54, i32 %var69)
|
||||
%var87 = getelementptr inbounds [4 x i8], [4 x i8]* @.str.0, i32 0, i32 0
|
||||
%var86 = call i32 @js_create_element(i8* %var87)
|
||||
%var88 = getelementptr inbounds [6 x i8], [6 x i8]* @.str.1, i32 0, i32 0
|
||||
%var89 = getelementptr inbounds [18 x i8], [18 x i8]* @.str.15, i32 0, i32 0
|
||||
call void @js_set_attribute(i32 %var86, i8* %var88, i8* %var89)
|
||||
%var90 = call i32 @js_create_text(i8* getelementptr inbounds ([29 x i8], [29 x i8]* @.str.16, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var86, i32 %var90)
|
||||
ret i32 %var54
|
||||
}
|
||||
|
||||
; Component render function
|
||||
define i32 @render_RitzMain() {
|
||||
entry:
|
||||
%var91 = call i32 @render_DebugConsole()
|
||||
ret i32 %var91
|
||||
}
|
||||
|
||||
; Main entry point
|
||||
define void @main() {
|
||||
entry:
|
||||
%root = call i32 @render_RitzMain()
|
||||
call void @js_set_root(i32 %root)
|
||||
ret void
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -77,6 +77,14 @@ class RitzRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
js_toggle_debug_console() {
|
||||
// Bridge function to toggle debug console from WASM
|
||||
if (window.ritzLoader) {
|
||||
window.ritzLoader.toggleDebugConsole();
|
||||
}
|
||||
this._log('[Debug] Toggle debug console from WASM');
|
||||
}
|
||||
|
||||
js_set_root(elemId) {
|
||||
const element = this.nodes.get(elemId);
|
||||
if (element) {
|
||||
@@ -151,11 +159,16 @@ class RitzRuntime {
|
||||
this._log(`[Reactive] ${name}()`);
|
||||
}
|
||||
}
|
||||
|
||||
// Update debug console stats if debug mode is enabled
|
||||
if (window.ritzLoader && this.debugEnabled) {
|
||||
window.ritzLoader.updateDebugStats();
|
||||
}
|
||||
}
|
||||
|
||||
updateSignalDisplay(signalName, value) {
|
||||
// Find DOM elements that should display this signal
|
||||
// For now, update any element with class matching signal name or data-signal attribute
|
||||
// IMPORTANT: Only search within this runtime's root element to avoid cross-contamination
|
||||
const selectors = [
|
||||
`[data-signal="${signalName}"]`,
|
||||
`.${signalName}`,
|
||||
@@ -163,7 +176,8 @@ class RitzRuntime {
|
||||
];
|
||||
|
||||
selectors.forEach(selector => {
|
||||
const elements = document.querySelectorAll(selector);
|
||||
// Only query within this runtime's root element, not the entire document
|
||||
const elements = this.rootElement.querySelectorAll(selector);
|
||||
elements.forEach(el => {
|
||||
// Check if text contains the signal name or looks like a counter
|
||||
const text = el.textContent;
|
||||
@@ -284,12 +298,20 @@ class RitzLoader {
|
||||
constructor(options = {}) {
|
||||
this.rootElement = options.rootElement || document.getElementById('app');
|
||||
this.statusElement = options.statusElement || document.getElementById('status');
|
||||
this.debug = options.debug !== undefined ? options.debug : false;
|
||||
this.onLoad = options.onLoad || (() => {});
|
||||
this.onError = options.onError || ((err) => console.error(err));
|
||||
this.debugConsoleRuntime = null;
|
||||
}
|
||||
|
||||
async load(wasmPath, componentName, props = {}) {
|
||||
async load(wasmPath, componentName = null, props = {}) {
|
||||
try {
|
||||
// Auto-detect component name from wasm filename if not provided
|
||||
if (!componentName) {
|
||||
// Extract from path: "Main.wasm" → "Main", "path/to/Main.wasm" → "Main"
|
||||
componentName = wasmPath.split('/').pop().replace('.wasm', '');
|
||||
}
|
||||
|
||||
this.setStatus('⏳ Creating runtime...');
|
||||
const runtime = new RitzRuntime(this.rootElement);
|
||||
|
||||
@@ -347,6 +369,11 @@ class RitzLoader {
|
||||
|
||||
this.onLoad(runtime, instance);
|
||||
|
||||
// Load debug console if debug mode is enabled
|
||||
if (this.debug) {
|
||||
await this.loadDebugConsole(componentName);
|
||||
}
|
||||
|
||||
return { runtime, instance };
|
||||
|
||||
} catch (error) {
|
||||
@@ -356,6 +383,149 @@ class RitzLoader {
|
||||
}
|
||||
}
|
||||
|
||||
async loadDebugConsole(appComponentName) {
|
||||
try {
|
||||
console.log('[Debug] Loading debug console...');
|
||||
|
||||
// Create a container for the debug console
|
||||
const debugContainer = document.createElement('div');
|
||||
debugContainer.id = 'debug-console-root';
|
||||
document.body.appendChild(debugContainer);
|
||||
|
||||
console.log('[Debug] Created container:', debugContainer);
|
||||
|
||||
// Create debug console runtime
|
||||
const debugRuntime = new RitzRuntime(debugContainer);
|
||||
debugRuntime.debugEnabled = false; // Don't log debug console's own operations
|
||||
|
||||
// Load DebugConsole.wasm from runtime directory
|
||||
const debugWasmPath = '../runtime/DebugConsole.wasm';
|
||||
console.log('[Debug] Fetching:', debugWasmPath);
|
||||
|
||||
const response = await fetch(debugWasmPath);
|
||||
if (!response.ok) {
|
||||
console.warn('Could not load debug console:', response.status);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Debug] DebugConsole.wasm loaded successfully');
|
||||
|
||||
const wasmBytes = await response.arrayBuffer();
|
||||
|
||||
// Create imports for debug console WASM
|
||||
const imports = {
|
||||
env: {
|
||||
js_create_element: (ptr) => debugRuntime.js_create_element(ptr),
|
||||
js_create_text: (ptr) => debugRuntime.js_create_text(ptr),
|
||||
js_create_text_with_int: (p, i) => debugRuntime.js_create_text_with_int(p, i),
|
||||
js_append_child: (p, c) => debugRuntime.js_append_child(p, c),
|
||||
js_set_attribute: (e, n, v) => debugRuntime.js_set_attribute(e, n, v),
|
||||
js_set_root: (e) => debugRuntime.js_set_root(e),
|
||||
js_bind_event: (e, n, h) => debugRuntime.js_bind_event(e, n, h),
|
||||
js_update_text_content: (n, t) => debugRuntime.js_update_text_content(n, t),
|
||||
js_update_text_with_int: (n, p, i) => debugRuntime.js_update_text_with_int(n, p, i),
|
||||
js_create_marker: () => debugRuntime.js_create_marker(),
|
||||
js_replace_at_marker: (m, o, n) => debugRuntime.js_replace_at_marker(m, o, n),
|
||||
js_toggle_debug_console: () => debugRuntime.js_toggle_debug_console(),
|
||||
}
|
||||
};
|
||||
|
||||
const { instance } = await WebAssembly.instantiate(wasmBytes, imports);
|
||||
|
||||
// Setup debug runtime
|
||||
debugRuntime.setMemory(instance.exports.memory);
|
||||
debugRuntime.setWasmInstance(instance);
|
||||
|
||||
// Render debug console
|
||||
console.log('[Debug] Rendering debug console...');
|
||||
instance.exports.main();
|
||||
debugRuntime.updateSignals();
|
||||
|
||||
this.debugConsoleRuntime = debugRuntime;
|
||||
|
||||
console.log('[Debug] Debug console rendered. Checking DOM...');
|
||||
const debugElement = document.querySelector('.ritz-debug');
|
||||
console.log('[Debug] Debug element:', debugElement);
|
||||
console.log('[Debug] Debug element classes:', debugElement?.className);
|
||||
|
||||
// Hide the loader status element (we have our own in debug console)
|
||||
if (this.statusElement) {
|
||||
this.statusElement.style.display = 'none';
|
||||
}
|
||||
|
||||
// Populate runtime stats
|
||||
this.updateDebugStats(appComponentName);
|
||||
|
||||
// Setup keyboard shortcut (Ctrl+`)
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey && e.key === '`') {
|
||||
e.preventDefault();
|
||||
console.log('[Debug] Toggle debug console');
|
||||
this.toggleDebugConsole();
|
||||
}
|
||||
});
|
||||
|
||||
// Make debug console and loader globally available
|
||||
window.ritzDebugConsole = debugRuntime;
|
||||
window.ritzLoader = this;
|
||||
|
||||
console.log('🎭 Ritz Debug Console loaded! Press Ctrl+` to toggle.');
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Failed to load debug console:', error);
|
||||
}
|
||||
}
|
||||
|
||||
toggleDebugConsole() {
|
||||
// Toggle the debug console by toggling body class
|
||||
// The Ritz debug console component manages its own visibility via signal
|
||||
// But we need to trigger the body class for layout
|
||||
document.body.classList.toggle('debug-open');
|
||||
|
||||
// Find the debug console element and toggle its hidden class
|
||||
const debugElement = document.querySelector('.ritz-debug');
|
||||
if (debugElement) {
|
||||
debugElement.classList.toggle('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
updateDebugStats(componentName = 'Main') {
|
||||
// Update status with component name (inside debug console)
|
||||
const statusEl = document.querySelector('#ritz-debug .status');
|
||||
if (statusEl) {
|
||||
statusEl.textContent = `✅ ${componentName} loaded!`;
|
||||
statusEl.className = 'status success';
|
||||
}
|
||||
|
||||
// Update component section
|
||||
const componentPre = document.querySelector('#ritz-debug .debug-section:nth-child(1) pre');
|
||||
if (componentPre && window.ritzInstance) {
|
||||
const wasmSize = window.ritzInstance.exports.memory.buffer.byteLength;
|
||||
componentPre.textContent = `${componentName}.wasm (${Math.round(wasmSize / 1024)}KB)`;
|
||||
}
|
||||
|
||||
// Update runtime stats
|
||||
const statsPre = document.querySelector('#ritz-debug .debug-section:nth-child(3) pre');
|
||||
if (statsPre && window.ritzRuntime && window.ritzInstance) {
|
||||
const memory = window.ritzInstance.exports.memory.buffer.byteLength;
|
||||
const memoryKB = Math.round(memory / 1024);
|
||||
const memoryMB = (memory / 1024 / 1024).toFixed(2);
|
||||
const nodeCount = window.ritzRuntime.nodes.size;
|
||||
|
||||
// Count signals (globals that start with @signal_)
|
||||
const exports = Object.keys(window.ritzInstance.exports);
|
||||
const signalCount = exports.filter(name =>
|
||||
name.startsWith('get_') || name.startsWith('set_')
|
||||
).length / 2;
|
||||
|
||||
statsPre.textContent =
|
||||
`Memory: ${memoryKB}KB (${memoryMB}MB)\n` +
|
||||
`Signals: ${signalCount} reactive\n` +
|
||||
`DOM nodes: ${nodeCount} cached\n` +
|
||||
`Log entries: ${window.ritzRuntime.log.length}`;
|
||||
}
|
||||
}
|
||||
|
||||
setStatus(message, type = '') {
|
||||
if (this.statusElement) {
|
||||
this.statusElement.textContent = message;
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
/* Ritz Framework - Shared Example Styles */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 800px;
|
||||
margin: 50px auto;
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
#status {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 12px 20px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#status.success {
|
||||
background: #d4edda;
|
||||
border-color: #c3e6cb;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
#status.error {
|
||||
background: #f8d7da;
|
||||
border-color: #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
margin-top: 0;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
background: #0066cc;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #0052a3;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
input:focus, select:focus, textarea:focus {
|
||||
outline: none;
|
||||
border-color: #0066cc;
|
||||
box-shadow: 0 0 0 3px rgba(0,102,204,0.1);
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Ritz Debug Console Styles
|
||||
======================================== */
|
||||
|
||||
.ritz-debug {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 400px;
|
||||
height: 100vh;
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
box-shadow: -2px 0 10px rgba(0,0,0,0.3);
|
||||
transition: transform 0.3s ease;
|
||||
z-index: 9999;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ritz-debug.hidden {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.ritz-debug .debug-header {
|
||||
background: #252525;
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #3e3e3e;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ritz-debug .debug-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.ritz-debug .debug-header button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #d4d4d4;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 1;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ritz-debug .debug-header button:hover {
|
||||
color: #fff;
|
||||
background: none;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#ritz-debug .status {
|
||||
position: static !important;
|
||||
margin: 15px;
|
||||
padding: 10px;
|
||||
background: #2d2d30;
|
||||
border: none !important;
|
||||
border-left: 3px solid #007acc !important;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
#ritz-debug .status.success {
|
||||
background: #2d2d30 !important;
|
||||
border-left-color: #4ec9b0 !important;
|
||||
color: #4ec9b0 !important;
|
||||
}
|
||||
|
||||
#ritz-debug .status.error {
|
||||
background: #2d2d30 !important;
|
||||
border-left-color: #f48771 !important;
|
||||
color: #f48771 !important;
|
||||
}
|
||||
|
||||
.debug-content {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.debug-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.debug-section h4 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
color: #858585;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.debug-section pre {
|
||||
background: #252525;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
line-height: 1.6;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.debug-toggle-hint {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: #252525;
|
||||
color: #d4d4d4;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 11px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||
opacity: 0;
|
||||
animation: fadeInOut 3s ease-in-out;
|
||||
pointer-events: none;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
@keyframes fadeInOut {
|
||||
0%, 100% { opacity: 0; }
|
||||
10%, 90% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Adjust app container when debug is open */
|
||||
body.debug-open #app {
|
||||
margin-right: 420px;
|
||||
}
|
||||
@@ -400,6 +400,7 @@ entry:
|
||||
%var101 = call i32 @js_create_text(i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.15, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var99, i32 %var101)
|
||||
call void @js_append_child(i32 %var89, i32 %var99)
|
||||
call void @js_append_child(i32 %var87, i32 %var89)
|
||||
ret i32 %var87
|
||||
}
|
||||
|
||||
@@ -427,6 +428,7 @@ entry:
|
||||
%var116 = call i32 @js_create_text(i8* getelementptr inbounds ([19 x i8], [19 x i8]* @.str.18, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var114, i32 %var116)
|
||||
call void @js_append_child(i32 %var104, i32 %var114)
|
||||
call void @js_append_child(i32 %var102, i32 %var104)
|
||||
ret i32 %var102
|
||||
}
|
||||
|
||||
@@ -454,6 +456,7 @@ entry:
|
||||
%var131 = call i32 @js_create_text(i8* getelementptr inbounds ([32 x i8], [32 x i8]* @.str.21, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var129, i32 %var131)
|
||||
call void @js_append_child(i32 %var119, i32 %var129)
|
||||
call void @js_append_child(i32 %var117, i32 %var119)
|
||||
ret i32 %var117
|
||||
}
|
||||
|
||||
@@ -471,6 +474,7 @@ entry:
|
||||
%var140 = call i32 @js_create_text(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str.22, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var138, i32 %var140)
|
||||
call void @js_append_child(i32 %var134, i32 %var138)
|
||||
call void @js_append_child(i32 %var132, i32 %var134)
|
||||
ret i32 %var132
|
||||
}
|
||||
|
||||
@@ -860,6 +864,7 @@ entry:
|
||||
call void @js_append_child(i32 %var286, i32 %var288)
|
||||
call void @js_append_child(i32 %var278, i32 %var286)
|
||||
call void @js_append_child(i32 %var265, i32 %var278)
|
||||
call void @js_append_child(i32 %var263, i32 %var265)
|
||||
ret i32 %var263
|
||||
}
|
||||
|
||||
@@ -903,6 +908,7 @@ entry:
|
||||
call void @js_append_child(i32 %var311, i32 %var313)
|
||||
call void @js_append_child(i32 %var301, i32 %var311)
|
||||
call void @js_append_child(i32 %var291, i32 %var301)
|
||||
call void @js_append_child(i32 %var289, i32 %var291)
|
||||
ret i32 %var289
|
||||
}
|
||||
|
||||
@@ -927,6 +933,7 @@ entry:
|
||||
call void @js_append_child(i32 %var316, i32 %var323)
|
||||
%var326 = call i32 @render_VerticalTabs()
|
||||
call void @js_append_child(i32 %var316, i32 %var326)
|
||||
call void @js_append_child(i32 %var314, i32 %var316)
|
||||
ret i32 %var314
|
||||
}
|
||||
|
||||
@@ -944,6 +951,7 @@ entry:
|
||||
%var335 = call i32 @js_create_text(i8* getelementptr inbounds ([28 x i8], [28 x i8]* @.str.53, i32 0, i32 0))
|
||||
call void @js_append_child(i32 %var333, i32 %var335)
|
||||
call void @js_append_child(i32 %var329, i32 %var333)
|
||||
call void @js_append_child(i32 %var327, i32 %var329)
|
||||
ret i32 %var327
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
+11
-32
@@ -3,45 +3,24 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{TITLE}} - Ritz Framework</title>
|
||||
<link rel="stylesheet" href="../runtime/ritz-debug.css">
|
||||
<link rel="stylesheet" href="{{COMPONENT}}.css">
|
||||
<title>Ritz Example</title>
|
||||
<link rel="stylesheet" href="../runtime/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="ritz-debug" class="ritz-debug hidden">
|
||||
<div class="debug-header">
|
||||
<h3>🎭 Ritz Debug</h3>
|
||||
<button onclick="ritzDebug.toggle()">×</button>
|
||||
</div>
|
||||
<div id="status" class="status">Loading...</div>
|
||||
<div class="debug-content">
|
||||
<div class="debug-section">
|
||||
<h4>Pipeline</h4>
|
||||
<pre>{{COMPONENT}}.py → AST → IR → LLVM → WASM</pre>
|
||||
</div>
|
||||
<div class="debug-section">
|
||||
<h4>Commands</h4>
|
||||
<pre>ritzRuntime.getLog()
|
||||
ritzInstance.exports</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="status"></div>
|
||||
|
||||
<script src="../runtime/ritz-runtime.js"></script>
|
||||
<script src="../runtime/ritz-debug.js"></script>
|
||||
<script>
|
||||
// Load component
|
||||
const loader = new RitzLoader({
|
||||
rootElement: document.getElementById('app'),
|
||||
statusElement: document.getElementById('status'),
|
||||
onLoad: (runtime, instance) => {
|
||||
console.log('✅ Component loaded');
|
||||
console.log('Debug: ritzRuntime, ritzInstance');
|
||||
}
|
||||
// Load Main.wasm with debug console enabled
|
||||
// The debug console is itself written in Ritz!
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new RitzLoader({ debug: true }).load('Main.wasm');
|
||||
});
|
||||
|
||||
loader.load('{{COMPONENT}}.wasm', '{{COMPONENT}}');
|
||||
} else {
|
||||
new RitzLoader({ debug: true }).load('Main.wasm');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
-2
@@ -39,7 +39,7 @@ def build_component(component_file: Path, output_file: Path, debug: bool = False
|
||||
print(" [3/4] Generating LLVM IR...")
|
||||
llvm_ir = generate_llvm_ir(ir)
|
||||
|
||||
# Save LLVM IR for inspection
|
||||
# Save LLVM IR for inspection (always Main.ll)
|
||||
ll_file = output_file.with_suffix(".ll")
|
||||
ll_file.write_text(llvm_ir)
|
||||
print(f" Saved LLVM IR: {ll_file}")
|
||||
@@ -130,7 +130,8 @@ def main() -> None:
|
||||
if args.output:
|
||||
output_file = args.output
|
||||
else:
|
||||
output_file = args.component.with_suffix(".wasm")
|
||||
# Convention: always output Main.wasm in the same directory as the component
|
||||
output_file = args.component.parent / "Main.wasm"
|
||||
|
||||
# Ensure output directory exists
|
||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -27,6 +27,7 @@ class SignalLLVMIRGenerator:
|
||||
self.strings: dict[str, str] = {} # value -> global name
|
||||
self.signals: dict[str, dict[str, Any]] = {} # signal_name -> {initial, index}
|
||||
self.event_handlers: list[dict[str, Any]] = [] # List of event handler functions
|
||||
self.external_functions: list[dict[str, Any]] = [] # List of @external functions
|
||||
self.conditionals: list[dict[str, Any]] = [] # List of conditional branches (if/match)
|
||||
self.conditional_map: dict[int, int] = {} # stmt id() -> conditional id
|
||||
self.next_string_id = 0
|
||||
@@ -73,6 +74,13 @@ class SignalLLVMIRGenerator:
|
||||
|
||||
def generate(self) -> str:
|
||||
"""Generate complete LLVM IR module with signals."""
|
||||
# Pre-collect external functions from ALL components before emitting declarations
|
||||
for component in self.components:
|
||||
for stmt in component["body"]:
|
||||
if stmt["type"] == "external_function":
|
||||
if not any(f["name"] == stmt["name"] for f in self.external_functions):
|
||||
self.external_functions.append(stmt)
|
||||
|
||||
# Emit shared header and function declarations once
|
||||
self._emit_header()
|
||||
self._emit_function_declarations()
|
||||
@@ -109,9 +117,13 @@ class SignalLLVMIRGenerator:
|
||||
self._emit_conditional_functions()
|
||||
self._emit_component(component)
|
||||
|
||||
# Emit main entry point for the last (top-level) component
|
||||
if self.components:
|
||||
self._emit_main_entry(self.components[-1]["name"])
|
||||
# Emit main entry point - always looks for RitzMain component
|
||||
ritz_main = next((c for c in self.components if c["name"] == "RitzMain"), None)
|
||||
if not ritz_main:
|
||||
raise ValueError(
|
||||
"No RitzMain component found! Every Ritz file must have a @component def RitzMain() as entry point."
|
||||
)
|
||||
self._emit_main_entry()
|
||||
|
||||
# Prepend string constants
|
||||
return self._finalize_with_strings()
|
||||
@@ -128,6 +140,10 @@ class SignalLLVMIRGenerator:
|
||||
}
|
||||
elif stmt["type"] == "function":
|
||||
self.event_handlers.append(stmt)
|
||||
elif stmt["type"] == "external_function":
|
||||
# Track external functions for declaration (avoid duplicates)
|
||||
if not any(f["name"] == stmt["name"] for f in self.external_functions):
|
||||
self.external_functions.append(stmt)
|
||||
|
||||
def _collect_conditionals(self, component: dict[str, Any]) -> None:
|
||||
"""Scan IR tree for if/match statements and assign IDs."""
|
||||
@@ -425,15 +441,15 @@ class SignalLLVMIRGenerator:
|
||||
self.lines.append("")
|
||||
|
||||
def _emit_reactive_text_globals(self) -> None:
|
||||
"""Emit global storage for reactive text node references."""
|
||||
"""Emit global storage for reactive text node references (scoped to component)."""
|
||||
if not self.reactive_text_nodes:
|
||||
return
|
||||
|
||||
self.lines.append("; Reactive text node storage")
|
||||
self.lines.append(f"; Reactive text node storage for {self.current_component}")
|
||||
for text_info in self.reactive_text_nodes:
|
||||
text_id = text_info["id"]
|
||||
# Store text node reference so we can update its content later
|
||||
self.lines.append(f"@reactive_text_{text_id}_node = global i32 0")
|
||||
# Store text node reference so we can update its content later (scoped to component)
|
||||
self.lines.append(f"@reactive_text_{self.current_component}_{text_id}_node = global i32 0")
|
||||
self.lines.append("")
|
||||
|
||||
def _emit_function_declarations(self) -> None:
|
||||
@@ -452,7 +468,22 @@ class SignalLLVMIRGenerator:
|
||||
self.lines.append("declare void @js_update_text_with_int(i32, i8*, i32)") # Update with prefix + int
|
||||
self.lines.append("declare i32 @js_create_marker()") # Create invisible marker element
|
||||
self.lines.append("declare void @js_replace_at_marker(i32, i32, i32)") # marker, old, new
|
||||
|
||||
# Emit external functions marked with @external decorator
|
||||
if self.external_functions:
|
||||
self.lines.append("")
|
||||
self.lines.append("; External functions (@external decorator)")
|
||||
for ext_func in self.external_functions:
|
||||
func_name = ext_func["name"]
|
||||
# For now, assume all external functions return void
|
||||
# TODO: Support return types via type hints
|
||||
self.lines.append(f"declare void @{func_name}()")
|
||||
|
||||
self.lines.append("")
|
||||
|
||||
def _is_external_function(self, func_name: str) -> bool:
|
||||
"""Check if a function is marked as external."""
|
||||
return any(f["name"] == func_name for f in self.external_functions)
|
||||
|
||||
def _emit_helper_functions(self) -> None:
|
||||
"""Emit helper functions for signal operations."""
|
||||
@@ -535,6 +566,9 @@ class SignalLLVMIRGenerator:
|
||||
self.lines.append(f" call void @{func_name}({arg_list})")
|
||||
else:
|
||||
self.lines.append(f" call void @{func_name}()")
|
||||
elif self._is_external_function(func_name):
|
||||
# External function marked with @external decorator
|
||||
self.lines.append(f" call void @{func_name}()")
|
||||
else:
|
||||
# Unknown function - skip or error
|
||||
pass
|
||||
@@ -629,9 +663,9 @@ class SignalLLVMIRGenerator:
|
||||
if not text_info:
|
||||
return # Text node not found
|
||||
|
||||
# Load the text node reference
|
||||
# Load the text node reference (scoped to component)
|
||||
node_var = self._new_var()
|
||||
self.lines.append(f" %{node_var} = load i32, i32* @reactive_text_{text_id}_node")
|
||||
self.lines.append(f" %{node_var} = load i32, i32* @reactive_text_{self.current_component}_{text_id}_node")
|
||||
|
||||
# Build the f-string text
|
||||
result = self._build_fstring_text(text_info["fstring"])
|
||||
@@ -920,7 +954,10 @@ class SignalLLVMIRGenerator:
|
||||
|
||||
# Render all statements in the branch body
|
||||
for stmt in body:
|
||||
self._emit_statement(stmt)
|
||||
child_var = self._emit_statement(stmt)
|
||||
# Append top-level elements to wrapper
|
||||
if child_var:
|
||||
self.lines.append(f" call void @js_append_child(i32 %{wrapper_var}, i32 %{child_var})")
|
||||
|
||||
# Pop wrapper from parent stack
|
||||
self.parent_element_stack.pop()
|
||||
@@ -930,12 +967,12 @@ class SignalLLVMIRGenerator:
|
||||
self.lines.append("}")
|
||||
self.lines.append("")
|
||||
|
||||
def _emit_main_entry(self, component_name: str) -> None:
|
||||
"""Emit main entry point that renders top-level component and sets root."""
|
||||
def _emit_main_entry(self) -> None:
|
||||
"""Emit main entry point that renders RitzMain component and sets root."""
|
||||
self.lines.append("; Main entry point")
|
||||
self.lines.append("define void @main() {")
|
||||
self.lines.append("entry:")
|
||||
self.lines.append(f" %root = call i32 @render_{component_name}()")
|
||||
self.lines.append(" %root = call i32 @render_RitzMain()")
|
||||
self.lines.append(" call void @js_set_root(i32 %root)")
|
||||
self.lines.append(" ret void")
|
||||
self.lines.append("}")
|
||||
@@ -1373,8 +1410,8 @@ class SignalLLVMIRGenerator:
|
||||
empty_ptr = self._emit_string_ptr("")
|
||||
self.lines.append(f" %{text_var} = call i32 @js_create_text(i8* {empty_ptr})")
|
||||
|
||||
# Store the text node reference for reactive updates
|
||||
self.lines.append(f" store i32 %{text_var}, i32* @reactive_text_{text_id}_node")
|
||||
# Store the text node reference for reactive updates (scoped to component)
|
||||
self.lines.append(f" store i32 %{text_var}, i32* @reactive_text_{self.current_component}_{text_id}_node")
|
||||
|
||||
return text_var
|
||||
else:
|
||||
|
||||
+39
-3
@@ -16,15 +16,31 @@ def parse_component(source: str) -> dict[str, Any] | list[dict[str, Any]]:
|
||||
"""
|
||||
tree = ast.parse(source)
|
||||
|
||||
# Find ALL @component decorated functions
|
||||
# Find @component decorated functions and @external functions
|
||||
components = []
|
||||
external_functions = []
|
||||
|
||||
for node in tree.body: # Only top-level functions, not ast.walk
|
||||
if isinstance(node, ast.FunctionDef) and _has_component_decorator(node):
|
||||
if isinstance(node, ast.FunctionDef):
|
||||
if _has_component_decorator(node):
|
||||
components.append(_transform_component(node))
|
||||
elif _has_external_decorator(node):
|
||||
# Transform external function
|
||||
external_functions.append({
|
||||
"type": "external_function",
|
||||
"name": node.name,
|
||||
"args": [arg.arg for arg in node.args.args],
|
||||
})
|
||||
|
||||
if not components:
|
||||
raise ValueError("No @component decorated function found")
|
||||
|
||||
# Add external functions to all components
|
||||
for component in components:
|
||||
if external_functions:
|
||||
# Prepend external functions to component body
|
||||
component["body"] = external_functions + component["body"]
|
||||
|
||||
# Return single component or list
|
||||
return components[0] if len(components) == 1 else components
|
||||
|
||||
@@ -141,6 +157,9 @@ def _transform_statement(stmt: ast.stmt) -> dict[str, Any]:
|
||||
return _transform_with(stmt)
|
||||
elif isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Yield):
|
||||
return _transform_yield(stmt.value)
|
||||
elif isinstance(stmt, ast.Expr):
|
||||
# Expression statement (e.g., function call like js_toggle_debug_console())
|
||||
return {"type": "expr", "value": _transform_expr(stmt.value)}
|
||||
elif isinstance(stmt, ast.Assign):
|
||||
return _transform_assign(stmt)
|
||||
elif isinstance(stmt, ast.AugAssign):
|
||||
@@ -254,8 +273,25 @@ def _transform_augassign(node: ast.AugAssign) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def _has_external_decorator(node: ast.FunctionDef) -> bool:
|
||||
"""Check if a function has the @external or @bridge decorator."""
|
||||
for decorator in node.decorator_list:
|
||||
if isinstance(decorator, ast.Name) and decorator.id in ("external", "bridge"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _transform_function(node: ast.FunctionDef) -> dict[str, Any]:
|
||||
"""Transform a function definition (e.g., event handlers)."""
|
||||
"""Transform a function definition (e.g., event handlers or external functions)."""
|
||||
# Check if this is an external JavaScript function
|
||||
if _has_external_decorator(node):
|
||||
return {
|
||||
"type": "external_function",
|
||||
"name": node.name,
|
||||
"args": [arg.arg for arg in node.args.args],
|
||||
}
|
||||
|
||||
# Regular function - transform body
|
||||
return {
|
||||
"type": "function",
|
||||
"name": node.name,
|
||||
|
||||
@@ -11,6 +11,16 @@ def component(func):
|
||||
return func
|
||||
|
||||
|
||||
def external(func):
|
||||
"""External decorator - marks a function as implemented in JavaScript."""
|
||||
return func
|
||||
|
||||
|
||||
def bridge(func):
|
||||
"""Bridge decorator - alias for external, marks a JavaScript bridge function."""
|
||||
return func
|
||||
|
||||
|
||||
def signal(initial_value):
|
||||
"""Create a reactive signal with an initial value."""
|
||||
pass
|
||||
@@ -74,6 +84,18 @@ class H3(_Element):
|
||||
pass
|
||||
|
||||
|
||||
class H4(_Element):
|
||||
"""<h4> element."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class Pre(_Element):
|
||||
"""<pre> element."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class Label(_Element):
|
||||
"""<label> element."""
|
||||
|
||||
@@ -155,6 +177,8 @@ class Img(_Element):
|
||||
# Export all for convenience
|
||||
__all__ = [
|
||||
"component",
|
||||
"external",
|
||||
"bridge",
|
||||
"signal",
|
||||
"Div",
|
||||
"P",
|
||||
@@ -163,6 +187,8 @@ __all__ = [
|
||||
"H1",
|
||||
"H2",
|
||||
"H3",
|
||||
"H4",
|
||||
"Pre",
|
||||
"Label",
|
||||
"Input",
|
||||
"Form",
|
||||
|
||||
Reference in new issue
Block a user