Add reactive conditional infrastructure
- Collect conditionals during IR scan (if/match statements) - Emit conditional state globals (marker, current, branch roots) - Generate init functions that pre-render all branches - Generate reevaluate functions that swap DOM on condition change - Add JS function declarations for markers and swapping Not yet integrated with actual rendering - still emitting static if/match. Next: modify _emit_if() to use reactive approach. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
9f601b1787
commit
8bf1c38858
2 files changed
+216
-1
No files matched your search
Binary file not shown.
@@ -27,9 +27,12 @@ class SignalLLVMIRGenerator:
|
||||
self.strings: dict[str, str] = {} # value -> global name
|
||||
self.signals: dict[str, dict[str, Any]] = {} # signal_name -> {initial, index}
|
||||
self.event_handlers: list[dict[str, Any]] = [] # List of event handler functions
|
||||
self.conditionals: list[dict[str, Any]] = [] # List of conditional branches (if/match)
|
||||
self.conditional_map: dict[int, int] = {} # stmt id() -> conditional id
|
||||
self.next_string_id = 0
|
||||
self.next_var_id = 0
|
||||
self.next_signal_id = 0
|
||||
self.next_conditional_id = 0
|
||||
|
||||
# Track signal-to-DOM bindings for updates
|
||||
self.signal_dom_bindings: dict[str, list[str]] = {} # signal_name -> [dom_node_ids]
|
||||
@@ -62,14 +65,19 @@ class SignalLLVMIRGenerator:
|
||||
# Reset component-specific state
|
||||
self.signals = {}
|
||||
self.event_handlers = []
|
||||
self.conditionals = []
|
||||
self.conditional_map = {}
|
||||
|
||||
# Collect signals and handlers for this component
|
||||
# Collect signals, handlers, and conditionals for this component
|
||||
self._collect_signals_and_handlers(component)
|
||||
self._collect_conditionals(component)
|
||||
|
||||
# Generate component-specific code
|
||||
self._emit_signal_globals(component)
|
||||
self._emit_conditional_globals()
|
||||
self._emit_helper_functions()
|
||||
self._emit_event_handlers()
|
||||
self._emit_conditional_functions()
|
||||
self._emit_component(component)
|
||||
|
||||
# Prepend string constants
|
||||
@@ -88,6 +96,43 @@ class SignalLLVMIRGenerator:
|
||||
elif stmt["type"] == "function":
|
||||
self.event_handlers.append(stmt)
|
||||
|
||||
def _collect_conditionals(self, component: dict[str, Any]) -> None:
|
||||
"""Scan IR tree for if/match statements and assign IDs."""
|
||||
|
||||
def scan_statements(stmts: list[dict[str, Any]]):
|
||||
for stmt in stmts:
|
||||
if stmt["type"] == "if":
|
||||
cond_id = self.next_conditional_id
|
||||
self.next_conditional_id += 1
|
||||
self.conditional_map[id(stmt)] = cond_id
|
||||
self.conditionals.append({
|
||||
"id": cond_id,
|
||||
"type": "if",
|
||||
"test": stmt["test"],
|
||||
"then_body": stmt["body"],
|
||||
"else_body": stmt.get("orelse", []),
|
||||
})
|
||||
# Recursively scan nested statements
|
||||
scan_statements(stmt["body"])
|
||||
scan_statements(stmt.get("orelse", []))
|
||||
elif stmt["type"] == "match":
|
||||
cond_id = self.next_conditional_id
|
||||
self.next_conditional_id += 1
|
||||
self.conditional_map[id(stmt)] = cond_id
|
||||
self.conditionals.append({
|
||||
"id": cond_id,
|
||||
"type": "match",
|
||||
"subject": stmt["subject"],
|
||||
"cases": stmt["cases"],
|
||||
})
|
||||
# Recursively scan nested statements in each case
|
||||
for case in stmt["cases"]:
|
||||
scan_statements(case.get("body", []))
|
||||
elif stmt["type"] == "with":
|
||||
scan_statements(stmt.get("body", []))
|
||||
|
||||
scan_statements(component["body"])
|
||||
|
||||
def _emit_header(self) -> None:
|
||||
"""Emit module header."""
|
||||
self.lines.append("; Generated by Ritz Framework with Signals")
|
||||
@@ -108,6 +153,31 @@ class SignalLLVMIRGenerator:
|
||||
self.lines.append(f"@signal_{component_name}_{signal_name} = global i32 {initial_value}")
|
||||
self.lines.append("")
|
||||
|
||||
def _emit_conditional_globals(self) -> None:
|
||||
"""Emit global state for reactive conditionals."""
|
||||
if not self.conditionals:
|
||||
return
|
||||
|
||||
self.lines.append("; Conditional rendering state")
|
||||
for cond_info in self.conditionals:
|
||||
cond_id = cond_info["id"]
|
||||
cond_type = cond_info["type"]
|
||||
|
||||
# Common state for all conditionals
|
||||
self.lines.append(f"@cond_{cond_id}_marker = global i32 0")
|
||||
self.lines.append(f"@cond_{cond_id}_current = global i32 0")
|
||||
|
||||
if cond_type == "if":
|
||||
# Binary conditional: then_root, else_root
|
||||
self.lines.append(f"@cond_{cond_id}_then_root = global i32 0")
|
||||
self.lines.append(f"@cond_{cond_id}_else_root = global i32 0")
|
||||
elif cond_type == "match":
|
||||
# Multi-way conditional: one root per case
|
||||
num_cases = len(cond_info["cases"])
|
||||
for case_idx in range(num_cases):
|
||||
self.lines.append(f"@cond_{cond_id}_case_{case_idx}_root = global i32 0")
|
||||
self.lines.append("")
|
||||
|
||||
def _emit_function_declarations(self) -> None:
|
||||
"""Emit JS bridge function declarations."""
|
||||
self.lines.append("; JavaScript bridge function declarations")
|
||||
@@ -120,6 +190,8 @@ class SignalLLVMIRGenerator:
|
||||
"declare void @js_bind_event(i32, i8*, i8*)"
|
||||
) # elem, event_name, handler_name
|
||||
self.lines.append("declare void @js_update_text_content(i32, i8*)") # Update DOM node text
|
||||
self.lines.append("declare i32 @js_create_marker()") # Create invisible marker element
|
||||
self.lines.append("declare void @js_replace_at_marker(i32, i32, i32)") # marker, old, new
|
||||
self.lines.append("")
|
||||
|
||||
def _emit_helper_functions(self) -> None:
|
||||
@@ -198,6 +270,149 @@ class SignalLLVMIRGenerator:
|
||||
# TODO: Trigger DOM updates for this signal
|
||||
self.lines.append(f" ; TODO: Update DOM nodes bound to {signal_name}")
|
||||
|
||||
def _emit_conditional_functions(self) -> None:
|
||||
"""Emit init and reevaluate functions for each conditional."""
|
||||
for cond_info in self.conditionals:
|
||||
if cond_info["type"] == "if":
|
||||
self._emit_if_reactive(cond_info)
|
||||
elif cond_info["type"] == "match":
|
||||
self._emit_match_reactive(cond_info)
|
||||
|
||||
def _emit_if_reactive(self, cond_info: dict[str, Any]) -> None:
|
||||
"""Emit reactive init and reevaluate functions for if/else."""
|
||||
cond_id = cond_info["id"]
|
||||
|
||||
# Emit init function
|
||||
self.lines.append(f"; Initialize conditional {cond_id}")
|
||||
self.lines.append(f"define void @_init_conditional_{cond_id}() {{")
|
||||
self.lines.append("entry:")
|
||||
|
||||
# Render both branches and store their roots
|
||||
self.lines.append(" ; Render both branches upfront")
|
||||
then_root_var = self._new_var()
|
||||
else_root_var = self._new_var()
|
||||
|
||||
self.lines.append(f" %{then_root_var} = call i32 @_render_cond_{cond_id}_then()")
|
||||
self.lines.append(f" %{else_root_var} = call i32 @_render_cond_{cond_id}_else()")
|
||||
|
||||
self.lines.append(f" store i32 %{then_root_var}, i32* @cond_{cond_id}_then_root")
|
||||
self.lines.append(f" store i32 %{else_root_var}, i32* @cond_{cond_id}_else_root")
|
||||
|
||||
# Evaluate condition and insert the active branch at marker
|
||||
test_var = self._emit_expr(cond_info["test"])
|
||||
cond_var = self._new_var()
|
||||
self.lines.append(f" %{cond_var} = icmp ne i32 %{test_var}, 0")
|
||||
|
||||
# Load marker
|
||||
marker_var = self._new_var()
|
||||
self.lines.append(f" %{marker_var} = load i32, i32* @cond_{cond_id}_marker")
|
||||
|
||||
# Branch to insert correct element
|
||||
self.lines.append(f" br i1 %{cond_var}, label %insert_then, label %insert_else")
|
||||
|
||||
self.lines.append("insert_then:")
|
||||
self.lines.append(f" call void @js_replace_at_marker(i32 %{marker_var}, i32 0, i32 %{then_root_var})")
|
||||
self.lines.append(f" store i32 1, i32* @cond_{cond_id}_current")
|
||||
self.lines.append(" ret void")
|
||||
|
||||
self.lines.append("insert_else:")
|
||||
self.lines.append(f" call void @js_replace_at_marker(i32 %{marker_var}, i32 0, i32 %{else_root_var})")
|
||||
self.lines.append(f" store i32 0, i32* @cond_{cond_id}_current")
|
||||
self.lines.append(" ret void")
|
||||
|
||||
self.lines.append("}")
|
||||
self.lines.append("")
|
||||
|
||||
# Emit reevaluate function
|
||||
self.lines.append(f"; Re-evaluate conditional {cond_id}")
|
||||
self.lines.append(f"define void @reevaluate_conditional_{cond_id}() {{")
|
||||
self.lines.append("entry:")
|
||||
|
||||
# Re-evaluate condition
|
||||
test_var2 = self._emit_expr(cond_info["test"])
|
||||
cond_var2 = self._new_var()
|
||||
self.lines.append(f" %{cond_var2} = icmp ne i32 %{test_var2}, 0")
|
||||
|
||||
# Convert to i32
|
||||
cond_i32 = self._new_var()
|
||||
self.lines.append(f" %{cond_i32} = zext i1 %{cond_var2} to i32")
|
||||
|
||||
# Load current state
|
||||
current_var = self._new_var()
|
||||
self.lines.append(f" %{current_var} = load i32, i32* @cond_{cond_id}_current")
|
||||
|
||||
# Check if changed
|
||||
changed_var = self._new_var()
|
||||
self.lines.append(f" %{changed_var} = icmp ne i32 %{current_var}, %{cond_i32}")
|
||||
self.lines.append(f" br i1 %{changed_var}, label %swap, label %done")
|
||||
|
||||
# Swap branch
|
||||
self.lines.append("swap:")
|
||||
self.lines.append(f" br i1 %{cond_var2}, label %swap_to_then, label %swap_to_else")
|
||||
|
||||
# Swap to then
|
||||
self.lines.append("swap_to_then:")
|
||||
old_var = self._new_var()
|
||||
new_var = self._new_var()
|
||||
marker_var2 = self._new_var()
|
||||
self.lines.append(f" %{old_var} = load i32, i32* @cond_{cond_id}_else_root")
|
||||
self.lines.append(f" %{new_var} = load i32, i32* @cond_{cond_id}_then_root")
|
||||
self.lines.append(f" %{marker_var2} = load i32, i32* @cond_{cond_id}_marker")
|
||||
self.lines.append(f" call void @js_replace_at_marker(i32 %{marker_var2}, i32 %{old_var}, i32 %{new_var})")
|
||||
self.lines.append(f" store i32 1, i32* @cond_{cond_id}_current")
|
||||
self.lines.append(" br label %done")
|
||||
|
||||
# Swap to else
|
||||
self.lines.append("swap_to_else:")
|
||||
old_var2 = self._new_var()
|
||||
new_var2 = self._new_var()
|
||||
marker_var3 = self._new_var()
|
||||
self.lines.append(f" %{old_var2} = load i32, i32* @cond_{cond_id}_then_root")
|
||||
self.lines.append(f" %{new_var2} = load i32, i32* @cond_{cond_id}_else_root")
|
||||
self.lines.append(f" %{marker_var3} = load i32, i32* @cond_{cond_id}_marker")
|
||||
self.lines.append(f" call void @js_replace_at_marker(i32 %{marker_var3}, i32 %{old_var2}, i32 %{new_var2})")
|
||||
self.lines.append(f" store i32 0, i32* @cond_{cond_id}_current")
|
||||
self.lines.append(" br label %done")
|
||||
|
||||
self.lines.append("done:")
|
||||
self.lines.append(" ret void")
|
||||
self.lines.append("}")
|
||||
self.lines.append("")
|
||||
|
||||
# Emit branch render functions
|
||||
self._emit_branch_render_function(cond_id, "then", cond_info["then_body"])
|
||||
self._emit_branch_render_function(cond_id, "else", cond_info["else_body"])
|
||||
|
||||
def _emit_match_reactive(self, cond_info: dict[str, Any]) -> None:
|
||||
"""Emit reactive init and reevaluate functions for match/case."""
|
||||
# TODO: Implement match reactive rendering
|
||||
pass
|
||||
|
||||
def _emit_branch_render_function(self, cond_id: int, branch_name: str, body: list[dict[str, Any]]) -> None:
|
||||
"""Emit a function that renders a branch and returns its root element."""
|
||||
self.lines.append(f"define i32 @_render_cond_{cond_id}_{branch_name}() {{")
|
||||
self.lines.append("entry:")
|
||||
|
||||
# Render branch body
|
||||
root_var = None
|
||||
for stmt in body:
|
||||
var = self._emit_statement(stmt)
|
||||
if root_var is None and var:
|
||||
root_var = var
|
||||
|
||||
# Return root element
|
||||
if root_var:
|
||||
self.lines.append(f" ret i32 %{root_var}")
|
||||
else:
|
||||
# Empty branch - create empty div
|
||||
empty_var = self._new_var()
|
||||
div_str = self._get_or_create_string("div")
|
||||
self.lines.append(f" %{empty_var} = call i32 @js_create_element(i8* getelementptr inbounds ({self._string_type('div')}, {self._string_type('div')}* {div_str}, i32 0, i32 0))")
|
||||
self.lines.append(f" ret i32 %{empty_var}")
|
||||
|
||||
self.lines.append("}")
|
||||
self.lines.append("")
|
||||
|
||||
def _emit_component(self, ir: dict[str, Any]) -> None:
|
||||
"""Emit component render function."""
|
||||
component_name = ir["name"]
|
||||
|
||||
Reference in new issue
Block a user