feat: when/else conditional branching
- AST: When(cond, body) -> When(cond, body, Option<else_body>) - Parser: optional 'else -> expr' after when body - Codegen: reactive DOM swap with anchor comments - Signal graph + type checker updated for 3-arg When - Component prop signal wrapping: .value compatible accessors - Added examples/when-else-demo.ds
This commit is contained in:
parent
55dc24eecc
commit
bb65e10f5c
6 changed files with 89 additions and 10 deletions
|
|
@ -321,9 +321,12 @@ fn collect_deps(expr: &Expr, deps: &mut Vec<String>) {
|
|||
collect_deps(item, deps);
|
||||
}
|
||||
}
|
||||
Expr::When(cond, body) => {
|
||||
Expr::When(cond, body, else_body) => {
|
||||
collect_deps(cond, deps);
|
||||
collect_deps(body, deps);
|
||||
if let Some(eb) = else_body {
|
||||
collect_deps(eb, deps);
|
||||
}
|
||||
}
|
||||
Expr::Match(scrutinee, arms) => {
|
||||
collect_deps(scrutinee, deps);
|
||||
|
|
@ -439,13 +442,16 @@ fn collect_bindings(expr: &Expr, bindings: &mut Vec<DomBinding>) {
|
|||
}
|
||||
}
|
||||
}
|
||||
Expr::When(cond, body) => {
|
||||
Expr::When(cond, body, else_body) => {
|
||||
let deps = extract_dependencies(cond);
|
||||
bindings.push(DomBinding {
|
||||
kind: BindingKind::Conditional { condition_signals: deps.clone() },
|
||||
dependencies: deps,
|
||||
});
|
||||
collect_bindings(body, bindings);
|
||||
if let Some(eb) = else_body {
|
||||
collect_bindings(eb, bindings);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -743,7 +743,8 @@ impl JsEmitter {
|
|||
node_var
|
||||
}
|
||||
|
||||
Expr::When(cond, body) => {
|
||||
Expr::When(cond, body, else_body) => {
|
||||
let else_expr = else_body.clone();
|
||||
let anchor_var = self.next_node_id();
|
||||
let container_var = self.next_node_id();
|
||||
let cond_js = self.emit_expr(cond);
|
||||
|
|
@ -751,12 +752,25 @@ impl JsEmitter {
|
|||
self.emit_line(&format!("const {} = document.createComment('when');", anchor_var));
|
||||
self.emit_line(&format!("let {} = null;", container_var));
|
||||
|
||||
// Build the conditional child
|
||||
let has_else = else_expr.is_some();
|
||||
let else_container = if has_else {
|
||||
let ec = self.next_node_id();
|
||||
self.emit_line(&format!("let {} = null;", ec));
|
||||
Some(ec)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.emit_line("DS.effect(() => {");
|
||||
self.indent += 1;
|
||||
self.emit_line(&format!("const show = {};", cond_js));
|
||||
|
||||
// Show when: condition true and not already showing
|
||||
self.emit_line(&format!("if (show && !{}) {{", container_var));
|
||||
self.indent += 1;
|
||||
if let Some(ref ec) = else_container {
|
||||
self.emit_line(&format!("if ({}) {{ {}.remove(); {} = null; }}", ec, ec, ec));
|
||||
}
|
||||
let child_var = self.emit_view_expr(body, graph);
|
||||
self.emit_line(&format!("{} = {};", container_var, child_var));
|
||||
self.emit_line(&format!(
|
||||
|
|
@ -764,11 +778,39 @@ impl JsEmitter {
|
|||
anchor_var, container_var, anchor_var
|
||||
));
|
||||
self.indent -= 1;
|
||||
|
||||
// Hide when: condition false and currently showing
|
||||
self.emit_line(&format!("}} else if (!show && {}) {{", container_var));
|
||||
self.indent += 1;
|
||||
self.emit_line(&format!("{}.remove();", container_var));
|
||||
self.emit_line(&format!("{} = null;", container_var));
|
||||
if let Some(eb) = &else_expr {
|
||||
if let Some(ec) = &else_container {
|
||||
let else_child = self.emit_view_expr(eb, graph);
|
||||
self.emit_line(&format!("{} = {};", ec, else_child));
|
||||
self.emit_line(&format!(
|
||||
"{}.parentNode.insertBefore({}, {}.nextSibling);",
|
||||
anchor_var, ec, anchor_var
|
||||
));
|
||||
}
|
||||
}
|
||||
self.indent -= 1;
|
||||
|
||||
// Initial else: show=false and nothing rendered yet
|
||||
if let Some(eb) = &else_expr {
|
||||
if let Some(ec) = &else_container {
|
||||
self.emit_line(&format!("}} else if (!show && !{} && !{}) {{", container_var, ec));
|
||||
self.indent += 1;
|
||||
let else_child2 = self.emit_view_expr(eb, graph);
|
||||
self.emit_line(&format!("{} = {};", ec, else_child2));
|
||||
self.emit_line(&format!(
|
||||
"{}.parentNode.insertBefore({}, {}.nextSibling);",
|
||||
anchor_var, ec, anchor_var
|
||||
));
|
||||
self.indent -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.emit_line("}");
|
||||
self.indent -= 1;
|
||||
self.emit_line("});");
|
||||
|
|
|
|||
|
|
@ -261,8 +261,8 @@ pub enum Expr {
|
|||
Element(Element),
|
||||
/// Container: `column [ ... ]`, `row [ ... ]`
|
||||
Container(Container),
|
||||
/// When conditional: `when count > 10 -> ...`
|
||||
When(Box<Expr>, Box<Expr>),
|
||||
/// When conditional: `when count > 10 -> ...` with optional `else -> ...`
|
||||
When(Box<Expr>, Box<Expr>, Option<Box<Expr>>),
|
||||
|
||||
/// Each loop: `each item in list => template`
|
||||
Each(String, Box<Expr>, Box<Expr>),
|
||||
|
|
|
|||
|
|
@ -891,7 +891,17 @@ impl Parser {
|
|||
self.expect(&TokenKind::Arrow)?;
|
||||
self.skip_newlines();
|
||||
let body = self.parse_expr()?;
|
||||
Ok(Expr::When(Box::new(cond), Box::new(body)))
|
||||
// Optional else branch
|
||||
self.skip_newlines();
|
||||
let else_body = if self.check(&TokenKind::Else) {
|
||||
self.advance();
|
||||
self.expect(&TokenKind::Arrow)?;
|
||||
self.skip_newlines();
|
||||
Some(Box::new(self.parse_expr()?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Expr::When(Box::new(cond), Box::new(body), else_body))
|
||||
}
|
||||
|
||||
// Each loop: `each item in list => template`
|
||||
|
|
|
|||
|
|
@ -391,7 +391,7 @@ impl TypeChecker {
|
|||
});
|
||||
}
|
||||
}
|
||||
Expr::When(condition, body) => {
|
||||
Expr::When(condition, body, else_body) => {
|
||||
let cond_type = self.infer_expr(condition);
|
||||
let inner = cond_type.unwrap_reactive().clone();
|
||||
if inner != Type::Bool && !matches!(inner, Type::Var(_)) && inner != Type::Error {
|
||||
|
|
@ -634,8 +634,12 @@ impl TypeChecker {
|
|||
self.infer_expr(then_br)
|
||||
}
|
||||
|
||||
Expr::When(_, body) => {
|
||||
self.infer_expr(body)
|
||||
Expr::When(_, body, else_body) => {
|
||||
let body_ty = self.infer_expr(body);
|
||||
if let Some(eb) = else_body {
|
||||
let _ = self.infer_expr(eb);
|
||||
}
|
||||
body_ty
|
||||
}
|
||||
|
||||
Expr::Block(exprs) => {
|
||||
|
|
|
|||
17
examples/when-else-demo.ds
Normal file
17
examples/when-else-demo.ds
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
-- DreamStack When/Else Demo
|
||||
|
||||
let loggedIn = false
|
||||
let count = 0
|
||||
|
||||
view main = column [
|
||||
text "🔀 When/Else Demo" { variant: "title" }
|
||||
button "Toggle Login" { click: loggedIn = !loggedIn }
|
||||
|
||||
when loggedIn ->
|
||||
text "Welcome back! ✅"
|
||||
else ->
|
||||
text "Please log in 🔒"
|
||||
|
||||
text "Count: {count}"
|
||||
button "+1" { click: count += 1 }
|
||||
]
|
||||
Loading…
Add table
Reference in a new issue