Skip to main content

doge_compiler/ast/
mod.rs

1use crate::token::Span;
2
3mod analysis;
4mod dump;
5#[cfg(test)]
6mod tests;
7
8pub use analysis::{celled_locals, child_funcdefs, free_names};
9pub use dump::dump;
10
11/// A whole parsed script: a sequence of top-level statements (the terminating
12/// `wow` is consumed by the parser and not stored).
13#[derive(Debug, Clone, PartialEq)]
14pub struct Script {
15    pub stmts: Vec<Stmt>,
16}
17
18/// One formal parameter in a function header: its name and, optionally, a
19/// default value. A default is a literal (docs/SYNTAX.md §6), so it references no
20/// names and is safe to evaluate fresh at every call site.
21#[derive(Debug, Clone, PartialEq)]
22pub struct Param {
23    pub name: String,
24    pub default: Option<Expr>,
25    pub span: Span,
26}
27
28/// A function header's parameters: the fixed/defaulted positional parameters in
29/// order, plus an optional trailing variadic parameter (`many rest`) that
30/// collects the surplus arguments into a List. Required parameters come first,
31/// then defaulted ones, then the variadic — the parser enforces that order.
32#[derive(Debug, Clone, PartialEq, Default)]
33pub struct Params {
34    pub params: Vec<Param>,
35    pub vararg: Option<String>,
36}
37
38impl Params {
39    /// True when the header declares no parameters at all.
40    pub fn is_empty(&self) -> bool {
41        self.params.is_empty() && self.vararg.is_none()
42    }
43
44    /// Whether a trailing `many rest` variadic parameter is present.
45    pub fn has_vararg(&self) -> bool {
46        self.vararg.is_some()
47    }
48
49    /// The number of leading parameters with no default — the minimum a call must
50    /// supply.
51    pub fn required(&self) -> usize {
52        self.params.iter().filter(|p| p.default.is_none()).count()
53    }
54
55    /// The largest number of positional arguments the header can bind without a
56    /// variadic parameter: every named parameter. `None` when a variadic is
57    /// present (unbounded).
58    pub fn max_positional(&self) -> Option<usize> {
59        if self.has_vararg() {
60            None
61        } else {
62            Some(self.params.len())
63        }
64    }
65
66    /// The names the body binds: every parameter, then the variadic (which
67    /// arrives as an already-packed List). This is what the compiled wrapper
68    /// takes as value parameters.
69    pub fn binding_names(&self) -> Vec<String> {
70        let mut names: Vec<String> = self.params.iter().map(|p| p.name.clone()).collect();
71        if let Some(rest) = &self.vararg {
72            names.push(rest.clone());
73        }
74        names
75    }
76
77    /// The call shape shown in an arity hint, e.g. `greet(name, mood = …, many rest)`.
78    pub fn render(&self, callee: &str) -> String {
79        let mut parts: Vec<String> = self
80            .params
81            .iter()
82            .map(|p| {
83                if p.default.is_some() {
84                    format!("{} = …", p.name)
85                } else {
86                    p.name.clone()
87                }
88            })
89            .collect();
90        if let Some(rest) = &self.vararg {
91            parts.push(format!("many {rest}"));
92        }
93        format!("{callee}({})", parts.join(", "))
94    }
95}
96
97/// One statement. Variants mirror the docs/GRAMMAR.md grammar.
98// `ExprStmt` is the conventional AST name for a bare-expression statement; the
99// suffix intentionally echoes the enum name, so silence that one style lint.
100#[allow(clippy::enum_variant_names)]
101#[derive(Debug, Clone, PartialEq)]
102pub enum Stmt {
103    /// `such x = e`, or a destructuring `such a, b = e` / `such a, many rest = e`.
104    /// `names` are the leading targets in order; `rest`, when present, is a
105    /// trailing `many` collector that gathers the surplus into a List. A plain
106    /// single declaration has one name and no `rest`.
107    Decl {
108        names: Vec<String>,
109        rest: Option<String>,
110        expr: Expr,
111        span: Span,
112    },
113    /// `so X = e`
114    ConstDecl {
115        name: String,
116        expr: Expr,
117        span: Span,
118    },
119    /// `so math`, or the string-path form `so "lib/utils.doge"`. `module` is the
120    /// binding name (the stem for a path import); `path` is the raw string of a
121    /// path import, `None` for a bare-name import.
122    Import {
123        module: String,
124        path: Option<String>,
125        span: Span,
126    },
127    /// `[very] target = e`, or a destructuring `[very] a, b = e` /
128    /// `[very] a, many rest = e` — `flavored` is true when written with `very`.
129    /// `targets` are the leading assignable targets in order; `rest`, when
130    /// present, is a trailing `many` collector target that gathers the surplus
131    /// into a List. `op` is `Some` for an augmented assignment (`target op= e`),
132    /// which reads the target, applies the binary operator, and stores the result
133    /// back — augmented assignment is single-target only (one `targets` entry, no
134    /// `rest`).
135    Assign {
136        targets: Vec<Expr>,
137        rest: Option<Expr>,
138        expr: Expr,
139        op: Option<BinOp>,
140        flavored: bool,
141        span: Span,
142    },
143    /// `bark e`
144    Bark { expr: Expr, span: Span },
145    /// `if / elif / else`. Each branch is (condition, body); `else_body` is the
146    /// optional trailing `else` block.
147    If {
148        branches: Vec<(Expr, Vec<Stmt>)>,
149        else_body: Option<Vec<Stmt>>,
150        span: Span,
151    },
152    /// `for v in iter:`, or a destructuring `for k, v in iter:` /
153    /// `for first, many rest in iter:`. `vars` are the leading loop variables in
154    /// order; `rest`, when present, is a trailing `many` collector that gathers
155    /// each element's surplus into a List. A plain loop has one var and no `rest`.
156    For {
157        vars: Vec<String>,
158        rest: Option<String>,
159        iter: Expr,
160        body: Vec<Stmt>,
161        span: Span,
162    },
163    /// `while cond:`
164    While {
165        cond: Expr,
166        body: Vec<Stmt>,
167        span: Span,
168    },
169    /// `such name much params: … wow`
170    FuncDef {
171        name: String,
172        params: Params,
173        body: Vec<Stmt>,
174        span: Span,
175    },
176    /// `many Name [much Parent]: … wow` — a body of function definitions
177    /// (methods). `parent` names the class it inherits from, when one is given.
178    ObjDef {
179        name: String,
180        parent: Option<String>,
181        methods: Vec<Stmt>,
182        span: Span,
183    },
184    /// `pls … oh no err! …`
185    Try {
186        body: Vec<Stmt>,
187        err_name: String,
188        handler: Vec<Stmt>,
189        span: Span,
190    },
191    /// `return [e]`
192    Return { expr: Option<Expr>, span: Span },
193    /// `bonk e` — raise a catchable error whose message is `e`'s display form.
194    Bonk { expr: Expr, span: Span },
195    /// `amaze cond [, message]` — assert: a no-op when `cond` is truthy, else a
196    /// catchable `AssertError` whose message is `message`'s display form (or a
197    /// default when omitted). The message is evaluated only on failure.
198    Amaze {
199        cond: Expr,
200        message: Option<Expr>,
201        span: Span,
202    },
203    /// `bork`
204    Bork { span: Span },
205    /// `continue`
206    Continue { span: Span },
207    /// A bare expression used as a statement, e.g. a call `greet(x)`.
208    ExprStmt { expr: Expr },
209}
210
211/// One expression.
212#[derive(Debug, Clone, PartialEq)]
213pub enum Expr {
214    Int {
215        value: i64,
216        span: Span,
217    },
218    Float {
219        value: f64,
220        span: Span,
221    },
222    Str {
223        value: String,
224        span: Span,
225    },
226    Bool {
227        value: bool,
228        span: Span,
229    },
230    None {
231        span: Span,
232    },
233    Ident {
234        name: String,
235        span: Span,
236    },
237    List {
238        items: Vec<Expr>,
239        span: Span,
240    },
241    Dict {
242        entries: Vec<(Expr, Expr)>,
243        span: Span,
244    },
245    Binary {
246        op: BinOp,
247        lhs: Box<Expr>,
248        rhs: Box<Expr>,
249        span: Span,
250    },
251    Unary {
252        op: UnOp,
253        operand: Box<Expr>,
254        span: Span,
255    },
256    /// `f(a, b, key = c)` — positional `args`, then any keyword arguments as
257    /// `(name, value)` pairs in source order. Keyword arguments are only accepted
258    /// where the callee is known at compile time (docs/SYNTAX.md §6).
259    Call {
260        callee: Box<Expr>,
261        args: Vec<Expr>,
262        kwargs: Vec<(String, Expr)>,
263        span: Span,
264    },
265    Index {
266        obj: Box<Expr>,
267        index: Box<Expr>,
268        span: Span,
269    },
270    /// `obj[start:end:step]` — each bound is optional (`None` means the default
271    /// end of that side), matching Python slice semantics.
272    Slice {
273        obj: Box<Expr>,
274        start: Option<Box<Expr>>,
275        end: Option<Box<Expr>>,
276        step: Option<Box<Expr>>,
277        span: Span,
278    },
279    /// `then if cond else otherwise` — only the taken branch is evaluated.
280    Ternary {
281        cond: Box<Expr>,
282        then: Box<Expr>,
283        otherwise: Box<Expr>,
284        span: Span,
285    },
286    Attr {
287        obj: Box<Expr>,
288        name: String,
289        span: Span,
290    },
291    /// `super.method(args)` — call a method inherited from the enclosing class's
292    /// parent, resolved statically. Only valid inside a method of a class that has
293    /// a parent (docs/SYNTAX.md §8).
294    SuperCall {
295        method: String,
296        args: Vec<Expr>,
297        span: Span,
298    },
299    StrInterp {
300        parts: Vec<InterpPart>,
301        span: Span,
302    },
303}
304
305/// One piece of a string-interpolation expression (`"a {b} c"`): literal text or
306/// an embedded expression whose display form is spliced in at runtime.
307#[derive(Debug, Clone, PartialEq)]
308pub enum InterpPart {
309    Lit(String),
310    Expr(Expr),
311}
312
313/// Binary operators, in the spelling they print with in the dump.
314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
315pub enum BinOp {
316    Add,
317    Sub,
318    Mul,
319    Div,
320    FloorDiv,
321    Rem,
322    Pow,
323    BitAnd,
324    BitOr,
325    BitXor,
326    Shl,
327    Shr,
328    Eq,
329    NotEq,
330    Lt,
331    LtEq,
332    Gt,
333    GtEq,
334    In,
335    NotIn,
336    And,
337    Or,
338}
339
340impl BinOp {
341    pub fn symbol(self) -> &'static str {
342        match self {
343            BinOp::Add => "+",
344            BinOp::Sub => "-",
345            BinOp::Mul => "*",
346            BinOp::Div => "/",
347            BinOp::FloorDiv => "//",
348            BinOp::Rem => "%",
349            BinOp::Pow => "**",
350            BinOp::BitAnd => "&",
351            BinOp::BitOr => "|",
352            BinOp::BitXor => "^",
353            BinOp::Shl => "<<",
354            BinOp::Shr => ">>",
355            BinOp::Eq => "==",
356            BinOp::NotEq => "!=",
357            BinOp::Lt => "<",
358            BinOp::LtEq => "<=",
359            BinOp::Gt => ">",
360            BinOp::GtEq => ">=",
361            BinOp::In => "in",
362            BinOp::NotIn => "not in",
363            BinOp::And => "and",
364            BinOp::Or => "or",
365        }
366    }
367}
368
369/// Unary operators: `not` (logical) and `neg` (numeric minus).
370#[derive(Debug, Clone, Copy, PartialEq, Eq)]
371pub enum UnOp {
372    Not,
373    Neg,
374    BitNot,
375}
376
377impl UnOp {
378    pub fn symbol(self) -> &'static str {
379        match self {
380            UnOp::Not => "not",
381            UnOp::Neg => "neg",
382            UnOp::BitNot => "~",
383        }
384    }
385}
386
387impl Stmt {
388    /// The source span this statement starts at.
389    pub fn span(&self) -> Span {
390        match self {
391            Stmt::Decl { span, .. }
392            | Stmt::ConstDecl { span, .. }
393            | Stmt::Import { span, .. }
394            | Stmt::Assign { span, .. }
395            | Stmt::Bark { span, .. }
396            | Stmt::If { span, .. }
397            | Stmt::For { span, .. }
398            | Stmt::While { span, .. }
399            | Stmt::FuncDef { span, .. }
400            | Stmt::ObjDef { span, .. }
401            | Stmt::Try { span, .. }
402            | Stmt::Return { span, .. }
403            | Stmt::Bonk { span, .. }
404            | Stmt::Amaze { span, .. }
405            | Stmt::Bork { span }
406            | Stmt::Continue { span } => *span,
407            Stmt::ExprStmt { expr } => expr.span(),
408        }
409    }
410}
411
412impl Expr {
413    /// The source span this expression starts at.
414    pub fn span(&self) -> Span {
415        match self {
416            Expr::Int { span, .. }
417            | Expr::Float { span, .. }
418            | Expr::Str { span, .. }
419            | Expr::Bool { span, .. }
420            | Expr::None { span }
421            | Expr::Ident { span, .. }
422            | Expr::List { span, .. }
423            | Expr::Dict { span, .. }
424            | Expr::Binary { span, .. }
425            | Expr::Unary { span, .. }
426            | Expr::Call { span, .. }
427            | Expr::Index { span, .. }
428            | Expr::Slice { span, .. }
429            | Expr::Ternary { span, .. }
430            | Expr::Attr { span, .. }
431            | Expr::SuperCall { span, .. }
432            | Expr::StrInterp { span, .. } => *span,
433        }
434    }
435}
436
437/// Call `f` with each statement block a statement contributes to its *enclosing*
438/// scope: an `if`'s branch and `else` bodies, a `for`/`while` body, and a
439/// `pls`/`oh no` body and handler. A nested function's or object's own body is
440/// deliberately not visited — those statements belong to their own scope, not
441/// this one. This match is exhaustive with no wildcard, so a new block-carrying
442/// statement cannot be silently skipped by the scope collectors and capture
443/// analysis that fold over it.
444pub fn for_each_child_block<'a>(stmt: &'a Stmt, f: &mut impl FnMut(&'a [Stmt])) {
445    match stmt {
446        Stmt::If {
447            branches,
448            else_body,
449            ..
450        } => {
451            for (_, body) in branches {
452                f(body);
453            }
454            if let Some(body) = else_body {
455                f(body);
456            }
457        }
458        Stmt::For { body, .. } | Stmt::While { body, .. } => f(body),
459        Stmt::Try { body, handler, .. } => {
460            f(body);
461            f(handler);
462        }
463        Stmt::Decl { .. }
464        | Stmt::ConstDecl { .. }
465        | Stmt::Import { .. }
466        | Stmt::Assign { .. }
467        | Stmt::Bark { .. }
468        | Stmt::FuncDef { .. }
469        | Stmt::ObjDef { .. }
470        | Stmt::Return { .. }
471        | Stmt::Bonk { .. }
472        | Stmt::Amaze { .. }
473        | Stmt::Bork { .. }
474        | Stmt::Continue { .. }
475        | Stmt::ExprStmt { .. } => {}
476    }
477}
478
479/// Every bound name in one scope — `such`/`so` declarations, `for` loop
480/// variables, `oh no` error names, and nested function definitions — in
481/// first-seen order, each once. These become the scope's `Env` fields or hoisted
482/// locals. A nested function's own body is not descended into: its inner names
483/// belong to its own scope.
484pub fn hoisted_names(stmts: &[Stmt]) -> Vec<String> {
485    let mut names = Vec::new();
486    collect_hoisted(stmts, &mut names);
487    names
488}
489
490/// The top-level hoisted names for `Env` fields: like [`hoisted_names`] but a
491/// direct top-level `such name:` / `many Name:` is a static definition, not an
492/// `Env` field, so those direct definitions are skipped (a function nested inside
493/// a top-level `if`/`for` block is still a closure and does get a field).
494pub(crate) fn toplevel_hoisted(stmts: &[Stmt]) -> Vec<String> {
495    let mut names = Vec::new();
496    for stmt in stmts {
497        if matches!(stmt, Stmt::FuncDef { .. } | Stmt::ObjDef { .. }) {
498            continue;
499        }
500        collect_hoisted(std::slice::from_ref(stmt), &mut names);
501    }
502    names
503}
504
505pub(crate) fn collect_hoisted(stmts: &[Stmt], names: &mut Vec<String>) {
506    for stmt in stmts {
507        match stmt {
508            // A nested function binds its name in this scope; its body is its own.
509            Stmt::ConstDecl { name, .. } | Stmt::FuncDef { name, .. } => push_unique(names, name),
510            Stmt::Decl {
511                names: decl_names,
512                rest,
513                ..
514            } => {
515                for name in decl_names {
516                    push_unique(names, name);
517                }
518                if let Some(rest) = rest {
519                    push_unique(names, rest);
520                }
521            }
522            Stmt::For { vars, rest, .. } => {
523                for var in vars {
524                    push_unique(names, var);
525                }
526                if let Some(rest) = rest {
527                    push_unique(names, rest);
528                }
529            }
530            Stmt::Try { err_name, .. } => push_unique(names, err_name),
531            _ => {}
532        }
533        for_each_child_block(stmt, &mut |body| collect_hoisted(body, names));
534    }
535}
536
537pub(crate) fn push_unique(names: &mut Vec<String>, name: &str) {
538    if !names.iter().any(|n| n == name) {
539        names.push(name.to_string());
540    }
541}