Add component props and lambda event handlers (TDD)

Implements two major features using Test-Driven Development:

1. Component Props/Parameters:
   - Components can accept parameters: @component def Button(label)
   - Arguments passed when yielding: yield Button("Click me")
   - Default values supported: def Tabs(orientation=0)
   - Codegen passes arguments correctly to render functions

2. Lambda Event Handlers:
   - Support onclick=lambda: function_call() syntax
   - Parser converts lambdas to synthetic functions
   - Synthetic functions auto-named: _lambda_0_ComponentName
   - Codegen emits lambda functions as event handlers
   - Clean syntax vs set_tab_0/1/2 boilerplate

Implementation:
   - Added _process_lambdas_in_component() to scan & convert
   - Lambda detection in _transform_expr()
   - "expr" statement type for lambda bodies
   - Component argument passing in _emit_yield_component()
   - Constants vs variables handled correctly in args

Tests (TDD Style):
   - test_component_props.py (8 tests) - all passing
   - test_lambda_simple.py (3 tests) - all passing
   - 46 total tests passing

Examples:
   # Before (verbose)
   def set_tab_0(): tab = 0
   def set_tab_1(): tab = 1
   with Button(onclick=set_tab_0): yield "Tab 0"

   # After (clean)
   with Button(onclick=lambda: set_tab(0)): yield "Tab 0"
   with Button(onclick=lambda: set_tab(1)): yield "Tab 1"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Aaron SinclairandClaude committed 2025-11-15 10:45:17 -08:00
1 parent e686903170
commit e09a6dedc8
4 files changed
+383 -7

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
"""Tests for simple lambda support - starting with valid Python syntax."""
import pytest
from ritz.parser import parse_component
from ritz.llvm_codegen_signals import generate_llvm_ir
def test_parse_lambda_calling_function():
"""Test lambda that calls a function - valid Python syntax."""
source = """
from ritz.syntax import *
@component
def App():
count = signal(0)
def increment():
count += 1
with Button(onclick=lambda: increment()):
yield "Click"
"""
ir = parse_component(source)
# Parser should recognize lambda in onclick and convert to synthetic function
with_stmt = [s for s in ir["body"] if s["type"] == "with"][0]
assert "onclick" in with_stmt["element"]["props"]
# After lambda processing, onclick should reference a synthetic function
onclick_ref = with_stmt["element"]["props"]["onclick"]
assert onclick_ref["type"] == "name"
assert onclick_ref["id"].startswith("_lambda_")
def test_codegen_lambda_calling_function():
"""Test LLVM codegen for lambda calling a function."""
source = """
from ritz.syntax import *
@component
def App():
count = signal(0)
def increment():
count += 1
with Button(onclick=lambda: increment()):
yield "+"
"""
ir = parse_component(source)
llvm_ir = generate_llvm_ir(ir)
# Should generate a lambda handler that calls increment
assert "define void @_lambda_0_App()" in llvm_ir
assert "call void @increment()" in llvm_ir
assert "js_bind_event" in llvm_ir
def test_lambda_inline_for_simple_setter():
"""Test using lambda for simple signal setter calls."""
source = """
from ritz.syntax import *
@component
def Tabs():
tab = signal(0)
def set_tab(value):
tab = value
with Button(onclick=lambda: set_tab(0)):
yield "Tab 0"
with Button(onclick=lambda: set_tab(1)):
yield "Tab 1"
"""
ir = parse_component(source)
# Should have two with statements with lambda onclick handlers (converted to functions)
with_stmts = [s for s in ir["body"] if s["type"] == "with"]
assert len([w for w in with_stmts if "onclick" in w["element"]["props"]]) == 2
# Check that both have synthetic lambda functions
for with_stmt in with_stmts:
if "onclick" in with_stmt["element"]["props"]:
onclick_ref = with_stmt["element"]["props"]["onclick"]
assert onclick_ref["type"] == "name"
assert onclick_ref["id"].startswith("_lambda_")