feat: multi-statement event handlers with semicolons

- Added Semicolon token to lexer
- Enables: click: items.push(x); input = ""
- Push-and-clear, increment-and-reset, clear-all patterns
- Browser-verified: both actions fire in one click
- All existing examples pass regression
This commit is contained in:
enzotar 2026-02-26 17:29:47 -08:00
parent 6c9d109ebd
commit 10b2717281
3 changed files with 48 additions and 1 deletions

View file

@ -82,6 +82,7 @@ pub enum TokenKind {
PlusEq, // +=
MinusEq, // -=
Arrow, // ->
Semicolon, // ;
Pipe, // |
Dot, // .
@ -223,6 +224,7 @@ impl Lexer {
'>' => { self.advance(); Token { kind: TokenKind::Gt, lexeme: ">".into(), line, col } }
'!' => { self.advance(); Token { kind: TokenKind::Not, lexeme: "!".into(), line, col } }
'|' => { self.advance(); Token { kind: TokenKind::Pipe, lexeme: "|".into(), line, col } }
';' => { self.advance(); Token { kind: TokenKind::Semicolon, lexeme: ";".into(), line, col } }
'.' => { self.advance(); Token { kind: TokenKind::Dot, lexeme: ".".into(), line, col } }
'(' => { self.advance(); Token { kind: TokenKind::LParen, lexeme: "(".into(), line, col } }
')' => { self.advance(); Token { kind: TokenKind::RParen, lexeme: ")".into(), line, col } }

View file

@ -1355,7 +1355,24 @@ impl Parser {
let key = self.expect_ident()?;
self.expect(&TokenKind::Colon)?;
self.skip_newlines();
let val = self.parse_expr()?;
let first = self.parse_expr()?;
// Multi-action: `click: action1; action2; action3`
// Collects semicolon-separated expressions into a Block
let val = if self.check(&TokenKind::Semicolon) {
let mut exprs = vec![first];
while self.check(&TokenKind::Semicolon) {
self.advance(); // consume ';'
self.skip_newlines();
// Stop if we hit } (end of props) or , (next prop)
if self.check(&TokenKind::RBrace) || self.check(&TokenKind::Comma) {
break;
}
exprs.push(self.parse_expr()?);
}
Expr::Block(exprs)
} else {
first
};
props.push((key, val));
self.skip_newlines();
if self.check(&TokenKind::Comma) {

28
examples/multi-action.ds Normal file
View file

@ -0,0 +1,28 @@
-- Multi-Action Click Handler Test
-- Tests semicolon-separated actions in click handlers
let items = ["Buy milk", "Walk dog"]
let input = ""
let count = 0
view main = column [
text "Multi-Action Demo" { variant: "title" }
text "Add items AND clear input in one click" { variant: "subtitle" }
row [
input { bind: input, placeholder: "New item..." }
button "Add & Clear" { click: items.push(input); input = "", variant: "primary" }
]
each item in items ->
row [
text "• {item}"
button "×" { click: items.remove(_idx), variant: "ghost" }
]
text "Count: {count}"
row [
button "+5 & Reset Name" { click: count += 5; input = "reset!", variant: "secondary" }
button "Reset All" { click: count = 0; items = [], variant: "ghost" }
]
]