ast.rs raw

//! The component AST: the surface file, structured but not yet validated.

use crate::expr::Expr;
use crate::sexpr::Span;

#[derive(Clone, Debug, PartialEq)]
pub struct Ident {
    pub name: String,
    pub span: Span,
}

/// The closed set of prop/state/output types. Arbitrary
/// Rust types are a planned extension riding on the same declarations.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TypeKind {
    I32,
    I64,
    F32,
    F64,
    Bool,
    String,
}

impl TypeKind {
    pub fn parse(name: &str) -> Option<Self> {
        Some(match name {
            "i32" => Self::I32,
            "i64" => Self::I64,
            "f32" => Self::F32,
            "f64" => Self::F64,
            "bool" => Self::Bool,
            "String" => Self::String,
            _ => return None,
        })
    }

    pub fn rust_name(self) -> &'static str {
        match self {
            Self::I32 => "i32",
            Self::I64 => "i64",
            Self::F32 => "f32",
            Self::F64 => "f64",
            Self::Bool => "bool",
            Self::String => "String",
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct Ty {
    pub kind: TyKind,
    /// `(List T)` — a list of `kind` elements; nested lists are a planned
    /// extension.
    pub list: bool,
    pub span: Span,
}

/// A type name as written: a scalar, or a capitalized record name (resolved
/// against the file's `(record …)` declarations during validation).
#[derive(Clone, Debug, PartialEq)]
pub enum TyKind {
    Scalar(TypeKind),
    Named(String),
}

#[derive(Clone, Debug, PartialEq)]
pub struct PropDecl {
    pub name: Ident,
    pub ty: Ty,
    pub default: Option<Expr>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct StateDecl {
    pub name: Ident,
    pub ty: Ty,
    pub init: Expr,
}

#[derive(Clone, Debug, PartialEq)]
pub struct OutputDecl {
    pub name: Ident,
    pub ty: Ty,
}

/// A declared handler: `(handler increment)`, or with payloads —
/// `(handler edited String)` for payload-carrying events like
/// `:on-change`, `(handler moved i64 i64)` for invocation wires passing
/// arguments.
#[derive(Clone, Debug, PartialEq)]
pub struct HandlerDecl {
    pub name: Ident,
    /// The payloads the handler receives, in order; empty for plain
    /// handlers.
    pub payloads: Vec<Ty>,
}

/// One `:name value` pair on a widget node. Whether it is a static style,
/// a bindable widget property, or an event wire is decided by validation
/// against the property registry.
#[derive(Clone, Debug, PartialEq)]
pub struct NodeProp {
    pub name: Ident,
    pub value: Expr,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Node {
    pub widget: Ident,
    pub props: Vec<NodeProp>,
    pub children: Vec<Node>,
    /// `Some` for the structural forms `(if …)` and `(for …)`; their
    /// branch/body nodes live in `children`.
    pub control: Option<Control>,
    pub span: Span,
}

/// A structural control form in widget position.
#[derive(Clone, Debug, PartialEq)]
pub enum Control {
    /// `(if cond then-node else-node?)` — children hold the branches.
    If { cond: Expr },
    /// `(for var list-name :index i :key expr body-node)` — the single
    /// child is the row template, instantiated per item.
    For {
        var: Ident,
        index: Option<Ident>,
        list: Expr,
        key: Option<Expr>,
    },
}

/// `(record Todo :id i64 :label String :done bool)` — keyword-shaped,
/// mirroring instance syntax. Field names own the keyword namespace inside
/// the form (a committed consequence: record-level options can never be
/// keywords).
#[derive(Clone, Debug, PartialEq)]
pub struct RecordDecl {
    pub name: Ident,
    pub fields: Vec<(Ident, Ty)>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Component {
    pub name: Ident,
    pub records: Vec<RecordDecl>,
    pub props: Vec<PropDecl>,
    pub outputs: Vec<OutputDecl>,
    pub states: Vec<StateDecl>,
    pub handlers: Vec<HandlerDecl>,
    pub root: Node,
    pub span: Span,
}