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
|
# Track parent element for appending children in if/match branches
|
||||||
self.parent_element_stack: list[str] = []
|
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
|
# Track if we're inside a conditional branch
|
||||||
self.in_conditional_branch = False
|
self.in_conditional_branch = False
|
||||||
|
|
||||||
@@ -266,8 +269,21 @@ class SignalLLVMIRGenerator:
|
|||||||
"""Emit event handler functions (exported for JS to call)."""
|
"""Emit event handler functions (exported for JS to call)."""
|
||||||
for handler in self.event_handlers:
|
for handler in self.event_handlers:
|
||||||
func_name = handler["name"]
|
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"; Event handler: {func_name}")
|
||||||
self.lines.append(f"define void @{func_name}() {{")
|
self.lines.append(f"define void @{func_name}() {{")
|
||||||
|
|
||||||
self.lines.append("entry:")
|
self.lines.append("entry:")
|
||||||
|
|
||||||
# Emit handler body
|
# Emit handler body
|
||||||
@@ -278,17 +294,31 @@ class SignalLLVMIRGenerator:
|
|||||||
self.lines.append("}")
|
self.lines.append("}")
|
||||||
self.lines.append("")
|
self.lines.append("")
|
||||||
|
|
||||||
|
# Clear function params
|
||||||
|
self.current_function_params = set()
|
||||||
|
|
||||||
def _emit_handler_statement(self, stmt: dict[str, Any]) -> None:
|
def _emit_handler_statement(self, stmt: dict[str, Any]) -> None:
|
||||||
"""Emit a statement inside an event handler."""
|
"""Emit a statement inside an event handler."""
|
||||||
if stmt["type"] == "expr":
|
if stmt["type"] == "expr":
|
||||||
# Expression statement (e.g., from lambda body calling a function)
|
# Expression statement (e.g., from lambda body calling a function)
|
||||||
expr = stmt["value"]
|
expr = stmt["value"]
|
||||||
if expr["type"] == "call":
|
if expr["type"] == "call":
|
||||||
# Function call - emit it
|
# Function call - emit it with arguments
|
||||||
func_name = expr["func"]
|
func_name = expr["func"]
|
||||||
# Check if it's an event handler function
|
# Check if it's an event handler function
|
||||||
if any(h["name"] == func_name for h in self.event_handlers):
|
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}()")
|
self.lines.append(f" call void @{func_name}()")
|
||||||
else:
|
else:
|
||||||
# Unknown function - skip or error
|
# Unknown function - skip or error
|
||||||
@@ -360,7 +390,7 @@ class SignalLLVMIRGenerator:
|
|||||||
# Evaluate condition and insert the active branch at marker
|
# Evaluate condition and insert the active branch at marker
|
||||||
test_var = self._emit_expr(cond_info["test"])
|
test_var = self._emit_expr(cond_info["test"])
|
||||||
cond_var = self._new_var()
|
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
|
# Load marker
|
||||||
marker_var = self._new_var()
|
marker_var = self._new_var()
|
||||||
@@ -390,7 +420,7 @@ class SignalLLVMIRGenerator:
|
|||||||
# Re-evaluate condition
|
# Re-evaluate condition
|
||||||
test_var2 = self._emit_expr(cond_info["test"])
|
test_var2 = self._emit_expr(cond_info["test"])
|
||||||
cond_var2 = self._new_var()
|
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
|
# Convert to i32
|
||||||
cond_i32 = self._new_var()
|
cond_i32 = self._new_var()
|
||||||
@@ -478,9 +508,9 @@ class SignalLLVMIRGenerator:
|
|||||||
# Build switch instruction
|
# Build switch instruction
|
||||||
insert_labels = [f"insert_case_{i}" for i in range(num_cases)]
|
insert_labels = [f"insert_case_{i}" for i in range(num_cases)]
|
||||||
if default_idx is not None:
|
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:
|
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):
|
for i, case in enumerate(cases):
|
||||||
pattern = case["pattern"]
|
pattern = case["pattern"]
|
||||||
@@ -514,9 +544,9 @@ class SignalLLVMIRGenerator:
|
|||||||
# Build switch to determine which case should be active
|
# Build switch to determine which case should be active
|
||||||
case_labels = [f"case_{i}" for i in range(num_cases)]
|
case_labels = [f"case_{i}" for i in range(num_cases)]
|
||||||
if default_idx is not None:
|
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:
|
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):
|
for i, case in enumerate(cases):
|
||||||
pattern = case["pattern"]
|
pattern = case["pattern"]
|
||||||
@@ -697,7 +727,7 @@ class SignalLLVMIRGenerator:
|
|||||||
|
|
||||||
# Convert to i1 (boolean)
|
# Convert to i1 (boolean)
|
||||||
cond_var = self._new_var()
|
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
|
# Branch based on whether there's an else
|
||||||
has_else = stmt.get("orelse") and len(stmt["orelse"]) > 0
|
has_else = stmt.get("orelse") and len(stmt["orelse"]) > 0
|
||||||
@@ -733,12 +763,9 @@ class SignalLLVMIRGenerator:
|
|||||||
arg_values = []
|
arg_values = []
|
||||||
|
|
||||||
for arg in args:
|
for arg in args:
|
||||||
# For constants, emit directly; for expressions, emit as variable
|
# Emit argument expression
|
||||||
if arg["type"] == "constant":
|
|
||||||
arg_values.append(f"i32 {arg['value']}")
|
|
||||||
else:
|
|
||||||
arg_var = self._emit_expr(arg)
|
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
|
# Build argument list and call
|
||||||
if arg_values:
|
if arg_values:
|
||||||
@@ -803,9 +830,9 @@ class SignalLLVMIRGenerator:
|
|||||||
|
|
||||||
# Emit switch
|
# Emit switch
|
||||||
if default_case_idx is not None:
|
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:
|
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:
|
for case_spec in switch_cases:
|
||||||
self.lines.append(f" {case_spec}")
|
self.lines.append(f" {case_spec}")
|
||||||
@@ -828,20 +855,52 @@ class SignalLLVMIRGenerator:
|
|||||||
|
|
||||||
return None
|
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:
|
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":
|
if expr["type"] == "name":
|
||||||
# Load signal value
|
var_name = expr["id"]
|
||||||
signal_name = expr["id"]
|
|
||||||
if signal_name in self.signals:
|
# 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()
|
load_var = self._new_var()
|
||||||
self.lines.append(f" %{load_var} = load i32, i32* {self._signal_global(signal_name)}")
|
self.lines.append(f" %{load_var} = load i32, i32* {self._signal_global(var_name)}")
|
||||||
return load_var
|
return f"%{load_var}"
|
||||||
else:
|
|
||||||
# Unknown variable
|
# Unknown variable - return constant 0
|
||||||
return "0"
|
return "0"
|
||||||
elif expr["type"] == "constant":
|
elif expr["type"] == "constant":
|
||||||
# Return constant inline
|
# Return constant inline (no % prefix)
|
||||||
return str(expr["value"])
|
return str(expr["value"])
|
||||||
elif expr["type"] == "compare":
|
elif expr["type"] == "compare":
|
||||||
# Handle comparison (e.g., count > 5)
|
# Handle comparison (e.g., count > 5)
|
||||||
@@ -860,12 +919,29 @@ class SignalLLVMIRGenerator:
|
|||||||
|
|
||||||
icmp_op = op_map.get(expr["op"], "eq")
|
icmp_op = op_map.get(expr["op"], "eq")
|
||||||
result_var = self._new_var()
|
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
|
# Convert i1 to i32 for compatibility
|
||||||
result_i32 = self._new_var()
|
result_i32 = self._new_var()
|
||||||
self.lines.append(f" %{result_i32} = zext i1 %{result_var} to i32")
|
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:
|
else:
|
||||||
# Unknown expression type
|
# Unknown expression type
|
||||||
return "0"
|
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(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))"
|
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
|
# Bind onclick if present
|
||||||
if onclick_handler:
|
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.
|
"op": type(expr.ops[0]).__name__, # Gt, Lt, Eq, etc.
|
||||||
"comparators": [_transform_expr(comp) for comp in expr.comparators],
|
"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:
|
else:
|
||||||
return {"type": "unknown", "ast_type": type(expr).__name__}
|
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