diff --git a/ritz/__pycache__/llvm_codegen_signals.cpython-313.pyc b/ritz/__pycache__/llvm_codegen_signals.cpython-313.pyc index 6361657..ab2f55d 100644 Binary files a/ritz/__pycache__/llvm_codegen_signals.cpython-313.pyc and b/ritz/__pycache__/llvm_codegen_signals.cpython-313.pyc differ diff --git a/ritz/llvm_codegen_signals.py b/ritz/llvm_codegen_signals.py index b4c2a3b..9ff8389 100644 --- a/ritz/llvm_codegen_signals.py +++ b/ritz/llvm_codegen_signals.py @@ -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"]