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.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"]
|
||||||
|
|||||||
Reference in new issue
Block a user