Skip to main content

daml_parser/
ast.rs

1//! Typed, lossless parse tree produced by the recursive-descent parser
2//! (src/parse.rs).
3//!
4//! Every node carries a source position and byte span. Downstream crates
5//! consume this tree directly: daml-fmt re-prints layout from the spans, and
6//! daml-lint lowers it onto its own rule-facing IR.
7
8pub use crate::lexer::Pos;
9
10/// Byte span of an AST node.
11///
12/// `[start, end)` into the original source, same basis as `Token::start`/
13/// `Token::end`. Covers every (non-virtual) token that belongs to the node —
14/// first token's `start` to last token's `end`.
15///
16/// Invariants the parser maintains (checked over the corpus by
17/// `render_from_ast`): a child's span is contained in its parent's span, and
18/// sibling spans are ordered and non-overlapping. Trivia (comments, blank
19/// lines) live *between* sibling spans.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub struct Span {
22    pub start: usize,
23    pub end: usize,
24}
25
26impl Span {
27    pub const fn new(start: usize, end: usize) -> Self {
28        Self { start, end }
29    }
30
31    /// True when the span is well-formed (`start <= end`).
32    pub const fn is_valid(&self) -> bool {
33        self.start <= self.end
34    }
35
36    /// True for a zero-width but still valid span.
37    pub const fn is_empty(&self) -> bool {
38        self.start == self.end
39    }
40
41    /// `self` fully contains `other`.
42    pub const fn contains(&self, other: &Self) -> bool {
43        self.is_valid() && other.is_valid() && self.start <= other.start && other.end <= self.end
44    }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48#[non_exhaustive]
49pub enum LitKind {
50    Int,
51    Decimal,
52    Text,
53    Char,
54}
55
56/// Side of an operator section.
57///
58/// `(+ 1)` stores `SectionSide::Right`, while `(1 +)` stores
59/// `SectionSide::Left`.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61#[non_exhaustive]
62pub enum SectionSide {
63    /// Right section: operator followed by right operand.
64    Right,
65    /// Left section: left operand followed by operator.
66    Left,
67}
68
69/// Import syntax style.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71#[non_exhaustive]
72pub enum ImportStyle {
73    /// Qualified import (`import qualified Foo.Bar`, `import Foo.Bar qualified`).
74    Qualified,
75    /// Unqualified import (`import Foo.Bar`, `import Foo.Bar as Baz`).
76    Unqualified,
77}
78
79#[derive(Debug, Clone)]
80pub struct FieldAssign {
81    pub name: String,
82    /// None for record puns (`Foo with owner` meaning `owner = owner`)
83    /// and `..` wildcards.
84    pub value: Option<Expr>,
85    pub pos: Pos,
86    pub span: Span,
87}
88
89#[derive(Debug, Clone)]
90pub struct Alt {
91    pub pat: Pat,
92    pub body: Expr,
93    pub pos: Pos,
94    pub span: Span,
95}
96
97#[derive(Debug, Clone)]
98pub struct Binding {
99    /// Left-hand side: a variable with parameters, or a destructuring pattern.
100    pub pat: Pat,
101    /// Parameter patterns when the LHS is a function binding (`f x y = ...`).
102    pub params: Vec<Pat>,
103    pub expr: Expr,
104    pub pos: Pos,
105    pub span: Span,
106}
107
108#[derive(Debug, Clone)]
109#[non_exhaustive]
110pub enum Pat {
111    Var {
112        name: String,
113        pos: Pos,
114        span: Span,
115    },
116    Wild {
117        pos: Pos,
118        span: Span,
119    },
120    Con {
121        qualifier: Option<String>,
122        name: String,
123        args: Vec<Self>,
124        pos: Pos,
125        span: Span,
126    },
127    Tuple {
128        items: Vec<Self>,
129        pos: Pos,
130        span: Span,
131    },
132    List {
133        items: Vec<Self>,
134        pos: Pos,
135        span: Span,
136    },
137    Lit {
138        kind: LitKind,
139        text: String,
140        pos: Pos,
141        span: Span,
142    },
143    /// `name@pat`
144    As {
145        name: String,
146        pat: Box<Self>,
147        pos: Pos,
148        span: Span,
149    },
150    /// Anything the parser couldn't classify; raw text preserved.
151    Other {
152        raw: String,
153        pos: Pos,
154        span: Span,
155    },
156}
157
158#[derive(Debug, Clone)]
159#[non_exhaustive]
160pub enum Expr {
161    /// Lowercase variable reference, possibly qualified.
162    Var {
163        qualifier: Option<String>,
164        name: String,
165        pos: Pos,
166        span: Span,
167    },
168    /// Constructor / data-constructor reference, possibly qualified.
169    Con {
170        qualifier: Option<String>,
171        name: String,
172        pos: Pos,
173        span: Span,
174    },
175    Lit {
176        kind: LitKind,
177        text: String,
178        pos: Pos,
179        span: Span,
180    },
181    /// Application, flattened: `f a b c` is one App with three args.
182    App {
183        func: Box<Self>,
184        args: Vec<Self>,
185        pos: Pos,
186        span: Span,
187    },
188    /// Binary operator application with source-level operator text.
189    BinOp {
190        op: String,
191        lhs: Box<Self>,
192        rhs: Box<Self>,
193        pos: Pos,
194        span: Span,
195    },
196    /// Unary negation.
197    Neg {
198        expr: Box<Self>,
199        pos: Pos,
200        span: Span,
201    },
202    Lambda {
203        params: Vec<Pat>,
204        body: Box<Self>,
205        pos: Pos,
206        span: Span,
207    },
208    If {
209        cond: Box<Self>,
210        then_branch: Box<Self>,
211        else_branch: Box<Self>,
212        pos: Pos,
213        span: Span,
214    },
215    Case {
216        scrutinee: Box<Self>,
217        alts: Vec<Alt>,
218        pos: Pos,
219        span: Span,
220    },
221    Do {
222        stmts: Vec<DoStmt>,
223        pos: Pos,
224        span: Span,
225    },
226    LetIn {
227        bindings: Vec<Binding>,
228        body: Box<Self>,
229        pos: Pos,
230        span: Span,
231    },
232    /// `base with f = e, ...` — record construction when base is a Con,
233    /// record update otherwise.
234    Record {
235        base: Box<Self>,
236        fields: Vec<FieldAssign>,
237        pos: Pos,
238        span: Span,
239    },
240    Tuple {
241        items: Vec<Self>,
242        pos: Pos,
243        span: Span,
244    },
245    List {
246        items: Vec<Self>,
247        pos: Pos,
248        span: Span,
249    },
250    /// `try <body> catch <alts>`
251    Try {
252        body: Box<Self>,
253        handlers: Vec<Alt>,
254        pos: Pos,
255        span: Span,
256    },
257    /// Right operator section like `(+ 1)` / left section `(1 +)`.
258    Section {
259        op: String,
260        operand: Option<Box<Self>>,
261        side: SectionSide,
262        pos: Pos,
263        span: Span,
264    },
265    /// Expression the parser could not understand; raw text preserved so
266    /// a parse failure degrades to the shim's behavior instead of dying.
267    Error { raw: String, pos: Pos, span: Span },
268}
269
270#[derive(Debug, Clone)]
271#[non_exhaustive]
272pub enum DoStmt {
273    /// `pat <- expr`
274    Bind {
275        pat: Pat,
276        expr: Expr,
277        pos: Pos,
278        span: Span,
279    },
280    /// `let x = e` (no `in`) inside a do block.
281    Let {
282        bindings: Vec<Binding>,
283        pos: Pos,
284        span: Span,
285    },
286    /// Bare expression statement.
287    Expr { expr: Expr, pos: Pos, span: Span },
288}
289
290/// Structured Daml type, parsed from the real token stream.
291///
292/// Scoped to the forms the corpus actually contains; it exists so consumers can
293/// tell a type *application* from a *function arrow* from an
294/// atomic constructor — a distinction a string matcher structurally cannot make.
295/// Every node carries a byte span so consumers can render exact source text from
296/// `(source, span)`.
297#[derive(Debug, Clone)]
298#[non_exhaustive]
299pub enum Type {
300    /// Type constructor, possibly qualified: `Party`, `DA.Map.Map`.
301    Con {
302        qualifier: Option<String>,
303        name: String,
304        span: Span,
305    },
306    /// Type application, head applied to one or more args: `ContractId Foo`,
307    /// `Map Text Int`, `Script ()`. Type-level nat literals (the `10` in
308    /// `Numeric 10`) are NOT types, so they are dropped from the arg list — a
309    /// `Numeric 10` collapses to the bare head `Con "Numeric"`.
310    App(Box<Self>, Vec<Self>, Span),
311    /// List type `[T]`.
312    List(Box<Self>, Span),
313    /// Tuple type `(a, b, ...)`.
314    Tuple(Vec<Self>, Span),
315    /// Function type `a -> b` (right-associative).
316    Fun(Box<Self>, Box<Self>, Span),
317    /// Lowercase type variable: `a`, `n`.
318    Var(String, Span),
319    /// The unit type `()`.
320    Unit(Span),
321    /// A constrained type `C a => T`: the context is not modeled, the body `T`
322    /// is kept.
323    Constrained(Box<Self>, Span),
324}
325
326impl Type {
327    pub const fn span(&self) -> Span {
328        match self {
329            Self::Con { span, .. }
330            | Self::App(_, _, span)
331            | Self::List(_, span)
332            | Self::Tuple(_, span)
333            | Self::Fun(_, _, span)
334            | Self::Var(_, span)
335            | Self::Unit(span)
336            | Self::Constrained(_, span) => *span,
337        }
338    }
339
340    pub(crate) const fn with_span(mut self, span: Span) -> Self {
341        match &mut self {
342            Self::Con { span: s, .. }
343            | Self::App(_, _, s)
344            | Self::List(_, s)
345            | Self::Tuple(_, s)
346            | Self::Fun(_, _, s)
347            | Self::Var(_, s)
348            | Self::Unit(s)
349            | Self::Constrained(_, s) => *s = span,
350        }
351        self
352    }
353}
354
355/// Type equality intentionally ignores source spans.
356///
357/// Spans describe where equivalent type syntax appeared in a source file; they
358/// are not part of the structural type identity used by parser consumers.
359impl PartialEq for Type {
360    fn eq(&self, other: &Self) -> bool {
361        match (self, other) {
362            (
363                Self::Con {
364                    qualifier: aq,
365                    name: an,
366                    ..
367                },
368                Self::Con {
369                    qualifier: bq,
370                    name: bn,
371                    ..
372                },
373            ) => aq == bq && an == bn,
374            (Self::App(ah, aa, _), Self::App(bh, ba, _)) => ah == bh && aa == ba,
375            (Self::List(a, _), Self::List(b, _))
376            | (Self::Constrained(a, _), Self::Constrained(b, _)) => a == b,
377            (Self::Tuple(a, _), Self::Tuple(b, _)) => a == b,
378            (Self::Fun(al, ar, _), Self::Fun(bl, br, _)) => al == bl && ar == br,
379            (Self::Var(a, _), Self::Var(b, _)) => a == b,
380            (Self::Unit(_), Self::Unit(_)) => true,
381            _ => false,
382        }
383    }
384}
385
386impl Eq for Type {}
387
388#[derive(Debug, Clone)]
389pub struct FieldDecl {
390    pub name: String,
391    /// Structured field type parsed from the token stream. `None` when the type
392    /// could not be parsed cleanly (analysis treats it as unknown).
393    pub ty: Option<Type>,
394    pub pos: Pos,
395    pub span: Span,
396}
397
398#[derive(Debug, Clone, Copy, PartialEq, Eq)]
399pub enum Consuming {
400    Consuming,
401    NonConsuming,
402    PreConsuming,
403    PostConsuming,
404}
405
406#[derive(Debug, Clone)]
407pub struct ChoiceDecl {
408    pub name: String,
409    pub consuming: Consuming,
410    /// Structured return type. `None` if it could not be parsed cleanly or the
411    /// choice declared no return type.
412    pub return_ty: Option<Type>,
413    pub params: Vec<FieldDecl>,
414    /// Comma-separated controller expressions.
415    pub controllers: Vec<Expr>,
416    /// Choice observers, if any.
417    pub observers: Vec<Expr>,
418    pub body: Option<Expr>,
419    pub pos: Pos,
420    pub span: Span,
421}
422
423#[derive(Debug, Clone)]
424pub enum TemplateBodyDecl {
425    Signatory {
426        parties: Vec<Expr>,
427        pos: Pos,
428        span: Span,
429    },
430    Observer {
431        parties: Vec<Expr>,
432        pos: Pos,
433        span: Span,
434    },
435    Ensure {
436        expr: Expr,
437        pos: Pos,
438        span: Span,
439    },
440    Key {
441        expr: Expr,
442        /// Structured key type. `None` if absent or not cleanly parseable.
443        ty: Option<Type>,
444        pos: Pos,
445        span: Span,
446    },
447    Maintainer {
448        expr: Expr,
449        pos: Pos,
450        span: Span,
451    },
452    Choice(ChoiceDecl),
453    InterfaceInstance(InterfaceInstanceDecl),
454    /// `agreement`, `let` blocks, deprecated `controller ... can`, etc.
455    Other {
456        raw: String,
457        pos: Pos,
458        span: Span,
459    },
460}
461
462#[derive(Debug, Clone)]
463pub struct InterfaceInstanceDecl {
464    /// Interface being implemented (`Disclosure.I`).
465    pub interface_name: String,
466    /// Template it is for (from `for Foo`); the enclosing template when
467    /// declared inside one.
468    pub for_template: String,
469    /// Method implementations: name → bound expression.
470    pub methods: Vec<Binding>,
471    pub pos: Pos,
472    pub span: Span,
473}
474
475#[derive(Debug, Clone)]
476pub struct TemplateDecl {
477    pub name: String,
478    pub fields: Vec<FieldDecl>,
479    pub body: Vec<TemplateBodyDecl>,
480    pub pos: Pos,
481    pub span: Span,
482}
483
484#[derive(Debug, Clone)]
485pub struct InterfaceDecl {
486    pub name: String,
487    /// Interfaces this interface requires (`requires Lockable.I, ...`).
488    pub requires: Vec<String>,
489    pub viewtype: Option<String>,
490    /// Method signatures: name and type text.
491    pub methods: Vec<FieldDecl>,
492    pub choices: Vec<ChoiceDecl>,
493    pub pos: Pos,
494    pub span: Span,
495}
496
497#[derive(Debug, Clone)]
498pub struct Equation {
499    pub params: Vec<Pat>,
500    pub body: Expr,
501    /// Guarded equations keep their guards as (guard, body) pairs; `body`
502    /// then holds the first guarded body for convenience.
503    pub guards: Vec<(Expr, Expr)>,
504    /// `where` helper bindings attached to this equation.
505    pub where_bindings: Vec<Binding>,
506    pub pos: Pos,
507    pub span: Span,
508}
509
510#[derive(Debug, Clone)]
511pub struct FunctionDecl {
512    pub name: String,
513    pub ty: Option<Type>,
514    pub equations: Vec<Equation>,
515    pub pos: Pos,
516    /// Span of the function's first appearance (signature or first equation).
517    /// Convenience anchor; a multi-equation function's precise ranges are the
518    /// per-`Equation` spans, since equations need not be contiguous in source.
519    pub span: Span,
520    /// Span of the standalone type signature `name : Type`, if one was seen.
521    pub sig_span: Option<Span>,
522}
523
524#[derive(Debug, Clone)]
525pub struct ImportDecl {
526    pub module_name: String,
527    pub style: ImportStyle,
528    pub alias: Option<String>,
529    pub pos: Pos,
530    pub span: Span,
531}
532
533#[derive(Debug, Clone)]
534#[non_exhaustive]
535pub enum Decl {
536    Template(TemplateDecl),
537    Interface(InterfaceDecl),
538    Function(FunctionDecl),
539    /// data/type/class/instance/exception — recorded with name + span.
540    TypeDef {
541        keyword: String,
542        name: String,
543        pos: Pos,
544        span: Span,
545    },
546    /// Anything unparseable at the top level (diagnostic already emitted).
547    Unknown {
548        raw: String,
549        pos: Pos,
550        span: Span,
551    },
552}
553
554#[derive(Debug, Clone)]
555pub struct Module {
556    pub name: String,
557    pub pos: Pos,
558    /// Whole-module extent: `[0, source.len())`. Container for all decls.
559    pub span: Span,
560    /// Span of the `module M (...) where` header clause; empty when the file
561    /// has no module header. Lets the span oracle treat header tokens as
562    /// covered without a dedicated header node.
563    pub header: Span,
564    pub imports: Vec<ImportDecl>,
565    pub decls: Vec<Decl>,
566}
567
568/// Why a [`ParseDiagnostic`] fired.
569///
570/// Lets a consumer separate syntax the parser deliberately does not model (still
571/// safe, just unanalyzed) from a genuine malformation, a recursion-limit
572/// degradation, or a lexical error.
573#[derive(Debug, Clone, Copy, PartialEq, Eq)]
574#[non_exhaustive]
575pub enum DiagnosticCategory {
576    /// A whole declaration could not be parsed and was skipped to the next item.
577    SkippedDecl,
578    /// A malformed expression, pattern, or expected-token error inside an
579    /// otherwise-recognized construct.
580    Malformed,
581    /// A construct the parser intentionally does not support, e.g. legacy
582    /// `controller ... can` choice syntax.
583    UnsupportedSyntax,
584    /// Expression/pattern nesting exceeded the recursion bound and was degraded
585    /// to raw text.
586    RecursionLimit,
587    /// A lexical error (unterminated string/comment, stray character).
588    Lex,
589}
590
591impl DiagnosticCategory {
592    /// Stable kebab-case tag for machine-readable output (JSON/SARIF) and logs.
593    pub const fn as_str(self) -> &'static str {
594        match self {
595            Self::SkippedDecl => "skipped-declaration",
596            Self::Malformed => "malformed",
597            Self::UnsupportedSyntax => "unsupported-syntax",
598            Self::RecursionLimit => "recursion-limit",
599            Self::Lex => "lexical-error",
600        }
601    }
602}
603
604/// Parse diagnostic — never fatal; the scan continues.
605#[derive(Debug, Clone)]
606pub struct ParseDiagnostic {
607    pub message: String,
608    pub pos: Pos,
609    /// Byte span of the offending region. The end is the actionable addition
610    /// over `pos`-alone; zero-width when only a point is known (lex errors,
611    /// EOF).
612    pub span: Span,
613    pub category: DiagnosticCategory,
614}
615
616impl Expr {
617    pub const fn pos(&self) -> Pos {
618        match self {
619            Self::Var { pos, .. }
620            | Self::Con { pos, .. }
621            | Self::Lit { pos, .. }
622            | Self::App { pos, .. }
623            | Self::BinOp { pos, .. }
624            | Self::Neg { pos, .. }
625            | Self::Lambda { pos, .. }
626            | Self::If { pos, .. }
627            | Self::Case { pos, .. }
628            | Self::Do { pos, .. }
629            | Self::LetIn { pos, .. }
630            | Self::Record { pos, .. }
631            | Self::Tuple { pos, .. }
632            | Self::List { pos, .. }
633            | Self::Try { pos, .. }
634            | Self::Section { pos, .. }
635            | Self::Error { pos, .. } => *pos,
636        }
637    }
638
639    /// Byte span covering the whole expression.
640    pub const fn span(&self) -> Span {
641        match self {
642            Self::Var { span, .. }
643            | Self::Con { span, .. }
644            | Self::Lit { span, .. }
645            | Self::App { span, .. }
646            | Self::BinOp { span, .. }
647            | Self::Neg { span, .. }
648            | Self::Lambda { span, .. }
649            | Self::If { span, .. }
650            | Self::Case { span, .. }
651            | Self::Do { span, .. }
652            | Self::LetIn { span, .. }
653            | Self::Record { span, .. }
654            | Self::Tuple { span, .. }
655            | Self::List { span, .. }
656            | Self::Try { span, .. }
657            | Self::Section { span, .. }
658            | Self::Error { span, .. } => *span,
659        }
660    }
661
662    /// Render back to compact, source-*like* text for diagnostics and `raw`
663    /// fields.
664    ///
665    /// This is **lossy and normalizing**, not byte-faithful: original layout is
666    /// dropped (e.g. `do`/`let` statements are joined with `; `), operators and
667    /// spacing are normalized, and comments/trivia are gone. Use it for a quick
668    /// human-readable echo of an expression; for source-exact reconstruction use
669    /// the node's [`span`](Self::span) into the original text (that is how
670    /// `daml-fmt` and [`crate::ast_span::render_from_ast`] stay lossless).
671    pub fn render(&self) -> String {
672        match self {
673            Self::Var {
674                qualifier, name, ..
675            }
676            | Self::Con {
677                qualifier, name, ..
678            } => qualifier
679                .as_ref()
680                .map_or_else(|| name.clone(), |q| format!("{q}.{name}")),
681            Self::Lit { kind, text, .. } => match kind {
682                LitKind::Text => format!("{text:?}"),
683                LitKind::Char => format!("'{text}'"),
684                _ => text.clone(),
685            },
686            Self::App { func, args, .. } => {
687                let mut s = func.render_atomic();
688                for a in args {
689                    s.push(' ');
690                    s.push_str(&a.render_atomic());
691                }
692                s
693            }
694            Self::BinOp { op, lhs, rhs, .. } => {
695                if op == "." {
696                    // Record projection / composition: `account.custodian`.
697                    format!("{}.{}", lhs.render_atomic(), rhs.render_atomic())
698                } else {
699                    format!("{} {} {}", lhs.render_atomic(), op, rhs.render_atomic())
700                }
701            }
702            Self::Neg { expr, .. } => format!("-{}", expr.render_atomic()),
703            Self::Lambda { params, body, .. } => {
704                let ps: Vec<String> = params.iter().map(|p| p.render()).collect();
705                format!("\\{} -> {}", ps.join(" "), body.render())
706            }
707            Self::If {
708                cond,
709                then_branch,
710                else_branch,
711                ..
712            } => format!(
713                "if {} then {} else {}",
714                cond.render(),
715                then_branch.render(),
716                else_branch.render()
717            ),
718            Self::Case {
719                scrutinee, alts, ..
720            } => {
721                let arms: Vec<String> = alts
722                    .iter()
723                    .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
724                    .collect();
725                format!("case {} of {}", scrutinee.render(), arms.join("; "))
726            }
727            Self::Do { stmts, .. } => {
728                let body: Vec<String> = stmts.iter().map(render_do_stmt).collect();
729                format!("do {}", body.join("; "))
730            }
731            Self::LetIn { bindings, body, .. } => {
732                let bs: Vec<String> = bindings.iter().map(render_binding).collect();
733                format!("let {} in {}", bs.join("; "), body.render())
734            }
735            Self::Record { base, fields, .. } => {
736                let fs: Vec<String> = fields
737                    .iter()
738                    .map(|f| {
739                        f.value.as_ref().map_or_else(
740                            || f.name.clone(),
741                            |v| format!("{} = {}", f.name, v.render()),
742                        )
743                    })
744                    .collect();
745                format!("{} with {}", base.render_atomic(), fs.join("; "))
746            }
747            Self::Tuple { items, .. } => {
748                let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
749                format!("({})", xs.join(", "))
750            }
751            Self::List { items, .. } => {
752                let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
753                format!("[{}]", xs.join(", "))
754            }
755            Self::Try { body, handlers, .. } => {
756                let hs: Vec<String> = handlers
757                    .iter()
758                    .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
759                    .collect();
760                format!("try {} catch {}", body.render(), hs.join("; "))
761            }
762            Self::Section {
763                op, operand, side, ..
764            } => match (operand, side) {
765                (Some(e), SectionSide::Left) => format!("({} {})", e.render(), op),
766                (Some(e), SectionSide::Right) => format!("({} {})", op, e.render()),
767                (None, _) => format!("({op})"),
768            },
769            Self::Error { raw, .. } => raw.clone(),
770        }
771    }
772
773    /// Render with parentheses if this expression wouldn't survive as an
774    /// application argument.
775    fn render_atomic(&self) -> String {
776        match self {
777            Self::Var { .. }
778            | Self::Con { .. }
779            | Self::Lit { .. }
780            | Self::Tuple { .. }
781            | Self::List { .. }
782            | Self::Section { .. }
783            | Self::Error { .. } => self.render(),
784            _ => format!("({})", self.render()),
785        }
786    }
787
788    /// The head of an application spine: for `Foo.exercise cid X`, the
789    /// `Foo.exercise` Var. For non-apps, the expression itself.
790    pub fn application_head(&self) -> &Self {
791        match self {
792            Self::App { func, .. } => func.application_head(),
793            _ => self,
794        }
795    }
796
797    /// Application arguments, empty for non-apps. The `App` spine is flattened
798    /// (see the [`App`](Self::App) variant), so for `f a b c` this returns all
799    /// three arguments `[a, b, c]`, not a single curried layer.
800    pub fn application_args(&self) -> &[Self] {
801        match self {
802            Self::App { args, .. } => args,
803            _ => &[],
804        }
805    }
806}
807
808fn render_do_stmt(s: &DoStmt) -> String {
809    match s {
810        DoStmt::Bind { pat, expr, .. } => format!("{} <- {}", pat.render(), expr.render()),
811        DoStmt::Let { bindings, .. } => {
812            let bs: Vec<String> = bindings.iter().map(render_binding).collect();
813            format!("let {}", bs.join("; "))
814        }
815        DoStmt::Expr { expr, .. } => expr.render(),
816    }
817}
818
819fn render_binding(b: &Binding) -> String {
820    let mut s = b.pat.render();
821    for p in &b.params {
822        s.push(' ');
823        s.push_str(&p.render());
824    }
825    format!("{} = {}", s, b.expr.render())
826}
827
828impl Pat {
829    pub const fn pos(&self) -> Pos {
830        match self {
831            Self::Var { pos, .. }
832            | Self::Wild { pos, .. }
833            | Self::Con { pos, .. }
834            | Self::Tuple { pos, .. }
835            | Self::List { pos, .. }
836            | Self::Lit { pos, .. }
837            | Self::As { pos, .. }
838            | Self::Other { pos, .. } => *pos,
839        }
840    }
841
842    /// Byte span covering the whole pattern.
843    pub const fn span(&self) -> Span {
844        match self {
845            Self::Var { span, .. }
846            | Self::Wild { span, .. }
847            | Self::Con { span, .. }
848            | Self::Tuple { span, .. }
849            | Self::List { span, .. }
850            | Self::Lit { span, .. }
851            | Self::As { span, .. }
852            | Self::Other { span, .. } => *span,
853        }
854    }
855
856    /// Render back to compact, source-*like* text. Lossy and normalizing in the
857    /// same way as [`Expr::render`]; use the node's [`span`](Self::span) for
858    /// byte-faithful text.
859    pub fn render(&self) -> String {
860        match self {
861            Self::Var { name, .. } => name.clone(),
862            Self::Wild { .. } => "_".to_string(),
863            Self::Con {
864                qualifier,
865                name,
866                args,
867                ..
868            } => {
869                let head = qualifier
870                    .as_ref()
871                    .map_or_else(|| name.clone(), |q| format!("{q}.{name}"));
872                if args.is_empty() {
873                    head
874                } else {
875                    let parts: Vec<String> = args.iter().map(|p| p.render()).collect();
876                    format!("({} {})", head, parts.join(" "))
877                }
878            }
879            Self::Tuple { items, .. } => {
880                let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
881                format!("({})", xs.join(", "))
882            }
883            Self::List { items, .. } => {
884                let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
885                format!("[{}]", xs.join(", "))
886            }
887            Self::Lit { kind, text, .. } => match kind {
888                LitKind::Text => format!("{text:?}"),
889                LitKind::Char => format!("'{text}'"),
890                _ => text.clone(),
891            },
892            Self::As { name, pat, .. } => format!("{}@{}", name, pat.render()),
893            Self::Other { raw, .. } => raw.clone(),
894        }
895    }
896}
897
898impl std::fmt::Display for Expr {
899    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
900        f.write_str(&self.render())
901    }
902}
903
904impl std::fmt::Display for Pat {
905    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
906        f.write_str(&self.render())
907    }
908}
909
910#[cfg(test)]
911mod tests {
912    use super::*;
913
914    fn pos() -> Pos {
915        Pos { line: 1, column: 1 }
916    }
917
918    fn span(start: usize, end: usize) -> Span {
919        Span::new(start, end)
920    }
921
922    #[test]
923    fn span_distinguishes_empty_from_invalid() {
924        assert!(span(3, 3).is_valid());
925        assert!(span(3, 3).is_empty());
926
927        assert!(!span(4, 3).is_valid());
928        assert!(!span(4, 3).is_empty());
929    }
930
931    #[test]
932    fn contains_rejects_invalid_spans() {
933        let parent = span(1, 10);
934
935        assert!(parent.contains(&span(3, 7)));
936        assert!(!parent.contains(&span(7, 3)));
937        assert!(!span(10, 1).contains(&span(3, 7)));
938    }
939
940    #[test]
941    fn expr_render_keeps_normalized_application_and_projection_shape() {
942        let projection = Expr::BinOp {
943            op: ".".to_string(),
944            lhs: Box::new(Expr::Var {
945                qualifier: None,
946                name: "this".to_string(),
947                pos: pos(),
948                span: span(0, 4),
949            }),
950            rhs: Box::new(Expr::Var {
951                qualifier: None,
952                name: "note".to_string(),
953                pos: pos(),
954                span: span(5, 9),
955            }),
956            pos: pos(),
957            span: span(0, 9),
958        };
959
960        let expr = Expr::App {
961            func: Box::new(Expr::Var {
962                qualifier: None,
963                name: "length".to_string(),
964                pos: pos(),
965                span: span(0, 6),
966            }),
967            args: vec![projection],
968            pos: pos(),
969            span: span(0, 16),
970        };
971
972        assert_eq!(expr.render(), "length (this.note)");
973    }
974
975    #[test]
976    fn section_render_depends_on_section_side() {
977        let expr_left = Expr::Section {
978            op: "+".to_string(),
979            operand: Some(Box::new(Expr::Var {
980                qualifier: None,
981                name: "x".to_string(),
982                pos: pos(),
983                span: span(0, 1),
984            })),
985            side: SectionSide::Left,
986            pos: pos(),
987            span: span(0, 4),
988        };
989        let expr_right = Expr::Section {
990            op: "+".to_string(),
991            operand: Some(Box::new(Expr::Lit {
992                kind: LitKind::Int,
993                text: "1".to_string(),
994                pos: pos(),
995                span: span(0, 1),
996            })),
997            side: SectionSide::Right,
998            pos: pos(),
999            span: span(0, 4),
1000        };
1001
1002        assert_eq!(expr_left.render(), "(x +)");
1003        assert_eq!(expr_right.render(), "(+ 1)");
1004    }
1005
1006    #[test]
1007    fn pat_render_preserves_collection_shape() {
1008        let pat = Pat::Tuple {
1009            items: vec![
1010                Pat::Var {
1011                    name: "owner".to_string(),
1012                    pos: pos(),
1013                    span: span(1, 6),
1014                },
1015                Pat::List {
1016                    items: Vec::new(),
1017                    pos: pos(),
1018                    span: span(8, 10),
1019                },
1020            ],
1021            pos: pos(),
1022            span: span(0, 11),
1023        };
1024
1025        assert_eq!(pat.render(), "(owner, [])");
1026    }
1027}