parse.rs
raw
//! 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<Component, Diagnostic> {
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<Component, Diagnostic> {
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<Node> = 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::<Result<Vec<_>, _>>()?;
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<RecordDecl, Diagnostic> {
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<PropDecl, Diagnostic> {
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<StateDecl, Diagnostic> {
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<OutputDecl, Diagnostic> {
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<Node, Diagnostic> {
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<Node, Diagnostic> {
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<Node, Diagnostic> {
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<Ident, Diagnostic> {
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<Ty, Diagnostic> {
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)),
}
}