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);
|
collect_deps(item, deps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::When(cond, body) => {
|
Expr::When(cond, body, else_body) => {
|
||||||
collect_deps(cond, deps);
|
collect_deps(cond, deps);
|
||||||
collect_deps(body, deps);
|
collect_deps(body, deps);
|
||||||
|
if let Some(eb) = else_body {
|
||||||
|
collect_deps(eb, deps);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Expr::Match(scrutinee, arms) => {
|
Expr::Match(scrutinee, arms) => {
|
||||||
collect_deps(scrutinee, deps);
|
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);
|
let deps = extract_dependencies(cond);
|
||||||
bindings.push(DomBinding {
|
bindings.push(DomBinding {
|
||||||
kind: BindingKind::Conditional { condition_signals: deps.clone() },
|
kind: BindingKind::Conditional { condition_signals: deps.clone() },
|
||||||
dependencies: deps,
|
dependencies: deps,
|
||||||
});
|
});
|
||||||
collect_bindings(body, bindings);
|
collect_bindings(body, bindings);
|
||||||
|
if let Some(eb) = else_body {
|
||||||
|
collect_bindings(eb, bindings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -743,7 +743,8 @@ impl JsEmitter {
|
||||||
node_var
|
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 anchor_var = self.next_node_id();
|
||||||
let container_var = self.next_node_id();
|
let container_var = self.next_node_id();
|
||||||
let cond_js = self.emit_expr(cond);
|
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!("const {} = document.createComment('when');", anchor_var));
|
||||||
self.emit_line(&format!("let {} = null;", container_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.emit_line("DS.effect(() => {");
|
||||||
self.indent += 1;
|
self.indent += 1;
|
||||||
self.emit_line(&format!("const show = {};", cond_js));
|
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.emit_line(&format!("if (show && !{}) {{", container_var));
|
||||||
self.indent += 1;
|
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);
|
let child_var = self.emit_view_expr(body, graph);
|
||||||
self.emit_line(&format!("{} = {};", container_var, child_var));
|
self.emit_line(&format!("{} = {};", container_var, child_var));
|
||||||
self.emit_line(&format!(
|
self.emit_line(&format!(
|
||||||
|
|
@ -764,11 +778,39 @@ impl JsEmitter {
|
||||||
anchor_var, container_var, anchor_var
|
anchor_var, container_var, anchor_var
|
||||||
));
|
));
|
||||||
self.indent -= 1;
|
self.indent -= 1;
|
||||||
|
|
||||||
|
// Hide when: condition false and currently showing
|
||||||
self.emit_line(&format!("}} else if (!show && {}) {{", container_var));
|
self.emit_line(&format!("}} else if (!show && {}) {{", container_var));
|
||||||
self.indent += 1;
|
self.indent += 1;
|
||||||
self.emit_line(&format!("{}.remove();", container_var));
|
self.emit_line(&format!("{}.remove();", container_var));
|
||||||
self.emit_line(&format!("{} = null;", 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;
|
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.emit_line("}");
|
||||||
self.indent -= 1;
|
self.indent -= 1;
|
||||||
self.emit_line("});");
|
self.emit_line("});");
|
||||||
|
|
|
||||||
|
|
@ -261,8 +261,8 @@ pub enum Expr {
|
||||||
Element(Element),
|
Element(Element),
|
||||||
/// Container: `column [ ... ]`, `row [ ... ]`
|
/// Container: `column [ ... ]`, `row [ ... ]`
|
||||||
Container(Container),
|
Container(Container),
|
||||||
/// When conditional: `when count > 10 -> ...`
|
/// When conditional: `when count > 10 -> ...` with optional `else -> ...`
|
||||||
When(Box<Expr>, Box<Expr>),
|
When(Box<Expr>, Box<Expr>, Option<Box<Expr>>),
|
||||||
|
|
||||||
/// Each loop: `each item in list => template`
|
/// Each loop: `each item in list => template`
|
||||||
Each(String, Box<Expr>, Box<Expr>),
|
Each(String, Box<Expr>, Box<Expr>),
|
||||||
|
|
|
||||||
|
|
@ -891,7 +891,17 @@ impl Parser {
|
||||||
self.expect(&TokenKind::Arrow)?;
|
self.expect(&TokenKind::Arrow)?;
|
||||||
self.skip_newlines();
|
self.skip_newlines();
|
||||||
let body = self.parse_expr()?;
|
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`
|
// 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 cond_type = self.infer_expr(condition);
|
||||||
let inner = cond_type.unwrap_reactive().clone();
|
let inner = cond_type.unwrap_reactive().clone();
|
||||||
if inner != Type::Bool && !matches!(inner, Type::Var(_)) && inner != Type::Error {
|
if inner != Type::Bool && !matches!(inner, Type::Var(_)) && inner != Type::Error {
|
||||||
|
|
@ -634,8 +634,12 @@ impl TypeChecker {
|
||||||
self.infer_expr(then_br)
|
self.infer_expr(then_br)
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::When(_, body) => {
|
Expr::When(_, body, else_body) => {
|
||||||
self.infer_expr(body)
|
let body_ty = self.infer_expr(body);
|
||||||
|
if let Some(eb) = else_body {
|
||||||
|
let _ = self.infer_expr(eb);
|
||||||
|
}
|
||||||
|
body_ty
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::Block(exprs) => {
|
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