Skip to main content

maya_mel/
ast.rs

1#![forbid(unsafe_code)]
2//! Typed AST shapes used by the parser and semantic layers.
3//!
4//! Most users reach these types through [`crate::Parse::syntax`] or the
5//! `syntax` field on parse results.
6
7use mel_syntax::{TextRange, text_slice};
8
9#[derive(Debug, Clone, PartialEq, Eq, Default)]
10pub struct SourceFile {
11    pub items: Vec<Item>,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum Item {
16    Proc(Box<ProcDef>),
17    Stmt(Box<Stmt>),
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct ProcDef {
22    pub return_type: Option<ProcReturnType>,
23    pub name_range: TextRange,
24    pub params: Vec<ProcParam>,
25    pub body: Stmt,
26    pub is_global: bool,
27    pub range: TextRange,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum Stmt {
32    Empty {
33        range: TextRange,
34    },
35    Proc {
36        proc_def: Box<ProcDef>,
37        range: TextRange,
38    },
39    Block {
40        statements: Vec<Stmt>,
41        range: TextRange,
42    },
43    Expr {
44        expr: Expr,
45        range: TextRange,
46    },
47    VarDecl {
48        decl: VarDecl,
49        range: TextRange,
50    },
51    If {
52        condition: Box<Expr>,
53        then_branch: Box<Stmt>,
54        else_branch: Option<Box<Stmt>>,
55        range: TextRange,
56    },
57    While {
58        condition: Box<Expr>,
59        body: Box<Stmt>,
60        range: TextRange,
61    },
62    DoWhile {
63        body: Box<Stmt>,
64        condition: Box<Expr>,
65        range: TextRange,
66    },
67    Switch {
68        control: Box<Expr>,
69        clauses: Vec<SwitchClause>,
70        range: TextRange,
71    },
72    For {
73        init: Option<Vec<Expr>>,
74        condition: Option<Box<Expr>>,
75        update: Option<Vec<Expr>>,
76        body: Box<Stmt>,
77        range: TextRange,
78    },
79    ForIn {
80        binding: Box<Expr>,
81        iterable: Box<Expr>,
82        body: Box<Stmt>,
83        range: TextRange,
84    },
85    Return {
86        expr: Option<Expr>,
87        range: TextRange,
88    },
89    Break {
90        range: TextRange,
91    },
92    Continue {
93        range: TextRange,
94    },
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub enum Expr {
99    Ident {
100        name_range: TextRange,
101        range: TextRange,
102    },
103    BareWord {
104        text: TextRange,
105        range: TextRange,
106    },
107    Int {
108        value: i64,
109        range: TextRange,
110    },
111    Float {
112        text: TextRange,
113        range: TextRange,
114    },
115    String {
116        text: TextRange,
117        range: TextRange,
118    },
119    Cast {
120        ty: TypeName,
121        expr: Box<Expr>,
122        range: TextRange,
123    },
124    VectorLiteral {
125        elements: Vec<Expr>,
126        range: TextRange,
127    },
128    ArrayLiteral {
129        elements: Vec<Expr>,
130        range: TextRange,
131    },
132    Unary {
133        op: UnaryOp,
134        expr: Box<Expr>,
135        range: TextRange,
136    },
137    Binary {
138        op: BinaryOp,
139        lhs: Box<Expr>,
140        rhs: Box<Expr>,
141        range: TextRange,
142    },
143    Ternary {
144        condition: Box<Expr>,
145        then_expr: Box<Expr>,
146        else_expr: Box<Expr>,
147        range: TextRange,
148    },
149    Index {
150        target: Box<Expr>,
151        index: Box<Expr>,
152        range: TextRange,
153    },
154    MemberAccess {
155        target: Box<Expr>,
156        member: TextRange,
157        range: TextRange,
158    },
159    ComponentAccess {
160        target: Box<Expr>,
161        component: VectorComponent,
162        range: TextRange,
163    },
164    Assign {
165        op: AssignOp,
166        lhs: Box<Expr>,
167        rhs: Box<Expr>,
168        range: TextRange,
169    },
170    PrefixUpdate {
171        op: UpdateOp,
172        expr: Box<Expr>,
173        range: TextRange,
174    },
175    PostfixUpdate {
176        op: UpdateOp,
177        expr: Box<Expr>,
178        range: TextRange,
179    },
180    Invoke(Box<InvokeExpr>),
181}
182
183impl Expr {
184    #[must_use]
185    pub const fn range(&self) -> TextRange {
186        match self {
187            Self::Ident { range, .. }
188            | Self::BareWord { range, .. }
189            | Self::Int { range, .. }
190            | Self::Float { range, .. }
191            | Self::String { range, .. }
192            | Self::Cast { range, .. }
193            | Self::VectorLiteral { range, .. }
194            | Self::ArrayLiteral { range, .. }
195            | Self::Unary { range, .. }
196            | Self::Binary { range, .. }
197            | Self::Ternary { range, .. }
198            | Self::Index { range, .. }
199            | Self::MemberAccess { range, .. }
200            | Self::ComponentAccess { range, .. }
201            | Self::Assign { range, .. }
202            | Self::PrefixUpdate { range, .. }
203            | Self::PostfixUpdate { range, .. } => *range,
204            Self::Invoke(invoke) => invoke.range,
205        }
206    }
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct VarDecl {
211    pub is_global: bool,
212    pub ty: TypeName,
213    pub declarators: Vec<Declarator>,
214    pub range: TextRange,
215}
216
217#[derive(Debug, Clone, PartialEq, Eq)]
218pub struct ProcParam {
219    pub ty: TypeName,
220    pub name_range: TextRange,
221    pub is_array: bool,
222    pub range: TextRange,
223}
224
225#[derive(Debug, Clone, PartialEq, Eq)]
226pub struct ProcReturnType {
227    pub ty: TypeName,
228    pub is_array: bool,
229    pub range: TextRange,
230}
231
232#[derive(Debug, Clone, PartialEq, Eq)]
233pub struct SwitchClause {
234    pub label: SwitchLabel,
235    pub statements: Vec<Stmt>,
236    pub range: TextRange,
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
240pub enum SwitchLabel {
241    Case(Expr),
242    Default { range: TextRange },
243}
244
245#[derive(Debug, Clone, PartialEq, Eq)]
246pub enum TypeName {
247    Int,
248    Float,
249    String,
250    Vector,
251    Matrix,
252}
253
254#[derive(Debug, Clone, PartialEq, Eq)]
255pub struct Declarator {
256    pub name_range: TextRange,
257    pub array_size: Option<Option<Expr>>,
258    pub initializer: Option<Expr>,
259    pub range: TextRange,
260}
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub enum VectorComponent {
264    X,
265    Y,
266    Z,
267}
268
269#[derive(Debug, Clone, PartialEq, Eq)]
270pub enum UnaryOp {
271    Not,
272    Negate,
273}
274
275#[derive(Debug, Clone, PartialEq, Eq)]
276pub enum BinaryOp {
277    Mul,
278    Div,
279    Rem,
280    Caret,
281    Add,
282    Sub,
283    Lt,
284    Le,
285    Gt,
286    Ge,
287    EqEq,
288    NotEq,
289    AndAnd,
290    OrOr,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq)]
294pub enum AssignOp {
295    Assign,
296    AddAssign,
297    SubAssign,
298    MulAssign,
299    DivAssign,
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
303pub enum UpdateOp {
304    Increment,
305    Decrement,
306}
307
308#[derive(Debug, Clone, PartialEq, Eq)]
309pub enum InvokeSurface {
310    Function {
311        head_range: TextRange,
312        args: Vec<Expr>,
313    },
314    ShellLike {
315        head_range: TextRange,
316        words: Vec<ShellWord>,
317        captured: bool,
318    },
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
322pub enum ShellWord {
323    Flag {
324        text: TextRange,
325        range: TextRange,
326    },
327    NumericLiteral {
328        text: TextRange,
329        range: TextRange,
330    },
331    BareWord {
332        text: TextRange,
333        range: TextRange,
334    },
335    QuotedString {
336        text: TextRange,
337        range: TextRange,
338    },
339    Variable {
340        expr: Box<Expr>,
341        range: TextRange,
342    },
343    GroupedExpr {
344        expr: Box<Expr>,
345        range: TextRange,
346    },
347    BraceList {
348        expr: Box<Expr>,
349        range: TextRange,
350    },
351    VectorLiteral {
352        expr: Box<Expr>,
353        range: TextRange,
354    },
355    Capture {
356        invoke: Box<InvokeExpr>,
357        range: TextRange,
358    },
359}
360
361#[derive(Debug, Clone, PartialEq, Eq)]
362pub struct InvokeExpr {
363    pub surface: InvokeSurface,
364    pub range: TextRange,
365}
366
367impl ProcDef {
368    #[must_use]
369    pub fn name_text<'a>(&self, source_text: &'a str) -> &'a str {
370        text_slice(source_text, self.name_range)
371    }
372}
373
374impl ProcParam {
375    #[must_use]
376    pub fn name_text<'a>(&self, source_text: &'a str) -> &'a str {
377        text_slice(source_text, self.name_range)
378    }
379}
380
381impl Declarator {
382    #[must_use]
383    pub fn name_text<'a>(&self, source_text: &'a str) -> &'a str {
384        text_slice(source_text, self.name_range)
385    }
386}
387
388impl Expr {
389    #[must_use]
390    pub fn ident_text<'a>(&self, source_text: &'a str) -> Option<&'a str> {
391        match self {
392            Self::Ident { name_range, .. } => Some(text_slice(source_text, *name_range)),
393            _ => None,
394        }
395    }
396}
397
398impl InvokeSurface {
399    #[must_use]
400    pub fn head_text<'a>(&self, source_text: &'a str) -> Option<&'a str> {
401        match self {
402            Self::Function { head_range, .. } | Self::ShellLike { head_range, .. } => {
403                Some(text_slice(source_text, *head_range))
404            }
405        }
406    }
407}
408
409impl ShellWord {
410    #[must_use]
411    pub fn text_range(&self) -> Option<TextRange> {
412        match self {
413            Self::Flag { text, .. }
414            | Self::NumericLiteral { text, .. }
415            | Self::BareWord { text, .. }
416            | Self::QuotedString { text, .. } => Some(*text),
417            Self::Variable { .. }
418            | Self::GroupedExpr { .. }
419            | Self::BraceList { .. }
420            | Self::VectorLiteral { .. }
421            | Self::Capture { .. } => None,
422        }
423    }
424
425    #[must_use]
426    pub fn text<'a>(&'a self, source_text: &'a str) -> Option<&'a str> {
427        match self {
428            Self::Flag { text, .. }
429            | Self::NumericLiteral { text, .. }
430            | Self::BareWord { text, .. }
431            | Self::QuotedString { text, .. } => Some(text_slice(source_text, *text)),
432            Self::Variable { .. }
433            | Self::GroupedExpr { .. }
434            | Self::BraceList { .. }
435            | Self::VectorLiteral { .. }
436            | Self::Capture { .. } => None,
437        }
438    }
439}
440
441#[cfg(test)]
442mod tests {
443    use super::{Expr, InvokeExpr, InvokeSurface, ShellWord};
444    use mel_syntax::text_range;
445    use std::mem::size_of;
446
447    #[test]
448    fn unresolved_invoke_is_constructible() {
449        let invoke = InvokeExpr {
450            surface: InvokeSurface::ShellLike {
451                head_range: text_range(0, 2),
452                words: vec![
453                    ShellWord::NumericLiteral {
454                        text: text_range(3, 4),
455                        range: text_range(3, 4),
456                    },
457                    ShellWord::Variable {
458                        expr: Box::new(Expr::Ident {
459                            name_range: text_range(5, 11),
460                            range: text_range(5, 11),
461                        }),
462                        range: text_range(5, 11),
463                    },
464                ],
465                captured: true,
466            },
467            range: text_range(0, 12),
468        };
469
470        assert!(matches!(invoke.surface, InvokeSurface::ShellLike { .. }));
471        assert_eq!(invoke.range, text_range(0, 12));
472    }
473
474    #[test]
475    fn boxed_stmt_payloads_keep_ast_layout_compact() {
476        assert_eq!(size_of::<super::Stmt>(), 72);
477    }
478}