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:
Aaron SinclairandClaude committed 2025-11-15 03:51:01 -08:00
1 parent 9f601b1787
commit 8bf1c38858
2 files changed
+216 -1

No files matched your search

+216 -1
View File
@@ -27,9 +27,12 @@ class SignalLLVMIRGenerator:
self.strings: dict[str, str] = {} # value -> global name self.strings: dict[str, str] = {} # value -> global name
self.signals: dict[str, dict[str, Any]] = {} # signal_name -> {initial, index} self.signals: dict[str, dict[str, Any]] = {} # signal_name -> {initial, index}
self.event_handlers: list[dict[str, Any]] = [] # List of event handler functions 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_string_id = 0
self.next_var_id = 0 self.next_var_id = 0
self.next_signal_id = 0 self.next_signal_id = 0
self.next_conditional_id = 0
# Track signal-to-DOM bindings for updates # Track signal-to-DOM bindings for updates
self.signal_dom_bindings: dict[str, list[str]] = {} # signal_name -> [dom_node_ids] self.signal_dom_bindings: dict[str, list[str]] = {} # signal_name -> [dom_node_ids]
@@ -62,14 +65,19 @@ class SignalLLVMIRGenerator:
# Reset component-specific state # Reset component-specific state
self.signals = {} self.signals = {}
self.event_handlers = [] 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_signals_and_handlers(component)
self._collect_conditionals(component)
# Generate component-specific code # Generate component-specific code
self._emit_signal_globals(component) self._emit_signal_globals(component)
self._emit_conditional_globals()
self._emit_helper_functions() self._emit_helper_functions()
self._emit_event_handlers() self._emit_event_handlers()
self._emit_conditional_functions()
self._emit_component(component) self._emit_component(component)
# Prepend string constants # Prepend string constants
@@ -88,6 +96,43 @@ class SignalLLVMIRGenerator:
elif stmt["type"] == "function": elif stmt["type"] == "function":
self.event_handlers.append(stmt) 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: def _emit_header(self) -> None:
"""Emit module header.""" """Emit module header."""
self.lines.append("; Generated by Ritz Framework with Signals") 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(f"@signal_{component_name}_{signal_name} = global i32 {initial_value}")
self.lines.append("") 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: def _emit_function_declarations(self) -> None:
"""Emit JS bridge function declarations.""" """Emit JS bridge function declarations."""
self.lines.append("; JavaScript bridge function declarations") self.lines.append("; JavaScript bridge function declarations")
@@ -120,6 +190,8 @@ class SignalLLVMIRGenerator:
"declare void @js_bind_event(i32, i8*, i8*)" "declare void @js_bind_event(i32, i8*, i8*)"
) # elem, event_name, handler_name ) # elem, event_name, handler_name
self.lines.append("declare void @js_update_text_content(i32, i8*)") # Update DOM node text 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("") self.lines.append("")
def _emit_helper_functions(self) -> None: def _emit_helper_functions(self) -> None:
@@ -198,6 +270,149 @@ class SignalLLVMIRGenerator:
# TODO: Trigger DOM updates for this signal # TODO: Trigger DOM updates for this signal
self.lines.append(f" ; TODO: Update DOM nodes bound to {signal_name}") 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: def _emit_component(self, ir: dict[str, Any]) -> None:
"""Emit component render function.""" """Emit component render function."""
component_name = ir["name"] component_name = ir["name"]