Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1575571f3e | ||
|
|
b0e38db22c | ||
|
|
adf95f424d | ||
|
|
c947bcf6e0 | ||
|
|
e09a6dedc8 | ||
|
|
e686903170 | ||
|
|
ac7726df46 | ||
|
|
3540c44abb | ||
|
|
44db8ff022 | ||
|
|
8bf1c38858 | ||
|
|
9f601b1787 | ||
|
|
cdddbf675a | ||
|
|
41474aceda | ||
|
|
cd89f75c1c | ||
|
|
e18baf3106 | ||
|
|
74ff1e7e9f | ||
|
|
0063a8536c | ||
|
|
044eda2293 | ||
|
|
a3cd1df40a | ||
|
|
81d51d5617 | ||
|
|
635d466874 |
No files matched your search
@@ -0,0 +1,163 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Ritz is a WebAssembly UI framework with Python-like syntax that compiles to high-performance WASM while remaining executable in Python for debugging. The framework uses **valid Python syntax** that can execute in CPython for prototyping/debugging, but is designed to compile to WebAssembly for production.
|
||||||
|
|
||||||
|
**Current Status**: Python prototype stage. The WASM compilation pipeline is conceptual/architectural.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Running the Prototype
|
||||||
|
```bash
|
||||||
|
# Run example application showing various components
|
||||||
|
python3 example.py
|
||||||
|
|
||||||
|
# Run simple test
|
||||||
|
python3 test.py
|
||||||
|
|
||||||
|
# Test AST parser
|
||||||
|
python3 ast_parser.py
|
||||||
|
|
||||||
|
# View module system concept
|
||||||
|
python3 module_system.py
|
||||||
|
|
||||||
|
# View compilation pipeline concept
|
||||||
|
python3 compiler.py
|
||||||
|
|
||||||
|
# View debugging concept
|
||||||
|
python3 debug_concept.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Architecture
|
||||||
|
|
||||||
|
### Component Model
|
||||||
|
Components are defined using the `@component` decorator with Python generator syntax (yield-based):
|
||||||
|
|
||||||
|
```python
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(0) # Reactive state
|
||||||
|
|
||||||
|
def increment():
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
with Div(class_='counter'): # Context managers for element nesting
|
||||||
|
yield H1('Counter') # Yield elements/strings/components
|
||||||
|
yield P(f'Count: {count}')
|
||||||
|
yield Button('Click me', onclick=increment)
|
||||||
|
```
|
||||||
|
|
||||||
|
Key patterns:
|
||||||
|
- **Generators**: Components are generator functions that yield children
|
||||||
|
- **Context managers**: Use `with Element():` for nested structures
|
||||||
|
- **Signals**: Reactive state via `signal(initial_value)`
|
||||||
|
- **Mixed content**: Can yield strings, elements, or other components
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
|
||||||
|
**Core Framework Files:**
|
||||||
|
- `framework.py` - Python prototype runtime (Signal, VNode, Element, component decorator)
|
||||||
|
- `ast_parser.py` - Parses Python components using Python's `ast` module to generate IR
|
||||||
|
- `compiler.py` - Complete compilation pipeline concept (Python → IR → LLVM → WASM)
|
||||||
|
- `module_system.py` - Progressive module loading architecture for code splitting
|
||||||
|
|
||||||
|
**Conceptual/Research:**
|
||||||
|
- `debug_concept.py` - Python-in-WASM debugging architecture (DWARF symbols, source maps)
|
||||||
|
- `RESEARCH.md` - Complete technical architecture and implementation roadmap
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `example.py` - Demonstrates component syntax with various examples
|
||||||
|
- `test.py` - Simple test for debugging framework behavior
|
||||||
|
|
||||||
|
### Compilation Pipeline (Conceptual)
|
||||||
|
|
||||||
|
The planned compilation flow:
|
||||||
|
1. **Parser**: Uses Python's `ast` module to parse components (no custom parser needed!)
|
||||||
|
2. **IR Generation**: Transform Python AST → Ritz intermediate representation
|
||||||
|
3. **LLVM Codegen**: Ritz IR → LLVM IR
|
||||||
|
4. **WASM Output**: LLVM IR → WebAssembly with debug symbols (DWARF)
|
||||||
|
5. **Module Splitting**: Split into multiple WASM modules (bootstrap, core, features)
|
||||||
|
|
||||||
|
### Reactive System
|
||||||
|
|
||||||
|
**Signals** are the primitive reactive unit:
|
||||||
|
- Store a value and list of subscribers
|
||||||
|
- When value changes, notify all subscribers
|
||||||
|
- In Python prototype: simple Python class
|
||||||
|
- In WASM target: efficient memory layout with direct DOM node references
|
||||||
|
|
||||||
|
**Key insight**: No virtual DOM. Signals map directly to DOM nodes for updates.
|
||||||
|
|
||||||
|
### Module System Architecture
|
||||||
|
|
||||||
|
Apps split into multiple WASM modules for security and performance:
|
||||||
|
- `bootstrap.wasm` (~50KB) - Login only, no auth required, no business logic
|
||||||
|
- `app-core.wasm` (~200KB) - Main app, requires authentication
|
||||||
|
- `features/*.wasm` - Lazy-loaded feature modules
|
||||||
|
|
||||||
|
**Security benefit**: Unauthenticated users never receive app code containing API endpoints or business logic.
|
||||||
|
|
||||||
|
## Important Implementation Details
|
||||||
|
|
||||||
|
### AST Parsing
|
||||||
|
The framework uses **Python's own `ast` module** to parse components (not a custom parser). This provides:
|
||||||
|
- Zero parsing overhead
|
||||||
|
- Perfect Python compatibility
|
||||||
|
- Automatic syntax error messages
|
||||||
|
- Type hints support
|
||||||
|
|
||||||
|
See `ast_parser.py` for the `RitzASTTransformer` that converts Python AST to Ritz IR.
|
||||||
|
|
||||||
|
### VNode Structure
|
||||||
|
Internal representation during Python execution:
|
||||||
|
- `VNode(tag, props, children)` - Virtual DOM node
|
||||||
|
- `to_html()` - Render to HTML string
|
||||||
|
- `to_ir()` - Convert to intermediate representation for compilation
|
||||||
|
|
||||||
|
### Context Stack
|
||||||
|
`_context_stack` is a global list tracking nested `with Element():` blocks. The `@component` decorator manages this stack to build the component tree.
|
||||||
|
|
||||||
|
## Debugging Philosophy
|
||||||
|
|
||||||
|
**Revolutionary concept**: Debug Python code running in WASM, not JavaScript!
|
||||||
|
- DWARF debug symbols embedded in WASM
|
||||||
|
- Source maps linking WASM instructions → Python source lines
|
||||||
|
- Chrome DevTools can set breakpoints in Python files
|
||||||
|
- Inspect Python variables, not mangled JS
|
||||||
|
|
||||||
|
See `debug_concept.py` and RESEARCH.md sections on debugging.
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
1. **Python-executable, WASM-compilable**: The same code runs in both environments
|
||||||
|
2. **Yield-based composition**: Natural Python syntax for building component trees
|
||||||
|
3. **Fine-grained reactivity**: Signals update DOM directly, no virtual DOM diffing
|
||||||
|
4. **Progressive loading**: Split code into secure, cacheable modules
|
||||||
|
5. **Developer experience**: Debug in Python, ship as WASM
|
||||||
|
|
||||||
|
## Performance Targets
|
||||||
|
|
||||||
|
Based on SolidJS/Leptos research:
|
||||||
|
- Bundle size: < 200KB compressed
|
||||||
|
- Startup time: < 500ms on slow 3G
|
||||||
|
- Update performance: Within 10% of vanilla JS
|
||||||
|
- Memory usage: 70% less than React
|
||||||
|
- DOM mutations: 99% fewer than virtual DOM frameworks
|
||||||
|
|
||||||
|
## When Modifying Code
|
||||||
|
|
||||||
|
1. **Python prototype** (`framework.py`): Currently functional, can be tested with Python
|
||||||
|
2. **Compilation pipeline** (`compiler.py`, `ast_parser.py`): Architectural/conceptual code that demonstrates intended design
|
||||||
|
3. **Module system** (`module_system.py`): Demonstrates progressive loading strategy
|
||||||
|
4. Components must be valid Python and use generator syntax with yield
|
||||||
|
5. All examples should run with `python3 example.py`
|
||||||
|
|
||||||
|
## Key Technical Decisions
|
||||||
|
|
||||||
|
- **Why yield?** Natural Python syntax, composable, clear execution order
|
||||||
|
- **Why compile vs interpret?** 10-100x performance, smaller bundle, type safety
|
||||||
|
- **Why LLVM?** Mature toolchain, excellent WASM backend, optimization passes
|
||||||
|
- **Why Python's ast module?** No custom parser needed, perfect compatibility
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# Ritz Framework TODO
|
||||||
|
|
||||||
|
## ✅ COMPLETED: Phase 1 & 2 - Working Reactive Framework!
|
||||||
|
|
||||||
|
### Phase 1: Hello World (Static Content) ✅
|
||||||
|
- [x] Set up project structure (pyproject.toml, ruff, black, pytest)
|
||||||
|
- [x] Write AST parser tests using Python's `ast` module
|
||||||
|
- [x] Implement AST parser (HelloWorld, props, nesting, augassign)
|
||||||
|
- [x] All parser tests passing (4/4)
|
||||||
|
- [x] Write LLVM IR codegen tests
|
||||||
|
- [x] **Implement LLVM IR code generator** (NOT C - direct LLVM!)
|
||||||
|
- [x] All codegen tests passing (4/4)
|
||||||
|
- [x] Create JavaScript DOM bridge/runtime (ritz-runtime.js)
|
||||||
|
- [x] Create build script (Python → AST → IR → LLVM → clang → WASM)
|
||||||
|
- [x] Write HelloWorld.py component
|
||||||
|
- [x] **✅ MILESTONE: HelloWorld.wasm renders in browser!**
|
||||||
|
|
||||||
|
### Phase 2: Reactive Signals ✅
|
||||||
|
- [x] Parse signal declarations and augmented assignment (+=)
|
||||||
|
- [x] Parse event handlers (onclick)
|
||||||
|
- [x] Parse f-strings with signal references
|
||||||
|
- [x] **Implement signals in WASM memory** (`@signal_name = global i32`)
|
||||||
|
- [x] Generate signal getter/setter functions
|
||||||
|
- [x] Generate exported event handler functions
|
||||||
|
- [x] Bind events in JS runtime to WASM functions
|
||||||
|
- [x] Update DOM when signals change
|
||||||
|
- [x] Write HighFive.py counter component
|
||||||
|
- [x] **✅ MILESTONE: Reactive counter working!**
|
||||||
|
|
||||||
|
### Phase 3: Developer Experience ✅
|
||||||
|
- [x] Clean runtime architecture (RitzRuntime + RitzLoader classes)
|
||||||
|
- [x] Debug bar with Ctrl+` toggle
|
||||||
|
- [x] Clean HTML examples with minimal boilerplate
|
||||||
|
- [x] Dev server with port reuse
|
||||||
|
- [x] Run black and ruff formatters
|
||||||
|
- [x] `from ritz.syntax import *` for clean component syntax
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Current State
|
||||||
|
|
||||||
|
**WORKING:**
|
||||||
|
- Python → AST → Ritz IR → LLVM IR → WASM pipeline
|
||||||
|
- Reactive signals stored in WASM memory
|
||||||
|
- Event handlers exported from WASM
|
||||||
|
- Clean runtime with debug tools
|
||||||
|
- Two working examples (HelloWorld, HighFive)
|
||||||
|
|
||||||
|
**Architecture:**
|
||||||
|
```
|
||||||
|
Python Component
|
||||||
|
↓ ast.parse()
|
||||||
|
Ritz IR
|
||||||
|
↓ LLVM IR Generator (llvm_codegen_signals.py)
|
||||||
|
LLVM IR (.ll file)
|
||||||
|
↓ clang --target=wasm32
|
||||||
|
WebAssembly
|
||||||
|
↓ RitzRuntime (JavaScript)
|
||||||
|
Live Reactive UI
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Next Steps
|
||||||
|
|
||||||
|
### In-Browser Compilation (THE BIG ONE)
|
||||||
|
- [ ] Research: Compile Ritz compiler to WASM (Pyodide vs Rust rewrite)
|
||||||
|
- [ ] Implement `<script type="text/ritz">` loader
|
||||||
|
- [ ] Parse inline Ritz code in browser
|
||||||
|
- [ ] Compile to WASM on-the-fly
|
||||||
|
- [ ] **✅ MILESTONE: Zero build step - write Python in HTML!**
|
||||||
|
|
||||||
|
### More Examples
|
||||||
|
- [ ] TodoMVC example
|
||||||
|
- [ ] Form with multiple inputs
|
||||||
|
- [ ] Nested components example
|
||||||
|
- [ ] List rendering with iteration
|
||||||
|
|
||||||
|
### Better Signal-DOM Binding
|
||||||
|
- [ ] Track signal-to-DOM bindings during render
|
||||||
|
- [ ] Efficient updates (only update bound elements)
|
||||||
|
- [ ] Proper f-string interpolation with signals
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- [ ] Playwright browser tests
|
||||||
|
- [ ] Test HelloWorld renders correctly
|
||||||
|
- [ ] Test HighFive counter increments
|
||||||
|
- [ ] CI/CD pipeline
|
||||||
|
|
||||||
|
### Advanced Features
|
||||||
|
- [ ] Component props/args
|
||||||
|
- [ ] Computed values (derived signals)
|
||||||
|
- [ ] Conditional rendering (if/else)
|
||||||
|
- [ ] List rendering (for loops with keys)
|
||||||
|
- [ ] Nested components (yield from)
|
||||||
|
- [ ] DWARF debug symbols for Python debugging
|
||||||
|
- [ ] Source maps (WASM → Python lines)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Focus
|
||||||
|
|
||||||
|
**JUST COMPLETED:** Full reactive framework with signals in WASM!
|
||||||
|
|
||||||
|
**NEXT UP:** Commit current work, then explore:
|
||||||
|
1. More complex examples
|
||||||
|
2. `<script type="text/ritz">` in-browser compilation
|
||||||
|
3. Better signal tracking and f-string support
|
||||||
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,67 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>HelloWorld - 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);
|
||||||
|
}
|
||||||
|
#app p {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</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>HelloWorld.py</pre>
|
||||||
|
</div>
|
||||||
|
<div class="debug-section">
|
||||||
|
<h4>Pipeline</h4>
|
||||||
|
<pre>Python → AST → IR → LLVM → WASM</pre>
|
||||||
|
</div>
|
||||||
|
<div class="debug-section">
|
||||||
|
<h4>Console Commands</h4>
|
||||||
|
<pre>ritzRuntime.getLog()
|
||||||
|
ritzInstance.exports</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('HelloWorld.wasm', 'HelloWorld');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
new RitzLoader().load('HelloWorld.wasm', 'HelloWorld');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
; Generated by Ritz Framework with Signals
|
||||||
|
; Component: HelloWorld
|
||||||
|
|
||||||
|
; String constants
|
||||||
|
@.str.0 = private unnamed_addr constant [2 x i8] c"p\00"
|
||||||
|
@.str.1 = private unnamed_addr constant [12 x i8] c"Hello World\00"
|
||||||
|
|
||||||
|
; 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*)
|
||||||
|
|
||||||
|
; Component render function
|
||||||
|
define void @render_HelloWorld() {
|
||||||
|
entry:
|
||||||
|
%var0 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.0, i32 0, i32 0))
|
||||||
|
%var1 = call i32 @js_create_text(i8* getelementptr inbounds ([12 x i8], [12 x i8]* @.str.1, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var0, i32 %var1)
|
||||||
|
call void @js_set_root(i32 %var0)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
"""
|
||||||
|
HelloWorld component - The simplest possible Ritz component.
|
||||||
|
|
||||||
|
This demonstrates a static <p> tag with text content.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
|
||||||
|
@component
|
||||||
|
def HelloWorld():
|
||||||
|
"""Render a simple paragraph with 'Hello World'."""
|
||||||
|
with P():
|
||||||
|
yield "Hello World"
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,96 @@
|
|||||||
|
<!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>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
; 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
HighFive counter component - Demonstrates reactive signals and event handlers.
|
||||||
|
|
||||||
|
This component uses a signal to track state and an onclick handler to update it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
|
||||||
|
@component
|
||||||
|
def HighFive():
|
||||||
|
"""A counter that tracks high fives."""
|
||||||
|
high_fives = signal(0)
|
||||||
|
|
||||||
|
def increment():
|
||||||
|
high_fives += 1
|
||||||
|
|
||||||
|
with Div(class_="high-five-app"):
|
||||||
|
with P():
|
||||||
|
yield "Give me a high five! ✋"
|
||||||
|
|
||||||
|
with P(class_="count"):
|
||||||
|
yield f"High fives: {high_fives}"
|
||||||
|
|
||||||
|
with Button(onclick=increment):
|
||||||
|
yield "High Five!"
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,314 @@
|
|||||||
|
<!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,215 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Ritz HelloWorld - Python → 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;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px;
|
||||||
|
background: #e8f4f8;
|
||||||
|
border-left: 4px solid #0066cc;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
background: #fee;
|
||||||
|
border-left-color: #c00;
|
||||||
|
color: #c00;
|
||||||
|
}
|
||||||
|
.success {
|
||||||
|
background: #efe;
|
||||||
|
border-left-color: #0a0;
|
||||||
|
color: #060;
|
||||||
|
}
|
||||||
|
#app {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
.pipeline {
|
||||||
|
margin: 20px 0;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎭 Ritz Framework</h1>
|
||||||
|
<p><strong>Python → LLVM IR → WebAssembly</strong></p>
|
||||||
|
<div class="pipeline">
|
||||||
|
HelloWorld.py → ast.parse() → Ritz IR → LLVM IR → clang → WASM
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status" id="status">⏳ Initializing...</div>
|
||||||
|
|
||||||
|
<div id="app"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Ritz Runtime - provides DOM bridge for WASM
|
||||||
|
class RitzRuntime {
|
||||||
|
constructor(rootElement) {
|
||||||
|
this.rootElement = rootElement;
|
||||||
|
this.nodes = new Map();
|
||||||
|
this.nextNodeId = 1;
|
||||||
|
this.log = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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_set_root(elemId) {
|
||||||
|
const element = this.nodes.get(elemId);
|
||||||
|
if (element) {
|
||||||
|
this.rootElement.innerHTML = '';
|
||||||
|
this.rootElement.appendChild(element);
|
||||||
|
this.log.push(`setRoot(${elemId})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 HelloWorld.wasm...';
|
||||||
|
const response = await fetch('HelloWorld.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),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { instance } = await WebAssembly.instantiate(wasmBytes, imports);
|
||||||
|
wasmInstance = instance; // Store globally for debugging
|
||||||
|
|
||||||
|
// Give runtime access to WASM memory
|
||||||
|
runtime.setMemory(instance.exports.memory);
|
||||||
|
|
||||||
|
status.textContent = '⏳ Rendering component...';
|
||||||
|
|
||||||
|
// Call the render function
|
||||||
|
instance.exports.render_HelloWorld();
|
||||||
|
|
||||||
|
status.textContent = '✅ HelloWorld rendered successfully!';
|
||||||
|
status.className = 'status success';
|
||||||
|
|
||||||
|
// Log the operations
|
||||||
|
console.log('Runtime operations:', runtime.getLog());
|
||||||
|
console.log('🎉 WASM successfully loaded and executed!');
|
||||||
|
console.log('Debug commands:');
|
||||||
|
console.log(' runtime.getLog() - View DOM operations');
|
||||||
|
console.log(' wasmInstance.exports - View WASM exports');
|
||||||
|
console.log(' wasmInstance.exports.render_HelloWorld() - Re-render');
|
||||||
|
|
||||||
|
} 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,131 @@
|
|||||||
|
/* Ritz Debug Bar 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%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-header {
|
||||||
|
background: #252525;
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #3e3e3e;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-header button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #d4d4d4;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-header button:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #2d2d30;
|
||||||
|
border-left: 3px solid #007acc;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.success {
|
||||||
|
border-left-color: #4ec9b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.error {
|
||||||
|
border-left-color: #f48771;
|
||||||
|
color: #f48771;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle hint */
|
||||||
|
.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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* App container default styles */
|
||||||
|
#app {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
transition: margin-right 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-open #app {
|
||||||
|
margin-right: 400px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* Ritz Debug Bar Controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
class RitzDebug {
|
||||||
|
constructor() {
|
||||||
|
this.debugBar = document.getElementById('ritz-debug');
|
||||||
|
this.isOpen = false;
|
||||||
|
|
||||||
|
// Setup keyboard shortcut (Ctrl+`)
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.ctrlKey && e.key === '`') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show hint on first load
|
||||||
|
this.showHint();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.isOpen = !this.isOpen;
|
||||||
|
|
||||||
|
if (this.debugBar) {
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.debugBar.classList.remove('hidden');
|
||||||
|
document.body.classList.add('debug-open');
|
||||||
|
} else {
|
||||||
|
this.debugBar.classList.add('hidden');
|
||||||
|
document.body.classList.remove('debug-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open() {
|
||||||
|
this.isOpen = true;
|
||||||
|
if (this.debugBar) {
|
||||||
|
this.debugBar.classList.remove('hidden');
|
||||||
|
document.body.classList.add('debug-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.isOpen = false;
|
||||||
|
if (this.debugBar) {
|
||||||
|
this.debugBar.classList.add('hidden');
|
||||||
|
document.body.classList.remove('debug-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showHint() {
|
||||||
|
// Show toggle hint for 3 seconds
|
||||||
|
const hint = document.createElement('div');
|
||||||
|
hint.className = 'debug-toggle-hint';
|
||||||
|
hint.textContent = 'Press Ctrl+` to toggle debug';
|
||||||
|
document.body.appendChild(hint);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
hint.remove();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
log(message, type = 'info') {
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
if (status) {
|
||||||
|
status.textContent = message;
|
||||||
|
status.className = 'status ' + type;
|
||||||
|
}
|
||||||
|
console.log(`[Ritz] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize debug controller
|
||||||
|
const ritzDebug = new RitzDebug();
|
||||||
|
|
||||||
|
// Export
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = RitzDebug;
|
||||||
|
} else {
|
||||||
|
window.RitzDebug = RitzDebug;
|
||||||
|
window.ritzDebug = ritzDebug;
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Ritz Framework Demo</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 50px auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
#app {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
border-bottom: 2px solid #333;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
background: #fee;
|
||||||
|
color: #c00;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎭 Ritz Framework</h1>
|
||||||
|
<p>Python components running in WebAssembly</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status" id="status">Loading...</div>
|
||||||
|
|
||||||
|
<div id="app"></div>
|
||||||
|
|
||||||
|
<script src="ritz.js"></script>
|
||||||
|
<script>
|
||||||
|
// Configuration
|
||||||
|
const COMPONENT_NAME = 'HelloWorld'; // Change this to your component name
|
||||||
|
const WASM_FILE = '../examples/HelloWorld.wasm';
|
||||||
|
|
||||||
|
async function loadComponent() {
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
const appRoot = document.getElementById('app');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Initialize Ritz runtime
|
||||||
|
status.textContent = '⏳ Initializing Ritz runtime...';
|
||||||
|
const runtime = new RitzRuntime(appRoot);
|
||||||
|
|
||||||
|
// Load WASM module
|
||||||
|
status.textContent = `⏳ Loading ${WASM_FILE}...`;
|
||||||
|
|
||||||
|
const response = await fetch(WASM_FILE);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch ${WASM_FILE}: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasmBytes = await response.arrayBuffer();
|
||||||
|
|
||||||
|
// Set up Module for Emscripten
|
||||||
|
window.Module = {
|
||||||
|
wasmBinary: wasmBytes,
|
||||||
|
ritz: runtime, // Provide runtime to WASM
|
||||||
|
onRuntimeInitialized: function() {
|
||||||
|
status.textContent = '⏳ Rendering component...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Call the render function
|
||||||
|
const renderFn = Module[`_render_${COMPONENT_NAME}`];
|
||||||
|
if (!renderFn) {
|
||||||
|
throw new Error(`Component render_${COMPONENT_NAME} not found in WASM exports`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the component
|
||||||
|
Module.ccall(`render_${COMPONENT_NAME}`, null, [], []);
|
||||||
|
|
||||||
|
status.textContent = `✅ ${COMPONENT_NAME} rendered successfully!`;
|
||||||
|
status.className = 'status';
|
||||||
|
} catch (error) {
|
||||||
|
status.textContent = `❌ Render error: ${error.message}`;
|
||||||
|
status.className = 'status error';
|
||||||
|
console.error('Render error:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
print: function(text) {
|
||||||
|
console.log('[WASM]', text);
|
||||||
|
},
|
||||||
|
printErr: function(text) {
|
||||||
|
console.error('[WASM]', text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load the Emscripten JS glue code
|
||||||
|
status.textContent = '⏳ Loading WASM runtime...';
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = WASM_FILE.replace('.wasm', '.js');
|
||||||
|
script.onerror = () => {
|
||||||
|
throw new Error('Failed to load WASM JS glue code');
|
||||||
|
};
|
||||||
|
document.body.appendChild(script);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
status.textContent = `❌ Error: ${error.message}`;
|
||||||
|
status.className = 'status error';
|
||||||
|
console.error('Load error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start loading when DOM is ready
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', loadComponent);
|
||||||
|
} else {
|
||||||
|
loadComponent();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/* Ritz Debug Bar 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%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-header {
|
||||||
|
background: #252525;
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #3e3e3e;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-header button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #d4d4d4;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-header button:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #2d2d30;
|
||||||
|
border-left: 3px solid #007acc;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.success {
|
||||||
|
border-left-color: #4ec9b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.error {
|
||||||
|
border-left-color: #f48771;
|
||||||
|
color: #f48771;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle hint */
|
||||||
|
.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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* App container default styles */
|
||||||
|
#app {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
transition: margin-right 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-open #app {
|
||||||
|
margin-right: 400px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* Ritz Debug Bar Controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
class RitzDebug {
|
||||||
|
constructor() {
|
||||||
|
this.debugBar = document.getElementById('ritz-debug');
|
||||||
|
this.isOpen = false;
|
||||||
|
|
||||||
|
// Setup keyboard shortcut (Ctrl+`)
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.ctrlKey && e.key === '`') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show hint on first load
|
||||||
|
this.showHint();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.isOpen = !this.isOpen;
|
||||||
|
|
||||||
|
if (this.debugBar) {
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.debugBar.classList.remove('hidden');
|
||||||
|
document.body.classList.add('debug-open');
|
||||||
|
} else {
|
||||||
|
this.debugBar.classList.add('hidden');
|
||||||
|
document.body.classList.remove('debug-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open() {
|
||||||
|
this.isOpen = true;
|
||||||
|
if (this.debugBar) {
|
||||||
|
this.debugBar.classList.remove('hidden');
|
||||||
|
document.body.classList.add('debug-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.isOpen = false;
|
||||||
|
if (this.debugBar) {
|
||||||
|
this.debugBar.classList.add('hidden');
|
||||||
|
document.body.classList.remove('debug-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showHint() {
|
||||||
|
// Show toggle hint for 3 seconds
|
||||||
|
const hint = document.createElement('div');
|
||||||
|
hint.className = 'debug-toggle-hint';
|
||||||
|
hint.textContent = 'Press Ctrl+` to toggle debug';
|
||||||
|
document.body.appendChild(hint);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
hint.remove();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
log(message, type = 'info') {
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
if (status) {
|
||||||
|
status.textContent = message;
|
||||||
|
status.className = 'status ' + type;
|
||||||
|
}
|
||||||
|
console.log(`[Ritz] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize debug controller
|
||||||
|
const ritzDebug = new RitzDebug();
|
||||||
|
|
||||||
|
// Export
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = RitzDebug;
|
||||||
|
} else {
|
||||||
|
window.RitzDebug = RitzDebug;
|
||||||
|
window.ritzDebug = ritzDebug;
|
||||||
|
}
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
/**
|
||||||
|
* Ritz Runtime - WebAssembly DOM Bridge
|
||||||
|
*
|
||||||
|
* Provides the runtime environment for Ritz components compiled to WASM.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class RitzRuntime {
|
||||||
|
constructor(rootElement) {
|
||||||
|
this.rootElement = rootElement || document.getElementById('app');
|
||||||
|
this.nodes = new Map();
|
||||||
|
this.nextNodeId = 1;
|
||||||
|
this.log = [];
|
||||||
|
this.wasmInstance = null;
|
||||||
|
this.debugEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== DOM Manipulation Bridge =====
|
||||||
|
|
||||||
|
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(`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(`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(`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(`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, () => {
|
||||||
|
this._log(`[Event] ${eventName} → ${handlerName}()`);
|
||||||
|
handler();
|
||||||
|
this.updateSignals();
|
||||||
|
});
|
||||||
|
this._log(`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(`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(`updateTextContent(${nodeId}, "${text}")`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Signal Management =====
|
||||||
|
|
||||||
|
updateSignals() {
|
||||||
|
// Discover and update all signals
|
||||||
|
if (!this.wasmInstance) return;
|
||||||
|
|
||||||
|
const exports = this.wasmInstance.exports;
|
||||||
|
|
||||||
|
// Find all get_* functions (signal getters)
|
||||||
|
for (const [name, fn] of Object.entries(exports)) {
|
||||||
|
if (name.startsWith('get_') && typeof fn === 'function') {
|
||||||
|
const signalName = name.substring(4); // Remove 'get_' prefix
|
||||||
|
const value = fn();
|
||||||
|
this._log(`[Signal] ${signalName} = ${value}`);
|
||||||
|
|
||||||
|
// Update DOM elements bound to this signal
|
||||||
|
// TODO: Track signal-to-DOM bindings during render for efficiency
|
||||||
|
this.updateSignalDisplay(signalName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-evaluate all reactive conditionals
|
||||||
|
for (const [name, fn] of Object.entries(exports)) {
|
||||||
|
if (name.startsWith('reevaluate_conditional_') && typeof fn === 'function') {
|
||||||
|
fn();
|
||||||
|
this._log(`[Reactive] ${name}()`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-evaluate all reactive match statements
|
||||||
|
for (const [name, fn] of Object.entries(exports)) {
|
||||||
|
if (name.startsWith('reevaluate_match_') && typeof fn === 'function') {
|
||||||
|
fn();
|
||||||
|
this._log(`[Reactive] ${name}()`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
const selectors = [
|
||||||
|
`[data-signal="${signalName}"]`,
|
||||||
|
`.${signalName}`,
|
||||||
|
'.count', // Hardcoded for demo - TODO: track bindings properly
|
||||||
|
];
|
||||||
|
|
||||||
|
selectors.forEach(selector => {
|
||||||
|
const elements = document.querySelectorAll(selector);
|
||||||
|
elements.forEach(el => {
|
||||||
|
// Check if text contains the signal name or looks like a counter
|
||||||
|
const text = el.textContent;
|
||||||
|
if (text.includes(signalName) || text.match(/\d+/)) {
|
||||||
|
// Replace the number in the text
|
||||||
|
el.textContent = text.replace(/\d+/, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Reactive Conditional Rendering =====
|
||||||
|
|
||||||
|
js_create_marker() {
|
||||||
|
// Create a comment node as a marker for conditional insertion
|
||||||
|
const marker = document.createComment('conditional-marker');
|
||||||
|
const id = this.nextNodeId++;
|
||||||
|
this.nodes.set(id, marker);
|
||||||
|
this._log(`Created marker node ${id}`);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
js_replace_at_marker(markerId, oldNodeId, newNodeId) {
|
||||||
|
// Replace DOM node at marker position
|
||||||
|
const marker = this.nodes.get(markerId);
|
||||||
|
if (!marker) {
|
||||||
|
console.error(`[replace_at_marker] Marker ${markerId} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = marker.parentNode;
|
||||||
|
if (!parent) {
|
||||||
|
console.error(`[replace_at_marker] Marker ${markerId} has no parent`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._log(`[replace_at_marker] marker=${markerId}, old=${oldNodeId}, new=${newNodeId}`);
|
||||||
|
|
||||||
|
// Remove old node if it exists (0 means no old node to remove)
|
||||||
|
if (oldNodeId !== 0) {
|
||||||
|
const oldNode = this.nodes.get(oldNodeId);
|
||||||
|
if (!oldNode) {
|
||||||
|
console.warn(`[replace_at_marker] Old node ${oldNodeId} not found in registry`);
|
||||||
|
} else if (!oldNode.parentNode) {
|
||||||
|
console.warn(`[replace_at_marker] Old node ${oldNodeId} not in DOM`);
|
||||||
|
} else if (oldNode.parentNode !== parent) {
|
||||||
|
console.warn(`[replace_at_marker] Old node ${oldNodeId} in wrong parent`);
|
||||||
|
oldNode.parentNode.removeChild(oldNode);
|
||||||
|
this._log(`[replace_at_marker] Removed ${oldNodeId} from wrong parent`);
|
||||||
|
} else {
|
||||||
|
// Remove from correct parent
|
||||||
|
parent.removeChild(oldNode);
|
||||||
|
this._log(`[replace_at_marker] Removed ${oldNodeId} from correct parent`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert new node after marker (only if newNodeId is not 0)
|
||||||
|
if (newNodeId !== 0) {
|
||||||
|
const newNode = this.nodes.get(newNodeId);
|
||||||
|
if (!newNode) {
|
||||||
|
console.error(`[replace_at_marker] New node ${newNodeId} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert right after the marker
|
||||||
|
// If newNode is already in DOM elsewhere, this will move it
|
||||||
|
const nextSibling = marker.nextSibling;
|
||||||
|
if (nextSibling) {
|
||||||
|
parent.insertBefore(newNode, nextSibling);
|
||||||
|
this._log(`[replace_at_marker] Inserted ${newNodeId} before sibling`);
|
||||||
|
} else {
|
||||||
|
parent.appendChild(newNode);
|
||||||
|
this._log(`[replace_at_marker] Appended ${newNodeId} (no sibling)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Memory & String Utilities =====
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Logging & Debug =====
|
||||||
|
|
||||||
|
_log(message) {
|
||||||
|
if (this.debugEnabled) {
|
||||||
|
this.log.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLog() {
|
||||||
|
return this.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLog() {
|
||||||
|
this.log = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RitzLoader - Simplified WASM loading
|
||||||
|
*/
|
||||||
|
class RitzLoader {
|
||||||
|
constructor(options = {}) {
|
||||||
|
this.rootElement = options.rootElement || document.getElementById('app');
|
||||||
|
this.statusElement = options.statusElement || document.getElementById('status');
|
||||||
|
this.onLoad = options.onLoad || (() => {});
|
||||||
|
this.onError = options.onError || ((err) => console.error(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(wasmPath, componentName, props = {}) {
|
||||||
|
try {
|
||||||
|
this.setStatus('⏳ Creating runtime...');
|
||||||
|
const runtime = new RitzRuntime(this.rootElement);
|
||||||
|
|
||||||
|
this.setStatus(`⏳ Loading ${wasmPath}...`);
|
||||||
|
const response = await fetch(wasmPath);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to load ${wasmPath}: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasmBytes = await response.arrayBuffer();
|
||||||
|
|
||||||
|
this.setStatus('⏳ Instantiating WASM...');
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
js_create_marker: () => runtime.js_create_marker(),
|
||||||
|
js_replace_at_marker: (m, o, n) => runtime.js_replace_at_marker(m, o, n),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { instance } = await WebAssembly.instantiate(wasmBytes, imports);
|
||||||
|
|
||||||
|
// Setup runtime
|
||||||
|
runtime.setMemory(instance.exports.memory);
|
||||||
|
runtime.setWasmInstance(instance);
|
||||||
|
|
||||||
|
this.setStatus(`⏳ Rendering ${componentName}...`);
|
||||||
|
|
||||||
|
// Call the main entry point (which renders top-level component and sets root)
|
||||||
|
const mainFn = instance.exports.main;
|
||||||
|
if (!mainFn) {
|
||||||
|
throw new Error(`main function not found in WASM exports`);
|
||||||
|
}
|
||||||
|
|
||||||
|
mainFn();
|
||||||
|
|
||||||
|
// Update signals
|
||||||
|
runtime.updateSignals();
|
||||||
|
|
||||||
|
this.setStatus(`✅ ${componentName} loaded!`, 'success');
|
||||||
|
|
||||||
|
// Make runtime globally available for debugging
|
||||||
|
window.ritzRuntime = runtime;
|
||||||
|
window.ritzInstance = instance;
|
||||||
|
|
||||||
|
this.onLoad(runtime, instance);
|
||||||
|
|
||||||
|
return { runtime, instance };
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.setStatus(`❌ Error: ${error.message}`, 'error');
|
||||||
|
this.onError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus(message, type = '') {
|
||||||
|
if (this.statusElement) {
|
||||||
|
this.statusElement.textContent = message;
|
||||||
|
this.statusElement.className = 'status ' + type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Export for use in modules or global scope
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = { RitzRuntime, RitzLoader };
|
||||||
|
} else {
|
||||||
|
window.RitzRuntime = RitzRuntime;
|
||||||
|
window.RitzLoader = RitzLoader;
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Ritz Runtime - DOM Bridge for WASM
|
||||||
|
*
|
||||||
|
* Provides DOM manipulation functions called by WASM code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class RitzRuntime {
|
||||||
|
constructor(rootElement) {
|
||||||
|
this.rootElement = rootElement || document.body;
|
||||||
|
this.nodes = new Map(); // Map of node IDs to DOM nodes
|
||||||
|
this.nextNodeId = 1;
|
||||||
|
this.rootNodeId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a DOM element and return its ID
|
||||||
|
*/
|
||||||
|
createElement(tag) {
|
||||||
|
const element = document.createElement(tag);
|
||||||
|
const id = this.nextNodeId++;
|
||||||
|
this.nodes.set(id, element);
|
||||||
|
console.log(`[Ritz] Created element <${tag}> with ID ${id}`);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a text node and return its ID
|
||||||
|
*/
|
||||||
|
createTextNode(text) {
|
||||||
|
const textNode = document.createTextNode(text);
|
||||||
|
const id = this.nextNodeId++;
|
||||||
|
this.nodes.set(id, textNode);
|
||||||
|
console.log(`[Ritz] Created text node "${text}" with ID ${id}`);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append child to parent
|
||||||
|
*/
|
||||||
|
appendChild(parentId, childId) {
|
||||||
|
const parent = this.nodes.get(parentId);
|
||||||
|
const child = this.nodes.get(childId);
|
||||||
|
|
||||||
|
if (!parent || !child) {
|
||||||
|
console.error(`[Ritz] appendChild failed: parent=${parentId}, child=${childId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.appendChild(child);
|
||||||
|
console.log(`[Ritz] Appended node ${childId} to node ${parentId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an attribute on an element
|
||||||
|
*/
|
||||||
|
setAttribute(elemId, name, value) {
|
||||||
|
const element = this.nodes.get(elemId);
|
||||||
|
|
||||||
|
if (!element) {
|
||||||
|
console.error(`[Ritz] setAttribute failed: element=${elemId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle special cases
|
||||||
|
if (name === 'class') {
|
||||||
|
element.className = value;
|
||||||
|
} else {
|
||||||
|
element.setAttribute(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[Ritz] Set attribute ${name}="${value}" on node ${elemId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the root element and mount it to DOM
|
||||||
|
*/
|
||||||
|
setRoot(elemId) {
|
||||||
|
const element = this.nodes.get(elemId);
|
||||||
|
|
||||||
|
if (!element) {
|
||||||
|
console.error(`[Ritz] setRoot failed: element=${elemId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rootNodeId = elemId;
|
||||||
|
|
||||||
|
// Clear the root element
|
||||||
|
this.rootElement.innerHTML = '';
|
||||||
|
|
||||||
|
// Mount the component
|
||||||
|
this.rootElement.appendChild(element);
|
||||||
|
|
||||||
|
console.log(`[Ritz] Mounted component (node ${elemId}) to DOM`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all nodes (for hot reload)
|
||||||
|
*/
|
||||||
|
clear() {
|
||||||
|
this.nodes.clear();
|
||||||
|
this.nextNodeId = 1;
|
||||||
|
this.rootNodeId = null;
|
||||||
|
this.rootElement.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for use in module or global scope
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = RitzRuntime;
|
||||||
|
} else {
|
||||||
|
window.RitzRuntime = RitzRuntime;
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,249 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Tabs - Multi-Panel Switcher - Ritz Framework</title>
|
||||||
|
<link rel="stylesheet" href="../runtime/ritz-debug.css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: system-ui, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#app {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 50px auto;
|
||||||
|
padding: 0;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.tabs-container {
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
.tabs-container h1 {
|
||||||
|
color: #667eea;
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.subtitle {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0 0 30px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab Menu - ul/li structure */
|
||||||
|
.tab-menu {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
border-bottom: 2px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
.tab-menu .tab {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.tab-menu .tab button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px 20px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #666;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.tab-menu .tab button:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
.tab-menu .tab.active button {
|
||||||
|
color: #667eea;
|
||||||
|
border-bottom-color: #667eea;
|
||||||
|
background: #f8f9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab Content */
|
||||||
|
.tab-content {
|
||||||
|
min-height: 300px;
|
||||||
|
padding: 30px;
|
||||||
|
background: #f8f9ff;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.tab-pane h2 {
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
color: #667eea;
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
.tab-pane p {
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #444;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.tab-pane ul {
|
||||||
|
margin: 20px 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
.tab-pane li {
|
||||||
|
margin: 8px 0;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specific pane styles */
|
||||||
|
.tab-pane.home {
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
}
|
||||||
|
.tab-pane.profile {
|
||||||
|
border-left: 4px solid #764ba2;
|
||||||
|
}
|
||||||
|
.tab-pane.settings {
|
||||||
|
border-left: 4px solid #f093fb;
|
||||||
|
}
|
||||||
|
.tab-pane.error {
|
||||||
|
border-left: 4px solid #f5576c;
|
||||||
|
background: #fff5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-info {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
.profile-info p {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
.profile-info p:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Debug footer */
|
||||||
|
.debug-footer {
|
||||||
|
margin-top: 30px;
|
||||||
|
padding: 15px;
|
||||||
|
background: #e8ebff;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.debug-footer p {
|
||||||
|
margin: 0;
|
||||||
|
font-family: monospace;
|
||||||
|
color: #667eea;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Vertical Tabs (nested inside Settings) */
|
||||||
|
.vertical-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.vertical-tab-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
.vertical-tab {
|
||||||
|
padding: 12px 20px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border: none;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.vertical-tab:hover {
|
||||||
|
background: #e8ebff;
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
.vertical-tab.active {
|
||||||
|
background: #f8f9ff;
|
||||||
|
border-left-color: #667eea;
|
||||||
|
color: #667eea;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.vertical-tab-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.setting-pane h3 {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
.setting-pane p {
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 8px;
|
||||||
|
background: #f8f9ff;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
.tab-pane {
|
||||||
|
animation: fadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
</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>Tabs.py (Multi-Panel Switcher)</pre>
|
||||||
|
</div>
|
||||||
|
<div class="debug-section">
|
||||||
|
<h4>Features</h4>
|
||||||
|
<pre>✓ Python match/case in WASM
|
||||||
|
✓ Beautiful ul/li tab structure
|
||||||
|
✓ Conditional CSS classes (active state)
|
||||||
|
✓ Component composition
|
||||||
|
✓ All logic in WASM</pre>
|
||||||
|
</div>
|
||||||
|
<div class="debug-section">
|
||||||
|
<h4>Console Commands</h4>
|
||||||
|
<pre>ritzRuntime.getLog()
|
||||||
|
ritzInstance.exports.get_active_tab()
|
||||||
|
ritzInstance.exports.go_home()</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('Tabs.wasm', 'Tabs', { orientation: 0 });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
new RitzLoader().load('Tabs.wasm', 'Tabs', { orientation: 0 });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,933 @@
|
|||||||
|
; Generated by Ritz Framework with Signals
|
||||||
|
; Components: VerticalTabs, Tabs
|
||||||
|
|
||||||
|
; String constants
|
||||||
|
@.str.0 = private unnamed_addr constant [20 x i8] c"vertical-tab active\00"
|
||||||
|
@.str.1 = private unnamed_addr constant [13 x i8] c"vertical-tab\00"
|
||||||
|
@.str.2 = private unnamed_addr constant [6 x i8] c"class\00"
|
||||||
|
@.str.3 = private unnamed_addr constant [4 x i8] c"div\00"
|
||||||
|
@.str.4 = private unnamed_addr constant [13 x i8] c"setting-pane\00"
|
||||||
|
@.str.5 = private unnamed_addr constant [3 x i8] c"h3\00"
|
||||||
|
@.str.6 = private unnamed_addr constant [24 x i8] c"⚙️ General Settings\00"
|
||||||
|
@.str.7 = private unnamed_addr constant [2 x i8] c"p\00"
|
||||||
|
@.str.8 = private unnamed_addr constant [18 x i8] c"Language: English\00"
|
||||||
|
@.str.9 = private unnamed_addr constant [15 x i8] c"Time Zone: UTC\00"
|
||||||
|
@.str.10 = private unnamed_addr constant [22 x i8] c"🔒 Privacy Settings\00"
|
||||||
|
@.str.11 = private unnamed_addr constant [26 x i8] c"Data Collection: Disabled\00"
|
||||||
|
@.str.12 = private unnamed_addr constant [19 x i8] c"Analytics: Opt-out\00"
|
||||||
|
@.str.13 = private unnamed_addr constant [23 x i8] c"🚀 Advanced Settings\00"
|
||||||
|
@.str.14 = private unnamed_addr constant [16 x i8] c"Debug Mode: Off\00"
|
||||||
|
@.str.15 = private unnamed_addr constant [32 x i8] c"Experimental Features: Disabled\00"
|
||||||
|
@.str.16 = private unnamed_addr constant [16 x i8] c"Invalid setting\00"
|
||||||
|
@.str.17 = private unnamed_addr constant [14 x i8] c"vertical-tabs\00"
|
||||||
|
@.str.18 = private unnamed_addr constant [18 x i8] c"vertical-tab-menu\00"
|
||||||
|
@.str.19 = private unnamed_addr constant [7 x i8] c"button\00"
|
||||||
|
@.str.20 = private unnamed_addr constant [6 x i8] c"click\00"
|
||||||
|
@.str.21 = private unnamed_addr constant [23 x i8] c"_lambda_0_VerticalTabs\00"
|
||||||
|
@.str.22 = private unnamed_addr constant [15 x i8] c"⚙️ General\00"
|
||||||
|
@.str.23 = private unnamed_addr constant [23 x i8] c"_lambda_1_VerticalTabs\00"
|
||||||
|
@.str.24 = private unnamed_addr constant [13 x i8] c"🔒 Privacy\00"
|
||||||
|
@.str.25 = private unnamed_addr constant [23 x i8] c"_lambda_2_VerticalTabs\00"
|
||||||
|
@.str.26 = private unnamed_addr constant [14 x i8] c"🚀 Advanced\00"
|
||||||
|
@.str.27 = private unnamed_addr constant [21 x i8] c"vertical-tab-content\00"
|
||||||
|
@.str.28 = private unnamed_addr constant [11 x i8] c"tab active\00"
|
||||||
|
@.str.29 = private unnamed_addr constant [4 x i8] c"tab\00"
|
||||||
|
@.str.30 = private unnamed_addr constant [14 x i8] c"tab-pane home\00"
|
||||||
|
@.str.31 = private unnamed_addr constant [3 x i8] c"h2\00"
|
||||||
|
@.str.32 = private unnamed_addr constant [18 x i8] c"🏠 Welcome Home\00"
|
||||||
|
@.str.33 = private unnamed_addr constant [29 x i8] c"This is your home dashboard.\00"
|
||||||
|
@.str.34 = private unnamed_addr constant [37 x i8] c"All your important info at a glance.\00"
|
||||||
|
@.str.35 = private unnamed_addr constant [3 x i8] c"ul\00"
|
||||||
|
@.str.36 = private unnamed_addr constant [3 x i8] c"li\00"
|
||||||
|
@.str.37 = private unnamed_addr constant [20 x i8] c"✓ Recent activity\00"
|
||||||
|
@.str.38 = private unnamed_addr constant [18 x i8] c"✓ Quick actions\00"
|
||||||
|
@.str.39 = private unnamed_addr constant [18 x i8] c"✓ Summary stats\00"
|
||||||
|
@.str.40 = private unnamed_addr constant [17 x i8] c"tab-pane profile\00"
|
||||||
|
@.str.41 = private unnamed_addr constant [18 x i8] c"👤 Your Profile\00"
|
||||||
|
@.str.42 = private unnamed_addr constant [30 x i8] c"Manage your account settings.\00"
|
||||||
|
@.str.43 = private unnamed_addr constant [13 x i8] c"profile-info\00"
|
||||||
|
@.str.44 = private unnamed_addr constant [16 x i8] c"Name: Ritz User\00"
|
||||||
|
@.str.45 = private unnamed_addr constant [21 x i8] c"Email: user@ritz.dev\00"
|
||||||
|
@.str.46 = private unnamed_addr constant [19 x i8] c"Member since: 2025\00"
|
||||||
|
@.str.47 = private unnamed_addr constant [18 x i8] c"tab-pane settings\00"
|
||||||
|
@.str.48 = private unnamed_addr constant [16 x i8] c"⚙️ Settings\00"
|
||||||
|
@.str.49 = private unnamed_addr constant [55 x i8] c"Configure your preferences using the categories below:\00"
|
||||||
|
@.str.50 = private unnamed_addr constant [15 x i8] c"tab-pane error\00"
|
||||||
|
@.str.51 = private unnamed_addr constant [28 x i8] c"⚠️ Invalid tab selected\00"
|
||||||
|
@.str.52 = private unnamed_addr constant [15 x i8] c"tabs-container\00"
|
||||||
|
@.str.53 = private unnamed_addr constant [3 x i8] c"h1\00"
|
||||||
|
@.str.54 = private unnamed_addr constant [19 x i8] c"Tab Component Demo\00"
|
||||||
|
@.str.55 = private unnamed_addr constant [9 x i8] c"subtitle\00"
|
||||||
|
@.str.56 = private unnamed_addr constant [58 x i8] c"Beautiful composition with TabMenu and TabPane components\00"
|
||||||
|
@.str.57 = private unnamed_addr constant [9 x i8] c"tab-menu\00"
|
||||||
|
@.str.58 = private unnamed_addr constant [15 x i8] c"_lambda_0_Tabs\00"
|
||||||
|
@.str.59 = private unnamed_addr constant [10 x i8] c"🏠 Home\00"
|
||||||
|
@.str.60 = private unnamed_addr constant [15 x i8] c"_lambda_1_Tabs\00"
|
||||||
|
@.str.61 = private unnamed_addr constant [13 x i8] c"👤 Profile\00"
|
||||||
|
@.str.62 = private unnamed_addr constant [15 x i8] c"_lambda_2_Tabs\00"
|
||||||
|
@.str.63 = private unnamed_addr constant [12 x i8] c"tab-content\00"
|
||||||
|
@.str.64 = private unnamed_addr constant [13 x i8] c"debug-footer\00"
|
||||||
|
@.str.65 = private unnamed_addr constant [2 x i8] c"0\00"
|
||||||
|
|
||||||
|
; 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*)
|
||||||
|
declare i32 @js_create_marker()
|
||||||
|
declare void @js_replace_at_marker(i32, i32, i32)
|
||||||
|
|
||||||
|
; Signal storage for VerticalTabs (reactive state in WASM memory)
|
||||||
|
@signal_VerticalTabs_setting_tab = global i32 0
|
||||||
|
|
||||||
|
; Conditional rendering state
|
||||||
|
@cond_0_marker = global i32 0
|
||||||
|
@cond_0_current = global i32 0
|
||||||
|
@cond_0_case_0_root = global i32 0
|
||||||
|
@cond_0_case_1_root = global i32 0
|
||||||
|
@cond_0_case_2_root = global i32 0
|
||||||
|
@cond_0_case_3_root = global i32 0
|
||||||
|
|
||||||
|
; Reactive attribute element storage
|
||||||
|
@reactive_attr_0_elem = global i32 0
|
||||||
|
@reactive_attr_1_elem = global i32 0
|
||||||
|
@reactive_attr_2_elem = global i32 0
|
||||||
|
|
||||||
|
; Signal helper functions
|
||||||
|
define i32 @get_setting_tab() {
|
||||||
|
entry:
|
||||||
|
%val = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
ret i32 %val
|
||||||
|
}
|
||||||
|
|
||||||
|
define void @set_setting_tab(i32 %newval) {
|
||||||
|
entry:
|
||||||
|
store i32 %newval, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
; TODO: Trigger DOM update callback
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: _lambda_0_VerticalTabs
|
||||||
|
define void @_lambda_0_VerticalTabs() {
|
||||||
|
entry:
|
||||||
|
call void @set_setting(i32 0)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: _lambda_1_VerticalTabs
|
||||||
|
define void @_lambda_1_VerticalTabs() {
|
||||||
|
entry:
|
||||||
|
call void @set_setting(i32 1)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: _lambda_2_VerticalTabs
|
||||||
|
define void @_lambda_2_VerticalTabs() {
|
||||||
|
entry:
|
||||||
|
call void @set_setting(i32 2)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: set_setting
|
||||||
|
define void @set_setting(i32 %value) {
|
||||||
|
entry:
|
||||||
|
store i32 %value, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
call void @reevaluate_conditional_0()
|
||||||
|
%var0 = load i32, i32* @reactive_attr_0_elem
|
||||||
|
%var1 = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
%var2 = icmp eq i32 %var1, 0
|
||||||
|
%var3 = zext i1 %var2 to i32
|
||||||
|
%var4 = icmp ne i32 %var3, 0
|
||||||
|
%var5 = select i1 %var4, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.0, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.1, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var0, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* %var5)
|
||||||
|
%var6 = load i32, i32* @reactive_attr_1_elem
|
||||||
|
%var7 = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
%var8 = icmp eq i32 %var7, 1
|
||||||
|
%var9 = zext i1 %var8 to i32
|
||||||
|
%var10 = icmp ne i32 %var9, 0
|
||||||
|
%var11 = select i1 %var10, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.0, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.1, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var6, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* %var11)
|
||||||
|
%var12 = load i32, i32* @reactive_attr_2_elem
|
||||||
|
%var13 = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
%var14 = icmp eq i32 %var13, 2
|
||||||
|
%var15 = zext i1 %var14 to i32
|
||||||
|
%var16 = icmp ne i32 %var15, 0
|
||||||
|
%var17 = select i1 %var16, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.0, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.1, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var12, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* %var17)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Initialize conditional 0 (match)
|
||||||
|
define void @_init_conditional_0() {
|
||||||
|
entry:
|
||||||
|
; Render all cases upfront
|
||||||
|
%var18 = call i32 @_render_cond_0_case_0()
|
||||||
|
store i32 %var18, i32* @cond_0_case_0_root
|
||||||
|
%var19 = call i32 @_render_cond_0_case_1()
|
||||||
|
store i32 %var19, i32* @cond_0_case_1_root
|
||||||
|
%var20 = call i32 @_render_cond_0_case_2()
|
||||||
|
store i32 %var20, i32* @cond_0_case_2_root
|
||||||
|
%var21 = call i32 @_render_cond_0_case_3()
|
||||||
|
store i32 %var21, i32* @cond_0_case_3_root
|
||||||
|
%var22 = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
%var23 = load i32, i32* @cond_0_marker
|
||||||
|
switch i32 %var22, label %insert_case_3 [
|
||||||
|
i32 0, label %insert_case_0
|
||||||
|
i32 1, label %insert_case_1
|
||||||
|
i32 2, label %insert_case_2
|
||||||
|
]
|
||||||
|
insert_case_0:
|
||||||
|
call void @js_replace_at_marker(i32 %var23, i32 0, i32 %var18)
|
||||||
|
store i32 0, i32* @cond_0_current
|
||||||
|
ret void
|
||||||
|
insert_case_1:
|
||||||
|
call void @js_replace_at_marker(i32 %var23, i32 0, i32 %var19)
|
||||||
|
store i32 1, i32* @cond_0_current
|
||||||
|
ret void
|
||||||
|
insert_case_2:
|
||||||
|
call void @js_replace_at_marker(i32 %var23, i32 0, i32 %var20)
|
||||||
|
store i32 2, i32* @cond_0_current
|
||||||
|
ret void
|
||||||
|
insert_case_3:
|
||||||
|
call void @js_replace_at_marker(i32 %var23, i32 0, i32 %var21)
|
||||||
|
store i32 3, i32* @cond_0_current
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Re-evaluate conditional 0 (match)
|
||||||
|
define void @reevaluate_conditional_0() {
|
||||||
|
entry:
|
||||||
|
%var24 = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
%var25 = load i32, i32* @cond_0_current
|
||||||
|
switch i32 %var24, label %case_3 [
|
||||||
|
i32 0, label %case_0
|
||||||
|
i32 1, label %case_1
|
||||||
|
i32 2, label %case_2
|
||||||
|
]
|
||||||
|
case_0:
|
||||||
|
%var26 = icmp ne i32 %var25, 0
|
||||||
|
br i1 %var26, label %swap_0, label %done
|
||||||
|
swap_0:
|
||||||
|
%var27 = load i32, i32* @cond_0_marker
|
||||||
|
%var28 = load i32, i32* @cond_0_case_0_root
|
||||||
|
switch i32 %var25, label %load_old_0_3 [
|
||||||
|
i32 0, label %load_old_0_0
|
||||||
|
i32 1, label %load_old_0_1
|
||||||
|
i32 2, label %load_old_0_2
|
||||||
|
i32 3, label %load_old_0_3
|
||||||
|
]
|
||||||
|
load_old_0_0:
|
||||||
|
%var30 = load i32, i32* @cond_0_case_0_root
|
||||||
|
br label %merge_0
|
||||||
|
load_old_0_1:
|
||||||
|
%var31 = load i32, i32* @cond_0_case_1_root
|
||||||
|
br label %merge_0
|
||||||
|
load_old_0_2:
|
||||||
|
%var32 = load i32, i32* @cond_0_case_2_root
|
||||||
|
br label %merge_0
|
||||||
|
load_old_0_3:
|
||||||
|
%var33 = load i32, i32* @cond_0_case_3_root
|
||||||
|
br label %merge_0
|
||||||
|
merge_0:
|
||||||
|
%var29 = phi i32 [ %var30, %load_old_0_0 ], [ %var31, %load_old_0_1 ], [ %var32, %load_old_0_2 ], [ %var33, %load_old_0_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var27, i32 %var29, i32 %var28)
|
||||||
|
store i32 0, i32* @cond_0_current
|
||||||
|
br label %done
|
||||||
|
case_1:
|
||||||
|
%var34 = icmp ne i32 %var25, 1
|
||||||
|
br i1 %var34, label %swap_1, label %done
|
||||||
|
swap_1:
|
||||||
|
%var35 = load i32, i32* @cond_0_marker
|
||||||
|
%var36 = load i32, i32* @cond_0_case_1_root
|
||||||
|
switch i32 %var25, label %load_old_1_3 [
|
||||||
|
i32 0, label %load_old_1_0
|
||||||
|
i32 1, label %load_old_1_1
|
||||||
|
i32 2, label %load_old_1_2
|
||||||
|
i32 3, label %load_old_1_3
|
||||||
|
]
|
||||||
|
load_old_1_0:
|
||||||
|
%var38 = load i32, i32* @cond_0_case_0_root
|
||||||
|
br label %merge_1
|
||||||
|
load_old_1_1:
|
||||||
|
%var39 = load i32, i32* @cond_0_case_1_root
|
||||||
|
br label %merge_1
|
||||||
|
load_old_1_2:
|
||||||
|
%var40 = load i32, i32* @cond_0_case_2_root
|
||||||
|
br label %merge_1
|
||||||
|
load_old_1_3:
|
||||||
|
%var41 = load i32, i32* @cond_0_case_3_root
|
||||||
|
br label %merge_1
|
||||||
|
merge_1:
|
||||||
|
%var37 = phi i32 [ %var38, %load_old_1_0 ], [ %var39, %load_old_1_1 ], [ %var40, %load_old_1_2 ], [ %var41, %load_old_1_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var35, i32 %var37, i32 %var36)
|
||||||
|
store i32 1, i32* @cond_0_current
|
||||||
|
br label %done
|
||||||
|
case_2:
|
||||||
|
%var42 = icmp ne i32 %var25, 2
|
||||||
|
br i1 %var42, label %swap_2, label %done
|
||||||
|
swap_2:
|
||||||
|
%var43 = load i32, i32* @cond_0_marker
|
||||||
|
%var44 = load i32, i32* @cond_0_case_2_root
|
||||||
|
switch i32 %var25, label %load_old_2_3 [
|
||||||
|
i32 0, label %load_old_2_0
|
||||||
|
i32 1, label %load_old_2_1
|
||||||
|
i32 2, label %load_old_2_2
|
||||||
|
i32 3, label %load_old_2_3
|
||||||
|
]
|
||||||
|
load_old_2_0:
|
||||||
|
%var46 = load i32, i32* @cond_0_case_0_root
|
||||||
|
br label %merge_2
|
||||||
|
load_old_2_1:
|
||||||
|
%var47 = load i32, i32* @cond_0_case_1_root
|
||||||
|
br label %merge_2
|
||||||
|
load_old_2_2:
|
||||||
|
%var48 = load i32, i32* @cond_0_case_2_root
|
||||||
|
br label %merge_2
|
||||||
|
load_old_2_3:
|
||||||
|
%var49 = load i32, i32* @cond_0_case_3_root
|
||||||
|
br label %merge_2
|
||||||
|
merge_2:
|
||||||
|
%var45 = phi i32 [ %var46, %load_old_2_0 ], [ %var47, %load_old_2_1 ], [ %var48, %load_old_2_2 ], [ %var49, %load_old_2_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var43, i32 %var45, i32 %var44)
|
||||||
|
store i32 2, i32* @cond_0_current
|
||||||
|
br label %done
|
||||||
|
case_3:
|
||||||
|
%var50 = icmp ne i32 %var25, 3
|
||||||
|
br i1 %var50, label %swap_3, label %done
|
||||||
|
swap_3:
|
||||||
|
%var51 = load i32, i32* @cond_0_marker
|
||||||
|
%var52 = load i32, i32* @cond_0_case_3_root
|
||||||
|
switch i32 %var25, label %load_old_3_3 [
|
||||||
|
i32 0, label %load_old_3_0
|
||||||
|
i32 1, label %load_old_3_1
|
||||||
|
i32 2, label %load_old_3_2
|
||||||
|
i32 3, label %load_old_3_3
|
||||||
|
]
|
||||||
|
load_old_3_0:
|
||||||
|
%var54 = load i32, i32* @cond_0_case_0_root
|
||||||
|
br label %merge_3
|
||||||
|
load_old_3_1:
|
||||||
|
%var55 = load i32, i32* @cond_0_case_1_root
|
||||||
|
br label %merge_3
|
||||||
|
load_old_3_2:
|
||||||
|
%var56 = load i32, i32* @cond_0_case_2_root
|
||||||
|
br label %merge_3
|
||||||
|
load_old_3_3:
|
||||||
|
%var57 = load i32, i32* @cond_0_case_3_root
|
||||||
|
br label %merge_3
|
||||||
|
merge_3:
|
||||||
|
%var53 = phi i32 [ %var54, %load_old_3_0 ], [ %var55, %load_old_3_1 ], [ %var56, %load_old_3_2 ], [ %var57, %load_old_3_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var51, i32 %var53, i32 %var52)
|
||||||
|
store i32 3, i32* @cond_0_current
|
||||||
|
br label %done
|
||||||
|
done:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_0_case_0() {
|
||||||
|
entry:
|
||||||
|
%var58 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
%var59 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var59, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.4, i32 0, i32 0))
|
||||||
|
%var60 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var61 = call i32 @js_create_text(i8* getelementptr inbounds ([24 x i8], [24 x i8]* @.str.6, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var60, i32 %var61)
|
||||||
|
call void @js_append_child(i32 %var59, i32 %var60)
|
||||||
|
call void @js_append_child(i32 %var59, i32 %var60)
|
||||||
|
%var62 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var63 = call i32 @js_create_text(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.8, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var62, i32 %var63)
|
||||||
|
call void @js_append_child(i32 %var59, i32 %var62)
|
||||||
|
call void @js_append_child(i32 %var59, i32 %var62)
|
||||||
|
%var64 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var65 = call i32 @js_create_text(i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.9, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var64, i32 %var65)
|
||||||
|
call void @js_append_child(i32 %var59, i32 %var64)
|
||||||
|
call void @js_append_child(i32 %var59, i32 %var64)
|
||||||
|
call void @js_append_child(i32 %var58, i32 %var59)
|
||||||
|
ret i32 %var58
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_0_case_1() {
|
||||||
|
entry:
|
||||||
|
%var66 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
%var67 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var67, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.4, i32 0, i32 0))
|
||||||
|
%var68 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var69 = call i32 @js_create_text(i8* getelementptr inbounds ([22 x i8], [22 x i8]* @.str.10, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var68, i32 %var69)
|
||||||
|
call void @js_append_child(i32 %var67, i32 %var68)
|
||||||
|
call void @js_append_child(i32 %var67, i32 %var68)
|
||||||
|
%var70 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var71 = call i32 @js_create_text(i8* getelementptr inbounds ([26 x i8], [26 x i8]* @.str.11, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var70, i32 %var71)
|
||||||
|
call void @js_append_child(i32 %var67, i32 %var70)
|
||||||
|
call void @js_append_child(i32 %var67, i32 %var70)
|
||||||
|
%var72 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var73 = call i32 @js_create_text(i8* getelementptr inbounds ([19 x i8], [19 x i8]* @.str.12, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var72, i32 %var73)
|
||||||
|
call void @js_append_child(i32 %var67, i32 %var72)
|
||||||
|
call void @js_append_child(i32 %var67, i32 %var72)
|
||||||
|
call void @js_append_child(i32 %var66, i32 %var67)
|
||||||
|
ret i32 %var66
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_0_case_2() {
|
||||||
|
entry:
|
||||||
|
%var74 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
%var75 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var75, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.4, i32 0, i32 0))
|
||||||
|
%var76 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var77 = call i32 @js_create_text(i8* getelementptr inbounds ([23 x i8], [23 x i8]* @.str.13, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var76, i32 %var77)
|
||||||
|
call void @js_append_child(i32 %var75, i32 %var76)
|
||||||
|
call void @js_append_child(i32 %var75, i32 %var76)
|
||||||
|
%var78 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var79 = call i32 @js_create_text(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str.14, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var78, i32 %var79)
|
||||||
|
call void @js_append_child(i32 %var75, i32 %var78)
|
||||||
|
call void @js_append_child(i32 %var75, i32 %var78)
|
||||||
|
%var80 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var81 = call i32 @js_create_text(i8* getelementptr inbounds ([32 x i8], [32 x i8]* @.str.15, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var80, i32 %var81)
|
||||||
|
call void @js_append_child(i32 %var75, i32 %var80)
|
||||||
|
call void @js_append_child(i32 %var75, i32 %var80)
|
||||||
|
call void @js_append_child(i32 %var74, i32 %var75)
|
||||||
|
ret i32 %var74
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_0_case_3() {
|
||||||
|
entry:
|
||||||
|
%var82 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
%var83 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var83, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.4, i32 0, i32 0))
|
||||||
|
%var84 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var85 = call i32 @js_create_text(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str.16, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var84, i32 %var85)
|
||||||
|
call void @js_append_child(i32 %var83, i32 %var84)
|
||||||
|
call void @js_append_child(i32 %var83, i32 %var84)
|
||||||
|
call void @js_append_child(i32 %var82, i32 %var83)
|
||||||
|
ret i32 %var82
|
||||||
|
}
|
||||||
|
|
||||||
|
; Component render function
|
||||||
|
define i32 @render_VerticalTabs() {
|
||||||
|
entry:
|
||||||
|
%var86 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var86, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str.17, i32 0, i32 0))
|
||||||
|
%var87 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var87, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.18, i32 0, i32 0))
|
||||||
|
%var88 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.19, i32 0, i32 0))
|
||||||
|
%var89 = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
%var90 = icmp eq i32 %var89, 0
|
||||||
|
%var91 = zext i1 %var90 to i32
|
||||||
|
%var92 = icmp ne i32 %var91, 0
|
||||||
|
%var93 = select i1 %var92, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.0, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.1, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var88, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* %var93)
|
||||||
|
call void @js_bind_event(i32 %var88, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.20, i32 0, i32 0), i8* getelementptr inbounds ([23 x i8], [23 x i8]* @.str.21, i32 0, i32 0))
|
||||||
|
store i32 %var88, i32* @reactive_attr_0_elem
|
||||||
|
%var94 = call i32 @js_create_text(i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.22, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var88, i32 %var94)
|
||||||
|
call void @js_append_child(i32 %var87, i32 %var88)
|
||||||
|
call void @js_append_child(i32 %var87, i32 %var88)
|
||||||
|
%var95 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.19, i32 0, i32 0))
|
||||||
|
%var96 = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
%var97 = icmp eq i32 %var96, 1
|
||||||
|
%var98 = zext i1 %var97 to i32
|
||||||
|
%var99 = icmp ne i32 %var98, 0
|
||||||
|
%var100 = select i1 %var99, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.0, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.1, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var95, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* %var100)
|
||||||
|
call void @js_bind_event(i32 %var95, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.20, i32 0, i32 0), i8* getelementptr inbounds ([23 x i8], [23 x i8]* @.str.23, i32 0, i32 0))
|
||||||
|
store i32 %var95, i32* @reactive_attr_1_elem
|
||||||
|
%var101 = call i32 @js_create_text(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.24, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var95, i32 %var101)
|
||||||
|
call void @js_append_child(i32 %var87, i32 %var95)
|
||||||
|
call void @js_append_child(i32 %var87, i32 %var95)
|
||||||
|
%var102 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.19, i32 0, i32 0))
|
||||||
|
%var103 = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
%var104 = icmp eq i32 %var103, 2
|
||||||
|
%var105 = zext i1 %var104 to i32
|
||||||
|
%var106 = icmp ne i32 %var105, 0
|
||||||
|
%var107 = select i1 %var106, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.0, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.1, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var102, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* %var107)
|
||||||
|
call void @js_bind_event(i32 %var102, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.20, i32 0, i32 0), i8* getelementptr inbounds ([23 x i8], [23 x i8]* @.str.25, i32 0, i32 0))
|
||||||
|
store i32 %var102, i32* @reactive_attr_2_elem
|
||||||
|
%var108 = call i32 @js_create_text(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str.26, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var102, i32 %var108)
|
||||||
|
call void @js_append_child(i32 %var87, i32 %var102)
|
||||||
|
call void @js_append_child(i32 %var87, i32 %var102)
|
||||||
|
call void @js_append_child(i32 %var86, i32 %var87)
|
||||||
|
call void @js_append_child(i32 %var86, i32 %var87)
|
||||||
|
%var109 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var109, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([21 x i8], [21 x i8]* @.str.27, i32 0, i32 0))
|
||||||
|
%var110 = call i32 @js_create_marker()
|
||||||
|
call void @js_append_child(i32 %var109, i32 %var110)
|
||||||
|
store i32 %var110, i32* @cond_0_marker
|
||||||
|
call void @_init_conditional_0()
|
||||||
|
call void @js_append_child(i32 %var86, i32 %var109)
|
||||||
|
call void @js_append_child(i32 %var86, i32 %var109)
|
||||||
|
ret i32 %var86
|
||||||
|
}
|
||||||
|
|
||||||
|
; Signal storage for Tabs (reactive state in WASM memory)
|
||||||
|
@signal_Tabs_tab_index = global i32 0
|
||||||
|
|
||||||
|
; Conditional rendering state
|
||||||
|
@cond_1_marker = global i32 0
|
||||||
|
@cond_1_current = global i32 0
|
||||||
|
@cond_1_case_0_root = global i32 0
|
||||||
|
@cond_1_case_1_root = global i32 0
|
||||||
|
@cond_1_case_2_root = global i32 0
|
||||||
|
@cond_1_case_3_root = global i32 0
|
||||||
|
|
||||||
|
; Reactive attribute element storage
|
||||||
|
@reactive_attr_3_elem = global i32 0
|
||||||
|
@reactive_attr_4_elem = global i32 0
|
||||||
|
@reactive_attr_5_elem = global i32 0
|
||||||
|
|
||||||
|
; Signal helper functions
|
||||||
|
define i32 @get_tab_index() {
|
||||||
|
entry:
|
||||||
|
%val = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
ret i32 %val
|
||||||
|
}
|
||||||
|
|
||||||
|
define void @set_tab_index(i32 %newval) {
|
||||||
|
entry:
|
||||||
|
store i32 %newval, i32* @signal_Tabs_tab_index
|
||||||
|
; TODO: Trigger DOM update callback
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: _lambda_0_Tabs
|
||||||
|
define void @_lambda_0_Tabs() {
|
||||||
|
entry:
|
||||||
|
call void @set_tab(i32 0)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: _lambda_1_Tabs
|
||||||
|
define void @_lambda_1_Tabs() {
|
||||||
|
entry:
|
||||||
|
call void @set_tab(i32 1)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: _lambda_2_Tabs
|
||||||
|
define void @_lambda_2_Tabs() {
|
||||||
|
entry:
|
||||||
|
call void @set_tab(i32 2)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: set_tab
|
||||||
|
define void @set_tab(i32 %value) {
|
||||||
|
entry:
|
||||||
|
store i32 %value, i32* @signal_Tabs_tab_index
|
||||||
|
call void @reevaluate_conditional_1()
|
||||||
|
%var111 = load i32, i32* @reactive_attr_3_elem
|
||||||
|
%var112 = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
%var113 = icmp eq i32 %var112, 0
|
||||||
|
%var114 = zext i1 %var113 to i32
|
||||||
|
%var115 = icmp ne i32 %var114, 0
|
||||||
|
%var116 = select i1 %var115, i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.28, i32 0, i32 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.29, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var111, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* %var116)
|
||||||
|
%var117 = load i32, i32* @reactive_attr_4_elem
|
||||||
|
%var118 = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
%var119 = icmp eq i32 %var118, 1
|
||||||
|
%var120 = zext i1 %var119 to i32
|
||||||
|
%var121 = icmp ne i32 %var120, 0
|
||||||
|
%var122 = select i1 %var121, i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.28, i32 0, i32 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.29, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var117, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* %var122)
|
||||||
|
%var123 = load i32, i32* @reactive_attr_5_elem
|
||||||
|
%var124 = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
%var125 = icmp eq i32 %var124, 2
|
||||||
|
%var126 = zext i1 %var125 to i32
|
||||||
|
%var127 = icmp ne i32 %var126, 0
|
||||||
|
%var128 = select i1 %var127, i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.28, i32 0, i32 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.29, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var123, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* %var128)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Initialize conditional 1 (match)
|
||||||
|
define void @_init_conditional_1() {
|
||||||
|
entry:
|
||||||
|
; Render all cases upfront
|
||||||
|
%var129 = call i32 @_render_cond_1_case_0()
|
||||||
|
store i32 %var129, i32* @cond_1_case_0_root
|
||||||
|
%var130 = call i32 @_render_cond_1_case_1()
|
||||||
|
store i32 %var130, i32* @cond_1_case_1_root
|
||||||
|
%var131 = call i32 @_render_cond_1_case_2()
|
||||||
|
store i32 %var131, i32* @cond_1_case_2_root
|
||||||
|
%var132 = call i32 @_render_cond_1_case_3()
|
||||||
|
store i32 %var132, i32* @cond_1_case_3_root
|
||||||
|
%var133 = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
%var134 = load i32, i32* @cond_1_marker
|
||||||
|
switch i32 %var133, label %insert_case_3 [
|
||||||
|
i32 0, label %insert_case_0
|
||||||
|
i32 1, label %insert_case_1
|
||||||
|
i32 2, label %insert_case_2
|
||||||
|
]
|
||||||
|
insert_case_0:
|
||||||
|
call void @js_replace_at_marker(i32 %var134, i32 0, i32 %var129)
|
||||||
|
store i32 0, i32* @cond_1_current
|
||||||
|
ret void
|
||||||
|
insert_case_1:
|
||||||
|
call void @js_replace_at_marker(i32 %var134, i32 0, i32 %var130)
|
||||||
|
store i32 1, i32* @cond_1_current
|
||||||
|
ret void
|
||||||
|
insert_case_2:
|
||||||
|
call void @js_replace_at_marker(i32 %var134, i32 0, i32 %var131)
|
||||||
|
store i32 2, i32* @cond_1_current
|
||||||
|
ret void
|
||||||
|
insert_case_3:
|
||||||
|
call void @js_replace_at_marker(i32 %var134, i32 0, i32 %var132)
|
||||||
|
store i32 3, i32* @cond_1_current
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Re-evaluate conditional 1 (match)
|
||||||
|
define void @reevaluate_conditional_1() {
|
||||||
|
entry:
|
||||||
|
%var135 = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
%var136 = load i32, i32* @cond_1_current
|
||||||
|
switch i32 %var135, label %case_3 [
|
||||||
|
i32 0, label %case_0
|
||||||
|
i32 1, label %case_1
|
||||||
|
i32 2, label %case_2
|
||||||
|
]
|
||||||
|
case_0:
|
||||||
|
%var137 = icmp ne i32 %var136, 0
|
||||||
|
br i1 %var137, label %swap_0, label %done
|
||||||
|
swap_0:
|
||||||
|
%var138 = load i32, i32* @cond_1_marker
|
||||||
|
%var139 = load i32, i32* @cond_1_case_0_root
|
||||||
|
switch i32 %var136, label %load_old_0_3 [
|
||||||
|
i32 0, label %load_old_0_0
|
||||||
|
i32 1, label %load_old_0_1
|
||||||
|
i32 2, label %load_old_0_2
|
||||||
|
i32 3, label %load_old_0_3
|
||||||
|
]
|
||||||
|
load_old_0_0:
|
||||||
|
%var141 = load i32, i32* @cond_1_case_0_root
|
||||||
|
br label %merge_0
|
||||||
|
load_old_0_1:
|
||||||
|
%var142 = load i32, i32* @cond_1_case_1_root
|
||||||
|
br label %merge_0
|
||||||
|
load_old_0_2:
|
||||||
|
%var143 = load i32, i32* @cond_1_case_2_root
|
||||||
|
br label %merge_0
|
||||||
|
load_old_0_3:
|
||||||
|
%var144 = load i32, i32* @cond_1_case_3_root
|
||||||
|
br label %merge_0
|
||||||
|
merge_0:
|
||||||
|
%var140 = phi i32 [ %var141, %load_old_0_0 ], [ %var142, %load_old_0_1 ], [ %var143, %load_old_0_2 ], [ %var144, %load_old_0_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var138, i32 %var140, i32 %var139)
|
||||||
|
store i32 0, i32* @cond_1_current
|
||||||
|
br label %done
|
||||||
|
case_1:
|
||||||
|
%var145 = icmp ne i32 %var136, 1
|
||||||
|
br i1 %var145, label %swap_1, label %done
|
||||||
|
swap_1:
|
||||||
|
%var146 = load i32, i32* @cond_1_marker
|
||||||
|
%var147 = load i32, i32* @cond_1_case_1_root
|
||||||
|
switch i32 %var136, label %load_old_1_3 [
|
||||||
|
i32 0, label %load_old_1_0
|
||||||
|
i32 1, label %load_old_1_1
|
||||||
|
i32 2, label %load_old_1_2
|
||||||
|
i32 3, label %load_old_1_3
|
||||||
|
]
|
||||||
|
load_old_1_0:
|
||||||
|
%var149 = load i32, i32* @cond_1_case_0_root
|
||||||
|
br label %merge_1
|
||||||
|
load_old_1_1:
|
||||||
|
%var150 = load i32, i32* @cond_1_case_1_root
|
||||||
|
br label %merge_1
|
||||||
|
load_old_1_2:
|
||||||
|
%var151 = load i32, i32* @cond_1_case_2_root
|
||||||
|
br label %merge_1
|
||||||
|
load_old_1_3:
|
||||||
|
%var152 = load i32, i32* @cond_1_case_3_root
|
||||||
|
br label %merge_1
|
||||||
|
merge_1:
|
||||||
|
%var148 = phi i32 [ %var149, %load_old_1_0 ], [ %var150, %load_old_1_1 ], [ %var151, %load_old_1_2 ], [ %var152, %load_old_1_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var146, i32 %var148, i32 %var147)
|
||||||
|
store i32 1, i32* @cond_1_current
|
||||||
|
br label %done
|
||||||
|
case_2:
|
||||||
|
%var153 = icmp ne i32 %var136, 2
|
||||||
|
br i1 %var153, label %swap_2, label %done
|
||||||
|
swap_2:
|
||||||
|
%var154 = load i32, i32* @cond_1_marker
|
||||||
|
%var155 = load i32, i32* @cond_1_case_2_root
|
||||||
|
switch i32 %var136, label %load_old_2_3 [
|
||||||
|
i32 0, label %load_old_2_0
|
||||||
|
i32 1, label %load_old_2_1
|
||||||
|
i32 2, label %load_old_2_2
|
||||||
|
i32 3, label %load_old_2_3
|
||||||
|
]
|
||||||
|
load_old_2_0:
|
||||||
|
%var157 = load i32, i32* @cond_1_case_0_root
|
||||||
|
br label %merge_2
|
||||||
|
load_old_2_1:
|
||||||
|
%var158 = load i32, i32* @cond_1_case_1_root
|
||||||
|
br label %merge_2
|
||||||
|
load_old_2_2:
|
||||||
|
%var159 = load i32, i32* @cond_1_case_2_root
|
||||||
|
br label %merge_2
|
||||||
|
load_old_2_3:
|
||||||
|
%var160 = load i32, i32* @cond_1_case_3_root
|
||||||
|
br label %merge_2
|
||||||
|
merge_2:
|
||||||
|
%var156 = phi i32 [ %var157, %load_old_2_0 ], [ %var158, %load_old_2_1 ], [ %var159, %load_old_2_2 ], [ %var160, %load_old_2_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var154, i32 %var156, i32 %var155)
|
||||||
|
store i32 2, i32* @cond_1_current
|
||||||
|
br label %done
|
||||||
|
case_3:
|
||||||
|
%var161 = icmp ne i32 %var136, 3
|
||||||
|
br i1 %var161, label %swap_3, label %done
|
||||||
|
swap_3:
|
||||||
|
%var162 = load i32, i32* @cond_1_marker
|
||||||
|
%var163 = load i32, i32* @cond_1_case_3_root
|
||||||
|
switch i32 %var136, label %load_old_3_3 [
|
||||||
|
i32 0, label %load_old_3_0
|
||||||
|
i32 1, label %load_old_3_1
|
||||||
|
i32 2, label %load_old_3_2
|
||||||
|
i32 3, label %load_old_3_3
|
||||||
|
]
|
||||||
|
load_old_3_0:
|
||||||
|
%var165 = load i32, i32* @cond_1_case_0_root
|
||||||
|
br label %merge_3
|
||||||
|
load_old_3_1:
|
||||||
|
%var166 = load i32, i32* @cond_1_case_1_root
|
||||||
|
br label %merge_3
|
||||||
|
load_old_3_2:
|
||||||
|
%var167 = load i32, i32* @cond_1_case_2_root
|
||||||
|
br label %merge_3
|
||||||
|
load_old_3_3:
|
||||||
|
%var168 = load i32, i32* @cond_1_case_3_root
|
||||||
|
br label %merge_3
|
||||||
|
merge_3:
|
||||||
|
%var164 = phi i32 [ %var165, %load_old_3_0 ], [ %var166, %load_old_3_1 ], [ %var167, %load_old_3_2 ], [ %var168, %load_old_3_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var162, i32 %var164, i32 %var163)
|
||||||
|
store i32 3, i32* @cond_1_current
|
||||||
|
br label %done
|
||||||
|
done:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_1_case_0() {
|
||||||
|
entry:
|
||||||
|
%var169 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
%var170 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var170, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str.30, i32 0, i32 0))
|
||||||
|
%var171 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.31, i32 0, i32 0))
|
||||||
|
%var172 = call i32 @js_create_text(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.32, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var171, i32 %var172)
|
||||||
|
call void @js_append_child(i32 %var170, i32 %var171)
|
||||||
|
call void @js_append_child(i32 %var170, i32 %var171)
|
||||||
|
%var173 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var174 = call i32 @js_create_text(i8* getelementptr inbounds ([29 x i8], [29 x i8]* @.str.33, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var173, i32 %var174)
|
||||||
|
call void @js_append_child(i32 %var170, i32 %var173)
|
||||||
|
call void @js_append_child(i32 %var170, i32 %var173)
|
||||||
|
%var175 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var176 = call i32 @js_create_text(i8* getelementptr inbounds ([37 x i8], [37 x i8]* @.str.34, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var175, i32 %var176)
|
||||||
|
call void @js_append_child(i32 %var170, i32 %var175)
|
||||||
|
call void @js_append_child(i32 %var170, i32 %var175)
|
||||||
|
%var177 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.35, i32 0, i32 0))
|
||||||
|
%var178 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.36, i32 0, i32 0))
|
||||||
|
%var179 = call i32 @js_create_text(i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.37, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var178, i32 %var179)
|
||||||
|
call void @js_append_child(i32 %var177, i32 %var178)
|
||||||
|
call void @js_append_child(i32 %var177, i32 %var178)
|
||||||
|
%var180 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.36, i32 0, i32 0))
|
||||||
|
%var181 = call i32 @js_create_text(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.38, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var180, i32 %var181)
|
||||||
|
call void @js_append_child(i32 %var177, i32 %var180)
|
||||||
|
call void @js_append_child(i32 %var177, i32 %var180)
|
||||||
|
%var182 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.36, i32 0, i32 0))
|
||||||
|
%var183 = call i32 @js_create_text(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.39, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var182, i32 %var183)
|
||||||
|
call void @js_append_child(i32 %var177, i32 %var182)
|
||||||
|
call void @js_append_child(i32 %var177, i32 %var182)
|
||||||
|
call void @js_append_child(i32 %var170, i32 %var177)
|
||||||
|
call void @js_append_child(i32 %var170, i32 %var177)
|
||||||
|
call void @js_append_child(i32 %var169, i32 %var170)
|
||||||
|
ret i32 %var169
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_1_case_1() {
|
||||||
|
entry:
|
||||||
|
%var184 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
%var185 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var185, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([17 x i8], [17 x i8]* @.str.40, i32 0, i32 0))
|
||||||
|
%var186 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.31, i32 0, i32 0))
|
||||||
|
%var187 = call i32 @js_create_text(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.41, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var186, i32 %var187)
|
||||||
|
call void @js_append_child(i32 %var185, i32 %var186)
|
||||||
|
call void @js_append_child(i32 %var185, i32 %var186)
|
||||||
|
%var188 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var189 = call i32 @js_create_text(i8* getelementptr inbounds ([30 x i8], [30 x i8]* @.str.42, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var188, i32 %var189)
|
||||||
|
call void @js_append_child(i32 %var185, i32 %var188)
|
||||||
|
call void @js_append_child(i32 %var185, i32 %var188)
|
||||||
|
%var190 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var190, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.43, i32 0, i32 0))
|
||||||
|
%var191 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var192 = call i32 @js_create_text(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str.44, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var191, i32 %var192)
|
||||||
|
call void @js_append_child(i32 %var190, i32 %var191)
|
||||||
|
call void @js_append_child(i32 %var190, i32 %var191)
|
||||||
|
%var193 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var194 = call i32 @js_create_text(i8* getelementptr inbounds ([21 x i8], [21 x i8]* @.str.45, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var193, i32 %var194)
|
||||||
|
call void @js_append_child(i32 %var190, i32 %var193)
|
||||||
|
call void @js_append_child(i32 %var190, i32 %var193)
|
||||||
|
%var195 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var196 = call i32 @js_create_text(i8* getelementptr inbounds ([19 x i8], [19 x i8]* @.str.46, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var195, i32 %var196)
|
||||||
|
call void @js_append_child(i32 %var190, i32 %var195)
|
||||||
|
call void @js_append_child(i32 %var190, i32 %var195)
|
||||||
|
call void @js_append_child(i32 %var185, i32 %var190)
|
||||||
|
call void @js_append_child(i32 %var185, i32 %var190)
|
||||||
|
call void @js_append_child(i32 %var184, i32 %var185)
|
||||||
|
ret i32 %var184
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_1_case_2() {
|
||||||
|
entry:
|
||||||
|
%var197 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
%var198 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var198, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.47, i32 0, i32 0))
|
||||||
|
%var199 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.31, i32 0, i32 0))
|
||||||
|
%var200 = call i32 @js_create_text(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str.48, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var199, i32 %var200)
|
||||||
|
call void @js_append_child(i32 %var198, i32 %var199)
|
||||||
|
call void @js_append_child(i32 %var198, i32 %var199)
|
||||||
|
%var201 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var202 = call i32 @js_create_text(i8* getelementptr inbounds ([55 x i8], [55 x i8]* @.str.49, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var201, i32 %var202)
|
||||||
|
call void @js_append_child(i32 %var198, i32 %var201)
|
||||||
|
call void @js_append_child(i32 %var198, i32 %var201)
|
||||||
|
%var203 = call i32 @render_VerticalTabs()
|
||||||
|
call void @js_append_child(i32 %var198, i32 %var203)
|
||||||
|
call void @js_append_child(i32 %var198, i32 %var203)
|
||||||
|
call void @js_append_child(i32 %var197, i32 %var198)
|
||||||
|
ret i32 %var197
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_1_case_3() {
|
||||||
|
entry:
|
||||||
|
%var204 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
%var205 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var205, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.50, i32 0, i32 0))
|
||||||
|
%var206 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var207 = call i32 @js_create_text(i8* getelementptr inbounds ([28 x i8], [28 x i8]* @.str.51, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var206, i32 %var207)
|
||||||
|
call void @js_append_child(i32 %var205, i32 %var206)
|
||||||
|
call void @js_append_child(i32 %var205, i32 %var206)
|
||||||
|
call void @js_append_child(i32 %var204, i32 %var205)
|
||||||
|
ret i32 %var204
|
||||||
|
}
|
||||||
|
|
||||||
|
; Component render function
|
||||||
|
define i32 @render_Tabs(i32 %orientation) {
|
||||||
|
entry:
|
||||||
|
%var208 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var208, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.52, i32 0, i32 0))
|
||||||
|
%var209 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.53, i32 0, i32 0))
|
||||||
|
%var210 = call i32 @js_create_text(i8* getelementptr inbounds ([19 x i8], [19 x i8]* @.str.54, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var209, i32 %var210)
|
||||||
|
call void @js_append_child(i32 %var208, i32 %var209)
|
||||||
|
call void @js_append_child(i32 %var208, i32 %var209)
|
||||||
|
%var211 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var211, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str.55, i32 0, i32 0))
|
||||||
|
%var212 = call i32 @js_create_text(i8* getelementptr inbounds ([58 x i8], [58 x i8]* @.str.56, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var211, i32 %var212)
|
||||||
|
call void @js_append_child(i32 %var208, i32 %var211)
|
||||||
|
call void @js_append_child(i32 %var208, i32 %var211)
|
||||||
|
%var213 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.35, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var213, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str.57, i32 0, i32 0))
|
||||||
|
%var214 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.36, i32 0, i32 0))
|
||||||
|
%var215 = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
%var216 = icmp eq i32 %var215, 0
|
||||||
|
%var217 = zext i1 %var216 to i32
|
||||||
|
%var218 = icmp ne i32 %var217, 0
|
||||||
|
%var219 = select i1 %var218, i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.28, i32 0, i32 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.29, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var214, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* %var219)
|
||||||
|
store i32 %var214, i32* @reactive_attr_3_elem
|
||||||
|
%var220 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.19, i32 0, i32 0))
|
||||||
|
call void @js_bind_event(i32 %var220, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.20, i32 0, i32 0), i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.58, i32 0, i32 0))
|
||||||
|
%var221 = call i32 @js_create_text(i8* getelementptr inbounds ([10 x i8], [10 x i8]* @.str.59, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var220, i32 %var221)
|
||||||
|
call void @js_append_child(i32 %var214, i32 %var220)
|
||||||
|
call void @js_append_child(i32 %var214, i32 %var220)
|
||||||
|
call void @js_append_child(i32 %var213, i32 %var214)
|
||||||
|
call void @js_append_child(i32 %var213, i32 %var214)
|
||||||
|
%var222 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.36, i32 0, i32 0))
|
||||||
|
%var223 = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
%var224 = icmp eq i32 %var223, 1
|
||||||
|
%var225 = zext i1 %var224 to i32
|
||||||
|
%var226 = icmp ne i32 %var225, 0
|
||||||
|
%var227 = select i1 %var226, i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.28, i32 0, i32 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.29, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var222, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* %var227)
|
||||||
|
store i32 %var222, i32* @reactive_attr_4_elem
|
||||||
|
%var228 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.19, i32 0, i32 0))
|
||||||
|
call void @js_bind_event(i32 %var228, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.20, i32 0, i32 0), i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.60, i32 0, i32 0))
|
||||||
|
%var229 = call i32 @js_create_text(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.61, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var228, i32 %var229)
|
||||||
|
call void @js_append_child(i32 %var222, i32 %var228)
|
||||||
|
call void @js_append_child(i32 %var222, i32 %var228)
|
||||||
|
call void @js_append_child(i32 %var213, i32 %var222)
|
||||||
|
call void @js_append_child(i32 %var213, i32 %var222)
|
||||||
|
%var230 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.36, i32 0, i32 0))
|
||||||
|
%var231 = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
%var232 = icmp eq i32 %var231, 2
|
||||||
|
%var233 = zext i1 %var232 to i32
|
||||||
|
%var234 = icmp ne i32 %var233, 0
|
||||||
|
%var235 = select i1 %var234, i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.28, i32 0, i32 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.29, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var230, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* %var235)
|
||||||
|
store i32 %var230, i32* @reactive_attr_5_elem
|
||||||
|
%var236 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.19, i32 0, i32 0))
|
||||||
|
call void @js_bind_event(i32 %var236, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.20, i32 0, i32 0), i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.62, i32 0, i32 0))
|
||||||
|
%var237 = call i32 @js_create_text(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str.48, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var236, i32 %var237)
|
||||||
|
call void @js_append_child(i32 %var230, i32 %var236)
|
||||||
|
call void @js_append_child(i32 %var230, i32 %var236)
|
||||||
|
call void @js_append_child(i32 %var213, i32 %var230)
|
||||||
|
call void @js_append_child(i32 %var213, i32 %var230)
|
||||||
|
call void @js_append_child(i32 %var208, i32 %var213)
|
||||||
|
call void @js_append_child(i32 %var208, i32 %var213)
|
||||||
|
%var238 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var238, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([12 x i8], [12 x i8]* @.str.63, i32 0, i32 0))
|
||||||
|
%var239 = call i32 @js_create_marker()
|
||||||
|
call void @js_append_child(i32 %var238, i32 %var239)
|
||||||
|
store i32 %var239, i32* @cond_1_marker
|
||||||
|
call void @_init_conditional_1()
|
||||||
|
call void @js_append_child(i32 %var208, i32 %var238)
|
||||||
|
call void @js_append_child(i32 %var208, i32 %var238)
|
||||||
|
%var240 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var240, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.64, i32 0, i32 0))
|
||||||
|
%var241 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
; TODO: Format f-string with signal tab_index
|
||||||
|
%var242 = call i32 @js_create_text(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.65, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var241, i32 %var242)
|
||||||
|
call void @js_append_child(i32 %var240, i32 %var241)
|
||||||
|
call void @js_append_child(i32 %var240, i32 %var241)
|
||||||
|
call void @js_append_child(i32 %var208, i32 %var240)
|
||||||
|
call void @js_append_child(i32 %var208, i32 %var240)
|
||||||
|
ret i32 %var208
|
||||||
|
}
|
||||||
|
|
||||||
|
; Main entry point
|
||||||
|
define void @main() {
|
||||||
|
entry:
|
||||||
|
%root = call i32 @render_Tabs()
|
||||||
|
call void @js_set_root(i32 %root)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
"""
|
||||||
|
Tabs component - Beautiful multi-panel tab switching with proper composition.
|
||||||
|
|
||||||
|
Demonstrates:
|
||||||
|
- Component composition (TabMenu, TabPane)
|
||||||
|
- Match/case for clean panel switching
|
||||||
|
- Conditional CSS classes for active state
|
||||||
|
- All logic in WASM, minimal syntax
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
|
||||||
|
@component
|
||||||
|
def VerticalTabs():
|
||||||
|
"""Vertical tabs for settings categories."""
|
||||||
|
setting_tab = signal(0) # 0 = General, 1 = Privacy, 2 = Advanced
|
||||||
|
|
||||||
|
def set_setting(value):
|
||||||
|
setting_tab = value
|
||||||
|
|
||||||
|
with Div(class_="vertical-tabs"):
|
||||||
|
# Vertical tab menu - clean ternary syntax!
|
||||||
|
with Div(class_="vertical-tab-menu"):
|
||||||
|
with Button(class_='vertical-tab active' if setting_tab == 0 else 'vertical-tab',
|
||||||
|
onclick=lambda: set_setting(0)):
|
||||||
|
yield "⚙️ General"
|
||||||
|
|
||||||
|
with Button(class_='vertical-tab active' if setting_tab == 1 else 'vertical-tab',
|
||||||
|
onclick=lambda: set_setting(1)):
|
||||||
|
yield "🔒 Privacy"
|
||||||
|
|
||||||
|
with Button(class_='vertical-tab active' if setting_tab == 2 else 'vertical-tab',
|
||||||
|
onclick=lambda: set_setting(2)):
|
||||||
|
yield "🚀 Advanced"
|
||||||
|
|
||||||
|
# Vertical tab content
|
||||||
|
with Div(class_="vertical-tab-content"):
|
||||||
|
match setting_tab:
|
||||||
|
case 0:
|
||||||
|
with Div(class_="setting-pane"):
|
||||||
|
with H3():
|
||||||
|
yield "⚙️ General Settings"
|
||||||
|
with P():
|
||||||
|
yield "Language: English"
|
||||||
|
with P():
|
||||||
|
yield "Time Zone: UTC"
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
with Div(class_="setting-pane"):
|
||||||
|
with H3():
|
||||||
|
yield "🔒 Privacy Settings"
|
||||||
|
with P():
|
||||||
|
yield "Data Collection: Disabled"
|
||||||
|
with P():
|
||||||
|
yield "Analytics: Opt-out"
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
with Div(class_="setting-pane"):
|
||||||
|
with H3():
|
||||||
|
yield "🚀 Advanced Settings"
|
||||||
|
with P():
|
||||||
|
yield "Debug Mode: Off"
|
||||||
|
with P():
|
||||||
|
yield "Experimental Features: Disabled"
|
||||||
|
|
||||||
|
case _:
|
||||||
|
with Div(class_="setting-pane"):
|
||||||
|
with P():
|
||||||
|
yield "Invalid setting"
|
||||||
|
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Tabs(orientation=0): # 0 = horizontal, 1 = vertical
|
||||||
|
"""Main tabs container - orchestrates menu and panes."""
|
||||||
|
tab_index = signal(0) # Track which tab is active: 0 = Home, 1 = Profile, 2 = Settings
|
||||||
|
|
||||||
|
def set_tab(value):
|
||||||
|
tab_index = value
|
||||||
|
|
||||||
|
with Div(class_="tabs-container"):
|
||||||
|
with H1():
|
||||||
|
yield "Tab Component Demo"
|
||||||
|
|
||||||
|
with P(class_="subtitle"):
|
||||||
|
yield "Beautiful composition with TabMenu and TabPane components"
|
||||||
|
|
||||||
|
# Tab menu - clean ternary syntax!
|
||||||
|
with Ul(class_="tab-menu"):
|
||||||
|
with Li(class_='tab active' if tab_index == 0 else 'tab'):
|
||||||
|
with Button(onclick=lambda: set_tab(0)):
|
||||||
|
yield "🏠 Home"
|
||||||
|
|
||||||
|
with Li(class_='tab active' if tab_index == 1 else 'tab'):
|
||||||
|
with Button(onclick=lambda: set_tab(1)):
|
||||||
|
yield "👤 Profile"
|
||||||
|
|
||||||
|
with Li(class_='tab active' if tab_index == 2 else 'tab'):
|
||||||
|
with Button(onclick=lambda: set_tab(2)):
|
||||||
|
yield "⚙️ Settings"
|
||||||
|
|
||||||
|
# Tab panes - match/case for clean switching
|
||||||
|
with Div(class_="tab-content"):
|
||||||
|
match tab_index:
|
||||||
|
case 0:
|
||||||
|
# Home pane
|
||||||
|
with Div(class_="tab-pane home"):
|
||||||
|
with H2():
|
||||||
|
yield "🏠 Welcome Home"
|
||||||
|
with P():
|
||||||
|
yield "This is your home dashboard."
|
||||||
|
with P():
|
||||||
|
yield "All your important info at a glance."
|
||||||
|
with Ul():
|
||||||
|
with Li():
|
||||||
|
yield "✓ Recent activity"
|
||||||
|
with Li():
|
||||||
|
yield "✓ Quick actions"
|
||||||
|
with Li():
|
||||||
|
yield "✓ Summary stats"
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
# Profile pane
|
||||||
|
with Div(class_="tab-pane profile"):
|
||||||
|
with H2():
|
||||||
|
yield "👤 Your Profile"
|
||||||
|
with P():
|
||||||
|
yield "Manage your account settings."
|
||||||
|
with Div(class_="profile-info"):
|
||||||
|
with P():
|
||||||
|
yield "Name: Ritz User"
|
||||||
|
with P():
|
||||||
|
yield "Email: user@ritz.dev"
|
||||||
|
with P():
|
||||||
|
yield "Member since: 2025"
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
# Settings pane with NESTED vertical tabs!
|
||||||
|
with Div(class_="tab-pane settings"):
|
||||||
|
with H2():
|
||||||
|
yield "⚙️ Settings"
|
||||||
|
with P():
|
||||||
|
yield "Configure your preferences using the categories below:"
|
||||||
|
|
||||||
|
# ← NESTED COMPONENT HERE!
|
||||||
|
yield VerticalTabs()
|
||||||
|
|
||||||
|
case _:
|
||||||
|
# Error fallback
|
||||||
|
with Div(class_="tab-pane error"):
|
||||||
|
with P():
|
||||||
|
yield "⚠️ Invalid tab selected"
|
||||||
|
|
||||||
|
# Debug footer
|
||||||
|
with Div(class_="debug-footer"):
|
||||||
|
with P():
|
||||||
|
yield f"Active: {tab_index}"
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,70 @@
|
|||||||
|
"""
|
||||||
|
Cleaner Tabs implementation using wrapper components to reduce repetition.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
|
||||||
|
@component
|
||||||
|
def VerticalTab(label, is_active, tab_value):
|
||||||
|
"""Reusable vertical tab button with active state handling."""
|
||||||
|
# Note: In current implementation, we still need the if/else for class
|
||||||
|
# But it's encapsulated in one place instead of repeated everywhere
|
||||||
|
if is_active:
|
||||||
|
with Button(class_="vertical-tab active"):
|
||||||
|
yield label
|
||||||
|
else:
|
||||||
|
with Button(class_="vertical-tab"):
|
||||||
|
yield label
|
||||||
|
|
||||||
|
|
||||||
|
@component
|
||||||
|
def VerticalTabs():
|
||||||
|
"""Vertical tabs for settings categories."""
|
||||||
|
setting_tab = signal(0)
|
||||||
|
|
||||||
|
def set_setting(value):
|
||||||
|
setting_tab = value
|
||||||
|
|
||||||
|
with Div(class_="vertical-tabs"):
|
||||||
|
# Vertical tab menu
|
||||||
|
with Div(class_="vertical-tab-menu"):
|
||||||
|
# Now each tab is just one line instead of 6!
|
||||||
|
yield VerticalTab("⚙️ General", setting_tab == 0, 0)
|
||||||
|
yield VerticalTab("🔒 Privacy", setting_tab == 1, 1)
|
||||||
|
yield VerticalTab("🚀 Advanced", setting_tab == 2, 2)
|
||||||
|
|
||||||
|
# Tab content (same as before)
|
||||||
|
with Div(class_="vertical-tab-content"):
|
||||||
|
match setting_tab:
|
||||||
|
case 0:
|
||||||
|
with Div(class_="setting-pane"):
|
||||||
|
with H3():
|
||||||
|
yield "⚙️ General Settings"
|
||||||
|
with P():
|
||||||
|
yield "Language: English"
|
||||||
|
with P():
|
||||||
|
yield "Time Zone: UTC"
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
with Div(class_="setting-pane"):
|
||||||
|
with H3():
|
||||||
|
yield "🔒 Privacy Settings"
|
||||||
|
with P():
|
||||||
|
yield "Data Collection: Disabled"
|
||||||
|
with P():
|
||||||
|
yield "Analytics: Opt-out"
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
with Div(class_="setting-pane"):
|
||||||
|
with H3():
|
||||||
|
yield "🚀 Advanced Settings"
|
||||||
|
with P():
|
||||||
|
yield "Debug Mode: Off"
|
||||||
|
with P():
|
||||||
|
yield "Experimental Features: Disabled"
|
||||||
|
|
||||||
|
case _:
|
||||||
|
with Div(class_="setting-pane"):
|
||||||
|
with P():
|
||||||
|
yield "Invalid setting"
|
||||||
@@ -0,0 +1,805 @@
|
|||||||
|
; Generated by Ritz Framework with Signals
|
||||||
|
; Components: VerticalTabs, Tabs
|
||||||
|
|
||||||
|
; 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 [13 x i8] c"setting-pane\00"
|
||||||
|
@.str.3 = private unnamed_addr constant [3 x i8] c"h3\00"
|
||||||
|
@.str.4 = private unnamed_addr constant [24 x i8] c"⚙️ General Settings\00"
|
||||||
|
@.str.5 = private unnamed_addr constant [2 x i8] c"p\00"
|
||||||
|
@.str.6 = private unnamed_addr constant [18 x i8] c"Language: English\00"
|
||||||
|
@.str.7 = private unnamed_addr constant [15 x i8] c"Time Zone: UTC\00"
|
||||||
|
@.str.8 = private unnamed_addr constant [22 x i8] c"🔒 Privacy Settings\00"
|
||||||
|
@.str.9 = private unnamed_addr constant [26 x i8] c"Data Collection: Disabled\00"
|
||||||
|
@.str.10 = private unnamed_addr constant [19 x i8] c"Analytics: Opt-out\00"
|
||||||
|
@.str.11 = private unnamed_addr constant [23 x i8] c"🚀 Advanced Settings\00"
|
||||||
|
@.str.12 = private unnamed_addr constant [16 x i8] c"Debug Mode: Off\00"
|
||||||
|
@.str.13 = private unnamed_addr constant [32 x i8] c"Experimental Features: Disabled\00"
|
||||||
|
@.str.14 = private unnamed_addr constant [16 x i8] c"Invalid setting\00"
|
||||||
|
@.str.15 = private unnamed_addr constant [14 x i8] c"vertical-tabs\00"
|
||||||
|
@.str.16 = private unnamed_addr constant [18 x i8] c"vertical-tab-menu\00"
|
||||||
|
@.str.17 = private unnamed_addr constant [7 x i8] c"button\00"
|
||||||
|
@.str.18 = private unnamed_addr constant [20 x i8] c"vertical-tab active\00"
|
||||||
|
@.str.19 = private unnamed_addr constant [13 x i8] c"vertical-tab\00"
|
||||||
|
@.str.20 = private unnamed_addr constant [6 x i8] c"click\00"
|
||||||
|
@.str.21 = private unnamed_addr constant [23 x i8] c"_lambda_0_VerticalTabs\00"
|
||||||
|
@.str.22 = private unnamed_addr constant [15 x i8] c"⚙️ General\00"
|
||||||
|
@.str.23 = private unnamed_addr constant [23 x i8] c"_lambda_1_VerticalTabs\00"
|
||||||
|
@.str.24 = private unnamed_addr constant [13 x i8] c"🔒 Privacy\00"
|
||||||
|
@.str.25 = private unnamed_addr constant [23 x i8] c"_lambda_2_VerticalTabs\00"
|
||||||
|
@.str.26 = private unnamed_addr constant [14 x i8] c"🚀 Advanced\00"
|
||||||
|
@.str.27 = private unnamed_addr constant [21 x i8] c"vertical-tab-content\00"
|
||||||
|
@.str.28 = private unnamed_addr constant [14 x i8] c"tab-pane home\00"
|
||||||
|
@.str.29 = private unnamed_addr constant [3 x i8] c"h2\00"
|
||||||
|
@.str.30 = private unnamed_addr constant [18 x i8] c"🏠 Welcome Home\00"
|
||||||
|
@.str.31 = private unnamed_addr constant [29 x i8] c"This is your home dashboard.\00"
|
||||||
|
@.str.32 = private unnamed_addr constant [17 x i8] c"tab-pane profile\00"
|
||||||
|
@.str.33 = private unnamed_addr constant [18 x i8] c"👤 Your Profile\00"
|
||||||
|
@.str.34 = private unnamed_addr constant [30 x i8] c"Manage your account settings.\00"
|
||||||
|
@.str.35 = private unnamed_addr constant [18 x i8] c"tab-pane settings\00"
|
||||||
|
@.str.36 = private unnamed_addr constant [16 x i8] c"⚙️ Settings\00"
|
||||||
|
@.str.37 = private unnamed_addr constant [55 x i8] c"Configure your preferences using the categories below:\00"
|
||||||
|
@.str.38 = private unnamed_addr constant [15 x i8] c"tab-pane error\00"
|
||||||
|
@.str.39 = private unnamed_addr constant [28 x i8] c"⚠️ Invalid tab selected\00"
|
||||||
|
@.str.40 = private unnamed_addr constant [15 x i8] c"tabs-container\00"
|
||||||
|
@.str.41 = private unnamed_addr constant [3 x i8] c"h1\00"
|
||||||
|
@.str.42 = private unnamed_addr constant [19 x i8] c"Tab Component Demo\00"
|
||||||
|
@.str.43 = private unnamed_addr constant [9 x i8] c"subtitle\00"
|
||||||
|
@.str.44 = private unnamed_addr constant [47 x i8] c"Beautiful composition with ternary expressions\00"
|
||||||
|
@.str.45 = private unnamed_addr constant [3 x i8] c"ul\00"
|
||||||
|
@.str.46 = private unnamed_addr constant [9 x i8] c"tab-menu\00"
|
||||||
|
@.str.47 = private unnamed_addr constant [3 x i8] c"li\00"
|
||||||
|
@.str.48 = private unnamed_addr constant [11 x i8] c"tab active\00"
|
||||||
|
@.str.49 = private unnamed_addr constant [4 x i8] c"tab\00"
|
||||||
|
@.str.50 = private unnamed_addr constant [15 x i8] c"_lambda_0_Tabs\00"
|
||||||
|
@.str.51 = private unnamed_addr constant [10 x i8] c"🏠 Home\00"
|
||||||
|
@.str.52 = private unnamed_addr constant [15 x i8] c"_lambda_1_Tabs\00"
|
||||||
|
@.str.53 = private unnamed_addr constant [13 x i8] c"👤 Profile\00"
|
||||||
|
@.str.54 = private unnamed_addr constant [15 x i8] c"_lambda_2_Tabs\00"
|
||||||
|
@.str.55 = private unnamed_addr constant [12 x i8] c"tab-content\00"
|
||||||
|
|
||||||
|
; 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*)
|
||||||
|
declare i32 @js_create_marker()
|
||||||
|
declare void @js_replace_at_marker(i32, i32, i32)
|
||||||
|
|
||||||
|
; Signal storage for VerticalTabs (reactive state in WASM memory)
|
||||||
|
@signal_VerticalTabs_setting_tab = global i32 0
|
||||||
|
|
||||||
|
; Conditional rendering state
|
||||||
|
@cond_0_marker = global i32 0
|
||||||
|
@cond_0_current = global i32 0
|
||||||
|
@cond_0_case_0_root = global i32 0
|
||||||
|
@cond_0_case_1_root = global i32 0
|
||||||
|
@cond_0_case_2_root = global i32 0
|
||||||
|
@cond_0_case_3_root = global i32 0
|
||||||
|
|
||||||
|
; Signal helper functions
|
||||||
|
define i32 @get_setting_tab() {
|
||||||
|
entry:
|
||||||
|
%val = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
ret i32 %val
|
||||||
|
}
|
||||||
|
|
||||||
|
define void @set_setting_tab(i32 %newval) {
|
||||||
|
entry:
|
||||||
|
store i32 %newval, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
; TODO: Trigger DOM update callback
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: _lambda_0_VerticalTabs
|
||||||
|
define void @_lambda_0_VerticalTabs() {
|
||||||
|
entry:
|
||||||
|
call void @set_setting(i32 0)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: _lambda_1_VerticalTabs
|
||||||
|
define void @_lambda_1_VerticalTabs() {
|
||||||
|
entry:
|
||||||
|
call void @set_setting(i32 1)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: _lambda_2_VerticalTabs
|
||||||
|
define void @_lambda_2_VerticalTabs() {
|
||||||
|
entry:
|
||||||
|
call void @set_setting(i32 2)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: set_setting
|
||||||
|
define void @set_setting(i32 %value) {
|
||||||
|
entry:
|
||||||
|
store i32 %value, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
call void @reevaluate_conditional_0()
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Initialize conditional 0 (match)
|
||||||
|
define void @_init_conditional_0() {
|
||||||
|
entry:
|
||||||
|
; Render all cases upfront
|
||||||
|
%var0 = call i32 @_render_cond_0_case_0()
|
||||||
|
store i32 %var0, i32* @cond_0_case_0_root
|
||||||
|
%var1 = call i32 @_render_cond_0_case_1()
|
||||||
|
store i32 %var1, i32* @cond_0_case_1_root
|
||||||
|
%var2 = call i32 @_render_cond_0_case_2()
|
||||||
|
store i32 %var2, i32* @cond_0_case_2_root
|
||||||
|
%var3 = call i32 @_render_cond_0_case_3()
|
||||||
|
store i32 %var3, i32* @cond_0_case_3_root
|
||||||
|
%var4 = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
%var5 = load i32, i32* @cond_0_marker
|
||||||
|
switch i32 %var4, label %insert_case_3 [
|
||||||
|
i32 0, label %insert_case_0
|
||||||
|
i32 1, label %insert_case_1
|
||||||
|
i32 2, label %insert_case_2
|
||||||
|
]
|
||||||
|
insert_case_0:
|
||||||
|
call void @js_replace_at_marker(i32 %var5, i32 0, i32 %var0)
|
||||||
|
store i32 0, i32* @cond_0_current
|
||||||
|
ret void
|
||||||
|
insert_case_1:
|
||||||
|
call void @js_replace_at_marker(i32 %var5, i32 0, i32 %var1)
|
||||||
|
store i32 1, i32* @cond_0_current
|
||||||
|
ret void
|
||||||
|
insert_case_2:
|
||||||
|
call void @js_replace_at_marker(i32 %var5, i32 0, i32 %var2)
|
||||||
|
store i32 2, i32* @cond_0_current
|
||||||
|
ret void
|
||||||
|
insert_case_3:
|
||||||
|
call void @js_replace_at_marker(i32 %var5, i32 0, i32 %var3)
|
||||||
|
store i32 3, i32* @cond_0_current
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Re-evaluate conditional 0 (match)
|
||||||
|
define void @reevaluate_conditional_0() {
|
||||||
|
entry:
|
||||||
|
%var6 = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
%var7 = load i32, i32* @cond_0_current
|
||||||
|
switch i32 %var6, label %case_3 [
|
||||||
|
i32 0, label %case_0
|
||||||
|
i32 1, label %case_1
|
||||||
|
i32 2, label %case_2
|
||||||
|
]
|
||||||
|
case_0:
|
||||||
|
%var8 = icmp ne i32 %var7, 0
|
||||||
|
br i1 %var8, label %swap_0, label %done
|
||||||
|
swap_0:
|
||||||
|
%var9 = load i32, i32* @cond_0_marker
|
||||||
|
%var10 = load i32, i32* @cond_0_case_0_root
|
||||||
|
switch i32 %var7, label %load_old_0_3 [
|
||||||
|
i32 0, label %load_old_0_0
|
||||||
|
i32 1, label %load_old_0_1
|
||||||
|
i32 2, label %load_old_0_2
|
||||||
|
i32 3, label %load_old_0_3
|
||||||
|
]
|
||||||
|
load_old_0_0:
|
||||||
|
%var12 = load i32, i32* @cond_0_case_0_root
|
||||||
|
br label %merge_0
|
||||||
|
load_old_0_1:
|
||||||
|
%var13 = load i32, i32* @cond_0_case_1_root
|
||||||
|
br label %merge_0
|
||||||
|
load_old_0_2:
|
||||||
|
%var14 = load i32, i32* @cond_0_case_2_root
|
||||||
|
br label %merge_0
|
||||||
|
load_old_0_3:
|
||||||
|
%var15 = load i32, i32* @cond_0_case_3_root
|
||||||
|
br label %merge_0
|
||||||
|
merge_0:
|
||||||
|
%var11 = phi i32 [ %var12, %load_old_0_0 ], [ %var13, %load_old_0_1 ], [ %var14, %load_old_0_2 ], [ %var15, %load_old_0_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var9, i32 %var11, i32 %var10)
|
||||||
|
store i32 0, i32* @cond_0_current
|
||||||
|
br label %done
|
||||||
|
case_1:
|
||||||
|
%var16 = icmp ne i32 %var7, 1
|
||||||
|
br i1 %var16, label %swap_1, label %done
|
||||||
|
swap_1:
|
||||||
|
%var17 = load i32, i32* @cond_0_marker
|
||||||
|
%var18 = load i32, i32* @cond_0_case_1_root
|
||||||
|
switch i32 %var7, label %load_old_1_3 [
|
||||||
|
i32 0, label %load_old_1_0
|
||||||
|
i32 1, label %load_old_1_1
|
||||||
|
i32 2, label %load_old_1_2
|
||||||
|
i32 3, label %load_old_1_3
|
||||||
|
]
|
||||||
|
load_old_1_0:
|
||||||
|
%var20 = load i32, i32* @cond_0_case_0_root
|
||||||
|
br label %merge_1
|
||||||
|
load_old_1_1:
|
||||||
|
%var21 = load i32, i32* @cond_0_case_1_root
|
||||||
|
br label %merge_1
|
||||||
|
load_old_1_2:
|
||||||
|
%var22 = load i32, i32* @cond_0_case_2_root
|
||||||
|
br label %merge_1
|
||||||
|
load_old_1_3:
|
||||||
|
%var23 = load i32, i32* @cond_0_case_3_root
|
||||||
|
br label %merge_1
|
||||||
|
merge_1:
|
||||||
|
%var19 = phi i32 [ %var20, %load_old_1_0 ], [ %var21, %load_old_1_1 ], [ %var22, %load_old_1_2 ], [ %var23, %load_old_1_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var17, i32 %var19, i32 %var18)
|
||||||
|
store i32 1, i32* @cond_0_current
|
||||||
|
br label %done
|
||||||
|
case_2:
|
||||||
|
%var24 = icmp ne i32 %var7, 2
|
||||||
|
br i1 %var24, label %swap_2, label %done
|
||||||
|
swap_2:
|
||||||
|
%var25 = load i32, i32* @cond_0_marker
|
||||||
|
%var26 = load i32, i32* @cond_0_case_2_root
|
||||||
|
switch i32 %var7, label %load_old_2_3 [
|
||||||
|
i32 0, label %load_old_2_0
|
||||||
|
i32 1, label %load_old_2_1
|
||||||
|
i32 2, label %load_old_2_2
|
||||||
|
i32 3, label %load_old_2_3
|
||||||
|
]
|
||||||
|
load_old_2_0:
|
||||||
|
%var28 = load i32, i32* @cond_0_case_0_root
|
||||||
|
br label %merge_2
|
||||||
|
load_old_2_1:
|
||||||
|
%var29 = load i32, i32* @cond_0_case_1_root
|
||||||
|
br label %merge_2
|
||||||
|
load_old_2_2:
|
||||||
|
%var30 = load i32, i32* @cond_0_case_2_root
|
||||||
|
br label %merge_2
|
||||||
|
load_old_2_3:
|
||||||
|
%var31 = load i32, i32* @cond_0_case_3_root
|
||||||
|
br label %merge_2
|
||||||
|
merge_2:
|
||||||
|
%var27 = phi i32 [ %var28, %load_old_2_0 ], [ %var29, %load_old_2_1 ], [ %var30, %load_old_2_2 ], [ %var31, %load_old_2_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var25, i32 %var27, i32 %var26)
|
||||||
|
store i32 2, i32* @cond_0_current
|
||||||
|
br label %done
|
||||||
|
case_3:
|
||||||
|
%var32 = icmp ne i32 %var7, 3
|
||||||
|
br i1 %var32, label %swap_3, label %done
|
||||||
|
swap_3:
|
||||||
|
%var33 = load i32, i32* @cond_0_marker
|
||||||
|
%var34 = load i32, i32* @cond_0_case_3_root
|
||||||
|
switch i32 %var7, label %load_old_3_3 [
|
||||||
|
i32 0, label %load_old_3_0
|
||||||
|
i32 1, label %load_old_3_1
|
||||||
|
i32 2, label %load_old_3_2
|
||||||
|
i32 3, label %load_old_3_3
|
||||||
|
]
|
||||||
|
load_old_3_0:
|
||||||
|
%var36 = load i32, i32* @cond_0_case_0_root
|
||||||
|
br label %merge_3
|
||||||
|
load_old_3_1:
|
||||||
|
%var37 = load i32, i32* @cond_0_case_1_root
|
||||||
|
br label %merge_3
|
||||||
|
load_old_3_2:
|
||||||
|
%var38 = load i32, i32* @cond_0_case_2_root
|
||||||
|
br label %merge_3
|
||||||
|
load_old_3_3:
|
||||||
|
%var39 = load i32, i32* @cond_0_case_3_root
|
||||||
|
br label %merge_3
|
||||||
|
merge_3:
|
||||||
|
%var35 = phi i32 [ %var36, %load_old_3_0 ], [ %var37, %load_old_3_1 ], [ %var38, %load_old_3_2 ], [ %var39, %load_old_3_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var33, i32 %var35, i32 %var34)
|
||||||
|
store i32 3, i32* @cond_0_current
|
||||||
|
br label %done
|
||||||
|
done:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_0_case_0() {
|
||||||
|
entry:
|
||||||
|
%var40 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.0, i32 0, i32 0))
|
||||||
|
%var41 = 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 %var41, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.2, i32 0, i32 0))
|
||||||
|
%var42 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
%var43 = call i32 @js_create_text(i8* getelementptr inbounds ([24 x i8], [24 x i8]* @.str.4, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var42, i32 %var43)
|
||||||
|
call void @js_append_child(i32 %var41, i32 %var42)
|
||||||
|
call void @js_append_child(i32 %var41, i32 %var42)
|
||||||
|
%var44 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var45 = call i32 @js_create_text(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.6, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var44, i32 %var45)
|
||||||
|
call void @js_append_child(i32 %var41, i32 %var44)
|
||||||
|
call void @js_append_child(i32 %var41, i32 %var44)
|
||||||
|
%var46 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var47 = call i32 @js_create_text(i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var46, i32 %var47)
|
||||||
|
call void @js_append_child(i32 %var41, i32 %var46)
|
||||||
|
call void @js_append_child(i32 %var41, i32 %var46)
|
||||||
|
call void @js_append_child(i32 %var40, i32 %var41)
|
||||||
|
ret i32 %var40
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_0_case_1() {
|
||||||
|
entry:
|
||||||
|
%var48 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.0, i32 0, i32 0))
|
||||||
|
%var49 = 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 %var49, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.2, i32 0, i32 0))
|
||||||
|
%var50 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
%var51 = call i32 @js_create_text(i8* getelementptr inbounds ([22 x i8], [22 x i8]* @.str.8, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var50, i32 %var51)
|
||||||
|
call void @js_append_child(i32 %var49, i32 %var50)
|
||||||
|
call void @js_append_child(i32 %var49, i32 %var50)
|
||||||
|
%var52 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var53 = call i32 @js_create_text(i8* getelementptr inbounds ([26 x i8], [26 x i8]* @.str.9, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var52, i32 %var53)
|
||||||
|
call void @js_append_child(i32 %var49, i32 %var52)
|
||||||
|
call void @js_append_child(i32 %var49, i32 %var52)
|
||||||
|
%var54 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var55 = call i32 @js_create_text(i8* getelementptr inbounds ([19 x i8], [19 x i8]* @.str.10, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var54, i32 %var55)
|
||||||
|
call void @js_append_child(i32 %var49, i32 %var54)
|
||||||
|
call void @js_append_child(i32 %var49, i32 %var54)
|
||||||
|
call void @js_append_child(i32 %var48, i32 %var49)
|
||||||
|
ret i32 %var48
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_0_case_2() {
|
||||||
|
entry:
|
||||||
|
%var56 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.0, i32 0, i32 0))
|
||||||
|
%var57 = 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 %var57, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.2, i32 0, i32 0))
|
||||||
|
%var58 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
%var59 = call i32 @js_create_text(i8* getelementptr inbounds ([23 x i8], [23 x i8]* @.str.11, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var58, i32 %var59)
|
||||||
|
call void @js_append_child(i32 %var57, i32 %var58)
|
||||||
|
call void @js_append_child(i32 %var57, i32 %var58)
|
||||||
|
%var60 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var61 = call i32 @js_create_text(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str.12, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var60, i32 %var61)
|
||||||
|
call void @js_append_child(i32 %var57, i32 %var60)
|
||||||
|
call void @js_append_child(i32 %var57, i32 %var60)
|
||||||
|
%var62 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var63 = call i32 @js_create_text(i8* getelementptr inbounds ([32 x i8], [32 x i8]* @.str.13, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var62, i32 %var63)
|
||||||
|
call void @js_append_child(i32 %var57, i32 %var62)
|
||||||
|
call void @js_append_child(i32 %var57, i32 %var62)
|
||||||
|
call void @js_append_child(i32 %var56, i32 %var57)
|
||||||
|
ret i32 %var56
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_0_case_3() {
|
||||||
|
entry:
|
||||||
|
%var64 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.0, i32 0, i32 0))
|
||||||
|
%var65 = 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 %var65, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.2, i32 0, i32 0))
|
||||||
|
%var66 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var67 = call i32 @js_create_text(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str.14, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var66, i32 %var67)
|
||||||
|
call void @js_append_child(i32 %var65, i32 %var66)
|
||||||
|
call void @js_append_child(i32 %var65, i32 %var66)
|
||||||
|
call void @js_append_child(i32 %var64, i32 %var65)
|
||||||
|
ret i32 %var64
|
||||||
|
}
|
||||||
|
|
||||||
|
; Component render function
|
||||||
|
define void @render_VerticalTabs() {
|
||||||
|
entry:
|
||||||
|
%var68 = 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 %var68, 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.15, i32 0, i32 0))
|
||||||
|
%var69 = 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 %var69, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.16, i32 0, i32 0))
|
||||||
|
%var70 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.17, i32 0, i32 0))
|
||||||
|
%var71 = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
%var72 = icmp eq i32 %var71, 0
|
||||||
|
%var73 = zext i1 %var72 to i32
|
||||||
|
%var74 = icmp ne i32 %var73, 0
|
||||||
|
%var75 = select i1 %var74, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.18, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.19, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var70, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* %var75)
|
||||||
|
call void @js_bind_event(i32 %var70, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.20, i32 0, i32 0), i8* getelementptr inbounds ([23 x i8], [23 x i8]* @.str.21, i32 0, i32 0))
|
||||||
|
%var76 = call i32 @js_create_text(i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.22, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var70, i32 %var76)
|
||||||
|
call void @js_append_child(i32 %var69, i32 %var70)
|
||||||
|
call void @js_append_child(i32 %var69, i32 %var70)
|
||||||
|
%var77 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.17, i32 0, i32 0))
|
||||||
|
%var78 = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
%var79 = icmp eq i32 %var78, 1
|
||||||
|
%var80 = zext i1 %var79 to i32
|
||||||
|
%var81 = icmp ne i32 %var80, 0
|
||||||
|
%var82 = select i1 %var81, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.18, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.19, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var77, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* %var82)
|
||||||
|
call void @js_bind_event(i32 %var77, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.20, i32 0, i32 0), i8* getelementptr inbounds ([23 x i8], [23 x i8]* @.str.23, i32 0, i32 0))
|
||||||
|
%var83 = call i32 @js_create_text(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.24, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var77, i32 %var83)
|
||||||
|
call void @js_append_child(i32 %var69, i32 %var77)
|
||||||
|
call void @js_append_child(i32 %var69, i32 %var77)
|
||||||
|
%var84 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.17, i32 0, i32 0))
|
||||||
|
%var85 = load i32, i32* @signal_VerticalTabs_setting_tab
|
||||||
|
%var86 = icmp eq i32 %var85, 2
|
||||||
|
%var87 = zext i1 %var86 to i32
|
||||||
|
%var88 = icmp ne i32 %var87, 0
|
||||||
|
%var89 = select i1 %var88, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.18, i32 0, i32 0), i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.19, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var84, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* %var89)
|
||||||
|
call void @js_bind_event(i32 %var84, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.20, i32 0, i32 0), i8* getelementptr inbounds ([23 x i8], [23 x i8]* @.str.25, i32 0, i32 0))
|
||||||
|
%var90 = call i32 @js_create_text(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str.26, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var84, i32 %var90)
|
||||||
|
call void @js_append_child(i32 %var69, i32 %var84)
|
||||||
|
call void @js_append_child(i32 %var69, i32 %var84)
|
||||||
|
call void @js_append_child(i32 %var68, i32 %var69)
|
||||||
|
call void @js_append_child(i32 %var68, i32 %var69)
|
||||||
|
%var91 = 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 %var91, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([21 x i8], [21 x i8]* @.str.27, i32 0, i32 0))
|
||||||
|
%var92 = call i32 @js_create_marker()
|
||||||
|
call void @js_append_child(i32 %var91, i32 %var92)
|
||||||
|
store i32 %var92, i32* @cond_0_marker
|
||||||
|
call void @_init_conditional_0()
|
||||||
|
call void @js_append_child(i32 %var68, i32 %var91)
|
||||||
|
call void @js_append_child(i32 %var68, i32 %var91)
|
||||||
|
call void @js_set_root(i32 %var68)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Signal storage for Tabs (reactive state in WASM memory)
|
||||||
|
@signal_Tabs_tab_index = global i32 0
|
||||||
|
|
||||||
|
; Conditional rendering state
|
||||||
|
@cond_1_marker = global i32 0
|
||||||
|
@cond_1_current = global i32 0
|
||||||
|
@cond_1_case_0_root = global i32 0
|
||||||
|
@cond_1_case_1_root = global i32 0
|
||||||
|
@cond_1_case_2_root = global i32 0
|
||||||
|
@cond_1_case_3_root = global i32 0
|
||||||
|
|
||||||
|
; Signal helper functions
|
||||||
|
define i32 @get_tab_index() {
|
||||||
|
entry:
|
||||||
|
%val = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
ret i32 %val
|
||||||
|
}
|
||||||
|
|
||||||
|
define void @set_tab_index(i32 %newval) {
|
||||||
|
entry:
|
||||||
|
store i32 %newval, i32* @signal_Tabs_tab_index
|
||||||
|
; TODO: Trigger DOM update callback
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: _lambda_0_Tabs
|
||||||
|
define void @_lambda_0_Tabs() {
|
||||||
|
entry:
|
||||||
|
call void @set_tab(i32 0)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: _lambda_1_Tabs
|
||||||
|
define void @_lambda_1_Tabs() {
|
||||||
|
entry:
|
||||||
|
call void @set_tab(i32 1)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: _lambda_2_Tabs
|
||||||
|
define void @_lambda_2_Tabs() {
|
||||||
|
entry:
|
||||||
|
call void @set_tab(i32 2)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: set_tab
|
||||||
|
define void @set_tab(i32 %value) {
|
||||||
|
entry:
|
||||||
|
store i32 %value, i32* @signal_Tabs_tab_index
|
||||||
|
call void @reevaluate_conditional_1()
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Initialize conditional 1 (match)
|
||||||
|
define void @_init_conditional_1() {
|
||||||
|
entry:
|
||||||
|
; Render all cases upfront
|
||||||
|
%var93 = call i32 @_render_cond_1_case_0()
|
||||||
|
store i32 %var93, i32* @cond_1_case_0_root
|
||||||
|
%var94 = call i32 @_render_cond_1_case_1()
|
||||||
|
store i32 %var94, i32* @cond_1_case_1_root
|
||||||
|
%var95 = call i32 @_render_cond_1_case_2()
|
||||||
|
store i32 %var95, i32* @cond_1_case_2_root
|
||||||
|
%var96 = call i32 @_render_cond_1_case_3()
|
||||||
|
store i32 %var96, i32* @cond_1_case_3_root
|
||||||
|
%var97 = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
%var98 = load i32, i32* @cond_1_marker
|
||||||
|
switch i32 %var97, label %insert_case_3 [
|
||||||
|
i32 0, label %insert_case_0
|
||||||
|
i32 1, label %insert_case_1
|
||||||
|
i32 2, label %insert_case_2
|
||||||
|
]
|
||||||
|
insert_case_0:
|
||||||
|
call void @js_replace_at_marker(i32 %var98, i32 0, i32 %var93)
|
||||||
|
store i32 0, i32* @cond_1_current
|
||||||
|
ret void
|
||||||
|
insert_case_1:
|
||||||
|
call void @js_replace_at_marker(i32 %var98, i32 0, i32 %var94)
|
||||||
|
store i32 1, i32* @cond_1_current
|
||||||
|
ret void
|
||||||
|
insert_case_2:
|
||||||
|
call void @js_replace_at_marker(i32 %var98, i32 0, i32 %var95)
|
||||||
|
store i32 2, i32* @cond_1_current
|
||||||
|
ret void
|
||||||
|
insert_case_3:
|
||||||
|
call void @js_replace_at_marker(i32 %var98, i32 0, i32 %var96)
|
||||||
|
store i32 3, i32* @cond_1_current
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Re-evaluate conditional 1 (match)
|
||||||
|
define void @reevaluate_conditional_1() {
|
||||||
|
entry:
|
||||||
|
%var99 = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
%var100 = load i32, i32* @cond_1_current
|
||||||
|
switch i32 %var99, label %case_3 [
|
||||||
|
i32 0, label %case_0
|
||||||
|
i32 1, label %case_1
|
||||||
|
i32 2, label %case_2
|
||||||
|
]
|
||||||
|
case_0:
|
||||||
|
%var101 = icmp ne i32 %var100, 0
|
||||||
|
br i1 %var101, label %swap_0, label %done
|
||||||
|
swap_0:
|
||||||
|
%var102 = load i32, i32* @cond_1_marker
|
||||||
|
%var103 = load i32, i32* @cond_1_case_0_root
|
||||||
|
switch i32 %var100, label %load_old_0_3 [
|
||||||
|
i32 0, label %load_old_0_0
|
||||||
|
i32 1, label %load_old_0_1
|
||||||
|
i32 2, label %load_old_0_2
|
||||||
|
i32 3, label %load_old_0_3
|
||||||
|
]
|
||||||
|
load_old_0_0:
|
||||||
|
%var105 = load i32, i32* @cond_1_case_0_root
|
||||||
|
br label %merge_0
|
||||||
|
load_old_0_1:
|
||||||
|
%var106 = load i32, i32* @cond_1_case_1_root
|
||||||
|
br label %merge_0
|
||||||
|
load_old_0_2:
|
||||||
|
%var107 = load i32, i32* @cond_1_case_2_root
|
||||||
|
br label %merge_0
|
||||||
|
load_old_0_3:
|
||||||
|
%var108 = load i32, i32* @cond_1_case_3_root
|
||||||
|
br label %merge_0
|
||||||
|
merge_0:
|
||||||
|
%var104 = phi i32 [ %var105, %load_old_0_0 ], [ %var106, %load_old_0_1 ], [ %var107, %load_old_0_2 ], [ %var108, %load_old_0_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var102, i32 %var104, i32 %var103)
|
||||||
|
store i32 0, i32* @cond_1_current
|
||||||
|
br label %done
|
||||||
|
case_1:
|
||||||
|
%var109 = icmp ne i32 %var100, 1
|
||||||
|
br i1 %var109, label %swap_1, label %done
|
||||||
|
swap_1:
|
||||||
|
%var110 = load i32, i32* @cond_1_marker
|
||||||
|
%var111 = load i32, i32* @cond_1_case_1_root
|
||||||
|
switch i32 %var100, label %load_old_1_3 [
|
||||||
|
i32 0, label %load_old_1_0
|
||||||
|
i32 1, label %load_old_1_1
|
||||||
|
i32 2, label %load_old_1_2
|
||||||
|
i32 3, label %load_old_1_3
|
||||||
|
]
|
||||||
|
load_old_1_0:
|
||||||
|
%var113 = load i32, i32* @cond_1_case_0_root
|
||||||
|
br label %merge_1
|
||||||
|
load_old_1_1:
|
||||||
|
%var114 = load i32, i32* @cond_1_case_1_root
|
||||||
|
br label %merge_1
|
||||||
|
load_old_1_2:
|
||||||
|
%var115 = load i32, i32* @cond_1_case_2_root
|
||||||
|
br label %merge_1
|
||||||
|
load_old_1_3:
|
||||||
|
%var116 = load i32, i32* @cond_1_case_3_root
|
||||||
|
br label %merge_1
|
||||||
|
merge_1:
|
||||||
|
%var112 = phi i32 [ %var113, %load_old_1_0 ], [ %var114, %load_old_1_1 ], [ %var115, %load_old_1_2 ], [ %var116, %load_old_1_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var110, i32 %var112, i32 %var111)
|
||||||
|
store i32 1, i32* @cond_1_current
|
||||||
|
br label %done
|
||||||
|
case_2:
|
||||||
|
%var117 = icmp ne i32 %var100, 2
|
||||||
|
br i1 %var117, label %swap_2, label %done
|
||||||
|
swap_2:
|
||||||
|
%var118 = load i32, i32* @cond_1_marker
|
||||||
|
%var119 = load i32, i32* @cond_1_case_2_root
|
||||||
|
switch i32 %var100, label %load_old_2_3 [
|
||||||
|
i32 0, label %load_old_2_0
|
||||||
|
i32 1, label %load_old_2_1
|
||||||
|
i32 2, label %load_old_2_2
|
||||||
|
i32 3, label %load_old_2_3
|
||||||
|
]
|
||||||
|
load_old_2_0:
|
||||||
|
%var121 = load i32, i32* @cond_1_case_0_root
|
||||||
|
br label %merge_2
|
||||||
|
load_old_2_1:
|
||||||
|
%var122 = load i32, i32* @cond_1_case_1_root
|
||||||
|
br label %merge_2
|
||||||
|
load_old_2_2:
|
||||||
|
%var123 = load i32, i32* @cond_1_case_2_root
|
||||||
|
br label %merge_2
|
||||||
|
load_old_2_3:
|
||||||
|
%var124 = load i32, i32* @cond_1_case_3_root
|
||||||
|
br label %merge_2
|
||||||
|
merge_2:
|
||||||
|
%var120 = phi i32 [ %var121, %load_old_2_0 ], [ %var122, %load_old_2_1 ], [ %var123, %load_old_2_2 ], [ %var124, %load_old_2_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var118, i32 %var120, i32 %var119)
|
||||||
|
store i32 2, i32* @cond_1_current
|
||||||
|
br label %done
|
||||||
|
case_3:
|
||||||
|
%var125 = icmp ne i32 %var100, 3
|
||||||
|
br i1 %var125, label %swap_3, label %done
|
||||||
|
swap_3:
|
||||||
|
%var126 = load i32, i32* @cond_1_marker
|
||||||
|
%var127 = load i32, i32* @cond_1_case_3_root
|
||||||
|
switch i32 %var100, label %load_old_3_3 [
|
||||||
|
i32 0, label %load_old_3_0
|
||||||
|
i32 1, label %load_old_3_1
|
||||||
|
i32 2, label %load_old_3_2
|
||||||
|
i32 3, label %load_old_3_3
|
||||||
|
]
|
||||||
|
load_old_3_0:
|
||||||
|
%var129 = load i32, i32* @cond_1_case_0_root
|
||||||
|
br label %merge_3
|
||||||
|
load_old_3_1:
|
||||||
|
%var130 = load i32, i32* @cond_1_case_1_root
|
||||||
|
br label %merge_3
|
||||||
|
load_old_3_2:
|
||||||
|
%var131 = load i32, i32* @cond_1_case_2_root
|
||||||
|
br label %merge_3
|
||||||
|
load_old_3_3:
|
||||||
|
%var132 = load i32, i32* @cond_1_case_3_root
|
||||||
|
br label %merge_3
|
||||||
|
merge_3:
|
||||||
|
%var128 = phi i32 [ %var129, %load_old_3_0 ], [ %var130, %load_old_3_1 ], [ %var131, %load_old_3_2 ], [ %var132, %load_old_3_3 ]
|
||||||
|
call void @js_replace_at_marker(i32 %var126, i32 %var128, i32 %var127)
|
||||||
|
store i32 3, i32* @cond_1_current
|
||||||
|
br label %done
|
||||||
|
done:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_1_case_0() {
|
||||||
|
entry:
|
||||||
|
%var133 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.0, i32 0, i32 0))
|
||||||
|
%var134 = 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 %var134, 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.28, i32 0, i32 0))
|
||||||
|
%var135 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.29, i32 0, i32 0))
|
||||||
|
%var136 = call i32 @js_create_text(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.30, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var135, i32 %var136)
|
||||||
|
call void @js_append_child(i32 %var134, i32 %var135)
|
||||||
|
call void @js_append_child(i32 %var134, i32 %var135)
|
||||||
|
%var137 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var138 = call i32 @js_create_text(i8* getelementptr inbounds ([29 x i8], [29 x i8]* @.str.31, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var137, i32 %var138)
|
||||||
|
call void @js_append_child(i32 %var134, i32 %var137)
|
||||||
|
call void @js_append_child(i32 %var134, i32 %var137)
|
||||||
|
call void @js_append_child(i32 %var133, i32 %var134)
|
||||||
|
ret i32 %var133
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_1_case_1() {
|
||||||
|
entry:
|
||||||
|
%var139 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.0, i32 0, i32 0))
|
||||||
|
%var140 = 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 %var140, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([17 x i8], [17 x i8]* @.str.32, i32 0, i32 0))
|
||||||
|
%var141 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.29, i32 0, i32 0))
|
||||||
|
%var142 = call i32 @js_create_text(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.33, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var141, i32 %var142)
|
||||||
|
call void @js_append_child(i32 %var140, i32 %var141)
|
||||||
|
call void @js_append_child(i32 %var140, i32 %var141)
|
||||||
|
%var143 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var144 = call i32 @js_create_text(i8* getelementptr inbounds ([30 x i8], [30 x i8]* @.str.34, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var143, i32 %var144)
|
||||||
|
call void @js_append_child(i32 %var140, i32 %var143)
|
||||||
|
call void @js_append_child(i32 %var140, i32 %var143)
|
||||||
|
call void @js_append_child(i32 %var139, i32 %var140)
|
||||||
|
ret i32 %var139
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_1_case_2() {
|
||||||
|
entry:
|
||||||
|
%var145 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.0, i32 0, i32 0))
|
||||||
|
%var146 = 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 %var146, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.35, i32 0, i32 0))
|
||||||
|
%var147 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.29, i32 0, i32 0))
|
||||||
|
%var148 = call i32 @js_create_text(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str.36, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var147, i32 %var148)
|
||||||
|
call void @js_append_child(i32 %var146, i32 %var147)
|
||||||
|
call void @js_append_child(i32 %var146, i32 %var147)
|
||||||
|
%var149 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var150 = call i32 @js_create_text(i8* getelementptr inbounds ([55 x i8], [55 x i8]* @.str.37, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var149, i32 %var150)
|
||||||
|
call void @js_append_child(i32 %var146, i32 %var149)
|
||||||
|
call void @js_append_child(i32 %var146, i32 %var149)
|
||||||
|
call void @render_VerticalTabs()
|
||||||
|
call void @js_append_child(i32 %var145, i32 %var146)
|
||||||
|
ret i32 %var145
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_1_case_3() {
|
||||||
|
entry:
|
||||||
|
%var151 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.0, i32 0, i32 0))
|
||||||
|
%var152 = 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 %var152, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.38, i32 0, i32 0))
|
||||||
|
%var153 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
%var154 = call i32 @js_create_text(i8* getelementptr inbounds ([28 x i8], [28 x i8]* @.str.39, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var153, i32 %var154)
|
||||||
|
call void @js_append_child(i32 %var152, i32 %var153)
|
||||||
|
call void @js_append_child(i32 %var152, i32 %var153)
|
||||||
|
call void @js_append_child(i32 %var151, i32 %var152)
|
||||||
|
ret i32 %var151
|
||||||
|
}
|
||||||
|
|
||||||
|
; Component render function
|
||||||
|
define void @render_Tabs() {
|
||||||
|
entry:
|
||||||
|
%var155 = 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 %var155, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.40, i32 0, i32 0))
|
||||||
|
%var156 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.41, i32 0, i32 0))
|
||||||
|
%var157 = call i32 @js_create_text(i8* getelementptr inbounds ([19 x i8], [19 x i8]* @.str.42, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var156, i32 %var157)
|
||||||
|
call void @js_append_child(i32 %var155, i32 %var156)
|
||||||
|
call void @js_append_child(i32 %var155, i32 %var156)
|
||||||
|
%var158 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.5, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var158, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str.43, i32 0, i32 0))
|
||||||
|
%var159 = call i32 @js_create_text(i8* getelementptr inbounds ([47 x i8], [47 x i8]* @.str.44, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var158, i32 %var159)
|
||||||
|
call void @js_append_child(i32 %var155, i32 %var158)
|
||||||
|
call void @js_append_child(i32 %var155, i32 %var158)
|
||||||
|
%var160 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.45, i32 0, i32 0))
|
||||||
|
call void @js_set_attribute(i32 %var160, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str.46, i32 0, i32 0))
|
||||||
|
%var161 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.47, i32 0, i32 0))
|
||||||
|
%var162 = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
%var163 = icmp eq i32 %var162, 0
|
||||||
|
%var164 = zext i1 %var163 to i32
|
||||||
|
%var165 = icmp ne i32 %var164, 0
|
||||||
|
%var166 = select i1 %var165, i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.48, i32 0, i32 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.49, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var161, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* %var166)
|
||||||
|
%var167 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.17, i32 0, i32 0))
|
||||||
|
call void @js_bind_event(i32 %var167, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.20, i32 0, i32 0), i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.50, i32 0, i32 0))
|
||||||
|
%var168 = call i32 @js_create_text(i8* getelementptr inbounds ([10 x i8], [10 x i8]* @.str.51, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var167, i32 %var168)
|
||||||
|
call void @js_append_child(i32 %var161, i32 %var167)
|
||||||
|
call void @js_append_child(i32 %var161, i32 %var167)
|
||||||
|
call void @js_append_child(i32 %var160, i32 %var161)
|
||||||
|
call void @js_append_child(i32 %var160, i32 %var161)
|
||||||
|
%var169 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.47, i32 0, i32 0))
|
||||||
|
%var170 = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
%var171 = icmp eq i32 %var170, 1
|
||||||
|
%var172 = zext i1 %var171 to i32
|
||||||
|
%var173 = icmp ne i32 %var172, 0
|
||||||
|
%var174 = select i1 %var173, i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.48, i32 0, i32 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.49, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var169, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* %var174)
|
||||||
|
%var175 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.17, i32 0, i32 0))
|
||||||
|
call void @js_bind_event(i32 %var175, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.20, i32 0, i32 0), i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.52, i32 0, i32 0))
|
||||||
|
%var176 = call i32 @js_create_text(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str.53, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var175, i32 %var176)
|
||||||
|
call void @js_append_child(i32 %var169, i32 %var175)
|
||||||
|
call void @js_append_child(i32 %var169, i32 %var175)
|
||||||
|
call void @js_append_child(i32 %var160, i32 %var169)
|
||||||
|
call void @js_append_child(i32 %var160, i32 %var169)
|
||||||
|
%var177 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.47, i32 0, i32 0))
|
||||||
|
%var178 = load i32, i32* @signal_Tabs_tab_index
|
||||||
|
%var179 = icmp eq i32 %var178, 2
|
||||||
|
%var180 = zext i1 %var179 to i32
|
||||||
|
%var181 = icmp ne i32 %var180, 0
|
||||||
|
%var182 = select i1 %var181, i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.48, i32 0, i32 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.49, i32 0, i32 0)
|
||||||
|
call void @js_set_attribute(i32 %var177, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* %var182)
|
||||||
|
%var183 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.17, i32 0, i32 0))
|
||||||
|
call void @js_bind_event(i32 %var183, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.20, i32 0, i32 0), i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str.54, i32 0, i32 0))
|
||||||
|
%var184 = call i32 @js_create_text(i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str.36, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var183, i32 %var184)
|
||||||
|
call void @js_append_child(i32 %var177, i32 %var183)
|
||||||
|
call void @js_append_child(i32 %var177, i32 %var183)
|
||||||
|
call void @js_append_child(i32 %var160, i32 %var177)
|
||||||
|
call void @js_append_child(i32 %var160, i32 %var177)
|
||||||
|
call void @js_append_child(i32 %var155, i32 %var160)
|
||||||
|
call void @js_append_child(i32 %var155, i32 %var160)
|
||||||
|
%var185 = 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 %var185, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([12 x i8], [12 x i8]* @.str.55, i32 0, i32 0))
|
||||||
|
%var186 = call i32 @js_create_marker()
|
||||||
|
call void @js_append_child(i32 %var185, i32 %var186)
|
||||||
|
store i32 %var186, i32* @cond_1_marker
|
||||||
|
call void @_init_conditional_1()
|
||||||
|
call void @js_append_child(i32 %var155, i32 %var185)
|
||||||
|
call void @js_append_child(i32 %var155, i32 %var185)
|
||||||
|
call void @js_set_root(i32 %var155)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
"""
|
||||||
|
Clean Tabs implementation using ternary expressions to eliminate repetition.
|
||||||
|
|
||||||
|
Before: 6 lines per tab button (if/else blocks)
|
||||||
|
After: 1 line per tab button (ternary expressions)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
|
||||||
|
@component
|
||||||
|
def VerticalTabs():
|
||||||
|
"""Vertical tabs for settings categories."""
|
||||||
|
setting_tab = signal(0)
|
||||||
|
|
||||||
|
def set_setting(value):
|
||||||
|
setting_tab = value
|
||||||
|
|
||||||
|
with Div(class_="vertical-tabs"):
|
||||||
|
# Vertical tab menu - now just 3 lines instead of 18!
|
||||||
|
with Div(class_="vertical-tab-menu"):
|
||||||
|
with Button(class_='vertical-tab active' if setting_tab == 0 else 'vertical-tab', onclick=lambda: set_setting(0)):
|
||||||
|
yield "⚙️ General"
|
||||||
|
|
||||||
|
with Button(class_='vertical-tab active' if setting_tab == 1 else 'vertical-tab', onclick=lambda: set_setting(1)):
|
||||||
|
yield "🔒 Privacy"
|
||||||
|
|
||||||
|
with Button(class_='vertical-tab active' if setting_tab == 2 else 'vertical-tab', onclick=lambda: set_setting(2)):
|
||||||
|
yield "🚀 Advanced"
|
||||||
|
|
||||||
|
# Tab content (same as before)
|
||||||
|
with Div(class_="vertical-tab-content"):
|
||||||
|
match setting_tab:
|
||||||
|
case 0:
|
||||||
|
with Div(class_="setting-pane"):
|
||||||
|
with H3():
|
||||||
|
yield "⚙️ General Settings"
|
||||||
|
with P():
|
||||||
|
yield "Language: English"
|
||||||
|
with P():
|
||||||
|
yield "Time Zone: UTC"
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
with Div(class_="setting-pane"):
|
||||||
|
with H3():
|
||||||
|
yield "🔒 Privacy Settings"
|
||||||
|
with P():
|
||||||
|
yield "Data Collection: Disabled"
|
||||||
|
with P():
|
||||||
|
yield "Analytics: Opt-out"
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
with Div(class_="setting-pane"):
|
||||||
|
with H3():
|
||||||
|
yield "🚀 Advanced Settings"
|
||||||
|
with P():
|
||||||
|
yield "Debug Mode: Off"
|
||||||
|
with P():
|
||||||
|
yield "Experimental Features: Disabled"
|
||||||
|
|
||||||
|
case _:
|
||||||
|
with Div(class_="setting-pane"):
|
||||||
|
with P():
|
||||||
|
yield "Invalid setting"
|
||||||
|
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Tabs():
|
||||||
|
"""Main tabs container with ternary expressions for clean tab buttons."""
|
||||||
|
tab_index = signal(0)
|
||||||
|
|
||||||
|
def set_tab(value):
|
||||||
|
tab_index = value
|
||||||
|
|
||||||
|
with Div(class_="tabs-container"):
|
||||||
|
with H1():
|
||||||
|
yield "Tab Component Demo"
|
||||||
|
|
||||||
|
with P(class_="subtitle"):
|
||||||
|
yield "Beautiful composition with ternary expressions"
|
||||||
|
|
||||||
|
# Tab menu - clean one-line buttons!
|
||||||
|
with Ul(class_="tab-menu"):
|
||||||
|
with Li(class_='tab active' if tab_index == 0 else 'tab'):
|
||||||
|
with Button(onclick=lambda: set_tab(0)):
|
||||||
|
yield "🏠 Home"
|
||||||
|
|
||||||
|
with Li(class_='tab active' if tab_index == 1 else 'tab'):
|
||||||
|
with Button(onclick=lambda: set_tab(1)):
|
||||||
|
yield "👤 Profile"
|
||||||
|
|
||||||
|
with Li(class_='tab active' if tab_index == 2 else 'tab'):
|
||||||
|
with Button(onclick=lambda: set_tab(2)):
|
||||||
|
yield "⚙️ Settings"
|
||||||
|
|
||||||
|
# Tab panes
|
||||||
|
with Div(class_="tab-content"):
|
||||||
|
match tab_index:
|
||||||
|
case 0:
|
||||||
|
with Div(class_="tab-pane home"):
|
||||||
|
with H2():
|
||||||
|
yield "🏠 Welcome Home"
|
||||||
|
with P():
|
||||||
|
yield "This is your home dashboard."
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
with Div(class_="tab-pane profile"):
|
||||||
|
with H2():
|
||||||
|
yield "👤 Your Profile"
|
||||||
|
with P():
|
||||||
|
yield "Manage your account settings."
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
with Div(class_="tab-pane settings"):
|
||||||
|
with H2():
|
||||||
|
yield "⚙️ Settings"
|
||||||
|
with P():
|
||||||
|
yield "Configure your preferences using the categories below:"
|
||||||
|
|
||||||
|
# Nested vertical tabs!
|
||||||
|
yield VerticalTabs()
|
||||||
|
|
||||||
|
case _:
|
||||||
|
with Div(class_="tab-pane error"):
|
||||||
|
with P():
|
||||||
|
yield "⚠️ Invalid tab selected"
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,354 @@
|
|||||||
|
/**
|
||||||
|
* Ritz Runtime - WebAssembly DOM Bridge
|
||||||
|
*
|
||||||
|
* Provides the runtime environment for Ritz components compiled to WASM.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class RitzRuntime {
|
||||||
|
constructor(rootElement) {
|
||||||
|
this.rootElement = rootElement || document.getElementById('app');
|
||||||
|
this.nodes = new Map();
|
||||||
|
this.nextNodeId = 1;
|
||||||
|
this.log = [];
|
||||||
|
this.wasmInstance = null;
|
||||||
|
this.debugEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== DOM Manipulation Bridge =====
|
||||||
|
|
||||||
|
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(`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(`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(`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(`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, () => {
|
||||||
|
this._log(`[Event] ${eventName} → ${handlerName}()`);
|
||||||
|
handler();
|
||||||
|
this.updateSignals();
|
||||||
|
});
|
||||||
|
this._log(`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(`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(`updateTextContent(${nodeId}, "${text}")`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Signal Management =====
|
||||||
|
|
||||||
|
updateSignals() {
|
||||||
|
// Discover and update all signals
|
||||||
|
if (!this.wasmInstance) return;
|
||||||
|
|
||||||
|
const exports = this.wasmInstance.exports;
|
||||||
|
|
||||||
|
// Find all get_* functions (signal getters)
|
||||||
|
for (const [name, fn] of Object.entries(exports)) {
|
||||||
|
if (name.startsWith('get_') && typeof fn === 'function') {
|
||||||
|
const signalName = name.substring(4); // Remove 'get_' prefix
|
||||||
|
const value = fn();
|
||||||
|
this._log(`[Signal] ${signalName} = ${value}`);
|
||||||
|
|
||||||
|
// Update DOM elements bound to this signal
|
||||||
|
// TODO: Track signal-to-DOM bindings during render for efficiency
|
||||||
|
this.updateSignalDisplay(signalName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-evaluate all reactive conditionals
|
||||||
|
for (const [name, fn] of Object.entries(exports)) {
|
||||||
|
if (name.startsWith('reevaluate_conditional_') && typeof fn === 'function') {
|
||||||
|
fn();
|
||||||
|
this._log(`[Reactive] ${name}()`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-evaluate all reactive match statements
|
||||||
|
for (const [name, fn] of Object.entries(exports)) {
|
||||||
|
if (name.startsWith('reevaluate_match_') && typeof fn === 'function') {
|
||||||
|
fn();
|
||||||
|
this._log(`[Reactive] ${name}()`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
const selectors = [
|
||||||
|
`[data-signal="${signalName}"]`,
|
||||||
|
`.${signalName}`,
|
||||||
|
'.count', // Hardcoded for demo - TODO: track bindings properly
|
||||||
|
];
|
||||||
|
|
||||||
|
selectors.forEach(selector => {
|
||||||
|
const elements = document.querySelectorAll(selector);
|
||||||
|
elements.forEach(el => {
|
||||||
|
// Check if text contains the signal name or looks like a counter
|
||||||
|
const text = el.textContent;
|
||||||
|
if (text.includes(signalName) || text.match(/\d+/)) {
|
||||||
|
// Replace the number in the text
|
||||||
|
el.textContent = text.replace(/\d+/, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Reactive Conditional Rendering =====
|
||||||
|
|
||||||
|
js_create_marker() {
|
||||||
|
// Create a comment node as a marker for conditional insertion
|
||||||
|
const marker = document.createComment('conditional-marker');
|
||||||
|
const id = this.nextNodeId++;
|
||||||
|
this.nodes.set(id, marker);
|
||||||
|
this._log(`Created marker node ${id}`);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
js_replace_at_marker(markerId, oldNodeId, newNodeId) {
|
||||||
|
// Replace DOM node at marker position
|
||||||
|
const marker = this.nodes.get(markerId);
|
||||||
|
if (!marker) {
|
||||||
|
console.error(`[replace_at_marker] Marker ${markerId} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = marker.parentNode;
|
||||||
|
if (!parent) {
|
||||||
|
console.error(`[replace_at_marker] Marker ${markerId} has no parent`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._log(`[replace_at_marker] marker=${markerId}, old=${oldNodeId}, new=${newNodeId}`);
|
||||||
|
|
||||||
|
// Remove old node if it exists (0 means no old node to remove)
|
||||||
|
if (oldNodeId !== 0) {
|
||||||
|
const oldNode = this.nodes.get(oldNodeId);
|
||||||
|
if (!oldNode) {
|
||||||
|
console.warn(`[replace_at_marker] Old node ${oldNodeId} not found in registry`);
|
||||||
|
} else if (!oldNode.parentNode) {
|
||||||
|
console.warn(`[replace_at_marker] Old node ${oldNodeId} not in DOM`);
|
||||||
|
} else if (oldNode.parentNode !== parent) {
|
||||||
|
console.warn(`[replace_at_marker] Old node ${oldNodeId} in wrong parent`);
|
||||||
|
oldNode.parentNode.removeChild(oldNode);
|
||||||
|
this._log(`[replace_at_marker] Removed ${oldNodeId} from wrong parent`);
|
||||||
|
} else {
|
||||||
|
// Remove from correct parent
|
||||||
|
parent.removeChild(oldNode);
|
||||||
|
this._log(`[replace_at_marker] Removed ${oldNodeId} from correct parent`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert new node after marker (only if newNodeId is not 0)
|
||||||
|
if (newNodeId !== 0) {
|
||||||
|
const newNode = this.nodes.get(newNodeId);
|
||||||
|
if (!newNode) {
|
||||||
|
console.error(`[replace_at_marker] New node ${newNodeId} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert right after the marker
|
||||||
|
// If newNode is already in DOM elsewhere, this will move it
|
||||||
|
const nextSibling = marker.nextSibling;
|
||||||
|
if (nextSibling) {
|
||||||
|
parent.insertBefore(newNode, nextSibling);
|
||||||
|
this._log(`[replace_at_marker] Inserted ${newNodeId} before sibling`);
|
||||||
|
} else {
|
||||||
|
parent.appendChild(newNode);
|
||||||
|
this._log(`[replace_at_marker] Appended ${newNodeId} (no sibling)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Memory & String Utilities =====
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Logging & Debug =====
|
||||||
|
|
||||||
|
_log(message) {
|
||||||
|
if (this.debugEnabled) {
|
||||||
|
this.log.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLog() {
|
||||||
|
return this.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLog() {
|
||||||
|
this.log = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RitzLoader - Simplified WASM loading
|
||||||
|
*/
|
||||||
|
class RitzLoader {
|
||||||
|
constructor(options = {}) {
|
||||||
|
this.rootElement = options.rootElement || document.getElementById('app');
|
||||||
|
this.statusElement = options.statusElement || document.getElementById('status');
|
||||||
|
this.onLoad = options.onLoad || (() => {});
|
||||||
|
this.onError = options.onError || ((err) => console.error(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(wasmPath, componentName, props = {}) {
|
||||||
|
try {
|
||||||
|
this.setStatus('⏳ Creating runtime...');
|
||||||
|
const runtime = new RitzRuntime(this.rootElement);
|
||||||
|
|
||||||
|
this.setStatus(`⏳ Loading ${wasmPath}...`);
|
||||||
|
const response = await fetch(wasmPath);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to load ${wasmPath}: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasmBytes = await response.arrayBuffer();
|
||||||
|
|
||||||
|
this.setStatus('⏳ Instantiating WASM...');
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
js_create_marker: () => runtime.js_create_marker(),
|
||||||
|
js_replace_at_marker: (m, o, n) => runtime.js_replace_at_marker(m, o, n),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { instance } = await WebAssembly.instantiate(wasmBytes, imports);
|
||||||
|
|
||||||
|
// Setup runtime
|
||||||
|
runtime.setMemory(instance.exports.memory);
|
||||||
|
runtime.setWasmInstance(instance);
|
||||||
|
|
||||||
|
this.setStatus(`⏳ Rendering ${componentName}...`);
|
||||||
|
|
||||||
|
// Call the render function with props
|
||||||
|
const renderFn = instance.exports[`render_${componentName}`];
|
||||||
|
if (!renderFn) {
|
||||||
|
throw new Error(`render_${componentName} not found in WASM exports`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass props as positional arguments
|
||||||
|
const propValues = Object.values(props);
|
||||||
|
renderFn(...propValues);
|
||||||
|
|
||||||
|
// Update signals
|
||||||
|
runtime.updateSignals();
|
||||||
|
|
||||||
|
this.setStatus(`✅ ${componentName} loaded!`, 'success');
|
||||||
|
|
||||||
|
// Make runtime globally available for debugging
|
||||||
|
window.ritzRuntime = runtime;
|
||||||
|
window.ritzInstance = instance;
|
||||||
|
|
||||||
|
this.onLoad(runtime, instance);
|
||||||
|
|
||||||
|
return { runtime, instance };
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.setStatus(`❌ Error: ${error.message}`, 'error');
|
||||||
|
this.onError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus(message, type = '') {
|
||||||
|
if (this.statusElement) {
|
||||||
|
this.statusElement.textContent = message;
|
||||||
|
this.statusElement.className = 'status ' + type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Export for use in modules or global scope
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = { RitzRuntime, RitzLoader };
|
||||||
|
} else {
|
||||||
|
window.RitzRuntime = RitzRuntime;
|
||||||
|
window.RitzLoader = RitzLoader;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<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">
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.load('{{COMPONENT}}.wasm', '{{COMPONENT}}');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Toggle - Conditional Rendering - Ritz Framework</title>
|
||||||
|
<link rel="stylesheet" href="../runtime/ritz-debug.css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: system-ui, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
#app {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 50px auto;
|
||||||
|
padding: 40px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.toggle-app {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.toggle-app h1 {
|
||||||
|
color: #667eea;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.toggle-app .info {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.toggle-app .controls {
|
||||||
|
margin: 30px 0;
|
||||||
|
}
|
||||||
|
.toggle-app button {
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 15px 40px;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.toggle-app button:hover {
|
||||||
|
background: #5568d3;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
.toggle-app button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
.toggle-app .display {
|
||||||
|
margin-top: 40px;
|
||||||
|
min-height: 150px;
|
||||||
|
}
|
||||||
|
.toggle-app .high,
|
||||||
|
.toggle-app .low {
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
animation: fadeIn 0.3s;
|
||||||
|
}
|
||||||
|
.toggle-app .high {
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.toggle-app .low {
|
||||||
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.toggle-app .high p,
|
||||||
|
.toggle-app .low p {
|
||||||
|
margin: 10px 0;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.toggle-app .count {
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: scale(0.95); }
|
||||||
|
to { opacity: 1; transform: scale(1); }
|
||||||
|
}
|
||||||
|
</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>Toggle.py (Conditional Rendering)</pre>
|
||||||
|
</div>
|
||||||
|
<div class="debug-section">
|
||||||
|
<h4>Features</h4>
|
||||||
|
<pre>✓ if/else statements in WASM
|
||||||
|
✓ Comparison operators (>)
|
||||||
|
✓ Conditional DOM rendering
|
||||||
|
✓ Signal-based conditions</pre>
|
||||||
|
</div>
|
||||||
|
<div class="debug-section">
|
||||||
|
<h4>Console Commands</h4>
|
||||||
|
<pre>ritzRuntime.getLog()
|
||||||
|
ritzInstance.exports.get_count()
|
||||||
|
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('Toggle.wasm', 'Toggle');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
new RitzLoader().load('Toggle.wasm', 'Toggle');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
; Generated by Ritz Framework with Signals
|
||||||
|
; Components: Toggle
|
||||||
|
|
||||||
|
; 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 [5 x i8] c"high\00"
|
||||||
|
@.str.3 = private unnamed_addr constant [2 x i8] c"p\00"
|
||||||
|
@.str.4 = private unnamed_addr constant [24 x i8] c"✨ Count is HIGH (> 5)\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 [4 x i8] c"low\00"
|
||||||
|
@.str.8 = private unnamed_addr constant [25 x i8] c"📉 Count is low (<= 5)\00"
|
||||||
|
@.str.9 = private unnamed_addr constant [11 x i8] c"toggle-app\00"
|
||||||
|
@.str.10 = private unnamed_addr constant [3 x i8] c"h1\00"
|
||||||
|
@.str.11 = private unnamed_addr constant [27 x i8] c"Conditional Rendering Demo\00"
|
||||||
|
@.str.12 = private unnamed_addr constant [5 x i8] c"info\00"
|
||||||
|
@.str.13 = private unnamed_addr constant [44 x i8] c"This demonstrates if/else rendering in WASM\00"
|
||||||
|
@.str.14 = private unnamed_addr constant [9 x i8] c"controls\00"
|
||||||
|
@.str.15 = private unnamed_addr constant [7 x i8] c"button\00"
|
||||||
|
@.str.16 = private unnamed_addr constant [6 x i8] c"click\00"
|
||||||
|
@.str.17 = private unnamed_addr constant [10 x i8] c"increment\00"
|
||||||
|
@.str.18 = private unnamed_addr constant [18 x i8] c"Increment Counter\00"
|
||||||
|
@.str.19 = private unnamed_addr constant [8 x i8] c"display\00"
|
||||||
|
|
||||||
|
; 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*)
|
||||||
|
declare i32 @js_create_marker()
|
||||||
|
declare void @js_replace_at_marker(i32, i32, i32)
|
||||||
|
|
||||||
|
; Signal storage for Toggle (reactive state in WASM memory)
|
||||||
|
@signal_Toggle_count = 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
|
||||||
|
|
||||||
|
; Signal helper functions
|
||||||
|
define i32 @get_count() {
|
||||||
|
entry:
|
||||||
|
%val = load i32, i32* @signal_Toggle_count
|
||||||
|
ret i32 %val
|
||||||
|
}
|
||||||
|
|
||||||
|
define void @set_count(i32 %newval) {
|
||||||
|
entry:
|
||||||
|
store i32 %newval, i32* @signal_Toggle_count
|
||||||
|
; TODO: Trigger DOM update callback
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Event handler: increment
|
||||||
|
define void @increment() {
|
||||||
|
entry:
|
||||||
|
%var0 = load i32, i32* @signal_Toggle_count
|
||||||
|
%var1 = add i32 %var0, 1
|
||||||
|
store i32 %var1, i32* @signal_Toggle_count
|
||||||
|
call void @reevaluate_conditional_0()
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Initialize conditional 0
|
||||||
|
define void @_init_conditional_0() {
|
||||||
|
entry:
|
||||||
|
; Render both branches upfront
|
||||||
|
%var2 = call i32 @_render_cond_0_then()
|
||||||
|
%var3 = call i32 @_render_cond_0_else()
|
||||||
|
store i32 %var2, i32* @cond_0_then_root
|
||||||
|
store i32 %var3, i32* @cond_0_else_root
|
||||||
|
%var4 = load i32, i32* @signal_Toggle_count
|
||||||
|
%var5 = icmp sgt i32 %var4, 5
|
||||||
|
%var6 = zext i1 %var5 to i32
|
||||||
|
%var7 = icmp ne i32 %var6, 0
|
||||||
|
%var8 = load i32, i32* @cond_0_marker
|
||||||
|
br i1 %var7, label %insert_then, label %insert_else
|
||||||
|
insert_then:
|
||||||
|
call void @js_replace_at_marker(i32 %var8, i32 0, i32 %var2)
|
||||||
|
store i32 1, i32* @cond_0_current
|
||||||
|
ret void
|
||||||
|
insert_else:
|
||||||
|
call void @js_replace_at_marker(i32 %var8, i32 0, i32 %var3)
|
||||||
|
store i32 0, i32* @cond_0_current
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Re-evaluate conditional 0
|
||||||
|
define void @reevaluate_conditional_0() {
|
||||||
|
entry:
|
||||||
|
%var9 = load i32, i32* @signal_Toggle_count
|
||||||
|
%var10 = icmp sgt i32 %var9, 5
|
||||||
|
%var11 = zext i1 %var10 to i32
|
||||||
|
%var12 = icmp ne i32 %var11, 0
|
||||||
|
%var13 = zext i1 %var12 to i32
|
||||||
|
%var14 = load i32, i32* @cond_0_current
|
||||||
|
%var15 = icmp ne i32 %var14, %var13
|
||||||
|
br i1 %var15, label %swap, label %done
|
||||||
|
swap:
|
||||||
|
br i1 %var12, label %swap_to_then, label %swap_to_else
|
||||||
|
swap_to_then:
|
||||||
|
%var16 = load i32, i32* @cond_0_else_root
|
||||||
|
%var17 = load i32, i32* @cond_0_then_root
|
||||||
|
%var18 = load i32, i32* @cond_0_marker
|
||||||
|
call void @js_replace_at_marker(i32 %var18, i32 %var16, i32 %var17)
|
||||||
|
store i32 1, i32* @cond_0_current
|
||||||
|
br label %done
|
||||||
|
swap_to_else:
|
||||||
|
%var19 = load i32, i32* @cond_0_then_root
|
||||||
|
%var20 = load i32, i32* @cond_0_else_root
|
||||||
|
%var21 = load i32, i32* @cond_0_marker
|
||||||
|
call void @js_replace_at_marker(i32 %var21, i32 %var19, i32 %var20)
|
||||||
|
store i32 0, i32* @cond_0_current
|
||||||
|
br label %done
|
||||||
|
done:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_0_then() {
|
||||||
|
entry:
|
||||||
|
%var22 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.0, i32 0, i32 0))
|
||||||
|
%var23 = 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 %var23, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([5 x i8], [5 x i8]* @.str.2, i32 0, i32 0))
|
||||||
|
%var24 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
%var25 = call i32 @js_create_text(i8* getelementptr inbounds ([24 x i8], [24 x i8]* @.str.4, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var24, i32 %var25)
|
||||||
|
call void @js_append_child(i32 %var23, i32 %var24)
|
||||||
|
call void @js_append_child(i32 %var23, i32 %var24)
|
||||||
|
%var26 = 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 %var26, 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 count
|
||||||
|
%var27 = 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 %var26, i32 %var27)
|
||||||
|
call void @js_append_child(i32 %var23, i32 %var26)
|
||||||
|
call void @js_append_child(i32 %var23, i32 %var26)
|
||||||
|
call void @js_append_child(i32 %var22, i32 %var23)
|
||||||
|
ret i32 %var22
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @_render_cond_0_else() {
|
||||||
|
entry:
|
||||||
|
%var28 = call i32 @js_create_element(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.0, i32 0, i32 0))
|
||||||
|
%var29 = 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 %var29, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.7, i32 0, i32 0))
|
||||||
|
%var30 = call i32 @js_create_element(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.3, i32 0, i32 0))
|
||||||
|
%var31 = call i32 @js_create_text(i8* getelementptr inbounds ([25 x i8], [25 x i8]* @.str.8, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var30, i32 %var31)
|
||||||
|
call void @js_append_child(i32 %var29, i32 %var30)
|
||||||
|
call void @js_append_child(i32 %var29, i32 %var30)
|
||||||
|
%var32 = 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 %var32, 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 count
|
||||||
|
%var33 = 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 %var32, i32 %var33)
|
||||||
|
call void @js_append_child(i32 %var29, i32 %var32)
|
||||||
|
call void @js_append_child(i32 %var29, i32 %var32)
|
||||||
|
call void @js_append_child(i32 %var28, i32 %var29)
|
||||||
|
ret i32 %var28
|
||||||
|
}
|
||||||
|
|
||||||
|
; Component render function
|
||||||
|
define void @render_Toggle() {
|
||||||
|
entry:
|
||||||
|
%var34 = 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 %var34, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.9, i32 0, i32 0))
|
||||||
|
%var35 = call i32 @js_create_element(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.10, i32 0, i32 0))
|
||||||
|
%var36 = call i32 @js_create_text(i8* getelementptr inbounds ([27 x i8], [27 x i8]* @.str.11, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var35, i32 %var36)
|
||||||
|
call void @js_append_child(i32 %var34, i32 %var35)
|
||||||
|
call void @js_append_child(i32 %var34, i32 %var35)
|
||||||
|
%var37 = 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 %var37, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([5 x i8], [5 x i8]* @.str.12, i32 0, i32 0))
|
||||||
|
%var38 = call i32 @js_create_text(i8* getelementptr inbounds ([44 x i8], [44 x i8]* @.str.13, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var37, i32 %var38)
|
||||||
|
call void @js_append_child(i32 %var34, i32 %var37)
|
||||||
|
call void @js_append_child(i32 %var34, i32 %var37)
|
||||||
|
%var39 = 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 %var39, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str.14, i32 0, i32 0))
|
||||||
|
%var40 = call i32 @js_create_element(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.15, i32 0, i32 0))
|
||||||
|
call void @js_bind_event(i32 %var40, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.16, i32 0, i32 0), i8* getelementptr inbounds ([10 x i8], [10 x i8]* @.str.17, i32 0, i32 0))
|
||||||
|
%var41 = call i32 @js_create_text(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @.str.18, i32 0, i32 0))
|
||||||
|
call void @js_append_child(i32 %var40, i32 %var41)
|
||||||
|
call void @js_append_child(i32 %var39, i32 %var40)
|
||||||
|
call void @js_append_child(i32 %var39, i32 %var40)
|
||||||
|
call void @js_append_child(i32 %var34, i32 %var39)
|
||||||
|
call void @js_append_child(i32 %var34, i32 %var39)
|
||||||
|
%var42 = 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 %var42, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str.19, i32 0, i32 0))
|
||||||
|
%var43 = call i32 @js_create_marker()
|
||||||
|
call void @js_append_child(i32 %var42, i32 %var43)
|
||||||
|
store i32 %var43, i32* @cond_0_marker
|
||||||
|
call void @_init_conditional_0()
|
||||||
|
call void @js_append_child(i32 %var34, i32 %var42)
|
||||||
|
call void @js_append_child(i32 %var34, i32 %var42)
|
||||||
|
call void @js_set_root(i32 %var34)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""
|
||||||
|
Toggle component - Demonstrates conditional rendering with if statements.
|
||||||
|
|
||||||
|
This component uses a signal with comparison to conditionally render content.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Toggle():
|
||||||
|
"""Counter that shows different content based on threshold."""
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
def increment():
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
with Div(class_="toggle-app"):
|
||||||
|
with H1():
|
||||||
|
yield "Conditional Rendering Demo"
|
||||||
|
|
||||||
|
with P(class_="info"):
|
||||||
|
yield "This demonstrates if/else rendering in WASM"
|
||||||
|
|
||||||
|
with Div(class_="controls"):
|
||||||
|
with Button(onclick=increment):
|
||||||
|
yield "Increment Counter"
|
||||||
|
|
||||||
|
with Div(class_="display"):
|
||||||
|
if count > 5:
|
||||||
|
with Div(class_="high"):
|
||||||
|
with P():
|
||||||
|
yield "✨ Count is HIGH (> 5)"
|
||||||
|
with P(class_="count"):
|
||||||
|
yield f"Current count: {count}"
|
||||||
|
else:
|
||||||
|
with Div(class_="low"):
|
||||||
|
with P():
|
||||||
|
yield "📉 Count is low (<= 5)"
|
||||||
|
with P(class_="count"):
|
||||||
|
yield f"Current count: {count}"
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,131 @@
|
|||||||
|
/* Ritz Debug Bar 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%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-header {
|
||||||
|
background: #252525;
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #3e3e3e;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-header button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #d4d4d4;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-header button:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #2d2d30;
|
||||||
|
border-left: 3px solid #007acc;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.success {
|
||||||
|
border-left-color: #4ec9b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.error {
|
||||||
|
border-left-color: #f48771;
|
||||||
|
color: #f48771;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle hint */
|
||||||
|
.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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* App container default styles */
|
||||||
|
#app {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
transition: margin-right 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-open #app {
|
||||||
|
margin-right: 400px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* Ritz Debug Bar Controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
class RitzDebug {
|
||||||
|
constructor() {
|
||||||
|
this.debugBar = document.getElementById('ritz-debug');
|
||||||
|
this.isOpen = false;
|
||||||
|
|
||||||
|
// Setup keyboard shortcut (Ctrl+`)
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.ctrlKey && e.key === '`') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show hint on first load
|
||||||
|
this.showHint();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.isOpen = !this.isOpen;
|
||||||
|
|
||||||
|
if (this.debugBar) {
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.debugBar.classList.remove('hidden');
|
||||||
|
document.body.classList.add('debug-open');
|
||||||
|
} else {
|
||||||
|
this.debugBar.classList.add('hidden');
|
||||||
|
document.body.classList.remove('debug-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open() {
|
||||||
|
this.isOpen = true;
|
||||||
|
if (this.debugBar) {
|
||||||
|
this.debugBar.classList.remove('hidden');
|
||||||
|
document.body.classList.add('debug-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.isOpen = false;
|
||||||
|
if (this.debugBar) {
|
||||||
|
this.debugBar.classList.add('hidden');
|
||||||
|
document.body.classList.remove('debug-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showHint() {
|
||||||
|
// Show toggle hint for 3 seconds
|
||||||
|
const hint = document.createElement('div');
|
||||||
|
hint.className = 'debug-toggle-hint';
|
||||||
|
hint.textContent = 'Press Ctrl+` to toggle debug';
|
||||||
|
document.body.appendChild(hint);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
hint.remove();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
log(message, type = 'info') {
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
if (status) {
|
||||||
|
status.textContent = message;
|
||||||
|
status.className = 'status ' + type;
|
||||||
|
}
|
||||||
|
console.log(`[Ritz] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize debug controller
|
||||||
|
const ritzDebug = new RitzDebug();
|
||||||
|
|
||||||
|
// Export
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = RitzDebug;
|
||||||
|
} else {
|
||||||
|
window.RitzDebug = RitzDebug;
|
||||||
|
window.ritzDebug = ritzDebug;
|
||||||
|
}
|
||||||
@@ -0,0 +1,354 @@
|
|||||||
|
/**
|
||||||
|
* Ritz Runtime - WebAssembly DOM Bridge
|
||||||
|
*
|
||||||
|
* Provides the runtime environment for Ritz components compiled to WASM.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class RitzRuntime {
|
||||||
|
constructor(rootElement) {
|
||||||
|
this.rootElement = rootElement || document.getElementById('app');
|
||||||
|
this.nodes = new Map();
|
||||||
|
this.nextNodeId = 1;
|
||||||
|
this.log = [];
|
||||||
|
this.wasmInstance = null;
|
||||||
|
this.debugEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== DOM Manipulation Bridge =====
|
||||||
|
|
||||||
|
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(`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(`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(`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(`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, () => {
|
||||||
|
this._log(`[Event] ${eventName} → ${handlerName}()`);
|
||||||
|
handler();
|
||||||
|
this.updateSignals();
|
||||||
|
});
|
||||||
|
this._log(`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(`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(`updateTextContent(${nodeId}, "${text}")`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Signal Management =====
|
||||||
|
|
||||||
|
updateSignals() {
|
||||||
|
// Discover and update all signals
|
||||||
|
if (!this.wasmInstance) return;
|
||||||
|
|
||||||
|
const exports = this.wasmInstance.exports;
|
||||||
|
|
||||||
|
// Find all get_* functions (signal getters)
|
||||||
|
for (const [name, fn] of Object.entries(exports)) {
|
||||||
|
if (name.startsWith('get_') && typeof fn === 'function') {
|
||||||
|
const signalName = name.substring(4); // Remove 'get_' prefix
|
||||||
|
const value = fn();
|
||||||
|
this._log(`[Signal] ${signalName} = ${value}`);
|
||||||
|
|
||||||
|
// Update DOM elements bound to this signal
|
||||||
|
// TODO: Track signal-to-DOM bindings during render for efficiency
|
||||||
|
this.updateSignalDisplay(signalName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-evaluate all reactive conditionals
|
||||||
|
for (const [name, fn] of Object.entries(exports)) {
|
||||||
|
if (name.startsWith('reevaluate_conditional_') && typeof fn === 'function') {
|
||||||
|
fn();
|
||||||
|
this._log(`[Reactive] ${name}()`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-evaluate all reactive match statements
|
||||||
|
for (const [name, fn] of Object.entries(exports)) {
|
||||||
|
if (name.startsWith('reevaluate_match_') && typeof fn === 'function') {
|
||||||
|
fn();
|
||||||
|
this._log(`[Reactive] ${name}()`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
const selectors = [
|
||||||
|
`[data-signal="${signalName}"]`,
|
||||||
|
`.${signalName}`,
|
||||||
|
'.count', // Hardcoded for demo - TODO: track bindings properly
|
||||||
|
];
|
||||||
|
|
||||||
|
selectors.forEach(selector => {
|
||||||
|
const elements = document.querySelectorAll(selector);
|
||||||
|
elements.forEach(el => {
|
||||||
|
// Check if text contains the signal name or looks like a counter
|
||||||
|
const text = el.textContent;
|
||||||
|
if (text.includes(signalName) || text.match(/\d+/)) {
|
||||||
|
// Replace the number in the text
|
||||||
|
el.textContent = text.replace(/\d+/, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Reactive Conditional Rendering =====
|
||||||
|
|
||||||
|
js_create_marker() {
|
||||||
|
// Create a comment node as a marker for conditional insertion
|
||||||
|
const marker = document.createComment('conditional-marker');
|
||||||
|
const id = this.nextNodeId++;
|
||||||
|
this.nodes.set(id, marker);
|
||||||
|
this._log(`Created marker node ${id}`);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
js_replace_at_marker(markerId, oldNodeId, newNodeId) {
|
||||||
|
// Replace DOM node at marker position
|
||||||
|
const marker = this.nodes.get(markerId);
|
||||||
|
if (!marker) {
|
||||||
|
console.error(`[replace_at_marker] Marker ${markerId} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = marker.parentNode;
|
||||||
|
if (!parent) {
|
||||||
|
console.error(`[replace_at_marker] Marker ${markerId} has no parent`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._log(`[replace_at_marker] marker=${markerId}, old=${oldNodeId}, new=${newNodeId}`);
|
||||||
|
|
||||||
|
// Remove old node if it exists (0 means no old node to remove)
|
||||||
|
if (oldNodeId !== 0) {
|
||||||
|
const oldNode = this.nodes.get(oldNodeId);
|
||||||
|
if (!oldNode) {
|
||||||
|
console.warn(`[replace_at_marker] Old node ${oldNodeId} not found in registry`);
|
||||||
|
} else if (!oldNode.parentNode) {
|
||||||
|
console.warn(`[replace_at_marker] Old node ${oldNodeId} not in DOM`);
|
||||||
|
} else if (oldNode.parentNode !== parent) {
|
||||||
|
console.warn(`[replace_at_marker] Old node ${oldNodeId} in wrong parent`);
|
||||||
|
oldNode.parentNode.removeChild(oldNode);
|
||||||
|
this._log(`[replace_at_marker] Removed ${oldNodeId} from wrong parent`);
|
||||||
|
} else {
|
||||||
|
// Remove from correct parent
|
||||||
|
parent.removeChild(oldNode);
|
||||||
|
this._log(`[replace_at_marker] Removed ${oldNodeId} from correct parent`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert new node after marker (only if newNodeId is not 0)
|
||||||
|
if (newNodeId !== 0) {
|
||||||
|
const newNode = this.nodes.get(newNodeId);
|
||||||
|
if (!newNode) {
|
||||||
|
console.error(`[replace_at_marker] New node ${newNodeId} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert right after the marker
|
||||||
|
// If newNode is already in DOM elsewhere, this will move it
|
||||||
|
const nextSibling = marker.nextSibling;
|
||||||
|
if (nextSibling) {
|
||||||
|
parent.insertBefore(newNode, nextSibling);
|
||||||
|
this._log(`[replace_at_marker] Inserted ${newNodeId} before sibling`);
|
||||||
|
} else {
|
||||||
|
parent.appendChild(newNode);
|
||||||
|
this._log(`[replace_at_marker] Appended ${newNodeId} (no sibling)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Memory & String Utilities =====
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Logging & Debug =====
|
||||||
|
|
||||||
|
_log(message) {
|
||||||
|
if (this.debugEnabled) {
|
||||||
|
this.log.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLog() {
|
||||||
|
return this.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLog() {
|
||||||
|
this.log = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RitzLoader - Simplified WASM loading
|
||||||
|
*/
|
||||||
|
class RitzLoader {
|
||||||
|
constructor(options = {}) {
|
||||||
|
this.rootElement = options.rootElement || document.getElementById('app');
|
||||||
|
this.statusElement = options.statusElement || document.getElementById('status');
|
||||||
|
this.onLoad = options.onLoad || (() => {});
|
||||||
|
this.onError = options.onError || ((err) => console.error(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(wasmPath, componentName, props = {}) {
|
||||||
|
try {
|
||||||
|
this.setStatus('⏳ Creating runtime...');
|
||||||
|
const runtime = new RitzRuntime(this.rootElement);
|
||||||
|
|
||||||
|
this.setStatus(`⏳ Loading ${wasmPath}...`);
|
||||||
|
const response = await fetch(wasmPath);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to load ${wasmPath}: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasmBytes = await response.arrayBuffer();
|
||||||
|
|
||||||
|
this.setStatus('⏳ Instantiating WASM...');
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
js_create_marker: () => runtime.js_create_marker(),
|
||||||
|
js_replace_at_marker: (m, o, n) => runtime.js_replace_at_marker(m, o, n),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { instance } = await WebAssembly.instantiate(wasmBytes, imports);
|
||||||
|
|
||||||
|
// Setup runtime
|
||||||
|
runtime.setMemory(instance.exports.memory);
|
||||||
|
runtime.setWasmInstance(instance);
|
||||||
|
|
||||||
|
this.setStatus(`⏳ Rendering ${componentName}...`);
|
||||||
|
|
||||||
|
// Call the render function with props
|
||||||
|
const renderFn = instance.exports[`render_${componentName}`];
|
||||||
|
if (!renderFn) {
|
||||||
|
throw new Error(`render_${componentName} not found in WASM exports`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass props as positional arguments
|
||||||
|
const propValues = Object.values(props);
|
||||||
|
renderFn(...propValues);
|
||||||
|
|
||||||
|
// Update signals
|
||||||
|
runtime.updateSignals();
|
||||||
|
|
||||||
|
this.setStatus(`✅ ${componentName} loaded!`, 'success');
|
||||||
|
|
||||||
|
// Make runtime globally available for debugging
|
||||||
|
window.ritzRuntime = runtime;
|
||||||
|
window.ritzInstance = instance;
|
||||||
|
|
||||||
|
this.onLoad(runtime, instance);
|
||||||
|
|
||||||
|
return { runtime, instance };
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.setStatus(`❌ Error: ${error.message}`, 'error');
|
||||||
|
this.onError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus(message, type = '') {
|
||||||
|
if (this.statusElement) {
|
||||||
|
this.statusElement.textContent = message;
|
||||||
|
this.statusElement.className = 'status ' + type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Export for use in modules or global scope
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = { RitzRuntime, RitzLoader };
|
||||||
|
} else {
|
||||||
|
window.RitzRuntime = RitzRuntime;
|
||||||
|
window.RitzLoader = RitzLoader;
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
[project]
|
||||||
|
name = "ritz"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A WebAssembly UI framework with Python-like syntax"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=8.3.0",
|
||||||
|
"pytest-cov>=6.0.0",
|
||||||
|
"pytest-watch>=4.2.0",
|
||||||
|
"black>=24.10.0",
|
||||||
|
"ruff>=0.8.0",
|
||||||
|
"mypy>=1.13.0",
|
||||||
|
"playwright>=1.48.0",
|
||||||
|
"pytest-playwright>=0.6.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=75.0.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 100
|
||||||
|
target-version = ['py311']
|
||||||
|
include = '\.pyi?$'
|
||||||
|
extend-exclude = '''
|
||||||
|
/(
|
||||||
|
# directories
|
||||||
|
\.eggs
|
||||||
|
| \.git
|
||||||
|
| \.hg
|
||||||
|
| \.mypy_cache
|
||||||
|
| \.tox
|
||||||
|
| \.venv
|
||||||
|
| build
|
||||||
|
| dist
|
||||||
|
)/
|
||||||
|
'''
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 100
|
||||||
|
target-version = "py311"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = [
|
||||||
|
"E", # pycodestyle errors
|
||||||
|
"W", # pycodestyle warnings
|
||||||
|
"F", # pyflakes
|
||||||
|
"I", # isort
|
||||||
|
"B", # flake8-bugbear
|
||||||
|
"C4", # flake8-comprehensions
|
||||||
|
"UP", # pyupgrade
|
||||||
|
]
|
||||||
|
ignore = [
|
||||||
|
"E501", # line too long, handled by black
|
||||||
|
"B008", # do not perform function calls in argument defaults
|
||||||
|
"C901", # too complex
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"__init__.py" = ["F401"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
minversion = "8.0"
|
||||||
|
addopts = "-ra -q --strict-markers"
|
||||||
|
testpaths = ["tests"]
|
||||||
|
python_files = ["test_*.py"]
|
||||||
|
python_classes = ["Test*"]
|
||||||
|
python_functions = ["test_*"]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.11"
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
warn_no_return = true
|
||||||
|
strict_equality = true
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = "tests.*"
|
||||||
|
disallow_untyped_defs = false
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
"""Ritz Framework - Python-syntax WebAssembly UI framework."""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+143
@@ -0,0 +1,143 @@
|
|||||||
|
"""Build script for compiling Python components to WASM."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ritz.llvm_codegen_signals import generate_llvm_ir
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
|
||||||
|
def build_component(component_file: Path, output_file: Path, debug: bool = False) -> None:
|
||||||
|
"""
|
||||||
|
Build a Python component to WASM.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
component_file: Path to Python component file
|
||||||
|
output_file: Path for output WASM file
|
||||||
|
debug: Whether to include debug symbols
|
||||||
|
"""
|
||||||
|
print(f"[Ritz Build] Compiling {component_file} → {output_file}")
|
||||||
|
|
||||||
|
# Step 1: Read component source
|
||||||
|
print(" [1/4] Reading component...")
|
||||||
|
source = component_file.read_text()
|
||||||
|
|
||||||
|
# Step 2: Parse to IR
|
||||||
|
print(" [2/4] Parsing Python to IR...")
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Handle single component or list of components
|
||||||
|
if isinstance(ir, list):
|
||||||
|
for component in ir:
|
||||||
|
print(f" Component: {component['name']}")
|
||||||
|
else:
|
||||||
|
print(f" Component: {ir['name']}")
|
||||||
|
|
||||||
|
# Step 3: Generate LLVM IR
|
||||||
|
print(" [3/4] Generating LLVM IR...")
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Save LLVM IR for inspection
|
||||||
|
ll_file = output_file.with_suffix(".ll")
|
||||||
|
ll_file.write_text(llvm_ir)
|
||||||
|
print(f" Saved LLVM IR: {ll_file}")
|
||||||
|
|
||||||
|
# Step 4: Compile to WASM with clang
|
||||||
|
print(" [4/4] Compiling to WASM...")
|
||||||
|
compile_to_wasm(ll_file, output_file, debug=debug)
|
||||||
|
|
||||||
|
print(f"✅ Build complete: {output_file}")
|
||||||
|
print(f" Size: {output_file.stat().st_size} bytes")
|
||||||
|
|
||||||
|
|
||||||
|
def compile_to_wasm(ll_file: Path, output_file: Path, debug: bool = False) -> None:
|
||||||
|
"""
|
||||||
|
Compile LLVM IR to WASM using clang.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ll_file: Path to LLVM IR source file
|
||||||
|
output_file: Path for output WASM file
|
||||||
|
debug: Whether to include debug symbols (-g)
|
||||||
|
"""
|
||||||
|
# Check if clang is available
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
["clang", "--version"],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
print("ERROR: clang not found. Install LLVM/clang")
|
||||||
|
print(" Ubuntu/Debian: apt install clang lld")
|
||||||
|
print(" macOS: brew install llvm")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Build clang command
|
||||||
|
cmd = [
|
||||||
|
"clang",
|
||||||
|
"--target=wasm32",
|
||||||
|
"-nostdlib", # No standard library
|
||||||
|
"-Wl,--no-entry", # No _start entry point
|
||||||
|
"-Wl,--export-all", # Export all functions
|
||||||
|
"-Wl,--allow-undefined", # Allow undefined JS imports
|
||||||
|
"-O2", # Optimization
|
||||||
|
"-o",
|
||||||
|
str(output_file),
|
||||||
|
str(ll_file),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add debug symbols if requested
|
||||||
|
if debug:
|
||||||
|
cmd.extend(["-g", "-Wl,--export-dynamic"])
|
||||||
|
print(" Debug symbols: enabled (DWARF in WASM)")
|
||||||
|
|
||||||
|
# Run clang
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if result.stderr:
|
||||||
|
# Filter out common warnings
|
||||||
|
stderr = result.stderr
|
||||||
|
if "warning" in stderr.lower() and len(stderr) < 500:
|
||||||
|
print(f" Warnings: {stderr}")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print("ERROR: Compilation failed")
|
||||||
|
print(f" Command: {' '.join(cmd)}")
|
||||||
|
print(f" stdout: {e.stdout}")
|
||||||
|
print(f" stderr: {e.stderr}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""CLI entry point."""
|
||||||
|
parser = argparse.ArgumentParser(description="Build Ritz components to WASM")
|
||||||
|
parser.add_argument("component", type=Path, help="Python component file")
|
||||||
|
parser.add_argument(
|
||||||
|
"-o", "--output", type=Path, help="Output WASM file (default: component.wasm)"
|
||||||
|
)
|
||||||
|
parser.add_argument("-g", "--debug", action="store_true", help="Include debug symbols")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Determine output file
|
||||||
|
if args.output:
|
||||||
|
output_file = args.output
|
||||||
|
else:
|
||||||
|
output_file = args.component.with_suffix(".wasm")
|
||||||
|
|
||||||
|
# Ensure output directory exists
|
||||||
|
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Build the component
|
||||||
|
build_component(args.component, output_file, debug=args.debug)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
"""C code generator for Ritz IR."""
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def generate_c_code(ir: dict[str, Any]) -> str:
|
||||||
|
"""
|
||||||
|
Generate C code from Ritz IR.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ir: Component IR dictionary
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
C source code that can be compiled with emscripten
|
||||||
|
"""
|
||||||
|
generator = CCodeGenerator(ir)
|
||||||
|
return generator.generate()
|
||||||
|
|
||||||
|
|
||||||
|
class CCodeGenerator:
|
||||||
|
"""Generates C code from Ritz IR."""
|
||||||
|
|
||||||
|
def __init__(self, ir: dict[str, Any]):
|
||||||
|
self.ir = ir
|
||||||
|
self.indent_level = 0
|
||||||
|
self.lines: list[str] = []
|
||||||
|
self.next_var_id = 0
|
||||||
|
|
||||||
|
def generate(self) -> str:
|
||||||
|
"""Generate complete C source code."""
|
||||||
|
self._emit_header()
|
||||||
|
self._emit_js_imports()
|
||||||
|
self._emit_component(self.ir)
|
||||||
|
return "\n".join(self.lines)
|
||||||
|
|
||||||
|
def _emit_header(self) -> None:
|
||||||
|
"""Emit C header includes."""
|
||||||
|
self.lines.append("#include <emscripten.h>")
|
||||||
|
self.lines.append("#include <string.h>")
|
||||||
|
self.lines.append("")
|
||||||
|
|
||||||
|
def _emit_js_imports(self) -> None:
|
||||||
|
"""Emit JavaScript function imports."""
|
||||||
|
self.lines.append("// JavaScript DOM manipulation functions (imported)")
|
||||||
|
self.lines.append("EM_JS(int, js_create_element, (const char* tag), {")
|
||||||
|
self.lines.append(" return Module.ritz.createElement(UTF8ToString(tag));")
|
||||||
|
self.lines.append("});")
|
||||||
|
self.lines.append("")
|
||||||
|
self.lines.append("EM_JS(int, js_create_text, (const char* text), {")
|
||||||
|
self.lines.append(" return Module.ritz.createTextNode(UTF8ToString(text));")
|
||||||
|
self.lines.append("});")
|
||||||
|
self.lines.append("")
|
||||||
|
self.lines.append("EM_JS(void, js_append_child, (int parent, int child), {")
|
||||||
|
self.lines.append(" Module.ritz.appendChild(parent, child);")
|
||||||
|
self.lines.append("});")
|
||||||
|
self.lines.append("")
|
||||||
|
self.lines.append(
|
||||||
|
"EM_JS(void, js_set_attribute, (int elem, const char* name, const char* value), {"
|
||||||
|
)
|
||||||
|
self.lines.append(
|
||||||
|
" Module.ritz.setAttribute(elem, UTF8ToString(name), UTF8ToString(value));"
|
||||||
|
)
|
||||||
|
self.lines.append("});")
|
||||||
|
self.lines.append("")
|
||||||
|
self.lines.append("EM_JS(void, js_set_root, (int elem), {")
|
||||||
|
self.lines.append(" Module.ritz.setRoot(elem);")
|
||||||
|
self.lines.append("});")
|
||||||
|
self.lines.append("")
|
||||||
|
|
||||||
|
def _emit_component(self, ir: dict[str, Any]) -> None:
|
||||||
|
"""Emit a component function."""
|
||||||
|
component_name = ir["name"]
|
||||||
|
self.lines.append(f"// Component: {component_name}")
|
||||||
|
self.lines.append("EMSCRIPTEN_KEEPALIVE")
|
||||||
|
self.lines.append(f"void render_{component_name}() {{")
|
||||||
|
self.indent_level += 1
|
||||||
|
|
||||||
|
# Process component body
|
||||||
|
root_var = None
|
||||||
|
for stmt in ir["body"]:
|
||||||
|
var = self._emit_statement(stmt)
|
||||||
|
if root_var is None and var:
|
||||||
|
root_var = var
|
||||||
|
|
||||||
|
# Set the root element
|
||||||
|
if root_var:
|
||||||
|
self._emit_line(f"js_set_root({root_var});")
|
||||||
|
|
||||||
|
self.indent_level -= 1
|
||||||
|
self.lines.append("}")
|
||||||
|
self.lines.append("")
|
||||||
|
|
||||||
|
def _emit_statement(self, stmt: dict[str, Any]) -> str | None:
|
||||||
|
"""
|
||||||
|
Emit a statement and return the variable name if it creates an element.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Variable name of created element, or None
|
||||||
|
"""
|
||||||
|
if stmt["type"] == "with":
|
||||||
|
return self._emit_with(stmt)
|
||||||
|
elif stmt["type"] == "yield":
|
||||||
|
return self._emit_yield(stmt)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _emit_with(self, stmt: dict[str, Any]) -> str:
|
||||||
|
"""Emit a 'with Element()' block."""
|
||||||
|
element_info = stmt["element"]
|
||||||
|
tag = element_info["tag"].lower()
|
||||||
|
props = element_info["props"]
|
||||||
|
|
||||||
|
# Create the element
|
||||||
|
elem_var = self._new_var("elem")
|
||||||
|
self._emit_line(f'int {elem_var} = js_create_element("{tag}");')
|
||||||
|
|
||||||
|
# Set attributes
|
||||||
|
for prop_name, prop_value in props.items():
|
||||||
|
# Convert class_ to class
|
||||||
|
attr_name = prop_name.rstrip("_")
|
||||||
|
if prop_value["type"] == "constant":
|
||||||
|
value = prop_value["value"]
|
||||||
|
self._emit_line(f'js_set_attribute({elem_var}, "{attr_name}", "{value}");')
|
||||||
|
|
||||||
|
# Process children
|
||||||
|
for child_stmt in stmt["body"]:
|
||||||
|
child_var = self._emit_statement(child_stmt)
|
||||||
|
if child_var:
|
||||||
|
self._emit_line(f"js_append_child({elem_var}, {child_var});")
|
||||||
|
|
||||||
|
return elem_var
|
||||||
|
|
||||||
|
def _emit_yield(self, stmt: dict[str, Any]) -> str | None:
|
||||||
|
"""Emit a yield statement."""
|
||||||
|
value = stmt.get("value")
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if value["type"] == "constant":
|
||||||
|
# Create text node
|
||||||
|
text_var = self._new_var("text")
|
||||||
|
text = value["value"]
|
||||||
|
self._emit_line(f'int {text_var} = js_create_text("{text}");')
|
||||||
|
return text_var
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _emit_line(self, code: str) -> None:
|
||||||
|
"""Emit a line of code with proper indentation."""
|
||||||
|
indent = " " * self.indent_level
|
||||||
|
self.lines.append(f"{indent}{code}")
|
||||||
|
|
||||||
|
def _new_var(self, prefix: str = "var") -> str:
|
||||||
|
"""Generate a new variable name."""
|
||||||
|
var_name = f"{prefix}{self.next_var_id}"
|
||||||
|
self.next_var_id += 1
|
||||||
|
return var_name
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"""LLVM IR code generator for Ritz components.
|
||||||
|
|
||||||
|
This module now re-exports from llvm_codegen_signals which has full support
|
||||||
|
for reactive signals, event handlers, and conditional rendering.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Re-export the signal-enabled codegen
|
||||||
|
from ritz.llvm_codegen_signals import generate_llvm_ir
|
||||||
|
|
||||||
|
__all__ = ["generate_llvm_ir"]
|
||||||
File diff suppressed because it is too large.
Load diff
+369
@@ -0,0 +1,369 @@
|
|||||||
|
"""AST parser for Ritz components using Python's ast module."""
|
||||||
|
|
||||||
|
import ast
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def parse_component(source: str) -> dict[str, Any] | list[dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Parse Python components into Ritz IR.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source: Python source code containing @component decorated function(s)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
IR dictionary (single component) or list of IR dicts (multiple components)
|
||||||
|
"""
|
||||||
|
tree = ast.parse(source)
|
||||||
|
|
||||||
|
# Find ALL @component decorated functions
|
||||||
|
components = []
|
||||||
|
for node in tree.body: # Only top-level functions, not ast.walk
|
||||||
|
if isinstance(node, ast.FunctionDef) and _has_component_decorator(node):
|
||||||
|
components.append(_transform_component(node))
|
||||||
|
|
||||||
|
if not components:
|
||||||
|
raise ValueError("No @component decorated function found")
|
||||||
|
|
||||||
|
# Return single component or list
|
||||||
|
return components[0] if len(components) == 1 else components
|
||||||
|
|
||||||
|
|
||||||
|
def _has_component_decorator(node: ast.FunctionDef) -> bool:
|
||||||
|
"""Check if a function has the @component decorator."""
|
||||||
|
for decorator in node.decorator_list:
|
||||||
|
if isinstance(decorator, ast.Name) and decorator.id == "component":
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _process_lambdas_in_component(component_ir: dict[str, Any]) -> None:
|
||||||
|
"""Convert lambdas in props to synthetic functions.
|
||||||
|
|
||||||
|
Scans component body for lambdas in event handler props (onclick, etc.)
|
||||||
|
and converts them to synthetic functions.
|
||||||
|
"""
|
||||||
|
lambda_counter = [0] # Use list to allow mutation in nested function
|
||||||
|
synthetic_functions = []
|
||||||
|
|
||||||
|
def process_props(props: dict[str, Any]) -> None:
|
||||||
|
"""Process props dict, converting lambdas to function references."""
|
||||||
|
for prop_name, prop_value in props.items():
|
||||||
|
if isinstance(prop_value, dict) and prop_value.get("type") == "lambda":
|
||||||
|
# Create synthetic function
|
||||||
|
func_name = f"_lambda_{lambda_counter[0]}_{component_ir['name']}"
|
||||||
|
lambda_counter[0] += 1
|
||||||
|
|
||||||
|
# Transform lambda body from AST to IR
|
||||||
|
body_ast = prop_value["body"]
|
||||||
|
if isinstance(body_ast, ast.Call):
|
||||||
|
# lambda: some_function() - just call the function
|
||||||
|
body_ir = [{"type": "expr", "value": _transform_expr(body_ast)}]
|
||||||
|
else:
|
||||||
|
# Other expressions - wrap in expr statement
|
||||||
|
body_ir = [{"type": "expr", "value": _transform_expr(body_ast)}]
|
||||||
|
|
||||||
|
synthetic_functions.append({
|
||||||
|
"type": "function",
|
||||||
|
"name": func_name,
|
||||||
|
"args": [],
|
||||||
|
"body": body_ir,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Replace lambda with function reference
|
||||||
|
props[prop_name] = {"type": "name", "id": func_name}
|
||||||
|
|
||||||
|
def scan_statement(stmt: dict[str, Any]) -> None:
|
||||||
|
"""Recursively scan statements for lambdas."""
|
||||||
|
if stmt["type"] == "with":
|
||||||
|
if "element" in stmt and "props" in stmt["element"]:
|
||||||
|
process_props(stmt["element"]["props"])
|
||||||
|
# Recurse into body
|
||||||
|
for child_stmt in stmt.get("body", []):
|
||||||
|
scan_statement(child_stmt)
|
||||||
|
elif stmt["type"] == "if":
|
||||||
|
for child_stmt in stmt.get("body", []):
|
||||||
|
scan_statement(child_stmt)
|
||||||
|
for child_stmt in stmt.get("orelse", []):
|
||||||
|
scan_statement(child_stmt)
|
||||||
|
elif stmt["type"] == "match":
|
||||||
|
for case in stmt.get("cases", []):
|
||||||
|
for child_stmt in case.get("body", []):
|
||||||
|
scan_statement(child_stmt)
|
||||||
|
|
||||||
|
# Scan all statements in component body
|
||||||
|
for stmt in component_ir["body"]:
|
||||||
|
scan_statement(stmt)
|
||||||
|
|
||||||
|
# Add synthetic functions to component body
|
||||||
|
component_ir["body"] = synthetic_functions + component_ir["body"]
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_component(node: ast.FunctionDef) -> dict[str, Any]:
|
||||||
|
"""Transform a component function into IR."""
|
||||||
|
# Extract parameters with default values
|
||||||
|
params = []
|
||||||
|
defaults = node.args.defaults
|
||||||
|
num_defaults = len(defaults)
|
||||||
|
num_args = len(node.args.args)
|
||||||
|
|
||||||
|
for i, arg in enumerate(node.args.args):
|
||||||
|
# Check if this arg has a default (defaults align to the end of args)
|
||||||
|
default_index = i - (num_args - num_defaults)
|
||||||
|
if default_index >= 0:
|
||||||
|
default_value = _transform_expr(defaults[default_index])
|
||||||
|
else:
|
||||||
|
default_value = None
|
||||||
|
|
||||||
|
params.append({
|
||||||
|
"name": arg.arg,
|
||||||
|
"default": default_value,
|
||||||
|
})
|
||||||
|
|
||||||
|
body = [_transform_statement(stmt) for stmt in node.body]
|
||||||
|
|
||||||
|
# Post-process: convert lambdas to synthetic functions
|
||||||
|
component_ir = {
|
||||||
|
"type": "component",
|
||||||
|
"name": node.name,
|
||||||
|
"params": params,
|
||||||
|
"body": body,
|
||||||
|
}
|
||||||
|
|
||||||
|
_process_lambdas_in_component(component_ir)
|
||||||
|
|
||||||
|
return component_ir
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_statement(stmt: ast.stmt) -> dict[str, Any]:
|
||||||
|
"""Transform a statement into IR."""
|
||||||
|
if isinstance(stmt, ast.With):
|
||||||
|
return _transform_with(stmt)
|
||||||
|
elif isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Yield):
|
||||||
|
return _transform_yield(stmt.value)
|
||||||
|
elif isinstance(stmt, ast.Assign):
|
||||||
|
return _transform_assign(stmt)
|
||||||
|
elif isinstance(stmt, ast.AugAssign):
|
||||||
|
return _transform_augassign(stmt)
|
||||||
|
elif isinstance(stmt, ast.FunctionDef):
|
||||||
|
return _transform_function(stmt)
|
||||||
|
elif isinstance(stmt, ast.If):
|
||||||
|
return _transform_if(stmt)
|
||||||
|
elif isinstance(stmt, ast.Match):
|
||||||
|
return _transform_match(stmt)
|
||||||
|
else:
|
||||||
|
return {"type": "unknown", "ast_type": type(stmt).__name__}
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_with(node: ast.With) -> dict[str, Any]:
|
||||||
|
"""Transform a 'with Element():' statement."""
|
||||||
|
# Get the context expression (the Element() call)
|
||||||
|
context_expr = node.items[0].context_expr
|
||||||
|
|
||||||
|
return {
|
||||||
|
"type": "with",
|
||||||
|
"element": _transform_element_call(context_expr),
|
||||||
|
"body": [_transform_statement(stmt) for stmt in node.body],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_element_call(expr: ast.expr) -> dict[str, Any]:
|
||||||
|
"""Transform an Element() call into IR."""
|
||||||
|
if not isinstance(expr, ast.Call):
|
||||||
|
return {"tag": "Unknown", "props": {}}
|
||||||
|
|
||||||
|
# Get the element tag name
|
||||||
|
tag = _get_name(expr.func)
|
||||||
|
|
||||||
|
# Get props from keyword arguments
|
||||||
|
props = {}
|
||||||
|
for keyword in expr.keywords:
|
||||||
|
if keyword.arg:
|
||||||
|
props[keyword.arg] = _transform_expr(keyword.value)
|
||||||
|
|
||||||
|
return {"tag": tag, "props": props}
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_yield(node: ast.Yield) -> dict[str, Any]:
|
||||||
|
"""Transform a yield statement."""
|
||||||
|
if node.value is None:
|
||||||
|
return {"type": "yield", "value": None}
|
||||||
|
|
||||||
|
# Check if yielding a component call (uppercase name that's not a known element)
|
||||||
|
if isinstance(node.value, ast.Call) and isinstance(node.value.func, ast.Name):
|
||||||
|
func_name = node.value.func.id
|
||||||
|
# Known HTML elements - these are NOT components
|
||||||
|
known_elements = {
|
||||||
|
"Div", "Span", "P", "H1", "H2", "H3", "H4", "H5", "H6",
|
||||||
|
"Button", "Input", "A", "Ul", "Li", "Ol", "Table", "Tr", "Td", "Th",
|
||||||
|
"Form", "Label", "Select", "Option", "Textarea", "Main", "Header",
|
||||||
|
"Footer", "Nav", "Section", "Article", "Aside"
|
||||||
|
}
|
||||||
|
|
||||||
|
# If it's uppercase and NOT a known element, it's a component
|
||||||
|
if func_name[0].isupper() and func_name not in known_elements:
|
||||||
|
return {
|
||||||
|
"type": "yield_component",
|
||||||
|
"component": func_name,
|
||||||
|
"args": [_transform_expr(arg) for arg in node.value.args],
|
||||||
|
"kwargs": {kw.arg: _transform_expr(kw.value) for kw in node.value.keywords if kw.arg},
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"type": "yield", "value": _transform_expr(node.value)}
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_assign(node: ast.Assign) -> dict[str, Any]:
|
||||||
|
"""Transform an assignment statement (e.g., count = signal(0))."""
|
||||||
|
target = node.targets[0]
|
||||||
|
if isinstance(target, ast.Name):
|
||||||
|
value = _transform_expr(node.value)
|
||||||
|
|
||||||
|
# Check if it's a signal assignment
|
||||||
|
if (
|
||||||
|
isinstance(value, dict)
|
||||||
|
and value.get("type") == "call"
|
||||||
|
and value.get("func") == "signal"
|
||||||
|
):
|
||||||
|
return {
|
||||||
|
"type": "signal",
|
||||||
|
"name": target.id,
|
||||||
|
"initial": (
|
||||||
|
value["args"][0] if value.get("args") else {"type": "constant", "value": None}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"type": "assign", "target": target.id, "value": value}
|
||||||
|
|
||||||
|
return {"type": "assign", "target": "unknown", "value": _transform_expr(node.value)}
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_augassign(node: ast.AugAssign) -> dict[str, Any]:
|
||||||
|
"""Transform an augmented assignment (e.g., count += 1)."""
|
||||||
|
if isinstance(node.target, ast.Name):
|
||||||
|
return {
|
||||||
|
"type": "augassign",
|
||||||
|
"target": node.target.id,
|
||||||
|
"op": type(node.op).__name__, # Add, Sub, Mult, etc.
|
||||||
|
"value": _transform_expr(node.value),
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"type": "augassign",
|
||||||
|
"target": "unknown",
|
||||||
|
"op": type(node.op).__name__,
|
||||||
|
"value": _transform_expr(node.value),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_function(node: ast.FunctionDef) -> dict[str, Any]:
|
||||||
|
"""Transform a function definition (e.g., event handlers)."""
|
||||||
|
return {
|
||||||
|
"type": "function",
|
||||||
|
"name": node.name,
|
||||||
|
"args": [arg.arg for arg in node.args.args],
|
||||||
|
"body": [_transform_statement(stmt) for stmt in node.body],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_if(node: ast.If) -> dict[str, Any]:
|
||||||
|
"""Transform an if statement for conditional rendering."""
|
||||||
|
return {
|
||||||
|
"type": "if",
|
||||||
|
"test": _transform_expr(node.test),
|
||||||
|
"body": [_transform_statement(stmt) for stmt in node.body],
|
||||||
|
"orelse": [_transform_statement(stmt) for stmt in node.orelse],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_match(node: ast.Match) -> dict[str, Any]:
|
||||||
|
"""Transform a match/case statement."""
|
||||||
|
return {
|
||||||
|
"type": "match",
|
||||||
|
"subject": _transform_expr(node.subject),
|
||||||
|
"cases": [_transform_match_case(case) for case in node.cases],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_match_case(case: ast.match_case) -> dict[str, Any]:
|
||||||
|
"""Transform a single match case."""
|
||||||
|
return {
|
||||||
|
"pattern": _transform_pattern(case.pattern),
|
||||||
|
"guard": _transform_expr(case.guard) if case.guard else None,
|
||||||
|
"body": [_transform_statement(stmt) for stmt in case.body],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_pattern(pattern: ast.pattern) -> dict[str, Any]:
|
||||||
|
"""Transform a match pattern."""
|
||||||
|
if isinstance(pattern, ast.MatchValue):
|
||||||
|
# case 0: or case "string":
|
||||||
|
return {
|
||||||
|
"type": "value",
|
||||||
|
"value": _transform_expr(pattern.value),
|
||||||
|
}
|
||||||
|
elif isinstance(pattern, ast.MatchAs) and pattern.pattern is None:
|
||||||
|
# case _: (wildcard)
|
||||||
|
return {"type": "wildcard"}
|
||||||
|
else:
|
||||||
|
# Other patterns not yet supported
|
||||||
|
return {"type": "unknown", "ast_type": type(pattern).__name__}
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_expr(expr: ast.expr | None) -> dict[str, Any]:
|
||||||
|
"""Transform an expression into IR."""
|
||||||
|
if expr is None:
|
||||||
|
return {"type": "constant", "value": None}
|
||||||
|
|
||||||
|
if isinstance(expr, ast.Constant):
|
||||||
|
return {"type": "constant", "value": expr.value}
|
||||||
|
elif isinstance(expr, ast.Name):
|
||||||
|
return {"type": "name", "id": expr.id}
|
||||||
|
elif isinstance(expr, ast.Lambda):
|
||||||
|
# Return lambda with its body for processing at component level
|
||||||
|
return {
|
||||||
|
"type": "lambda",
|
||||||
|
"body": expr.body, # Keep AST node for now, will transform later
|
||||||
|
}
|
||||||
|
elif isinstance(expr, ast.Call):
|
||||||
|
return {
|
||||||
|
"type": "call",
|
||||||
|
"func": _get_name(expr.func),
|
||||||
|
"args": [_transform_expr(arg) for arg in expr.args],
|
||||||
|
"kwargs": {kw.arg: _transform_expr(kw.value) for kw in expr.keywords if kw.arg},
|
||||||
|
}
|
||||||
|
elif isinstance(expr, ast.Attribute):
|
||||||
|
return {
|
||||||
|
"type": "attribute",
|
||||||
|
"value": _transform_expr(expr.value),
|
||||||
|
"attr": expr.attr,
|
||||||
|
}
|
||||||
|
elif isinstance(expr, ast.JoinedStr): # f-strings
|
||||||
|
return {"type": "fstring", "parts": [_transform_expr(v) for v in expr.values]}
|
||||||
|
elif isinstance(expr, ast.FormattedValue):
|
||||||
|
return {"type": "formatted_value", "value": _transform_expr(expr.value)}
|
||||||
|
elif isinstance(expr, ast.Compare):
|
||||||
|
# Handle comparisons (e.g., count > 5)
|
||||||
|
return {
|
||||||
|
"type": "compare",
|
||||||
|
"left": _transform_expr(expr.left),
|
||||||
|
"op": type(expr.ops[0]).__name__, # Gt, Lt, Eq, etc.
|
||||||
|
"comparators": [_transform_expr(comp) for comp in expr.comparators],
|
||||||
|
}
|
||||||
|
elif isinstance(expr, ast.IfExp):
|
||||||
|
# Handle ternary expressions (e.g., 'active' if flag else 'inactive')
|
||||||
|
return {
|
||||||
|
"type": "ifexp",
|
||||||
|
"test": _transform_expr(expr.test),
|
||||||
|
"body": _transform_expr(expr.body),
|
||||||
|
"orelse": _transform_expr(expr.orelse),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {"type": "unknown", "ast_type": type(expr).__name__}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_name(node: ast.expr) -> str:
|
||||||
|
"""Extract name from a Name or Attribute node."""
|
||||||
|
if isinstance(node, ast.Name):
|
||||||
|
return node.id
|
||||||
|
elif isinstance(node, ast.Attribute):
|
||||||
|
return f"{_get_name(node.value)}.{node.attr}"
|
||||||
|
return "Unknown"
|
||||||
+179
@@ -0,0 +1,179 @@
|
|||||||
|
"""
|
||||||
|
Ritz syntax placeholders for writing components.
|
||||||
|
|
||||||
|
These are runtime placeholders that allow components to be valid Python.
|
||||||
|
The compiler ignores imports and uses AST parsing instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def component(func):
|
||||||
|
"""Component decorator - marks a function as a Ritz component."""
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
def signal(initial_value):
|
||||||
|
"""Create a reactive signal with an initial value."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# HTML Elements - Context managers for building DOM
|
||||||
|
class _Element:
|
||||||
|
"""Base element class."""
|
||||||
|
|
||||||
|
def __init__(self, *children, **props):
|
||||||
|
self.children = children
|
||||||
|
self.props = props
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Standard HTML elements
|
||||||
|
class Div(_Element):
|
||||||
|
"""<div> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class P(_Element):
|
||||||
|
"""<p> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Button(_Element):
|
||||||
|
"""<button> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Span(_Element):
|
||||||
|
"""<span> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class H1(_Element):
|
||||||
|
"""<h1> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class H2(_Element):
|
||||||
|
"""<h2> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class H3(_Element):
|
||||||
|
"""<h3> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Label(_Element):
|
||||||
|
"""<label> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Input(_Element):
|
||||||
|
"""<input> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Form(_Element):
|
||||||
|
"""<form> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Section(_Element):
|
||||||
|
"""<section> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Article(_Element):
|
||||||
|
"""<article> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Header(_Element):
|
||||||
|
"""<header> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Footer(_Element):
|
||||||
|
"""<footer> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Nav(_Element):
|
||||||
|
"""<nav> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Ul(_Element):
|
||||||
|
"""<ul> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Ol(_Element):
|
||||||
|
"""<ol> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Li(_Element):
|
||||||
|
"""<li> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class A(_Element):
|
||||||
|
"""<a> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Img(_Element):
|
||||||
|
"""<img> element."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Export all for convenience
|
||||||
|
__all__ = [
|
||||||
|
"component",
|
||||||
|
"signal",
|
||||||
|
"Div",
|
||||||
|
"P",
|
||||||
|
"Button",
|
||||||
|
"Span",
|
||||||
|
"H1",
|
||||||
|
"H2",
|
||||||
|
"H3",
|
||||||
|
"Label",
|
||||||
|
"Input",
|
||||||
|
"Form",
|
||||||
|
"Section",
|
||||||
|
"Article",
|
||||||
|
"Header",
|
||||||
|
"Footer",
|
||||||
|
"Nav",
|
||||||
|
"Ul",
|
||||||
|
"Ol",
|
||||||
|
"Li",
|
||||||
|
"A",
|
||||||
|
"Img",
|
||||||
|
]
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Simple dev server for testing Ritz components."""
|
||||||
|
|
||||||
|
import http.server
|
||||||
|
import socketserver
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
PORT = 8111
|
||||||
|
DIRECTORY = Path(__file__).parent / "examples"
|
||||||
|
|
||||||
|
|
||||||
|
class WASMHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
|
"""HTTP handler with proper WASM MIME type."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, directory=str(DIRECTORY), **kwargs)
|
||||||
|
|
||||||
|
def end_headers(self):
|
||||||
|
# Set CORS headers for local development
|
||||||
|
self.send_header("Access-Control-Allow-Origin", "*")
|
||||||
|
# Ensure WASM has correct MIME type
|
||||||
|
if self.path.endswith(".wasm"):
|
||||||
|
self.send_header("Content-Type", "application/wasm")
|
||||||
|
super().end_headers()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
socketserver.TCPServer.allow_reuse_address = True
|
||||||
|
with socketserver.TCPServer(("", PORT), WASMHandler) as httpd:
|
||||||
|
print("🚀 Ritz Dev Server")
|
||||||
|
print(f" Serving: {DIRECTORY}")
|
||||||
|
print(f" URL: http://localhost:{PORT}")
|
||||||
|
print(f"\n Open http://localhost:{PORT}/index.html in your browser")
|
||||||
|
print("\n Press Ctrl+C to stop")
|
||||||
|
try:
|
||||||
|
httpd.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n👋 Server stopped")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Tests for Ritz framework."""
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,129 @@
|
|||||||
|
"""Tests for C code generation from IR."""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_codegen_hello_world():
|
||||||
|
"""Test generating C code for simple HelloWorld component."""
|
||||||
|
from ritz.codegen import generate_c_code
|
||||||
|
|
||||||
|
ir = {
|
||||||
|
"type": "component",
|
||||||
|
"name": "HelloWorld",
|
||||||
|
"args": [],
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "with",
|
||||||
|
"element": {"tag": "P", "props": {}},
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "yield",
|
||||||
|
"value": {"type": "constant", "value": "Hello World"},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
c_code = generate_c_code(ir)
|
||||||
|
|
||||||
|
# Verify C code structure
|
||||||
|
assert "#include <emscripten.h>" in c_code
|
||||||
|
assert "void HelloWorld()" in c_code or "void render_HelloWorld()" in c_code
|
||||||
|
|
||||||
|
# Should create a P element
|
||||||
|
assert "create_element" in c_code or "createElement" in c_code
|
||||||
|
assert '"p"' in c_code or '"P"' in c_code.lower()
|
||||||
|
|
||||||
|
# Should create text node
|
||||||
|
assert '"Hello World"' in c_code
|
||||||
|
assert "create_text" in c_code or "createTextNode" in c_code or "set_text_content" in c_code
|
||||||
|
|
||||||
|
|
||||||
|
def test_codegen_with_props():
|
||||||
|
"""Test generating C code for element with props."""
|
||||||
|
from ritz.codegen import generate_c_code
|
||||||
|
|
||||||
|
ir = {
|
||||||
|
"type": "component",
|
||||||
|
"name": "StyledDiv",
|
||||||
|
"args": [],
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "with",
|
||||||
|
"element": {
|
||||||
|
"tag": "Div",
|
||||||
|
"props": {
|
||||||
|
"class_": {"type": "constant", "value": "container"},
|
||||||
|
"id": {"type": "constant", "value": "main"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"body": [{"type": "yield", "value": {"type": "constant", "value": "Content"}}],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
c_code = generate_c_code(ir)
|
||||||
|
|
||||||
|
# Should set attributes
|
||||||
|
assert "set_attribute" in c_code or "setAttribute" in c_code
|
||||||
|
assert '"class"' in c_code or '"className"' in c_code
|
||||||
|
assert '"container"' in c_code
|
||||||
|
assert '"id"' in c_code
|
||||||
|
assert '"main"' in c_code
|
||||||
|
|
||||||
|
|
||||||
|
def test_codegen_nested_elements():
|
||||||
|
"""Test generating C code for nested elements."""
|
||||||
|
from ritz.codegen import generate_c_code
|
||||||
|
|
||||||
|
ir = {
|
||||||
|
"type": "component",
|
||||||
|
"name": "Nested",
|
||||||
|
"args": [],
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "with",
|
||||||
|
"element": {"tag": "Div", "props": {}},
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "with",
|
||||||
|
"element": {"tag": "P", "props": {}},
|
||||||
|
"body": [
|
||||||
|
{"type": "yield", "value": {"type": "constant", "value": "Inner"}}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
c_code = generate_c_code(ir)
|
||||||
|
|
||||||
|
# Should have append_child calls to build hierarchy
|
||||||
|
assert "append_child" in c_code or "appendChild" in c_code
|
||||||
|
|
||||||
|
|
||||||
|
def test_codegen_includes_runtime_functions():
|
||||||
|
"""Test that generated C code includes necessary runtime declarations."""
|
||||||
|
from ritz.codegen import generate_c_code
|
||||||
|
|
||||||
|
ir = {
|
||||||
|
"type": "component",
|
||||||
|
"name": "Simple",
|
||||||
|
"args": [],
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "with",
|
||||||
|
"element": {"tag": "P", "props": {}},
|
||||||
|
"body": [{"type": "yield", "value": {"type": "constant", "value": "test"}}],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
c_code = generate_c_code(ir)
|
||||||
|
|
||||||
|
# Should include emscripten
|
||||||
|
assert "#include <emscripten.h>" in c_code
|
||||||
|
|
||||||
|
# Should have EMSCRIPTEN_KEEPALIVE for exported function
|
||||||
|
assert "EMSCRIPTEN_KEEPALIVE" in c_code
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
"""Tests for component parameters/props."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
from ritz.llvm_codegen_signals import generate_llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_component_with_params():
|
||||||
|
"""Test parsing component definition with parameters."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Greeting(name):
|
||||||
|
with H1():
|
||||||
|
yield f"Hello, {name}!"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Should parse component with params
|
||||||
|
assert ir["name"] == "Greeting"
|
||||||
|
assert len(ir["params"]) == 1
|
||||||
|
assert ir["params"][0]["name"] == "name"
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_component_with_multiple_params():
|
||||||
|
"""Test parsing component with multiple parameters."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Card(title, count):
|
||||||
|
with Div():
|
||||||
|
with H2():
|
||||||
|
yield title
|
||||||
|
with P():
|
||||||
|
yield f"Count: {count}"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
assert len(ir["params"]) == 2
|
||||||
|
assert ir["params"][0]["name"] == "title"
|
||||||
|
assert ir["params"][1]["name"] == "count"
|
||||||
|
|
||||||
|
|
||||||
|
def test_codegen_component_with_params():
|
||||||
|
"""Test LLVM IR generation for component with parameters."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Button(label):
|
||||||
|
with Button():
|
||||||
|
yield label
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should generate render function with parameter
|
||||||
|
assert "define i32 @render_Button(i32 %label)" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_yield_component_with_args():
|
||||||
|
"""Test parsing yield ComponentName(arg1, arg2)."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Greeting(name):
|
||||||
|
with H1():
|
||||||
|
yield name
|
||||||
|
|
||||||
|
@component
|
||||||
|
def App():
|
||||||
|
with Div():
|
||||||
|
yield Greeting("World")
|
||||||
|
"""
|
||||||
|
components = parse_component(source)
|
||||||
|
|
||||||
|
# Should have both components
|
||||||
|
assert len(components) == 2
|
||||||
|
|
||||||
|
# Find App component
|
||||||
|
app = next(c for c in components if c["name"] == "App")
|
||||||
|
|
||||||
|
# Should have yield_component statement with args
|
||||||
|
body_stmts = [s for s in app["body"] if s["type"] not in ["signal", "function"]]
|
||||||
|
with_stmt = body_stmts[0]
|
||||||
|
yield_stmt = with_stmt["body"][0]
|
||||||
|
|
||||||
|
assert yield_stmt["type"] == "yield_component"
|
||||||
|
assert yield_stmt["component"] == "Greeting"
|
||||||
|
assert len(yield_stmt["args"]) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_codegen_yield_component_with_args():
|
||||||
|
"""Test LLVM IR generation for yield ComponentName(args)."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Child(value):
|
||||||
|
with P():
|
||||||
|
yield "Child"
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Parent():
|
||||||
|
with Div():
|
||||||
|
yield Child(42)
|
||||||
|
"""
|
||||||
|
components = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(components)
|
||||||
|
|
||||||
|
# Should call render_Child with argument and capture return value
|
||||||
|
assert "call i32 @render_Child(i32 42)" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_component_with_default_param():
|
||||||
|
"""Test component with default parameter value."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Tabs(orientation=0):
|
||||||
|
if orientation == 0:
|
||||||
|
with Div(class_="horizontal"):
|
||||||
|
yield "Horizontal"
|
||||||
|
else:
|
||||||
|
with Div(class_="vertical"):
|
||||||
|
yield "Vertical"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
assert len(ir["params"]) == 1
|
||||||
|
assert ir["params"][0]["name"] == "orientation"
|
||||||
|
assert ir["params"][0]["default"]["value"] == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_yield_component_with_keyword_args():
|
||||||
|
"""Test yielding component with keyword arguments."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Panel(title, visible):
|
||||||
|
with Div():
|
||||||
|
yield title
|
||||||
|
|
||||||
|
@component
|
||||||
|
def App():
|
||||||
|
with Div():
|
||||||
|
yield Panel(title="Settings", visible=1)
|
||||||
|
"""
|
||||||
|
components = parse_component(source)
|
||||||
|
app = next(c for c in components if c["name"] == "App")
|
||||||
|
|
||||||
|
# Find yield_component statement
|
||||||
|
with_stmt = [s for s in app["body"] if s["type"] == "with"][0]
|
||||||
|
yield_stmt = with_stmt["body"][0]
|
||||||
|
|
||||||
|
assert yield_stmt["type"] == "yield_component"
|
||||||
|
# Note: Keyword args are captured in the AST but not yet supported in our IR
|
||||||
|
# For now we'll skip this check - this is a future enhancement
|
||||||
|
# assert len(yield_stmt["args"]) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_component_param_used_in_signal():
|
||||||
|
"""Test using component parameter to initialize signal."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Counter(initial_count):
|
||||||
|
count = signal(initial_count)
|
||||||
|
|
||||||
|
with P():
|
||||||
|
yield f"Count: {count}"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should accept parameter
|
||||||
|
assert "define i32 @render_Counter(i32 %initial_count)" in llvm_ir
|
||||||
|
|
||||||
|
# Signal should be initialized with parameter value
|
||||||
|
# This will require storing the param value to the signal global
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
"""Tests for conditional rendering (if statements)."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
|
||||||
|
def test_if_statement():
|
||||||
|
"""Test parsing if statement for conditional rendering."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Toggle():
|
||||||
|
visible = signal(True)
|
||||||
|
|
||||||
|
if visible:
|
||||||
|
with P():
|
||||||
|
yield "I am visible!"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Find the if statement
|
||||||
|
if_stmt = None
|
||||||
|
for stmt in ir["body"]:
|
||||||
|
if stmt["type"] == "if":
|
||||||
|
if_stmt = stmt
|
||||||
|
break
|
||||||
|
|
||||||
|
assert if_stmt is not None
|
||||||
|
assert if_stmt["test"]["type"] == "name"
|
||||||
|
assert if_stmt["test"]["id"] == "visible"
|
||||||
|
assert len(if_stmt["body"]) == 1
|
||||||
|
assert if_stmt["body"][0]["type"] == "with"
|
||||||
|
|
||||||
|
|
||||||
|
def test_if_else_statement():
|
||||||
|
"""Test parsing if/else for conditional rendering."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Toggle():
|
||||||
|
visible = signal(True)
|
||||||
|
|
||||||
|
if visible:
|
||||||
|
with P():
|
||||||
|
yield "Shown"
|
||||||
|
else:
|
||||||
|
with P():
|
||||||
|
yield "Hidden"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Find the if statement
|
||||||
|
if_stmt = None
|
||||||
|
for stmt in ir["body"]:
|
||||||
|
if stmt["type"] == "if":
|
||||||
|
if_stmt = stmt
|
||||||
|
break
|
||||||
|
|
||||||
|
assert if_stmt is not None
|
||||||
|
assert len(if_stmt["body"]) == 1
|
||||||
|
assert len(if_stmt["orelse"]) == 1
|
||||||
|
assert if_stmt["body"][0]["element"]["tag"] == "P"
|
||||||
|
assert if_stmt["orelse"][0]["element"]["tag"] == "P"
|
||||||
|
|
||||||
|
|
||||||
|
def test_if_comparison():
|
||||||
|
"""Test parsing if statement with comparison."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
if count > 5:
|
||||||
|
with P():
|
||||||
|
yield "High count!"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Find the if statement
|
||||||
|
if_stmt = None
|
||||||
|
for stmt in ir["body"]:
|
||||||
|
if stmt["type"] == "if":
|
||||||
|
if_stmt = stmt
|
||||||
|
break
|
||||||
|
|
||||||
|
assert if_stmt is not None
|
||||||
|
assert if_stmt["test"]["type"] == "compare"
|
||||||
|
assert if_stmt["test"]["op"] == "Gt"
|
||||||
|
|
||||||
|
|
||||||
|
def test_nested_if():
|
||||||
|
"""Test parsing nested if statements."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Nested():
|
||||||
|
a = signal(True)
|
||||||
|
b = signal(False)
|
||||||
|
|
||||||
|
if a:
|
||||||
|
if b:
|
||||||
|
with P():
|
||||||
|
yield "Both true"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Find outer if
|
||||||
|
outer_if = None
|
||||||
|
for stmt in ir["body"]:
|
||||||
|
if stmt["type"] == "if":
|
||||||
|
outer_if = stmt
|
||||||
|
break
|
||||||
|
|
||||||
|
assert outer_if is not None
|
||||||
|
assert outer_if["body"][0]["type"] == "if"
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
"""Tests for simple lambda support - starting with valid Python syntax."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
from ritz.llvm_codegen_signals import generate_llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_lambda_calling_function():
|
||||||
|
"""Test lambda that calls a function - valid Python syntax."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def App():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
def increment():
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
with Button(onclick=lambda: increment()):
|
||||||
|
yield "Click"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Parser should recognize lambda in onclick and convert to synthetic function
|
||||||
|
with_stmt = [s for s in ir["body"] if s["type"] == "with"][0]
|
||||||
|
assert "onclick" in with_stmt["element"]["props"]
|
||||||
|
# After lambda processing, onclick should reference a synthetic function
|
||||||
|
onclick_ref = with_stmt["element"]["props"]["onclick"]
|
||||||
|
assert onclick_ref["type"] == "name"
|
||||||
|
assert onclick_ref["id"].startswith("_lambda_")
|
||||||
|
|
||||||
|
|
||||||
|
def test_codegen_lambda_calling_function():
|
||||||
|
"""Test LLVM codegen for lambda calling a function."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def App():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
def increment():
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
with Button(onclick=lambda: increment()):
|
||||||
|
yield "+"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should generate a lambda handler that calls increment
|
||||||
|
assert "define void @_lambda_0_App()" in llvm_ir
|
||||||
|
assert "call void @increment()" in llvm_ir
|
||||||
|
assert "js_bind_event" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_lambda_inline_for_simple_setter():
|
||||||
|
"""Test using lambda for simple signal setter calls."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Tabs():
|
||||||
|
tab = signal(0)
|
||||||
|
|
||||||
|
def set_tab(value):
|
||||||
|
tab = value
|
||||||
|
|
||||||
|
with Button(onclick=lambda: set_tab(0)):
|
||||||
|
yield "Tab 0"
|
||||||
|
|
||||||
|
with Button(onclick=lambda: set_tab(1)):
|
||||||
|
yield "Tab 1"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Should have two with statements with lambda onclick handlers (converted to functions)
|
||||||
|
with_stmts = [s for s in ir["body"] if s["type"] == "with"]
|
||||||
|
assert len([w for w in with_stmts if "onclick" in w["element"]["props"]]) == 2
|
||||||
|
|
||||||
|
# Check that both have synthetic lambda functions
|
||||||
|
for with_stmt in with_stmts:
|
||||||
|
if "onclick" in with_stmt["element"]["props"]:
|
||||||
|
onclick_ref = with_stmt["element"]["props"]["onclick"]
|
||||||
|
assert onclick_ref["type"] == "name"
|
||||||
|
assert onclick_ref["id"].startswith("_lambda_")
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
"""Tests for LLVM IR code generation."""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def test_llvm_hello_world_structure():
|
||||||
|
"""Test that LLVM IR has correct structure for HelloWorld."""
|
||||||
|
from ritz.llvm_codegen import generate_llvm_ir
|
||||||
|
|
||||||
|
ir_dict = {
|
||||||
|
"type": "component",
|
||||||
|
"name": "HelloWorld",
|
||||||
|
"args": [],
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "with",
|
||||||
|
"element": {"tag": "P", "props": {}},
|
||||||
|
"body": [{"type": "yield", "value": {"type": "constant", "value": "Hello World"}}],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm_ir = generate_llvm_ir(ir_dict)
|
||||||
|
|
||||||
|
# Check basic LLVM IR structure
|
||||||
|
assert "define i32 @render_HelloWorld()" in llvm_ir
|
||||||
|
assert "ret void" in llvm_ir
|
||||||
|
|
||||||
|
# Should have string constants
|
||||||
|
assert '"p\\00"' in llvm_ir or 'c"p\\00"' in llvm_ir
|
||||||
|
assert "Hello World" in llvm_ir
|
||||||
|
|
||||||
|
# Should call JS bridge functions
|
||||||
|
assert "@js_create_element" in llvm_ir
|
||||||
|
assert "@js_create_text" in llvm_ir
|
||||||
|
assert "@js_append_child" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_llvm_ir_has_function_declarations():
|
||||||
|
"""Test that LLVM IR declares imported JS functions."""
|
||||||
|
from ritz.llvm_codegen import generate_llvm_ir
|
||||||
|
|
||||||
|
ir_dict = {
|
||||||
|
"type": "component",
|
||||||
|
"name": "Simple",
|
||||||
|
"args": [],
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "with",
|
||||||
|
"element": {"tag": "P", "props": {}},
|
||||||
|
"body": [{"type": "yield", "value": {"type": "constant", "value": "test"}}],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm_ir = generate_llvm_ir(ir_dict)
|
||||||
|
|
||||||
|
# Should declare imported functions
|
||||||
|
assert "declare i32 @js_create_element" in llvm_ir
|
||||||
|
assert "declare i32 @js_create_text" in llvm_ir
|
||||||
|
assert "declare void @js_append_child" in llvm_ir
|
||||||
|
assert "declare void @js_set_root" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_llvm_ir_with_attributes():
|
||||||
|
"""Test LLVM IR generation with element attributes."""
|
||||||
|
from ritz.llvm_codegen import generate_llvm_ir
|
||||||
|
|
||||||
|
ir_dict = {
|
||||||
|
"type": "component",
|
||||||
|
"name": "Styled",
|
||||||
|
"args": [],
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "with",
|
||||||
|
"element": {
|
||||||
|
"tag": "Div",
|
||||||
|
"props": {
|
||||||
|
"class_": {"type": "constant", "value": "container"},
|
||||||
|
"id": {"type": "constant", "value": "main"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"body": [],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm_ir = generate_llvm_ir(ir_dict)
|
||||||
|
|
||||||
|
# Should have js_set_attribute calls
|
||||||
|
assert "@js_set_attribute" in llvm_ir
|
||||||
|
assert "container" in llvm_ir
|
||||||
|
assert "main" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_llvm_ir_syntax_valid():
|
||||||
|
"""Test that generated LLVM IR has valid syntax (basic checks)."""
|
||||||
|
from ritz.llvm_codegen import generate_llvm_ir
|
||||||
|
|
||||||
|
ir_dict = {
|
||||||
|
"type": "component",
|
||||||
|
"name": "Test",
|
||||||
|
"args": [],
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "with",
|
||||||
|
"element": {"tag": "P", "props": {}},
|
||||||
|
"body": [{"type": "yield", "value": {"type": "constant", "value": "x"}}],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm_ir = generate_llvm_ir(ir_dict)
|
||||||
|
|
||||||
|
# Basic syntax checks
|
||||||
|
assert llvm_ir.count("define") == llvm_ir.count("ret") # All functions have returns
|
||||||
|
assert "%" in llvm_ir # Has SSA variables
|
||||||
|
assert "call" in llvm_ir # Has function calls
|
||||||
|
assert "@" in llvm_ir # Has global symbols
|
||||||
|
assert "define void @main()" in llvm_ir # Has main entry point
|
||||||
|
assert "define i32 @render_" in llvm_ir # Component functions return i32
|
||||||
|
|
||||||
|
|
||||||
|
def test_llvm_ir_compiles_with_llc():
|
||||||
|
"""Test that generated LLVM IR actually compiles with llc."""
|
||||||
|
from ritz.llvm_codegen import generate_llvm_ir
|
||||||
|
|
||||||
|
ir_dict = {
|
||||||
|
"type": "component",
|
||||||
|
"name": "CompileTest",
|
||||||
|
"args": [],
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "with",
|
||||||
|
"element": {"tag": "P", "props": {}},
|
||||||
|
"body": [{"type": "yield", "value": {"type": "constant", "value": "test"}}],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm_ir = generate_llvm_ir(ir_dict)
|
||||||
|
|
||||||
|
# Check if llc is available
|
||||||
|
try:
|
||||||
|
subprocess.run(["llc", "--version"], check=True, capture_output=True)
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
# Skip if llc not installed
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
pytest.skip("llc not available")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Try to compile the IR
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".ll", delete=False) as f:
|
||||||
|
f.write(llvm_ir)
|
||||||
|
ll_file = Path(f.name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Compile to object file (not WASM yet, just validate syntax)
|
||||||
|
result = subprocess.run(
|
||||||
|
["llc", "-filetype=obj", str(ll_file)],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should compile without errors
|
||||||
|
assert result.returncode == 0, f"llc failed:\n{result.stderr}"
|
||||||
|
|
||||||
|
finally:
|
||||||
|
ll_file.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def test_llvm_ir_compiles_to_wasm():
|
||||||
|
"""Test that LLVM IR can be compiled to WASM."""
|
||||||
|
from ritz.llvm_codegen import generate_llvm_ir
|
||||||
|
|
||||||
|
ir_dict = {
|
||||||
|
"type": "component",
|
||||||
|
"name": "WasmTest",
|
||||||
|
"args": [],
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "with",
|
||||||
|
"element": {"tag": "P", "props": {}},
|
||||||
|
"body": [{"type": "yield", "value": {"type": "constant", "value": "wasm"}}],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm_ir = generate_llvm_ir(ir_dict)
|
||||||
|
|
||||||
|
# Check if clang with wasm target is available
|
||||||
|
try:
|
||||||
|
result = subprocess.run(["clang", "--version"], check=True, capture_output=True, text=True)
|
||||||
|
# Check if wasm target is supported
|
||||||
|
if "wasm" not in result.stdout.lower():
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
pytest.skip("clang wasm target not available")
|
||||||
|
return
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
pytest.skip("clang not available")
|
||||||
|
return
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".ll", delete=False) as f:
|
||||||
|
f.write(llvm_ir)
|
||||||
|
ll_file = Path(f.name)
|
||||||
|
|
||||||
|
wasm_file = ll_file.with_suffix(".wasm")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Compile to WASM
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
"clang",
|
||||||
|
"--target=wasm32",
|
||||||
|
"-nostdlib",
|
||||||
|
"-Wl,--no-entry",
|
||||||
|
"-Wl,--export-all",
|
||||||
|
"-o",
|
||||||
|
str(wasm_file),
|
||||||
|
str(ll_file),
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should compile successfully
|
||||||
|
assert result.returncode == 0, f"clang failed:\n{result.stderr}"
|
||||||
|
assert wasm_file.exists(), "WASM file not created"
|
||||||
|
assert wasm_file.stat().st_size > 0, "WASM file is empty"
|
||||||
|
|
||||||
|
finally:
|
||||||
|
ll_file.unlink()
|
||||||
|
if wasm_file.exists():
|
||||||
|
wasm_file.unlink()
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
"""Tests for LLVM IR code generation with conditional rendering."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
from ritz.llvm_codegen_signals import generate_llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_if_statement_codegen():
|
||||||
|
"""Test LLVM IR generation for if statement with reactive rendering."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Toggle():
|
||||||
|
visible = signal(1)
|
||||||
|
|
||||||
|
if visible:
|
||||||
|
with P():
|
||||||
|
yield "I am visible!"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should have signal global (scoped to component)
|
||||||
|
assert "@signal_Toggle_visible = global i32 1" in llvm_ir
|
||||||
|
|
||||||
|
# Should have conditional branch
|
||||||
|
assert "br i1" in llvm_ir
|
||||||
|
|
||||||
|
# Should have reactive conditional infrastructure
|
||||||
|
assert "@cond_0_marker" in llvm_ir
|
||||||
|
assert "_init_conditional_0" in llvm_ir
|
||||||
|
assert "_render_cond_0_then" in llvm_ir
|
||||||
|
assert "_render_cond_0_else" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_if_else_codegen():
|
||||||
|
"""Test LLVM IR generation for if/else with reactive rendering."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Toggle():
|
||||||
|
visible = signal(1)
|
||||||
|
|
||||||
|
if visible:
|
||||||
|
with P():
|
||||||
|
yield "Shown"
|
||||||
|
else:
|
||||||
|
with P():
|
||||||
|
yield "Hidden"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should have conditional branch
|
||||||
|
assert "br i1" in llvm_ir
|
||||||
|
|
||||||
|
# Should have reactive infrastructure with both branches
|
||||||
|
assert "_render_cond_0_then" in llvm_ir
|
||||||
|
assert "_render_cond_0_else" in llvm_ir
|
||||||
|
assert "reevaluate_conditional_0" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_if_comparison_codegen():
|
||||||
|
"""Test LLVM IR generation for if with comparison."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
if count > 5:
|
||||||
|
with P():
|
||||||
|
yield "High count!"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should have icmp instruction for comparison
|
||||||
|
assert "icmp" in llvm_ir
|
||||||
|
|
||||||
|
# Should have greater-than comparison (sgt = signed greater than)
|
||||||
|
assert "sgt" in llvm_ir or ">" in llvm_ir
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
"""Tests for match/case statement parsing."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
|
||||||
|
def test_match_statement():
|
||||||
|
"""Test parsing match statement."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Tabs():
|
||||||
|
tab = signal(0)
|
||||||
|
|
||||||
|
match tab:
|
||||||
|
case 0:
|
||||||
|
with P():
|
||||||
|
yield "First"
|
||||||
|
case 1:
|
||||||
|
with P():
|
||||||
|
yield "Second"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Find the match statement
|
||||||
|
match_stmt = None
|
||||||
|
for stmt in ir["body"]:
|
||||||
|
if stmt["type"] == "match":
|
||||||
|
match_stmt = stmt
|
||||||
|
break
|
||||||
|
|
||||||
|
assert match_stmt is not None
|
||||||
|
assert match_stmt["subject"]["type"] == "name"
|
||||||
|
assert match_stmt["subject"]["id"] == "tab"
|
||||||
|
assert len(match_stmt["cases"]) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_match_with_wildcard():
|
||||||
|
"""Test match with wildcard case."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Tabs():
|
||||||
|
tab = signal(0)
|
||||||
|
|
||||||
|
match tab:
|
||||||
|
case 0:
|
||||||
|
with P():
|
||||||
|
yield "Home"
|
||||||
|
case _:
|
||||||
|
with P():
|
||||||
|
yield "Other"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Find the match statement
|
||||||
|
match_stmt = None
|
||||||
|
for stmt in ir["body"]:
|
||||||
|
if stmt["type"] == "match":
|
||||||
|
match_stmt = stmt
|
||||||
|
break
|
||||||
|
|
||||||
|
assert match_stmt is not None
|
||||||
|
assert len(match_stmt["cases"]) == 2
|
||||||
|
# Wildcard should be the last case
|
||||||
|
assert match_stmt["cases"][1]["pattern"]["type"] == "wildcard"
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
"""Tests for AST parser that converts Python components to IR."""
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_hello_world_component():
|
||||||
|
"""Test parsing the simplest possible component - static p tag with text."""
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
source = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
@component
|
||||||
|
def HelloWorld():
|
||||||
|
with P():
|
||||||
|
yield "Hello World"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Verify component structure
|
||||||
|
assert ir["type"] == "component"
|
||||||
|
assert ir["name"] == "HelloWorld"
|
||||||
|
assert ir["params"] == []
|
||||||
|
|
||||||
|
# Verify body contains a with statement
|
||||||
|
assert len(ir["body"]) == 1
|
||||||
|
with_stmt = ir["body"][0]
|
||||||
|
assert with_stmt["type"] == "with"
|
||||||
|
assert with_stmt["element"]["tag"] == "P"
|
||||||
|
assert with_stmt["element"]["props"] == {}
|
||||||
|
|
||||||
|
# Verify yield inside with
|
||||||
|
assert len(with_stmt["body"]) == 1
|
||||||
|
yield_stmt = with_stmt["body"][0]
|
||||||
|
assert yield_stmt["type"] == "yield"
|
||||||
|
assert yield_stmt["value"]["type"] == "constant"
|
||||||
|
assert yield_stmt["value"]["value"] == "Hello World"
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_component_with_props():
|
||||||
|
"""Test parsing element with props (class, id, etc)."""
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
source = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
@component
|
||||||
|
def StyledDiv():
|
||||||
|
with Div(class_="container", id="main"):
|
||||||
|
yield "Content"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
ir = parse_component(source)
|
||||||
|
with_stmt = ir["body"][0]
|
||||||
|
|
||||||
|
assert with_stmt["element"]["tag"] == "Div"
|
||||||
|
# Props are transformed to IR nodes
|
||||||
|
assert with_stmt["element"]["props"]["class_"] == {"type": "constant", "value": "container"}
|
||||||
|
assert with_stmt["element"]["props"]["id"] == {"type": "constant", "value": "main"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_nested_elements():
|
||||||
|
"""Test parsing nested with statements."""
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
source = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
@component
|
||||||
|
def Nested():
|
||||||
|
with Div():
|
||||||
|
with P():
|
||||||
|
yield "Inner text"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
ir = parse_component(source)
|
||||||
|
outer_with = ir["body"][0]
|
||||||
|
assert outer_with["element"]["tag"] == "Div"
|
||||||
|
|
||||||
|
inner_with = outer_with["body"][0]
|
||||||
|
assert inner_with["type"] == "with"
|
||||||
|
assert inner_with["element"]["tag"] == "P"
|
||||||
|
|
||||||
|
yield_stmt = inner_with["body"][0]
|
||||||
|
assert yield_stmt["value"]["value"] == "Inner text"
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_multiple_yields():
|
||||||
|
"""Test parsing multiple yield statements in sequence."""
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
source = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
@component
|
||||||
|
def Multiple():
|
||||||
|
with Div():
|
||||||
|
yield "First"
|
||||||
|
yield "Second"
|
||||||
|
yield "Third"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
ir = parse_component(source)
|
||||||
|
with_stmt = ir["body"][0]
|
||||||
|
|
||||||
|
assert len(with_stmt["body"]) == 3
|
||||||
|
assert all(stmt["type"] == "yield" for stmt in with_stmt["body"])
|
||||||
|
assert with_stmt["body"][0]["value"]["value"] == "First"
|
||||||
|
assert with_stmt["body"][1]["value"]["value"] == "Second"
|
||||||
|
assert with_stmt["body"][2]["value"]["value"] == "Third"
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
"""Tests for reactive attributes that update when signals change."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
from ritz.llvm_codegen_signals import generate_llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_reactive_attribute():
|
||||||
|
"""Test parsing attribute with signal dependency."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Toggle():
|
||||||
|
active = signal(1)
|
||||||
|
|
||||||
|
with Button(class_='active' if active else 'inactive'):
|
||||||
|
yield "Click"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Should have ternary expression in class attribute
|
||||||
|
with_stmt = [s for s in ir["body"] if s["type"] == "with"][0]
|
||||||
|
class_prop = with_stmt["element"]["props"]["class_"]
|
||||||
|
assert class_prop["type"] == "ifexp"
|
||||||
|
assert class_prop["test"]["id"] == "active"
|
||||||
|
|
||||||
|
|
||||||
|
def test_reactive_attribute_infrastructure():
|
||||||
|
"""Test that reactive attribute tracking is generated."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Toggle():
|
||||||
|
active = signal(1)
|
||||||
|
|
||||||
|
def toggle():
|
||||||
|
active = 0 if active else 1
|
||||||
|
|
||||||
|
with Button(class_='btn-active' if active else 'btn-inactive', onclick=toggle):
|
||||||
|
yield "Toggle"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should have storage for the button element
|
||||||
|
assert "@reactive_attr_" in llvm_ir or "@elem_" in llvm_ir
|
||||||
|
|
||||||
|
# Should update attribute when signal changes in toggle()
|
||||||
|
# The toggle function should re-evaluate the class expression and update
|
||||||
|
assert "js_set_attribute" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_reactive_attributes():
|
||||||
|
"""Test multiple elements with reactive attributes."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Tabs():
|
||||||
|
tab = signal(0)
|
||||||
|
|
||||||
|
def set_tab(value):
|
||||||
|
tab = value
|
||||||
|
|
||||||
|
with Ul():
|
||||||
|
with Li(class_='active' if tab == 0 else ''):
|
||||||
|
yield "Tab 0"
|
||||||
|
|
||||||
|
with Li(class_='active' if tab == 1 else ''):
|
||||||
|
yield "Tab 1"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Both Li elements should have reactive attributes
|
||||||
|
# When tab signal changes, both should update
|
||||||
|
assert llvm_ir.count("js_set_attribute") >= 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_reactive_attribute_with_comparison():
|
||||||
|
"""Test reactive attribute with comparison in ternary."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(5)
|
||||||
|
|
||||||
|
with Div(class_='high' if count > 10 else 'low'):
|
||||||
|
yield "Value"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should emit comparison when evaluating class
|
||||||
|
assert "icmp" in llvm_ir
|
||||||
|
assert "select" in llvm_ir
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
"""Tests for reactive conditional rendering with signal updates."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
from ritz.llvm_codegen_signals import generate_llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_reactive_conditional_infrastructure():
|
||||||
|
"""Test that reactive conditional infrastructure is generated."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Toggle():
|
||||||
|
visible = signal(1)
|
||||||
|
|
||||||
|
if visible:
|
||||||
|
with P():
|
||||||
|
yield "Visible"
|
||||||
|
else:
|
||||||
|
with P():
|
||||||
|
yield "Hidden"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should have marker global
|
||||||
|
assert "@cond_0_marker = global i32 0" in llvm_ir
|
||||||
|
|
||||||
|
# Should have current state global
|
||||||
|
assert "@cond_0_current = global i32 0" in llvm_ir
|
||||||
|
|
||||||
|
# Should have root globals for both branches
|
||||||
|
assert "@cond_0_then_root = global i32 0" in llvm_ir
|
||||||
|
assert "@cond_0_else_root = global i32 0" in llvm_ir
|
||||||
|
|
||||||
|
# Should have init function
|
||||||
|
assert "define void @_init_conditional_0()" in llvm_ir
|
||||||
|
|
||||||
|
# Should have reevaluate function (for signal updates)
|
||||||
|
assert "define void @reevaluate_conditional_0()" in llvm_ir
|
||||||
|
|
||||||
|
# Should have branch render functions
|
||||||
|
assert "define i32 @_render_cond_0_then()" in llvm_ir
|
||||||
|
assert "define i32 @_render_cond_0_else()" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_reactive_conditional_init_logic():
|
||||||
|
"""Test that init function pre-renders both branches."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Toggle():
|
||||||
|
visible = signal(1)
|
||||||
|
|
||||||
|
if visible:
|
||||||
|
with P():
|
||||||
|
yield "Shown"
|
||||||
|
else:
|
||||||
|
with P():
|
||||||
|
yield "Hidden"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Init should call both render functions
|
||||||
|
assert "call i32 @_render_cond_0_then()" in llvm_ir
|
||||||
|
assert "call i32 @_render_cond_0_else()" in llvm_ir
|
||||||
|
|
||||||
|
# Init should store both roots
|
||||||
|
assert "store i32" in llvm_ir and "@cond_0_then_root" in llvm_ir
|
||||||
|
assert "store i32" in llvm_ir and "@cond_0_else_root" in llvm_ir
|
||||||
|
|
||||||
|
# Init should insert active branch at marker
|
||||||
|
assert "call void @js_replace_at_marker" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_reactive_conditional_swap_logic():
|
||||||
|
"""Test that reevaluate function swaps branches when condition changes."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Toggle():
|
||||||
|
visible = signal(0)
|
||||||
|
|
||||||
|
if visible:
|
||||||
|
with P():
|
||||||
|
yield "On"
|
||||||
|
else:
|
||||||
|
with P():
|
||||||
|
yield "Off"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Reevaluate should re-evaluate the condition
|
||||||
|
assert "reevaluate_conditional_0" in llvm_ir
|
||||||
|
|
||||||
|
# Should check if condition changed
|
||||||
|
assert "icmp ne" in llvm_ir
|
||||||
|
|
||||||
|
# Should have swap logic
|
||||||
|
assert "swap:" in llvm_ir or "swap_to_then:" in llvm_ir
|
||||||
|
assert "swap_to_then:" in llvm_ir
|
||||||
|
assert "swap_to_else:" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_reactive_conditionals():
|
||||||
|
"""Test multiple if statements in same component get unique IDs."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def MultiToggle():
|
||||||
|
flag1 = signal(1)
|
||||||
|
flag2 = signal(0)
|
||||||
|
|
||||||
|
if flag1:
|
||||||
|
with P():
|
||||||
|
yield "First on"
|
||||||
|
|
||||||
|
if flag2:
|
||||||
|
with P():
|
||||||
|
yield "Second on"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should have two separate conditionals
|
||||||
|
assert "@cond_0_marker" in llvm_ir
|
||||||
|
assert "@cond_1_marker" in llvm_ir
|
||||||
|
|
||||||
|
# Should have init functions for both
|
||||||
|
assert "define void @_init_conditional_0()" in llvm_ir
|
||||||
|
assert "define void @_init_conditional_1()" in llvm_ir
|
||||||
|
|
||||||
|
# Should have reevaluate functions for both
|
||||||
|
assert "define void @reevaluate_conditional_0()" in llvm_ir
|
||||||
|
assert "define void @reevaluate_conditional_1()" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_reactive_conditional_with_comparison():
|
||||||
|
"""Test reactive conditional with comparison operator."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
if count > 5:
|
||||||
|
with P():
|
||||||
|
yield "High"
|
||||||
|
else:
|
||||||
|
with P():
|
||||||
|
yield "Low"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should generate icmp for comparison
|
||||||
|
assert "icmp" in llvm_ir
|
||||||
|
assert "sgt" in llvm_ir # signed greater than
|
||||||
|
|
||||||
|
# Should still have reactive infrastructure
|
||||||
|
assert "@_init_conditional_0()" in llvm_ir
|
||||||
|
assert "@reevaluate_conditional_0()" in llvm_ir
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
"""Tests for Signal codegen to LLVM IR."""
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
|
||||||
|
def test_signal_llvm_ir_structure():
|
||||||
|
"""Test that signal generates proper LLVM IR with memory allocation."""
|
||||||
|
from ritz.llvm_codegen import generate_llvm_ir
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
source = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
with P():
|
||||||
|
yield "test"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should have global for signal value
|
||||||
|
assert "@signal_count" in llvm_ir or "signal" in llvm_ir.lower()
|
||||||
|
|
||||||
|
# Should have signal getter function
|
||||||
|
assert "get_signal" in llvm_ir or "@get_count" in llvm_ir
|
||||||
|
|
||||||
|
# Should have signal setter function
|
||||||
|
assert "set_signal" in llvm_ir or "@set_count" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_signal_with_fstring():
|
||||||
|
"""Test signal reference in f-string generates proper IR.
|
||||||
|
|
||||||
|
NOTE: Full f-string interpolation is not yet implemented - this is a TODO.
|
||||||
|
Currently generates a placeholder text node.
|
||||||
|
"""
|
||||||
|
from ritz.llvm_codegen import generate_llvm_ir
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
source = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
with P():
|
||||||
|
yield f"Count: {count}"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should have signal storage (scoped to component)
|
||||||
|
assert "@signal_Counter_count" in llvm_ir
|
||||||
|
|
||||||
|
# Should have getter function
|
||||||
|
assert "get_count" in llvm_ir
|
||||||
|
|
||||||
|
# TODO: Full f-string support - currently creates placeholder
|
||||||
|
assert "js_create_text" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_event_handler_export():
|
||||||
|
"""Test that event handler functions are exported for JS to call."""
|
||||||
|
from ritz.llvm_codegen import generate_llvm_ir
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
source = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
def increment():
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
with Button(onclick=increment):
|
||||||
|
yield "Click"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Event handler should be exported as separate function
|
||||||
|
assert "define" in llvm_ir and "increment" in llvm_ir
|
||||||
|
|
||||||
|
# Should update the signal
|
||||||
|
assert "store" in llvm_ir or "set_count" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_signal_update_in_handler():
|
||||||
|
"""Test that count += 1 generates proper signal update code."""
|
||||||
|
from ritz.llvm_codegen import generate_llvm_ir
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
source = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
def increment():
|
||||||
|
count += 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should load current value, add 1, store back
|
||||||
|
# This is the signal update pattern
|
||||||
|
found_load = "load" in llvm_ir or "get_count" in llvm_ir
|
||||||
|
found_add = "add" in llvm_ir
|
||||||
|
found_store = "store" in llvm_ir or "set_count" in llvm_ir
|
||||||
|
|
||||||
|
assert found_load, "Should load signal value"
|
||||||
|
assert found_add, "Should add to value"
|
||||||
|
assert found_store, "Should store updated value"
|
||||||
|
|
||||||
|
|
||||||
|
def test_onclick_binding():
|
||||||
|
"""Test that onclick prop generates event binding code."""
|
||||||
|
from ritz.llvm_codegen import generate_llvm_ir
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
source = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
def handleClick():
|
||||||
|
pass
|
||||||
|
|
||||||
|
with Button(onclick=handleClick):
|
||||||
|
yield "Click"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should call js_bind_event or similar to register handler
|
||||||
|
assert "js_bind_event" in llvm_ir or "onclick" in llvm_ir
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
"""Tests for Signal reactive system - parsing, codegen, and execution."""
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_signal_declaration():
|
||||||
|
"""Test parsing a signal assignment."""
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
source = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
with P():
|
||||||
|
yield "test"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Should have signal in body
|
||||||
|
assert len(ir["body"]) == 2
|
||||||
|
signal_stmt = ir["body"][0]
|
||||||
|
|
||||||
|
assert signal_stmt["type"] == "signal"
|
||||||
|
assert signal_stmt["name"] == "count"
|
||||||
|
assert signal_stmt["initial"]["type"] == "constant"
|
||||||
|
assert signal_stmt["initial"]["value"] == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_signal_in_template():
|
||||||
|
"""Test parsing signal reference in f-string."""
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
source = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
with P():
|
||||||
|
yield f"Count: {count}"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Find the yield statement
|
||||||
|
with_stmt = ir["body"][1]
|
||||||
|
yield_stmt = with_stmt["body"][0]
|
||||||
|
|
||||||
|
# Should be an f-string
|
||||||
|
assert yield_stmt["value"]["type"] == "fstring"
|
||||||
|
parts = yield_stmt["value"]["parts"]
|
||||||
|
|
||||||
|
# Should have "Count: " as constant and count as formatted value
|
||||||
|
assert any(p["type"] == "constant" for p in parts)
|
||||||
|
assert any(p["type"] == "formatted_value" for p in parts)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_event_handler():
|
||||||
|
"""Test parsing onclick event handler."""
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
source = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
def increment():
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
with Button(onclick=increment):
|
||||||
|
yield "Click me"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Should have signal, function, and with statement
|
||||||
|
assert len(ir["body"]) == 3
|
||||||
|
|
||||||
|
# Check function definition
|
||||||
|
func_stmt = ir["body"][1]
|
||||||
|
assert func_stmt["type"] == "function"
|
||||||
|
assert func_stmt["name"] == "increment"
|
||||||
|
|
||||||
|
# Check onclick prop
|
||||||
|
with_stmt = ir["body"][2]
|
||||||
|
onclick_prop = with_stmt["element"]["props"].get("onclick")
|
||||||
|
assert onclick_prop is not None
|
||||||
|
assert onclick_prop["type"] == "name"
|
||||||
|
assert onclick_prop["id"] == "increment"
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_signal_update():
|
||||||
|
"""Test parsing signal increment (count += 1)."""
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
|
||||||
|
source = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
def increment():
|
||||||
|
count += 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Get the increment function
|
||||||
|
func_stmt = ir["body"][1]
|
||||||
|
func_body = func_stmt["body"][0]
|
||||||
|
|
||||||
|
# Should be an augmented assignment
|
||||||
|
assert func_body["type"] == "augassign"
|
||||||
|
assert func_body["target"] == "count"
|
||||||
|
assert func_body["op"] == "Add"
|
||||||
|
assert func_body["value"]["type"] == "constant"
|
||||||
|
assert func_body["value"]["value"] == 1
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
"""Tests for ternary expression support in props."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
from ritz.llvm_codegen_signals import generate_llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_ternary_in_prop():
|
||||||
|
"""Test parsing ternary expression in element prop."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Toggle():
|
||||||
|
active = signal(1)
|
||||||
|
|
||||||
|
with Button(class_='btn active' if active else 'btn'):
|
||||||
|
yield "Click"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Find the with statement
|
||||||
|
with_stmt = [s for s in ir["body"] if s["type"] == "with"][0]
|
||||||
|
|
||||||
|
# Check that class_ prop contains a ternary (ifexp)
|
||||||
|
class_prop = with_stmt["element"]["props"]["class_"]
|
||||||
|
assert class_prop["type"] == "ifexp"
|
||||||
|
assert class_prop["test"]["id"] == "active"
|
||||||
|
assert class_prop["body"]["value"] == "btn active"
|
||||||
|
assert class_prop["orelse"]["value"] == "btn"
|
||||||
|
|
||||||
|
|
||||||
|
def test_codegen_ternary_in_prop():
|
||||||
|
"""Test LLVM codegen for ternary expression in prop."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Toggle():
|
||||||
|
active = signal(1)
|
||||||
|
|
||||||
|
with Button(class_='btn active' if active else 'btn'):
|
||||||
|
yield "Click"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should generate code to evaluate the condition
|
||||||
|
assert "icmp" in llvm_ir # Comparison for active signal
|
||||||
|
|
||||||
|
# Should select between the two string values
|
||||||
|
# This will likely use select instruction or branching
|
||||||
|
assert "btn active" in llvm_ir
|
||||||
|
assert "btn" in llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_ternary_with_comparison():
|
||||||
|
"""Test ternary with comparison expression."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def Counter():
|
||||||
|
count = signal(0)
|
||||||
|
|
||||||
|
with Div(class_='high' if count > 5 else 'low'):
|
||||||
|
yield "Value"
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Find the with statement
|
||||||
|
with_stmt = [s for s in ir["body"] if s["type"] == "with"][0]
|
||||||
|
|
||||||
|
# Check ternary structure
|
||||||
|
class_prop = with_stmt["element"]["props"]["class_"]
|
||||||
|
assert class_prop["type"] == "ifexp"
|
||||||
|
assert class_prop["test"]["type"] == "compare"
|
||||||
|
assert class_prop["test"]["op"] == "Gt"
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
"""Tests for wrapper component pattern to reduce repetition."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ritz.parser import parse_component
|
||||||
|
from ritz.llvm_codegen_signals import generate_llvm_ir
|
||||||
|
|
||||||
|
|
||||||
|
def test_tab_button_wrapper_component():
|
||||||
|
"""Test wrapper component pattern for reducing tab button repetition."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def TabButton(label, index, active, onclick):
|
||||||
|
\"\"\"Wrapper component for tab buttons with active state.\"\"\"
|
||||||
|
if active:
|
||||||
|
with Button(class_="tab active", onclick=onclick):
|
||||||
|
yield label
|
||||||
|
else:
|
||||||
|
with Button(class_="tab", onclick=onclick):
|
||||||
|
yield label
|
||||||
|
|
||||||
|
@component
|
||||||
|
def SimpleTabs():
|
||||||
|
tab_index = signal(0)
|
||||||
|
|
||||||
|
def set_tab(value):
|
||||||
|
tab_index = value
|
||||||
|
|
||||||
|
with Div(class_="tab-menu"):
|
||||||
|
yield TabButton("Home", 0, tab_index == 0, lambda: set_tab(0))
|
||||||
|
yield TabButton("Profile", 1, tab_index == 1, lambda: set_tab(1))
|
||||||
|
yield TabButton("Settings", 2, tab_index == 2, lambda: set_tab(2))
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
|
||||||
|
# Should parse both components
|
||||||
|
assert isinstance(ir, list)
|
||||||
|
assert len(ir) == 2
|
||||||
|
assert ir[0]["name"] == "TabButton"
|
||||||
|
assert ir[1]["name"] == "SimpleTabs"
|
||||||
|
|
||||||
|
# TabButton should have parameters
|
||||||
|
assert len(ir[0]["params"]) == 4
|
||||||
|
assert ir[0]["params"][0]["name"] == "label"
|
||||||
|
assert ir[0]["params"][1]["name"] == "index"
|
||||||
|
assert ir[0]["params"][2]["name"] == "active"
|
||||||
|
assert ir[0]["params"][3]["name"] == "onclick"
|
||||||
|
|
||||||
|
|
||||||
|
def test_tab_button_wrapper_codegen():
|
||||||
|
"""Test LLVM codegen for wrapper component pattern."""
|
||||||
|
source = """
|
||||||
|
from ritz.syntax import *
|
||||||
|
|
||||||
|
@component
|
||||||
|
def TabButton(label, active, onclick):
|
||||||
|
if active:
|
||||||
|
with Button(class_="tab active", onclick=onclick):
|
||||||
|
yield label
|
||||||
|
else:
|
||||||
|
with Button(class_="tab", onclick=onclick):
|
||||||
|
yield label
|
||||||
|
|
||||||
|
@component
|
||||||
|
def SimpleTabs():
|
||||||
|
tab_index = signal(0)
|
||||||
|
|
||||||
|
def set_tab(value):
|
||||||
|
tab_index = value
|
||||||
|
|
||||||
|
with Div():
|
||||||
|
yield TabButton("Home", tab_index == 0, lambda: set_tab(0))
|
||||||
|
"""
|
||||||
|
ir = parse_component(source)
|
||||||
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
|
# Should generate render function for TabButton with parameters
|
||||||
|
assert "define i32 @render_TabButton(" in llvm_ir
|
||||||
|
|
||||||
|
# Should call TabButton from SimpleTabs with arguments
|
||||||
|
assert "call i32 @render_TabButton(" in llvm_ir
|
||||||
Reference in new issue
Block a user