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