//! Sexpr → component AST: structural parsing of the `(component …)` form. use crate::ast::*; use crate::diagnostics::Diagnostic; use crate::expr; use crate::sexpr::{self, Sexpr, Span}; /// Parse one component from `.gdc` source text. pub fn parse(source: &str) -> Result { let doc = sexpr::read(source).map_err(|e| Diagnostic::new(e.message, e.span))?; let mut components = doc.values.iter(); let Some(form) = components.next() else { return Err(Diagnostic::new( "file contains no `(component …)` form", Span::new(0, 0), )); }; if let Some(extra) = components.next() { return Err(Diagnostic::new( "a `.gdc` file holds exactly one component; found a second top-level form", extra.span(), )); } component(form, source) } fn component(form: &Sexpr, source: &str) -> Result { let items = expect_form(form, "component")?; let name = expect_ident( items.get(1), "component name (a bare symbol, e.g. `Counter`)", form.span(), )?; let mut records = Vec::new(); let mut props = Vec::new(); let mut outputs = Vec::new(); let mut states = Vec::new(); let mut handlers = Vec::new(); let mut root: Option = None; for item in &items[2..] { let Some(list) = item.as_list() else { return Err(Diagnostic::new( format!( "expected a declaration or widget list, found {}", item.kind_name() ), item.span(), )); }; let head = list.first().and_then(Sexpr::as_symbol).ok_or_else(|| { Diagnostic::new("expected a form starting with a symbol", item.span()) })?; match head { "record" => records.push(record_decl(list, item.span())?), "prop" => props.push(prop_decl(list, item.span(), source)?), "output" => outputs.push(output_decl(list, item.span())?), "state" => states.push(state_decl(list, item.span(), source)?), "handler" => { let name = expect_ident(list.get(1), "handler name", item.span())?; let payloads = list[2..] .iter() .map(|ty| expect_type(Some(ty), item.span())) .collect::, _>>()?; handlers.push(HandlerDecl { name, payloads }); } _ => { // Anything else is the widget tree; exactly one allowed. let node = widget_node(item, source)?; if root.is_some() { return Err(Diagnostic::new( "component already has a widget tree; only one root is allowed", item.span(), )); } root = Some(node); } } } let root = root.ok_or_else(|| Diagnostic::new("component has no widget tree", form.span()))?; Ok(Component { name, records, props, outputs, states, handlers, root, span: form.span(), }) } /// `(record Name :field Type …)` fn record_decl(list: &[Sexpr], span: Span) -> Result { let name = expect_ident(list.get(1), "record name", span)?; let mut fields = Vec::new(); let mut rest = list[2..].iter(); while let Some(item) = rest.next() { let Sexpr::Keyword(field, kw_span) = item else { return Err(Diagnostic::new( "`record` takes `:field Type` pairs after the name", item.span(), )); }; let ty = expect_type(rest.next(), *kw_span)?; fields.push(( Ident { name: field.clone(), span: *kw_span, }, ty, )); } if fields.is_empty() { return Err(Diagnostic::new("a record needs at least one field", span)); } Ok(RecordDecl { name, fields }) } /// `(prop name Type)` or `(prop name Type :default expr)` fn prop_decl(list: &[Sexpr], span: Span, source: &str) -> Result { let name = expect_ident(list.get(1), "prop name", span)?; let ty = expect_type(list.get(2), span)?; let mut default = None; let mut rest = list[3..].iter(); while let Some(item) = rest.next() { match item { Sexpr::Keyword(kw, kw_span) if kw == "default" => { let value = rest .next() .ok_or_else(|| Diagnostic::new("`:default` needs a value", *kw_span))?; default = Some(expr::from_sexpr(value, source)?); } other => { return Err(Diagnostic::new( format!("unexpected {} in prop declaration", other.kind_name()), other.span(), )); } } } Ok(PropDecl { name, ty, default }) } /// `(state name Type :init expr)` fn state_decl(list: &[Sexpr], span: Span, source: &str) -> Result { let name = expect_ident(list.get(1), "state name", span)?; let ty = expect_type(list.get(2), span)?; let mut init = None; let mut rest = list[3..].iter(); while let Some(item) = rest.next() { match item { Sexpr::Keyword(kw, kw_span) if kw == "init" => { let value = rest .next() .ok_or_else(|| Diagnostic::new("`:init` needs a value", *kw_span))?; init = Some(expr::from_sexpr(value, source)?); } other => { return Err(Diagnostic::new( format!("unexpected {} in state declaration", other.kind_name()), other.span(), )); } } } let init = init.ok_or_else(|| { Diagnostic::new( format!("state `{}` needs an `:init` value", name.name), span, ) })?; Ok(StateDecl { name, ty, init }) } /// `(output name Type)` fn output_decl(list: &[Sexpr], span: Span) -> Result { let name = expect_ident(list.get(1), "output name", span)?; let ty = expect_type(list.get(2), span)?; if list.len() > 3 { return Err(Diagnostic::new( "`output` takes a name and a type", list[3].span(), )); } Ok(OutputDecl { name, ty }) } /// `(widget :prop value … child-nodes…)`, or one of the structural forms /// `(if …)` / `(for …)`. fn widget_node(form: &Sexpr, source: &str) -> Result { let Some(list) = form.as_list() else { return Err(Diagnostic::new( format!("expected a widget list, found {}", form.kind_name()), form.span(), )); }; let widget = expect_ident(list.first(), "widget name", form.span())?; match widget.name.as_str() { "if" => return if_node(widget, list, form.span(), source), "for" => return for_node(widget, list, form.span(), source), _ => {} } let mut props = Vec::new(); let mut children = Vec::new(); let mut rest = list[1..].iter(); while let Some(item) = rest.next() { match item { Sexpr::Keyword(name, kw_span) => { let value = rest .next() .ok_or_else(|| Diagnostic::new(format!("`:{name}` needs a value"), *kw_span))?; props.push(NodeProp { name: Ident { name: name.clone(), span: *kw_span, }, value: expr::from_sexpr(value, source)?, }); } Sexpr::List(..) => { children.push(widget_node(item, source)?); } other => { return Err(Diagnostic::new( format!( "expected `:property value` pairs or child widgets, found {}", other.kind_name() ), other.span(), )); } } } Ok(Node { widget, props, children, control: None, span: form.span(), }) } /// `(if cond then-node else-node?)` — the condition is an expression, the /// branches are widget nodes. fn if_node(widget: Ident, list: &[Sexpr], span: Span, source: &str) -> Result { if !(3..=4).contains(&list.len()) { return Err(Diagnostic::new( "`if` takes a condition, a then-node, and an optional else-node", span, )); } let cond = expr::from_sexpr(&list[1], source)?; let mut children = vec![widget_node(&list[2], source)?]; if let Some(else_form) = list.get(3) { children.push(widget_node(else_form, source)?); } Ok(Node { widget, props: Vec::new(), children, control: Some(Control::If { cond }), span, }) } /// `(for var list-expr :index name :key expr body-node)` — one body node, /// instantiated per item. fn for_node(widget: Ident, list: &[Sexpr], span: Span, source: &str) -> Result { let var = expect_ident(list.get(1), "loop variable", span)?; let Some(list_form) = list.get(2) else { return Err(Diagnostic::new( "`for` takes a loop variable, the list to iterate, options, and \ one body node", span, )); }; let list_expr = expr::from_sexpr(list_form, source)?; let mut index = None; let mut key = None; let mut body = None; let mut rest = list[3..].iter(); while let Some(item) = rest.next() { match item { Sexpr::Keyword(kw, kw_span) if kw == "index" => { let value = rest .next() .ok_or_else(|| Diagnostic::new("`:index` needs a name", *kw_span))?; index = Some(expect_ident(Some(value), "index name", *kw_span)?); } Sexpr::Keyword(kw, kw_span) if kw == "key" => { let value = rest .next() .ok_or_else(|| Diagnostic::new("`:key` needs an expression", *kw_span))?; key = Some(expr::from_sexpr(value, source)?); } Sexpr::Keyword(kw, kw_span) => { return Err(Diagnostic::new( format!("`for` knows `:index` and `:key`, not `:{kw}`"), *kw_span, )); } node_form => { if body.is_some() { return Err(Diagnostic::new( "`for` takes exactly one body node; wrap multiple \ widgets in a container", node_form.span(), )); } body = Some(widget_node(node_form, source)?); } } } let body = body.ok_or_else(|| Diagnostic::new("`for` needs a body node to repeat", span))?; Ok(Node { widget, props: Vec::new(), children: vec![body], control: Some(Control::For { var, index, list: list_expr, key, }), span, }) } fn expect_form<'a>(form: &'a Sexpr, head: &str) -> Result<&'a [Sexpr], Diagnostic> { let items = form.as_list().ok_or_else(|| { Diagnostic::new( format!("expected a `({head} …)` form, found {}", form.kind_name()), form.span(), ) })?; match items.first().and_then(Sexpr::as_symbol) { Some(s) if s == head => Ok(items), _ => Err(Diagnostic::new( format!("expected a `({head} …)` form"), form.span(), )), } } fn expect_ident(value: Option<&Sexpr>, what: &str, parent: Span) -> Result { match value { Some(Sexpr::Symbol(name, span)) => Ok(Ident { name: name.clone(), span: *span, }), Some(other) => Err(Diagnostic::new( format!("expected {what}, found {}", other.kind_name()), other.span(), )), None => Err(Diagnostic::new(format!("missing {what}"), parent)), } } fn expect_type(value: Option<&Sexpr>, parent: Span) -> Result { match value { Some(Sexpr::Symbol(name, span)) => match TypeKind::parse(name) { Some(kind) => Ok(Ty { kind: TyKind::Scalar(kind), list: false, span: *span, }), // A capitalized name is a record reference, resolved during // validation; `String` is already claimed by the scalar set. None if name.chars().next().is_some_and(|c| c.is_ascii_uppercase()) => Ok(Ty { kind: TyKind::Named(name.clone()), list: false, span: *span, }), None => Err(Diagnostic::new( format!( "unknown type `{name}`; expected one of i32, i64, f32, f64, \ bool, String, a record name, or `(List T)`" ), *span, )), }, // `(List T)` — a list of scalar elements. Some(list_form @ Sexpr::List(items, span)) => { let head = items.first().and_then(Sexpr::as_symbol); if head != Some("List") { return Err(Diagnostic::new( format!( "expected a type symbol or `(List T)`, found {}", list_form.kind_name() ), *span, )); } if items.len() != 2 { return Err(Diagnostic::new( "`List` takes exactly one element type", *span, )); } let element = expect_type(items.get(1), *span)?; if element.list { return Err(Diagnostic::new( "nested lists are not supported; element types are the \ scalar set", element.span, )); } Ok(Ty { kind: element.kind, list: true, span: *span, }) } Some(other) => Err(Diagnostic::new( format!("expected a type symbol, found {}", other.kind_name()), other.span(), )), None => Err(Diagnostic::new("missing type", parent)), } }