drawlang-syntax 0.1.2

Lexer, parser, lossless syntax tree, and formatter for the drawlang DSL
Documentation
//! AST. Every node carries its span; statements carry their leading and
//! trailing comments so the formatter can reproduce them.

use crate::span::Span;

#[derive(Debug, Clone)]
pub struct File {
    /// `drawl 0.1` header.
    pub header: Option<Header>,
    pub stmts: Vec<Stmt>,
}

#[derive(Debug, Clone)]
pub struct Header {
    pub version: String,
    pub span: Span,
}

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

/// Comments attached to a statement: full-line comments above it, plus an
/// optional same-line trailing comment.
#[derive(Debug, Clone, Default)]
pub struct Trivia {
    pub leading: Vec<String>,
    pub trailing: Option<String>,
    /// Number of blank lines before this statement (capped at 1 by fmt).
    pub blank_before: bool,
}

#[derive(Debug, Clone)]
pub struct Stmt {
    pub kind: StmtKind,
    pub span: Span,
    pub trivia: Trivia,
}

#[derive(Debug, Clone)]
pub enum StmtKind {
    /// `canvas { ... }`
    Canvas(Block),
    /// `def name(a, b) { ... }`
    Def(Def),
    /// `group id "Label" { ... }`
    Group(Group),
    /// `class name { ... }`
    Class(Class),
    /// `constrain { ... }`
    Constrain(Vec<Constraint>),
    /// `pin path at (x, y)`
    Pin(Pin),
    /// `for i in 0..4 { ... }`
    For(For),
    /// `port id { ... }` — only meaningful inside a node/def body.
    Port(Port),
    /// `key: value` / `label.wrap: true`
    Prop(Prop),
    /// Node declaration, container, or component instantiation.
    Node(Node),
    /// `a.b -> c.d : "label" { props }`
    Edge(Edge),
}

#[derive(Debug, Clone)]
pub struct Block {
    pub stmts: Vec<Stmt>,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub struct Def {
    pub name: Ident,
    pub params: Vec<Ident>,
    pub body: Block,
}

#[derive(Debug, Clone)]
pub struct Group {
    pub name: Ident,
    pub label: Option<StrLit>,
    pub body: Block,
}

#[derive(Debug, Clone)]
pub struct Class {
    pub name: Ident,
    pub body: Block,
}

#[derive(Debug, Clone)]
pub struct Constraint {
    /// `align`, `gap`, `below`, `left-of`, ...
    pub name: Ident,
    pub args: Vec<ConstraintArg>,
    pub span: Span,
    pub trivia: Trivia,
}

#[derive(Debug, Clone)]
pub enum ConstraintArg {
    /// Element path; a bare keyword like `top` is a one-segment path,
    /// disambiguated during semantic analysis.
    Path(PathRef),
    Num(f64, Span),
}

impl ConstraintArg {
    pub fn span(&self) -> Span {
        match self {
            ConstraintArg::Path(p) => p.span,
            ConstraintArg::Num(_, s) => *s,
        }
    }
}

#[derive(Debug, Clone)]
pub struct Pin {
    pub target: PathRef,
    pub x: Expr,
    pub y: Expr,
}

#[derive(Debug, Clone)]
pub struct For {
    pub var: Ident,
    pub start: Expr,
    pub end: Expr,
    pub body: Block,
}

#[derive(Debug, Clone)]
pub struct Port {
    pub name: Ident,
    pub body: Block,
}

#[derive(Debug, Clone)]
pub struct Prop {
    /// Dotted key path: `label.wrap` → ["label", "wrap"].
    pub key: Vec<Ident>,
    pub value: Value,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub enum Value {
    Str(StrLit),
    Num(f64, Span),
    /// Bare word: `top`, `fill`, `orthogonal`, `true`, ...
    Word(Ident),
    /// `@accent`
    ThemeToken(Ident),
    /// `#1a2b3c`
    Color(String, Span),
}

impl Value {
    pub fn span(&self) -> Span {
        match self {
            Value::Str(s) => s.span,
            Value::Num(_, s) => *s,
            Value::Word(i) | Value::ThemeToken(i) => i.span,
            Value::Color(_, s) => *s,
        }
    }
}

#[derive(Debug, Clone)]
pub struct Node {
    /// `cpu { ... }` → Some(cpu); bare instantiation `gpu(i)` → None.
    pub name: Option<Ident>,
    pub kind: NodeKind,
}

#[derive(Debug, Clone)]
pub enum NodeKind {
    /// `cpu { ... }` or bare `cpu` (empty body).
    Plain { body: Block },
    /// `gpus: row { ... }` / `cores: grid 2x4 { ... }`
    Container {
        ctype: ContainerType,
        ctype_span: Span,
        body: Block,
    },
    /// `gpu(i)` or `g0: gpu(0) { overrides }`
    Call {
        callee: Ident,
        args: Vec<Expr>,
        body: Option<Block>,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContainerType {
    Row,
    Column,
    Grid { cols: u32, rows: u32 },
}

#[derive(Debug, Clone)]
pub struct Edge {
    pub from: PathRef,
    pub op: EdgeOp,
    pub op_span: Span,
    pub to: PathRef,
    pub label: Option<StrLit>,
    pub props: Option<Block>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EdgeOp {
    /// `->`
    Forward,
    /// `<->`
    Bidirectional,
}

/// `host.cpu`, `gpus[i].pcie`, `gpus[*]`
#[derive(Debug, Clone)]
pub struct PathRef {
    pub segments: Vec<PathSeg>,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub enum PathSeg {
    Name(Ident),
    Index(Expr),
    /// `[*]` — every child; valid only in constraint arguments.
    Wildcard(Span),
}

impl PathRef {
    /// Render back to source-ish text for use in error messages.
    pub fn display(&self) -> String {
        let mut out = String::new();
        for (i, seg) in self.segments.iter().enumerate() {
            match seg {
                PathSeg::Name(id) => {
                    if i > 0 {
                        out.push('.');
                    }
                    out.push_str(&id.name);
                }
                PathSeg::Index(_) => out.push_str("[..]"),
                PathSeg::Wildcard(_) => out.push_str("[*]"),
            }
        }
        out
    }
}

/// String literal, possibly interpolated: `"GPU {i}"`.
#[derive(Debug, Clone)]
pub struct StrLit {
    pub parts: Vec<StrPart>,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub enum StrPart {
    Text(String),
    Expr(Expr),
}

impl StrLit {
    /// The literal text if the string has no interpolation.
    pub fn as_plain(&self) -> Option<String> {
        match self.parts.as_slice() {
            [] => Some(String::new()),
            [StrPart::Text(t)] => Some(t.clone()),
            _ => None,
        }
    }
}

#[derive(Debug, Clone)]
pub struct Expr {
    pub kind: ExprKind,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub enum ExprKind {
    Num(f64),
    Str(Box<StrLit>),
    Var(Ident),
    Unary(UnOp, Box<Expr>),
    Binary(BinOp, Box<Expr>, Box<Expr>),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnOp {
    Neg,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp {
    Add,
    Sub,
    Mul,
    Div,
    Mod,
}

impl BinOp {
    pub fn symbol(self) -> &'static str {
        match self {
            BinOp::Add => "+",
            BinOp::Sub => "-",
            BinOp::Mul => "*",
            BinOp::Div => "/",
            BinOp::Mod => "%",
        }
    }
}