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
+1282
-665
No files matched your search
+117
-32
@@ -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"]
|
||||
self.lines.append(f"; Event handler: {func_name}")
|
||||
self.lines.append(f"define void @{func_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,18 +294,32 @@ 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)
|
||||
self.lines.append(f" call void @{func_name}()")
|
||||
# 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
|
||||
pass
|
||||
@@ -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:
|
||||
arg_var = self._emit_expr(arg)
|
||||
arg_values.append(f"i32 %{arg_var}")
|
||||
# Emit argument expression
|
||||
arg_var = self._emit_expr(arg)
|
||||
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
|
||||
return "0"
|
||||
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__}
|
||||
|
||||
|
||||
Reference in new issue
Block a user