Skip to main content

doge_compiler/ast/
mod.rs

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