Fix lambda event handlers & add ternary expression support
Fixes: - Lambda functions now correctly pass arguments to handlers - Function parameters properly emitted in LLVM IR - Event handlers defined with parameter lists New feature - Ternary expressions: - Support Python ternary syntax (x if cond else y) in props - Eliminates repetitive if/else blocks for active states - Before: 6 lines per tab button - After: 1 line with ternary expression Example: class_='tab active' if active else 'tab' All tests passing (51 passed, 2 skipped) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
c947bcf6e0
commit
adf95f424d
7 files changed
+1277
-660
No files matched your search
+871
-633
File diff suppressed because it is too large.
Load diff
Binary file not shown.
@@ -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"
|
||||
+112
-27
@@ -45,6 +45,9 @@ class SignalLLVMIRGenerator:
|
||||
|
||||
# Track parent element for appending children in if/match branches
|
||||
self.parent_element_stack: list[str] = []
|
||||
|
||||
# Track current function parameters for parameter reference handling
|
||||
self.current_function_params: set[str] = set()
|
||||
# Track if we're inside a conditional branch
|
||||
self.in_conditional_branch = False
|
||||
|
||||
@@ -266,8 +269,21 @@ class SignalLLVMIRGenerator:
|
||||
"""Emit event handler functions (exported for JS to call)."""
|
||||
for handler in self.event_handlers:
|
||||
func_name = handler["name"]
|
||||
|
||||
# Build parameter list (all params are i32 for now)
|
||||
args = handler.get("args", [])
|
||||
|
||||
# Track current function parameters for _emit_expr
|
||||
self.current_function_params = set(args)
|
||||
|
||||
if args:
|
||||
param_list = ", ".join([f"i32 %{arg}" for arg in args])
|
||||
self.lines.append(f"; Event handler: {func_name}")
|
||||
self.lines.append(f"define void @{func_name}({param_list}) {{")
|
||||
else:
|
||||
self.lines.append(f"; Event handler: {func_name}")
|
||||
self.lines.append(f"define void @{func_name}() {{")
|
||||
|
||||
self.lines.append("entry:")
|
||||
|
||||
# Emit handler body
|
||||
@@ -278,17 +294,31 @@ class SignalLLVMIRGenerator:
|
||||
self.lines.append("}")
|
||||
self.lines.append("")
|
||||
|
||||
# Clear function params
|
||||
self.current_function_params = set()
|
||||
|
||||
def _emit_handler_statement(self, stmt: dict[str, Any]) -> None:
|
||||
"""Emit a statement inside an event handler."""
|
||||
if stmt["type"] == "expr":
|
||||
# Expression statement (e.g., from lambda body calling a function)
|
||||
expr = stmt["value"]
|
||||
if expr["type"] == "call":
|
||||
# Function call - emit it
|
||||
# Function call - emit it with arguments
|
||||
func_name = expr["func"]
|
||||
# Check if it's an event handler function
|
||||
if any(h["name"] == func_name for h in self.event_handlers):
|
||||
# Call the handler function (functions are emitted without component suffix)
|
||||
# Emit arguments if any
|
||||
args = expr.get("args", [])
|
||||
arg_values = []
|
||||
for arg in args:
|
||||
arg_var = self._emit_expr(arg)
|
||||
arg_values.append(f"i32 {arg_var}")
|
||||
|
||||
# Call the handler function with arguments
|
||||
if arg_values:
|
||||
arg_list = ", ".join(arg_values)
|
||||
self.lines.append(f" call void @{func_name}({arg_list})")
|
||||
else:
|
||||
self.lines.append(f" call void @{func_name}()")
|
||||
else:
|
||||
# Unknown function - skip or error
|
||||
@@ -360,7 +390,7 @@ class SignalLLVMIRGenerator:
|
||||
# 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")
|
||||
self.lines.append(f" %{cond_var} = icmp ne i32 {test_var}, 0")
|
||||
|
||||
# Load marker
|
||||
marker_var = self._new_var()
|
||||
@@ -390,7 +420,7 @@ class SignalLLVMIRGenerator:
|
||||
# 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")
|
||||
self.lines.append(f" %{cond_var2} = icmp ne i32 {test_var2}, 0")
|
||||
|
||||
# Convert to i32
|
||||
cond_i32 = self._new_var()
|
||||
@@ -478,9 +508,9 @@ class SignalLLVMIRGenerator:
|
||||
# Build switch instruction
|
||||
insert_labels = [f"insert_case_{i}" for i in range(num_cases)]
|
||||
if default_idx is not None:
|
||||
self.lines.append(f" switch i32 %{subject_var}, label %{insert_labels[default_idx]} [")
|
||||
self.lines.append(f" switch i32 {subject_var}, label %{insert_labels[default_idx]} [")
|
||||
else:
|
||||
self.lines.append(f" switch i32 %{subject_var}, label %insert_case_0 [")
|
||||
self.lines.append(f" switch i32 {subject_var}, label %insert_case_0 [")
|
||||
|
||||
for i, case in enumerate(cases):
|
||||
pattern = case["pattern"]
|
||||
@@ -514,9 +544,9 @@ class SignalLLVMIRGenerator:
|
||||
# Build switch to determine which case should be active
|
||||
case_labels = [f"case_{i}" for i in range(num_cases)]
|
||||
if default_idx is not None:
|
||||
self.lines.append(f" switch i32 %{subject_var2}, label %{case_labels[default_idx]} [")
|
||||
self.lines.append(f" switch i32 {subject_var2}, label %{case_labels[default_idx]} [")
|
||||
else:
|
||||
self.lines.append(f" switch i32 %{subject_var2}, label %{case_labels[0]} [")
|
||||
self.lines.append(f" switch i32 {subject_var2}, label %{case_labels[0]} [")
|
||||
|
||||
for i, case in enumerate(cases):
|
||||
pattern = case["pattern"]
|
||||
@@ -697,7 +727,7 @@ class SignalLLVMIRGenerator:
|
||||
|
||||
# Convert to i1 (boolean)
|
||||
cond_var = self._new_var()
|
||||
self.lines.append(f" %{cond_var} = icmp ne i32 %{test_var}, 0")
|
||||
self.lines.append(f" %{cond_var} = icmp ne i32 {test_var}, 0")
|
||||
|
||||
# Branch based on whether there's an else
|
||||
has_else = stmt.get("orelse") and len(stmt["orelse"]) > 0
|
||||
@@ -733,12 +763,9 @@ class SignalLLVMIRGenerator:
|
||||
arg_values = []
|
||||
|
||||
for arg in args:
|
||||
# For constants, emit directly; for expressions, emit as variable
|
||||
if arg["type"] == "constant":
|
||||
arg_values.append(f"i32 {arg['value']}")
|
||||
else:
|
||||
# Emit argument expression
|
||||
arg_var = self._emit_expr(arg)
|
||||
arg_values.append(f"i32 %{arg_var}")
|
||||
arg_values.append(f"i32 {arg_var}")
|
||||
|
||||
# Build argument list and call
|
||||
if arg_values:
|
||||
@@ -803,9 +830,9 @@ class SignalLLVMIRGenerator:
|
||||
|
||||
# Emit switch
|
||||
if default_case_idx is not None:
|
||||
self.lines.append(f" switch i32 %{subject_var}, label %{case_labels[default_case_idx]} [")
|
||||
self.lines.append(f" switch i32 {subject_var}, label %{case_labels[default_case_idx]} [")
|
||||
else:
|
||||
self.lines.append(f" switch i32 %{subject_var}, label %{default_label} [")
|
||||
self.lines.append(f" switch i32 {subject_var}, label %{default_label} [")
|
||||
|
||||
for case_spec in switch_cases:
|
||||
self.lines.append(f" {case_spec}")
|
||||
@@ -828,20 +855,52 @@ class SignalLLVMIRGenerator:
|
||||
|
||||
return None
|
||||
|
||||
def _emit_string_ifexp(self, expr: dict[str, Any]) -> str:
|
||||
"""Emit a ternary expression that returns a string pointer (i8*)."""
|
||||
# Evaluate the condition
|
||||
test_val = self._emit_expr(expr["test"])
|
||||
|
||||
# Convert to i1 if it's i32
|
||||
cond_var = self._new_var()
|
||||
self.lines.append(f" %{cond_var} = icmp ne i32 {test_val}, 0")
|
||||
|
||||
# Get string constants for both branches
|
||||
true_str = str(expr["body"]["value"])
|
||||
false_str = str(expr["orelse"]["value"])
|
||||
|
||||
true_str_global = self._get_or_create_string(true_str)
|
||||
false_str_global = self._get_or_create_string(false_str)
|
||||
|
||||
# Use select with inline getelementptr
|
||||
result_var = self._new_var()
|
||||
self.lines.append(
|
||||
f" %{result_var} = select i1 %{cond_var}, "
|
||||
f"i8* getelementptr inbounds ({self._string_type(true_str)}, {self._string_type(true_str)}* {true_str_global}, i32 0, i32 0), "
|
||||
f"i8* getelementptr inbounds ({self._string_type(false_str)}, {self._string_type(false_str)}* {false_str_global}, i32 0, i32 0)"
|
||||
)
|
||||
|
||||
return result_var
|
||||
|
||||
def _emit_expr(self, expr: dict[str, Any]) -> str:
|
||||
"""Emit an expression and return the result variable name."""
|
||||
"""Emit an expression and return the complete LLVM IR value (with % prefix for vars)."""
|
||||
if expr["type"] == "name":
|
||||
# Load signal value
|
||||
signal_name = expr["id"]
|
||||
if signal_name in self.signals:
|
||||
var_name = expr["id"]
|
||||
|
||||
# Check if it's a function parameter
|
||||
if var_name in self.current_function_params:
|
||||
# Return parameter with % prefix
|
||||
return f"%{var_name}"
|
||||
|
||||
# Check if it's a signal
|
||||
if var_name in self.signals:
|
||||
load_var = self._new_var()
|
||||
self.lines.append(f" %{load_var} = load i32, i32* {self._signal_global(signal_name)}")
|
||||
return load_var
|
||||
else:
|
||||
# Unknown variable
|
||||
self.lines.append(f" %{load_var} = load i32, i32* {self._signal_global(var_name)}")
|
||||
return f"%{load_var}"
|
||||
|
||||
# Unknown variable - return constant 0
|
||||
return "0"
|
||||
elif expr["type"] == "constant":
|
||||
# Return constant inline
|
||||
# Return constant inline (no % prefix)
|
||||
return str(expr["value"])
|
||||
elif expr["type"] == "compare":
|
||||
# Handle comparison (e.g., count > 5)
|
||||
@@ -860,12 +919,29 @@ class SignalLLVMIRGenerator:
|
||||
|
||||
icmp_op = op_map.get(expr["op"], "eq")
|
||||
result_var = self._new_var()
|
||||
self.lines.append(f" %{result_var} = icmp {icmp_op} i32 %{left_var}, {right_var}")
|
||||
self.lines.append(f" %{result_var} = icmp {icmp_op} i32 {left_var}, {right_var}")
|
||||
|
||||
# Convert i1 to i32 for compatibility
|
||||
result_i32 = self._new_var()
|
||||
self.lines.append(f" %{result_i32} = zext i1 %{result_var} to i32")
|
||||
return result_i32
|
||||
return f"%{result_i32}"
|
||||
elif expr["type"] == "ifexp":
|
||||
# Handle ternary expressions (e.g., 'active' if flag else 'inactive')
|
||||
# Evaluate the condition
|
||||
test_val = self._emit_expr(expr["test"])
|
||||
|
||||
# Convert to i1 if it's i32
|
||||
cond_var = self._new_var()
|
||||
self.lines.append(f" %{cond_var} = icmp ne i32 {test_val}, 0")
|
||||
|
||||
# Evaluate both branches
|
||||
true_val = self._emit_expr(expr["body"])
|
||||
false_val = self._emit_expr(expr["orelse"])
|
||||
|
||||
# Use select instruction to choose between them
|
||||
result_var = self._new_var()
|
||||
self.lines.append(f" %{result_var} = select i1 %{cond_var}, i32 {true_val}, i32 {false_val}")
|
||||
return f"%{result_var}"
|
||||
else:
|
||||
# Unknown expression type
|
||||
return "0"
|
||||
@@ -902,6 +978,15 @@ class SignalLLVMIRGenerator:
|
||||
f"i8* getelementptr inbounds ({self._string_type(attr_name)}, {self._string_type(attr_name)}* {name_str}, i32 0, i32 0), "
|
||||
f"i8* getelementptr inbounds ({self._string_type(value)}, {self._string_type(value)}* {value_str}, i32 0, i32 0))"
|
||||
)
|
||||
elif prop_value["type"] == "ifexp":
|
||||
# Handle ternary expression for attribute value
|
||||
value_ptr = self._emit_string_ifexp(prop_value)
|
||||
name_str = self._get_or_create_string(attr_name)
|
||||
self.lines.append(
|
||||
f" call void @js_set_attribute(i32 %{elem_var}, "
|
||||
f"i8* getelementptr inbounds ({self._string_type(attr_name)}, {self._string_type(attr_name)}* {name_str}, i32 0, i32 0), "
|
||||
f"i8* %{value_ptr})"
|
||||
)
|
||||
|
||||
# Bind onclick if present
|
||||
if onclick_handler:
|
||||
|
||||
@@ -348,6 +348,14 @@ def _transform_expr(expr: ast.expr | None) -> dict[str, Any]:
|
||||
"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__}
|
||||
|
||||
|
||||
@@ -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 void @render_TabButton(" in llvm_ir
|
||||
|
||||
# Should call TabButton from SimpleTabs with arguments
|
||||
assert "call void @render_TabButton(" in llvm_ir
|
||||
Reference in new issue
Block a user