Skip to main content

drawlang_syntax/
ast.rs

1//! AST. Every node carries its span; statements carry their leading and
2//! trailing comments so the formatter can reproduce them.
3
4use crate::span::Span;
5
6#[derive(Debug, Clone)]
7pub struct File {
8    /// `drawl 0.1` header.
9    pub header: Option<Header>,
10    pub stmts: Vec<Stmt>,
11}
12
13#[derive(Debug, Clone)]
14pub struct Header {
15    pub version: String,
16    pub span: Span,
17}
18
19#[derive(Debug, Clone, PartialEq)]
20pub struct Ident {
21    pub name: String,
22    pub span: Span,
23}
24
25/// Comments attached to a statement: full-line comments above it, plus an
26/// optional same-line trailing comment.
27#[derive(Debug, Clone, Default)]
28pub struct Trivia {
29    pub leading: Vec<String>,
30    pub trailing: Option<String>,
31    /// Number of blank lines before this statement (capped at 1 by fmt).
32    pub blank_before: bool,
33}
34
35#[derive(Debug, Clone)]
36pub struct Stmt {
37    pub kind: StmtKind,
38    pub span: Span,
39    pub trivia: Trivia,
40}
41
42#[derive(Debug, Clone)]
43pub enum StmtKind {
44    /// `canvas { ... }`
45    Canvas(Block),
46    /// `def name(a, b) { ... }`
47    Def(Def),
48    /// `group id "Label" { ... }`
49    Group(Group),
50    /// `class name { ... }`
51    Class(Class),
52    /// `constrain { ... }`
53    Constrain(Vec<Constraint>),
54    /// `pin path at (x, y)`
55    Pin(Pin),
56    /// `for i in 0..4 { ... }`
57    For(For),
58    /// `port id { ... }` — only meaningful inside a node/def body.
59    Port(Port),
60    /// `key: value` / `label.wrap: true`
61    Prop(Prop),
62    /// Node declaration, container, or component instantiation.
63    Node(Node),
64    /// `a.b -> c.d : "label" { props }`
65    Edge(Edge),
66}
67
68#[derive(Debug, Clone)]
69pub struct Block {
70    pub stmts: Vec<Stmt>,
71    pub span: Span,
72}
73
74#[derive(Debug, Clone)]
75pub struct Def {
76    pub name: Ident,
77    pub params: Vec<Ident>,
78    pub body: Block,
79}
80
81#[derive(Debug, Clone)]
82pub struct Group {
83    pub name: Ident,
84    pub label: Option<StrLit>,
85    pub body: Block,
86}
87
88#[derive(Debug, Clone)]
89pub struct Class {
90    pub name: Ident,
91    pub body: Block,
92}
93
94#[derive(Debug, Clone)]
95pub struct Constraint {
96    /// `align`, `gap`, `below`, `left-of`, ...
97    pub name: Ident,
98    pub args: Vec<ConstraintArg>,
99    pub span: Span,
100    pub trivia: Trivia,
101}
102
103#[derive(Debug, Clone)]
104pub enum ConstraintArg {
105    /// Element path; a bare keyword like `top` is a one-segment path,
106    /// disambiguated during semantic analysis.
107    Path(PathRef),
108    Num(f64, Span),
109}
110
111impl ConstraintArg {
112    pub fn span(&self) -> Span {
113        match self {
114            ConstraintArg::Path(p) => p.span,
115            ConstraintArg::Num(_, s) => *s,
116        }
117    }
118}
119
120#[derive(Debug, Clone)]
121pub struct Pin {
122    pub target: PathRef,
123    pub x: Expr,
124    pub y: Expr,
125}
126
127#[derive(Debug, Clone)]
128pub struct For {
129    pub var: Ident,
130    pub start: Expr,
131    pub end: Expr,
132    pub body: Block,
133}
134
135#[derive(Debug, Clone)]
136pub struct Port {
137    pub name: Ident,
138    pub body: Block,
139}
140
141#[derive(Debug, Clone)]
142pub struct Prop {
143    /// Dotted key path: `label.wrap` → ["label", "wrap"].
144    pub key: Vec<Ident>,
145    pub value: Value,
146    pub span: Span,
147}
148
149#[derive(Debug, Clone)]
150pub enum Value {
151    Str(StrLit),
152    Num(f64, Span),
153    /// Bare word: `top`, `fill`, `orthogonal`, `true`, ...
154    Word(Ident),
155    /// `@accent`
156    ThemeToken(Ident),
157    /// `#1a2b3c`
158    Color(String, Span),
159}
160
161impl Value {
162    pub fn span(&self) -> Span {
163        match self {
164            Value::Str(s) => s.span,
165            Value::Num(_, s) => *s,
166            Value::Word(i) | Value::ThemeToken(i) => i.span,
167            Value::Color(_, s) => *s,
168        }
169    }
170}
171
172#[derive(Debug, Clone)]
173pub struct Node {
174    /// `cpu { ... }` → Some(cpu); bare instantiation `gpu(i)` → None.
175    pub name: Option<Ident>,
176    pub kind: NodeKind,
177}
178
179#[derive(Debug, Clone)]
180pub enum NodeKind {
181    /// `cpu { ... }` or bare `cpu` (empty body).
182    Plain { body: Block },
183    /// `gpus: row { ... }` / `cores: grid 2x4 { ... }`
184    Container {
185        ctype: ContainerType,
186        ctype_span: Span,
187        body: Block,
188    },
189    /// `gpu(i)` or `g0: gpu(0) { overrides }`
190    Call {
191        callee: Ident,
192        args: Vec<Expr>,
193        body: Option<Block>,
194    },
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub enum ContainerType {
199    Row,
200    Column,
201    Grid { cols: u32, rows: u32 },
202}
203
204#[derive(Debug, Clone)]
205pub struct Edge {
206    pub from: PathRef,
207    pub op: EdgeOp,
208    pub op_span: Span,
209    pub to: PathRef,
210    pub label: Option<StrLit>,
211    pub props: Option<Block>,
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215pub enum EdgeOp {
216    /// `->`
217    Forward,
218    /// `<->`
219    Bidirectional,
220}
221
222/// `host.cpu`, `gpus[i].pcie`, `gpus[*]`
223#[derive(Debug, Clone)]
224pub struct PathRef {
225    pub segments: Vec<PathSeg>,
226    pub span: Span,
227}
228
229#[derive(Debug, Clone)]
230pub enum PathSeg {
231    Name(Ident),
232    Index(Expr),
233    /// `[*]` — every child; valid only in constraint arguments.
234    Wildcard(Span),
235}
236
237impl PathRef {
238    /// Render back to source-ish text for use in error messages.
239    pub fn display(&self) -> String {
240        let mut out = String::new();
241        for (i, seg) in self.segments.iter().enumerate() {
242            match seg {
243                PathSeg::Name(id) => {
244                    if i > 0 {
245                        out.push('.');
246                    }
247                    out.push_str(&id.name);
248                }
249                PathSeg::Index(_) => out.push_str("[..]"),
250                PathSeg::Wildcard(_) => out.push_str("[*]"),
251            }
252        }
253        out
254    }
255}
256
257/// String literal, possibly interpolated: `"GPU {i}"`.
258#[derive(Debug, Clone)]
259pub struct StrLit {
260    pub parts: Vec<StrPart>,
261    pub span: Span,
262}
263
264#[derive(Debug, Clone)]
265pub enum StrPart {
266    Text(String),
267    Expr(Expr),
268}
269
270impl StrLit {
271    /// The literal text if the string has no interpolation.
272    pub fn as_plain(&self) -> Option<String> {
273        match self.parts.as_slice() {
274            [] => Some(String::new()),
275            [StrPart::Text(t)] => Some(t.clone()),
276            _ => None,
277        }
278    }
279}
280
281#[derive(Debug, Clone)]
282pub struct Expr {
283    pub kind: ExprKind,
284    pub span: Span,
285}
286
287#[derive(Debug, Clone)]
288pub enum ExprKind {
289    Num(f64),
290    Str(Box<StrLit>),
291    Var(Ident),
292    Unary(UnOp, Box<Expr>),
293    Binary(BinOp, Box<Expr>, Box<Expr>),
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub enum UnOp {
298    Neg,
299}
300
301#[derive(Debug, Clone, Copy, PartialEq, Eq)]
302pub enum BinOp {
303    Add,
304    Sub,
305    Mul,
306    Div,
307    Mod,
308}
309
310impl BinOp {
311    pub fn symbol(self) -> &'static str {
312        match self {
313            BinOp::Add => "+",
314            BinOp::Sub => "-",
315            BinOp::Mul => "*",
316            BinOp::Div => "/",
317            BinOp::Mod => "%",
318        }
319    }
320}