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)),
    }
}