Skip to main content

draxl_ast/
lib.rs

1#![forbid(unsafe_code)]
2//! Typed IR for Draxl.
3//!
4//! This crate defines the shared syntax model for the entire workspace:
5//!
6//! - stable metadata on modeled nodes
7//! - typed items, statements, expressions, patterns, and types
8//! - deterministic JSON emission for test fixtures and tooling
9//!
10//! It deliberately does not own parsing, validation rules, formatting policy,
11//! lowering, or patch application.
12
13mod canonical;
14
15use std::fmt::Write;
16
17/// Stable identifier attached to an Draxl node.
18pub type NodeId = String;
19
20/// Opaque ordering key for ordered list slots.
21pub type Rank = String;
22
23/// Explicit name for the slot that owns a node.
24pub type SlotName = String;
25
26/// Ordinary language a Draxl source file is intended to lower into.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum LowerLanguage {
29    /// Draxl Rust profile over `.rs.dx` files.
30    Rust,
31}
32
33/// Byte range in the original source file.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct Span {
36    pub start: usize,
37    pub end: usize,
38}
39
40/// Shared metadata carried by Draxl nodes.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Meta {
43    pub id: NodeId,
44    pub rank: Option<Rank>,
45    pub anchor: Option<NodeId>,
46    pub slot: Option<SlotName>,
47    pub span: Option<Span>,
48}
49
50impl Meta {
51    /// Returns a copy with source spans removed.
52    pub fn without_span(&self) -> Self {
53        let mut meta = self.clone();
54        meta.span = None;
55        meta
56    }
57}
58
59/// Parsed Draxl file.
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct File {
62    pub items: Vec<Item>,
63}
64
65impl File {
66    /// Returns a clone with all spans stripped for semantic comparisons.
67    pub fn without_spans(&self) -> Self {
68        let mut file = self.clone();
69        file.clear_spans();
70        file
71    }
72
73    /// Removes span data from the file in place.
74    pub fn clear_spans(&mut self) {
75        for item in &mut self.items {
76            item.clear_spans();
77        }
78    }
79
80    /// Emits deterministic JSON for the file.
81    pub fn to_json_pretty(&self) -> String {
82        let mut out = String::new();
83        write_file_json(self, &mut out, 0);
84        out.push('\n');
85        out
86    }
87}
88
89/// Returns a canonicalized clone of the file.
90pub fn canonicalize_file(file: &File) -> File {
91    canonical::canonicalize_file(file)
92}
93
94/// Top-level or module item.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum Item {
97    Mod(ItemMod),
98    Use(ItemUse),
99    Struct(ItemStruct),
100    Enum(ItemEnum),
101    Fn(ItemFn),
102    Doc(DocNode),
103    Comment(CommentNode),
104}
105
106impl Item {
107    /// Returns the metadata for the item.
108    pub fn meta(&self) -> &Meta {
109        match self {
110            Self::Mod(node) => &node.meta,
111            Self::Use(node) => &node.meta,
112            Self::Struct(node) => &node.meta,
113            Self::Enum(node) => &node.meta,
114            Self::Fn(node) => &node.meta,
115            Self::Doc(node) => &node.meta,
116            Self::Comment(node) => &node.meta,
117        }
118    }
119
120    /// Returns mutable metadata for the item.
121    pub fn meta_mut(&mut self) -> &mut Meta {
122        match self {
123            Self::Mod(node) => &mut node.meta,
124            Self::Use(node) => &mut node.meta,
125            Self::Struct(node) => &mut node.meta,
126            Self::Enum(node) => &mut node.meta,
127            Self::Fn(node) => &mut node.meta,
128            Self::Doc(node) => &mut node.meta,
129            Self::Comment(node) => &mut node.meta,
130        }
131    }
132
133    /// Removes span data from the item in place.
134    pub fn clear_spans(&mut self) {
135        self.meta_mut().span = None;
136        match self {
137            Self::Mod(node) => {
138                for item in &mut node.items {
139                    item.clear_spans();
140                }
141            }
142            Self::Use(_) => {}
143            Self::Struct(node) => {
144                for field in &mut node.fields {
145                    field.clear_spans();
146                }
147            }
148            Self::Enum(node) => {
149                for variant in &mut node.variants {
150                    variant.clear_spans();
151                }
152            }
153            Self::Fn(node) => {
154                for param in &mut node.params {
155                    param.clear_spans();
156                }
157                if let Some(ret_ty) = &mut node.ret_ty {
158                    ret_ty.clear_spans();
159                }
160                node.body.clear_spans();
161            }
162            Self::Doc(_) => {}
163            Self::Comment(_) => {}
164        }
165    }
166}
167
168/// Module item.
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct ItemMod {
171    pub meta: Meta,
172    pub name: String,
173    pub items: Vec<Item>,
174}
175
176/// Use item.
177#[derive(Debug, Clone, PartialEq, Eq)]
178pub struct ItemUse {
179    /// Draxl metadata for the `use` item.
180    pub meta: Meta,
181    /// Structured `use` tree for the imported path set.
182    pub tree: UseTree,
183}
184
185/// Tree form for `use` items in the bootstrap subset.
186#[derive(Debug, Clone, PartialEq, Eq)]
187pub enum UseTree {
188    /// Single imported name.
189    Name(UseName),
190    /// Prefix-plus-child path segment.
191    Path(UsePathTree),
192    /// Braced `use` tree group.
193    Group(UseGroup),
194    /// Glob import.
195    Glob(UseGlob),
196}
197
198/// Single-name `use` tree segment.
199#[derive(Debug, Clone, PartialEq, Eq)]
200pub struct UseName {
201    /// Imported name.
202    pub name: String,
203}
204
205/// Prefix-plus-child `use` tree segment.
206#[derive(Debug, Clone, PartialEq, Eq)]
207pub struct UsePathTree {
208    /// Leading path segment.
209    pub prefix: String,
210    /// Remaining `use` tree after the prefix.
211    pub tree: Box<UseTree>,
212}
213
214/// Braced `use` tree group.
215#[derive(Debug, Clone, PartialEq, Eq)]
216pub struct UseGroup {
217    /// Children inside the braced group.
218    pub items: Vec<UseTree>,
219}
220
221/// Glob `use` tree leaf.
222#[derive(Debug, Clone, PartialEq, Eq)]
223pub struct UseGlob;
224
225/// Struct item.
226#[derive(Debug, Clone, PartialEq, Eq)]
227pub struct ItemStruct {
228    pub meta: Meta,
229    pub name: String,
230    pub fields: Vec<Field>,
231}
232
233/// Enum item.
234#[derive(Debug, Clone, PartialEq, Eq)]
235pub struct ItemEnum {
236    pub meta: Meta,
237    pub name: String,
238    pub variants: Vec<Variant>,
239}
240
241/// Function item.
242#[derive(Debug, Clone, PartialEq, Eq)]
243pub struct ItemFn {
244    pub meta: Meta,
245    pub name: String,
246    pub params: Vec<Param>,
247    pub ret_ty: Option<Type>,
248    pub body: Block,
249}
250
251/// Struct field.
252#[derive(Debug, Clone, PartialEq, Eq)]
253pub struct Field {
254    pub meta: Meta,
255    pub name: String,
256    pub ty: Type,
257}
258
259impl Field {
260    /// Removes span data from the field in place.
261    pub fn clear_spans(&mut self) {
262        self.meta.span = None;
263        self.ty.clear_spans();
264    }
265}
266
267/// Enum variant.
268#[derive(Debug, Clone, PartialEq, Eq)]
269pub struct Variant {
270    pub meta: Meta,
271    pub name: String,
272}
273
274impl Variant {
275    /// Removes span data from the variant in place.
276    pub fn clear_spans(&mut self) {
277        self.meta.span = None;
278    }
279}
280
281/// Function parameter.
282#[derive(Debug, Clone, PartialEq, Eq)]
283pub struct Param {
284    pub meta: Meta,
285    pub name: String,
286    pub ty: Type,
287}
288
289impl Param {
290    /// Removes span data from the parameter in place.
291    pub fn clear_spans(&mut self) {
292        self.meta.span = None;
293        self.ty.clear_spans();
294    }
295}
296
297/// Block expression body.
298#[derive(Debug, Clone, PartialEq, Eq)]
299pub struct Block {
300    pub meta: Option<Meta>,
301    pub stmts: Vec<Stmt>,
302}
303
304impl Block {
305    /// Removes span data from the block in place.
306    pub fn clear_spans(&mut self) {
307        if let Some(meta) = &mut self.meta {
308            meta.span = None;
309        }
310        for stmt in &mut self.stmts {
311            stmt.clear_spans();
312        }
313    }
314}
315
316/// Statement inside a block.
317#[derive(Debug, Clone, PartialEq, Eq)]
318pub enum Stmt {
319    Let(StmtLet),
320    Expr(StmtExpr),
321    Item(Item),
322    Doc(DocNode),
323    Comment(CommentNode),
324}
325
326impl Stmt {
327    /// Returns the metadata for ranked block children when present.
328    pub fn meta(&self) -> Option<&Meta> {
329        match self {
330            Self::Let(node) => Some(&node.meta),
331            Self::Expr(node) => Some(&node.meta),
332            Self::Item(node) => Some(node.meta()),
333            Self::Doc(node) => Some(&node.meta),
334            Self::Comment(node) => Some(&node.meta),
335        }
336    }
337
338    /// Removes span data from the statement in place.
339    pub fn clear_spans(&mut self) {
340        match self {
341            Self::Let(node) => node.clear_spans(),
342            Self::Expr(node) => node.clear_spans(),
343            Self::Item(node) => node.clear_spans(),
344            Self::Doc(node) => node.meta.span = None,
345            Self::Comment(node) => node.meta.span = None,
346        }
347    }
348}
349
350/// Let statement.
351#[derive(Debug, Clone, PartialEq, Eq)]
352pub struct StmtLet {
353    pub meta: Meta,
354    pub pat: Pattern,
355    pub value: Expr,
356}
357
358impl StmtLet {
359    /// Removes span data from the statement in place.
360    pub fn clear_spans(&mut self) {
361        self.meta.span = None;
362        self.pat.clear_spans();
363        self.value.clear_spans();
364    }
365}
366
367/// Expression statement.
368#[derive(Debug, Clone, PartialEq, Eq)]
369pub struct StmtExpr {
370    /// Draxl metadata for the statement node.
371    pub meta: Meta,
372    /// Expression carried by the statement.
373    pub expr: Expr,
374    /// Whether the statement ends with a semicolon.
375    pub has_semi: bool,
376}
377
378impl StmtExpr {
379    /// Removes span data from the statement in place.
380    pub fn clear_spans(&mut self) {
381        self.meta.span = None;
382        self.expr.clear_spans();
383    }
384}
385
386/// Expression in the bootstrap subset.
387#[derive(Debug, Clone, PartialEq, Eq)]
388pub enum Expr {
389    Path(ExprPath),
390    Lit(ExprLit),
391    Group(ExprGroup),
392    Binary(ExprBinary),
393    Unary(ExprUnary),
394    Call(ExprCall),
395    Match(ExprMatch),
396    Block(Block),
397}
398
399impl Expr {
400    /// Returns metadata when the expression carries explicit Draxl metadata.
401    pub fn meta(&self) -> Option<&Meta> {
402        match self {
403            Self::Path(node) => node.meta.as_ref(),
404            Self::Lit(node) => node.meta.as_ref(),
405            Self::Group(node) => node.meta.as_ref(),
406            Self::Binary(node) => node.meta.as_ref(),
407            Self::Unary(node) => node.meta.as_ref(),
408            Self::Call(node) => node.meta.as_ref(),
409            Self::Match(node) => node.meta.as_ref(),
410            Self::Block(node) => node.meta.as_ref(),
411        }
412    }
413
414    /// Returns mutable metadata when the expression carries explicit Draxl metadata.
415    pub fn meta_mut(&mut self) -> Option<&mut Meta> {
416        match self {
417            Self::Path(node) => node.meta.as_mut(),
418            Self::Lit(node) => node.meta.as_mut(),
419            Self::Group(node) => node.meta.as_mut(),
420            Self::Binary(node) => node.meta.as_mut(),
421            Self::Unary(node) => node.meta.as_mut(),
422            Self::Call(node) => node.meta.as_mut(),
423            Self::Match(node) => node.meta.as_mut(),
424            Self::Block(node) => node.meta.as_mut(),
425        }
426    }
427
428    /// Removes span data from the expression in place.
429    pub fn clear_spans(&mut self) {
430        if let Some(meta) = self.meta_mut() {
431            meta.span = None;
432        }
433        match self {
434            Self::Path(_) => {}
435            Self::Lit(_) => {}
436            Self::Group(node) => {
437                node.expr.clear_spans();
438            }
439            Self::Binary(node) => {
440                node.lhs.clear_spans();
441                node.rhs.clear_spans();
442            }
443            Self::Unary(node) => {
444                node.expr.clear_spans();
445            }
446            Self::Call(node) => {
447                node.callee.clear_spans();
448                for arg in &mut node.args {
449                    arg.clear_spans();
450                }
451            }
452            Self::Match(node) => {
453                node.scrutinee.clear_spans();
454                for arm in &mut node.arms {
455                    arm.clear_spans();
456                }
457            }
458            Self::Block(node) => node.clear_spans(),
459        }
460    }
461}
462
463/// Path expression.
464#[derive(Debug, Clone, PartialEq, Eq)]
465pub struct ExprPath {
466    pub meta: Option<Meta>,
467    pub path: Path,
468}
469
470/// Literal expression.
471#[derive(Debug, Clone, PartialEq, Eq)]
472pub struct ExprLit {
473    pub meta: Option<Meta>,
474    pub value: Literal,
475}
476
477/// Grouped expression that preserves source parentheses.
478#[derive(Debug, Clone, PartialEq, Eq)]
479pub struct ExprGroup {
480    /// Optional Draxl metadata attached to the grouped expression.
481    pub meta: Option<Meta>,
482    /// Inner expression wrapped by the source parentheses.
483    pub expr: Box<Expr>,
484}
485
486/// Binary expression.
487#[derive(Debug, Clone, PartialEq, Eq)]
488pub struct ExprBinary {
489    pub meta: Option<Meta>,
490    pub lhs: Box<Expr>,
491    pub op: BinaryOp,
492    pub rhs: Box<Expr>,
493}
494
495/// Unary expression.
496#[derive(Debug, Clone, PartialEq, Eq)]
497pub struct ExprUnary {
498    pub meta: Option<Meta>,
499    pub op: UnaryOp,
500    pub expr: Box<Expr>,
501}
502
503/// Call expression.
504#[derive(Debug, Clone, PartialEq, Eq)]
505pub struct ExprCall {
506    pub meta: Option<Meta>,
507    pub callee: Box<Expr>,
508    pub args: Vec<Expr>,
509}
510
511/// Match expression.
512#[derive(Debug, Clone, PartialEq, Eq)]
513pub struct ExprMatch {
514    pub meta: Option<Meta>,
515    pub scrutinee: Box<Expr>,
516    pub arms: Vec<MatchArm>,
517}
518
519/// Match arm.
520#[derive(Debug, Clone, PartialEq, Eq)]
521pub struct MatchArm {
522    pub meta: Meta,
523    pub pat: Pattern,
524    pub guard: Option<Expr>,
525    pub body: Expr,
526}
527
528impl MatchArm {
529    /// Removes span data from the match arm in place.
530    pub fn clear_spans(&mut self) {
531        self.meta.span = None;
532        self.pat.clear_spans();
533        if let Some(guard) = &mut self.guard {
534            guard.clear_spans();
535        }
536        self.body.clear_spans();
537    }
538}
539
540/// Pattern in the bootstrap subset.
541#[derive(Debug, Clone, PartialEq, Eq)]
542pub enum Pattern {
543    Ident(PatIdent),
544    Wild(PatWild),
545}
546
547impl Pattern {
548    /// Returns metadata when the pattern carries explicit Draxl metadata.
549    pub fn meta(&self) -> Option<&Meta> {
550        match self {
551            Self::Ident(node) => node.meta.as_ref(),
552            Self::Wild(node) => node.meta.as_ref(),
553        }
554    }
555
556    /// Removes span data from the pattern in place.
557    pub fn clear_spans(&mut self) {
558        match self {
559            Self::Ident(node) => {
560                if let Some(meta) = &mut node.meta {
561                    meta.span = None;
562                }
563            }
564            Self::Wild(node) => {
565                if let Some(meta) = &mut node.meta {
566                    meta.span = None;
567                }
568            }
569        }
570    }
571}
572
573/// Identifier pattern.
574#[derive(Debug, Clone, PartialEq, Eq)]
575pub struct PatIdent {
576    pub meta: Option<Meta>,
577    pub name: String,
578}
579
580/// Wildcard pattern.
581#[derive(Debug, Clone, PartialEq, Eq)]
582pub struct PatWild {
583    pub meta: Option<Meta>,
584}
585
586/// Type in the bootstrap subset.
587#[derive(Debug, Clone, PartialEq, Eq)]
588pub enum Type {
589    Path(TypePath),
590}
591
592impl Type {
593    /// Returns the metadata attached to the type.
594    pub fn meta(&self) -> &Meta {
595        match self {
596            Self::Path(node) => &node.meta,
597        }
598    }
599
600    /// Removes span data from the type in place.
601    pub fn clear_spans(&mut self) {
602        match self {
603            Self::Path(node) => node.meta.span = None,
604        }
605    }
606}
607
608/// Path type.
609#[derive(Debug, Clone, PartialEq, Eq)]
610pub struct TypePath {
611    pub meta: Meta,
612    pub path: Path,
613}
614
615/// Path value.
616#[derive(Debug, Clone, PartialEq, Eq)]
617pub struct Path {
618    pub segments: Vec<String>,
619}
620
621/// Literal value.
622#[derive(Debug, Clone, PartialEq, Eq)]
623pub enum Literal {
624    Int(i64),
625    Str(String),
626}
627
628/// Binary operator in the bootstrap subset.
629#[derive(Debug, Clone, Copy, PartialEq, Eq)]
630pub enum BinaryOp {
631    Add,
632    Sub,
633    Lt,
634}
635
636/// Unary operator in the bootstrap subset.
637#[derive(Debug, Clone, Copy, PartialEq, Eq)]
638pub enum UnaryOp {
639    Neg,
640}
641
642/// Doc comment node.
643#[derive(Debug, Clone, PartialEq, Eq)]
644pub struct DocNode {
645    pub meta: Meta,
646    pub text: String,
647}
648
649/// Line comment node.
650#[derive(Debug, Clone, PartialEq, Eq)]
651pub struct CommentNode {
652    pub meta: Meta,
653    pub text: String,
654}
655
656fn write_indent(out: &mut String, level: usize) {
657    for _ in 0..level {
658        out.push_str("  ");
659    }
660}
661
662fn write_json_string(value: &str, out: &mut String) {
663    out.push('"');
664    for ch in value.chars() {
665        match ch {
666            '"' => out.push_str("\\\""),
667            '\\' => out.push_str("\\\\"),
668            '\n' => out.push_str("\\n"),
669            '\r' => out.push_str("\\r"),
670            '\t' => out.push_str("\\t"),
671            _ => out.push(ch),
672        }
673    }
674    out.push('"');
675}
676
677fn write_json_meta(meta: &Meta, out: &mut String, level: usize) {
678    out.push_str("{\n");
679    write_indent(out, level + 1);
680    out.push_str("\"id\": ");
681    write_json_string(&meta.id, out);
682    out.push_str(",\n");
683    write_indent(out, level + 1);
684    out.push_str("\"rank\": ");
685    match &meta.rank {
686        Some(rank) => write_json_string(rank, out),
687        None => out.push_str("null"),
688    }
689    out.push_str(",\n");
690    write_indent(out, level + 1);
691    out.push_str("\"anchor\": ");
692    match &meta.anchor {
693        Some(anchor) => write_json_string(anchor, out),
694        None => out.push_str("null"),
695    }
696    out.push_str(",\n");
697    write_indent(out, level + 1);
698    out.push_str("\"slot\": ");
699    match &meta.slot {
700        Some(slot) => write_json_string(slot, out),
701        None => out.push_str("null"),
702    }
703    out.push_str(",\n");
704    write_indent(out, level + 1);
705    out.push_str("\"span\": ");
706    match meta.span {
707        Some(span) => {
708            out.push_str("{\n");
709            write_indent(out, level + 2);
710            let _ = write!(out, "\"start\": {},\n", span.start);
711            write_indent(out, level + 2);
712            let _ = write!(out, "\"end\": {}\n", span.end);
713            write_indent(out, level + 1);
714            out.push('}');
715        }
716        None => out.push_str("null"),
717    }
718    out.push('\n');
719    write_indent(out, level);
720    out.push('}');
721}
722
723fn write_json_path(path: &Path, out: &mut String, level: usize) {
724    out.push_str("{\n");
725    write_indent(out, level + 1);
726    out.push_str("\"segments\": [");
727    for (index, segment) in path.segments.iter().enumerate() {
728        if index > 0 {
729            out.push_str(", ");
730        }
731        write_json_string(segment, out);
732    }
733    out.push_str("]\n");
734    write_indent(out, level);
735    out.push('}');
736}
737
738fn write_json_use_tree(tree: &UseTree, out: &mut String, level: usize) {
739    match tree {
740        UseTree::Name(node) => {
741            out.push_str("{\n");
742            write_indent(out, level + 1);
743            out.push_str("\"kind\": \"Name\",\n");
744            write_indent(out, level + 1);
745            out.push_str("\"name\": ");
746            write_json_string(&node.name, out);
747            out.push('\n');
748            write_indent(out, level);
749            out.push('}');
750        }
751        UseTree::Path(node) => {
752            out.push_str("{\n");
753            write_indent(out, level + 1);
754            out.push_str("\"kind\": \"Path\",\n");
755            write_indent(out, level + 1);
756            out.push_str("\"prefix\": ");
757            write_json_string(&node.prefix, out);
758            out.push_str(",\n");
759            write_indent(out, level + 1);
760            out.push_str("\"tree\": ");
761            write_json_use_tree(&node.tree, out, level + 1);
762            out.push('\n');
763            write_indent(out, level);
764            out.push('}');
765        }
766        UseTree::Group(node) => {
767            out.push_str("{\n");
768            write_indent(out, level + 1);
769            out.push_str("\"kind\": \"Group\",\n");
770            write_indent(out, level + 1);
771            out.push_str("\"items\": [\n");
772            for (index, item) in node.items.iter().enumerate() {
773                if index > 0 {
774                    out.push_str(",\n");
775                }
776                write_indent(out, level + 2);
777                write_json_use_tree(item, out, level + 2);
778            }
779            out.push('\n');
780            write_indent(out, level + 1);
781            out.push_str("]\n");
782            write_indent(out, level);
783            out.push('}');
784        }
785        UseTree::Glob(_) => {
786            out.push_str("{\n");
787            write_indent(out, level + 1);
788            out.push_str("\"kind\": \"Glob\"\n");
789            write_indent(out, level);
790            out.push('}');
791        }
792    }
793}
794
795fn write_json_literal(literal: &Literal, out: &mut String, level: usize) {
796    out.push_str("{\n");
797    match literal {
798        Literal::Int(value) => {
799            write_indent(out, level + 1);
800            out.push_str("\"kind\": \"Int\",\n");
801            write_indent(out, level + 1);
802            let _ = write!(out, "\"value\": {}\n", value);
803        }
804        Literal::Str(value) => {
805            write_indent(out, level + 1);
806            out.push_str("\"kind\": \"Str\",\n");
807            write_indent(out, level + 1);
808            out.push_str("\"value\": ");
809            write_json_string(value, out);
810            out.push('\n');
811        }
812    }
813    write_indent(out, level);
814    out.push('}');
815}
816
817fn write_json_type(ty: &Type, out: &mut String, level: usize) {
818    match ty {
819        Type::Path(node) => {
820            out.push_str("{\n");
821            write_indent(out, level + 1);
822            out.push_str("\"kind\": \"Path\",\n");
823            write_indent(out, level + 1);
824            out.push_str("\"meta\": ");
825            write_json_meta(&node.meta, out, level + 1);
826            out.push_str(",\n");
827            write_indent(out, level + 1);
828            out.push_str("\"path\": ");
829            write_json_path(&node.path, out, level + 1);
830            out.push('\n');
831            write_indent(out, level);
832            out.push('}');
833        }
834    }
835}
836
837fn write_json_pattern(pattern: &Pattern, out: &mut String, level: usize) {
838    match pattern {
839        Pattern::Ident(node) => {
840            out.push_str("{\n");
841            write_indent(out, level + 1);
842            out.push_str("\"kind\": \"Ident\",\n");
843            write_indent(out, level + 1);
844            out.push_str("\"meta\": ");
845            match &node.meta {
846                Some(meta) => write_json_meta(meta, out, level + 1),
847                None => out.push_str("null"),
848            }
849            out.push_str(",\n");
850            write_indent(out, level + 1);
851            out.push_str("\"name\": ");
852            write_json_string(&node.name, out);
853            out.push('\n');
854            write_indent(out, level);
855            out.push('}');
856        }
857        Pattern::Wild(node) => {
858            out.push_str("{\n");
859            write_indent(out, level + 1);
860            out.push_str("\"kind\": \"Wild\",\n");
861            write_indent(out, level + 1);
862            out.push_str("\"meta\": ");
863            match &node.meta {
864                Some(meta) => write_json_meta(meta, out, level + 1),
865                None => out.push_str("null"),
866            }
867            out.push('\n');
868            write_indent(out, level);
869            out.push('}');
870        }
871    }
872}
873
874fn write_json_expr(expr: &Expr, out: &mut String, level: usize) {
875    match expr {
876        Expr::Path(node) => {
877            out.push_str("{\n");
878            write_indent(out, level + 1);
879            out.push_str("\"kind\": \"Path\",\n");
880            write_indent(out, level + 1);
881            out.push_str("\"meta\": ");
882            match &node.meta {
883                Some(meta) => write_json_meta(meta, out, level + 1),
884                None => out.push_str("null"),
885            }
886            out.push_str(",\n");
887            write_indent(out, level + 1);
888            out.push_str("\"path\": ");
889            write_json_path(&node.path, out, level + 1);
890            out.push('\n');
891            write_indent(out, level);
892            out.push('}');
893        }
894        Expr::Lit(node) => {
895            out.push_str("{\n");
896            write_indent(out, level + 1);
897            out.push_str("\"kind\": \"Lit\",\n");
898            write_indent(out, level + 1);
899            out.push_str("\"meta\": ");
900            match &node.meta {
901                Some(meta) => write_json_meta(meta, out, level + 1),
902                None => out.push_str("null"),
903            }
904            out.push_str(",\n");
905            write_indent(out, level + 1);
906            out.push_str("\"value\": ");
907            write_json_literal(&node.value, out, level + 1);
908            out.push('\n');
909            write_indent(out, level);
910            out.push('}');
911        }
912        Expr::Group(node) => {
913            out.push_str("{\n");
914            write_indent(out, level + 1);
915            out.push_str("\"kind\": \"Group\",\n");
916            write_indent(out, level + 1);
917            out.push_str("\"meta\": ");
918            match &node.meta {
919                Some(meta) => write_json_meta(meta, out, level + 1),
920                None => out.push_str("null"),
921            }
922            out.push_str(",\n");
923            write_indent(out, level + 1);
924            out.push_str("\"expr\": ");
925            write_json_expr(&node.expr, out, level + 1);
926            out.push('\n');
927            write_indent(out, level);
928            out.push('}');
929        }
930        Expr::Binary(node) => {
931            out.push_str("{\n");
932            write_indent(out, level + 1);
933            out.push_str("\"kind\": \"Binary\",\n");
934            write_indent(out, level + 1);
935            out.push_str("\"meta\": ");
936            match &node.meta {
937                Some(meta) => write_json_meta(meta, out, level + 1),
938                None => out.push_str("null"),
939            }
940            out.push_str(",\n");
941            write_indent(out, level + 1);
942            out.push_str("\"lhs\": ");
943            write_json_expr(&node.lhs, out, level + 1);
944            out.push_str(",\n");
945            write_indent(out, level + 1);
946            out.push_str("\"op\": ");
947            write_json_string(
948                match node.op {
949                    BinaryOp::Add => "Add",
950                    BinaryOp::Sub => "Sub",
951                    BinaryOp::Lt => "Lt",
952                },
953                out,
954            );
955            out.push_str(",\n");
956            write_indent(out, level + 1);
957            out.push_str("\"rhs\": ");
958            write_json_expr(&node.rhs, out, level + 1);
959            out.push('\n');
960            write_indent(out, level);
961            out.push('}');
962        }
963        Expr::Unary(node) => {
964            out.push_str("{\n");
965            write_indent(out, level + 1);
966            out.push_str("\"kind\": \"Unary\",\n");
967            write_indent(out, level + 1);
968            out.push_str("\"meta\": ");
969            match &node.meta {
970                Some(meta) => write_json_meta(meta, out, level + 1),
971                None => out.push_str("null"),
972            }
973            out.push_str(",\n");
974            write_indent(out, level + 1);
975            out.push_str("\"op\": ");
976            write_json_string("Neg", out);
977            out.push_str(",\n");
978            write_indent(out, level + 1);
979            out.push_str("\"expr\": ");
980            write_json_expr(&node.expr, out, level + 1);
981            out.push('\n');
982            write_indent(out, level);
983            out.push('}');
984        }
985        Expr::Call(node) => {
986            out.push_str("{\n");
987            write_indent(out, level + 1);
988            out.push_str("\"kind\": \"Call\",\n");
989            write_indent(out, level + 1);
990            out.push_str("\"meta\": ");
991            match &node.meta {
992                Some(meta) => write_json_meta(meta, out, level + 1),
993                None => out.push_str("null"),
994            }
995            out.push_str(",\n");
996            write_indent(out, level + 1);
997            out.push_str("\"callee\": ");
998            write_json_expr(&node.callee, out, level + 1);
999            out.push_str(",\n");
1000            write_indent(out, level + 1);
1001            out.push_str("\"args\": [\n");
1002            for (index, arg) in node.args.iter().enumerate() {
1003                if index > 0 {
1004                    out.push_str(",\n");
1005                }
1006                write_indent(out, level + 2);
1007                write_json_expr(arg, out, level + 2);
1008            }
1009            out.push('\n');
1010            write_indent(out, level + 1);
1011            out.push_str("]\n");
1012            write_indent(out, level);
1013            out.push('}');
1014        }
1015        Expr::Match(node) => {
1016            out.push_str("{\n");
1017            write_indent(out, level + 1);
1018            out.push_str("\"kind\": \"Match\",\n");
1019            write_indent(out, level + 1);
1020            out.push_str("\"meta\": ");
1021            match &node.meta {
1022                Some(meta) => write_json_meta(meta, out, level + 1),
1023                None => out.push_str("null"),
1024            }
1025            out.push_str(",\n");
1026            write_indent(out, level + 1);
1027            out.push_str("\"scrutinee\": ");
1028            write_json_expr(&node.scrutinee, out, level + 1);
1029            out.push_str(",\n");
1030            write_indent(out, level + 1);
1031            out.push_str("\"arms\": [\n");
1032            for (index, arm) in node.arms.iter().enumerate() {
1033                if index > 0 {
1034                    out.push_str(",\n");
1035                }
1036                write_indent(out, level + 2);
1037                write_json_match_arm(arm, out, level + 2);
1038            }
1039            out.push('\n');
1040            write_indent(out, level + 1);
1041            out.push_str("]\n");
1042            write_indent(out, level);
1043            out.push('}');
1044        }
1045        Expr::Block(node) => write_json_block(node, out, level),
1046    }
1047}
1048
1049fn write_json_match_arm(arm: &MatchArm, out: &mut String, level: usize) {
1050    out.push_str("{\n");
1051    write_indent(out, level + 1);
1052    out.push_str("\"meta\": ");
1053    write_json_meta(&arm.meta, out, level + 1);
1054    out.push_str(",\n");
1055    write_indent(out, level + 1);
1056    out.push_str("\"pat\": ");
1057    write_json_pattern(&arm.pat, out, level + 1);
1058    out.push_str(",\n");
1059    write_indent(out, level + 1);
1060    out.push_str("\"guard\": ");
1061    match &arm.guard {
1062        Some(guard) => write_json_expr(guard, out, level + 1),
1063        None => out.push_str("null"),
1064    }
1065    out.push_str(",\n");
1066    write_indent(out, level + 1);
1067    out.push_str("\"body\": ");
1068    write_json_expr(&arm.body, out, level + 1);
1069    out.push('\n');
1070    write_indent(out, level);
1071    out.push('}');
1072}
1073
1074fn write_json_block(block: &Block, out: &mut String, level: usize) {
1075    out.push_str("{\n");
1076    write_indent(out, level + 1);
1077    out.push_str("\"kind\": \"Block\",\n");
1078    write_indent(out, level + 1);
1079    out.push_str("\"meta\": ");
1080    match &block.meta {
1081        Some(meta) => write_json_meta(meta, out, level + 1),
1082        None => out.push_str("null"),
1083    }
1084    out.push_str(",\n");
1085    write_indent(out, level + 1);
1086    out.push_str("\"stmts\": [\n");
1087    for (index, stmt) in block.stmts.iter().enumerate() {
1088        if index > 0 {
1089            out.push_str(",\n");
1090        }
1091        write_indent(out, level + 2);
1092        write_json_stmt(stmt, out, level + 2);
1093    }
1094    out.push('\n');
1095    write_indent(out, level + 1);
1096    out.push_str("]\n");
1097    write_indent(out, level);
1098    out.push('}');
1099}
1100
1101fn write_json_stmt(stmt: &Stmt, out: &mut String, level: usize) {
1102    match stmt {
1103        Stmt::Let(node) => {
1104            out.push_str("{\n");
1105            write_indent(out, level + 1);
1106            out.push_str("\"kind\": \"Let\",\n");
1107            write_indent(out, level + 1);
1108            out.push_str("\"meta\": ");
1109            write_json_meta(&node.meta, out, level + 1);
1110            out.push_str(",\n");
1111            write_indent(out, level + 1);
1112            out.push_str("\"pat\": ");
1113            write_json_pattern(&node.pat, out, level + 1);
1114            out.push_str(",\n");
1115            write_indent(out, level + 1);
1116            out.push_str("\"value\": ");
1117            write_json_expr(&node.value, out, level + 1);
1118            out.push('\n');
1119            write_indent(out, level);
1120            out.push('}');
1121        }
1122        Stmt::Expr(node) => {
1123            out.push_str("{\n");
1124            write_indent(out, level + 1);
1125            out.push_str("\"kind\": \"Expr\",\n");
1126            write_indent(out, level + 1);
1127            out.push_str("\"meta\": ");
1128            write_json_meta(&node.meta, out, level + 1);
1129            out.push_str(",\n");
1130            write_indent(out, level + 1);
1131            let _ = write!(out, "\"has_semi\": {},\n", node.has_semi);
1132            write_indent(out, level + 1);
1133            out.push_str("\"expr\": ");
1134            write_json_expr(&node.expr, out, level + 1);
1135            out.push('\n');
1136            write_indent(out, level);
1137            out.push('}');
1138        }
1139        Stmt::Item(node) => {
1140            out.push_str("{\n");
1141            write_indent(out, level + 1);
1142            out.push_str("\"kind\": \"Item\",\n");
1143            write_indent(out, level + 1);
1144            out.push_str("\"item\": ");
1145            write_json_item(node, out, level + 1);
1146            out.push('\n');
1147            write_indent(out, level);
1148            out.push('}');
1149        }
1150        Stmt::Doc(node) => {
1151            out.push_str("{\n");
1152            write_indent(out, level + 1);
1153            out.push_str("\"kind\": \"Doc\",\n");
1154            write_indent(out, level + 1);
1155            out.push_str("\"meta\": ");
1156            write_json_meta(&node.meta, out, level + 1);
1157            out.push_str(",\n");
1158            write_indent(out, level + 1);
1159            out.push_str("\"text\": ");
1160            write_json_string(&node.text, out);
1161            out.push('\n');
1162            write_indent(out, level);
1163            out.push('}');
1164        }
1165        Stmt::Comment(node) => {
1166            out.push_str("{\n");
1167            write_indent(out, level + 1);
1168            out.push_str("\"kind\": \"Comment\",\n");
1169            write_indent(out, level + 1);
1170            out.push_str("\"meta\": ");
1171            write_json_meta(&node.meta, out, level + 1);
1172            out.push_str(",\n");
1173            write_indent(out, level + 1);
1174            out.push_str("\"text\": ");
1175            write_json_string(&node.text, out);
1176            out.push('\n');
1177            write_indent(out, level);
1178            out.push('}');
1179        }
1180    }
1181}
1182
1183fn write_json_item(item: &Item, out: &mut String, level: usize) {
1184    match item {
1185        Item::Mod(node) => {
1186            out.push_str("{\n");
1187            write_indent(out, level + 1);
1188            out.push_str("\"kind\": \"Mod\",\n");
1189            write_indent(out, level + 1);
1190            out.push_str("\"meta\": ");
1191            write_json_meta(&node.meta, out, level + 1);
1192            out.push_str(",\n");
1193            write_indent(out, level + 1);
1194            out.push_str("\"name\": ");
1195            write_json_string(&node.name, out);
1196            out.push_str(",\n");
1197            write_indent(out, level + 1);
1198            out.push_str("\"items\": [\n");
1199            for (index, child) in node.items.iter().enumerate() {
1200                if index > 0 {
1201                    out.push_str(",\n");
1202                }
1203                write_indent(out, level + 2);
1204                write_json_item(child, out, level + 2);
1205            }
1206            out.push('\n');
1207            write_indent(out, level + 1);
1208            out.push_str("]\n");
1209            write_indent(out, level);
1210            out.push('}');
1211        }
1212        Item::Use(node) => {
1213            out.push_str("{\n");
1214            write_indent(out, level + 1);
1215            out.push_str("\"kind\": \"Use\",\n");
1216            write_indent(out, level + 1);
1217            out.push_str("\"meta\": ");
1218            write_json_meta(&node.meta, out, level + 1);
1219            out.push_str(",\n");
1220            write_indent(out, level + 1);
1221            out.push_str("\"tree\": ");
1222            write_json_use_tree(&node.tree, out, level + 1);
1223            out.push('\n');
1224            write_indent(out, level);
1225            out.push('}');
1226        }
1227        Item::Struct(node) => {
1228            out.push_str("{\n");
1229            write_indent(out, level + 1);
1230            out.push_str("\"kind\": \"Struct\",\n");
1231            write_indent(out, level + 1);
1232            out.push_str("\"meta\": ");
1233            write_json_meta(&node.meta, out, level + 1);
1234            out.push_str(",\n");
1235            write_indent(out, level + 1);
1236            out.push_str("\"name\": ");
1237            write_json_string(&node.name, out);
1238            out.push_str(",\n");
1239            write_indent(out, level + 1);
1240            out.push_str("\"fields\": [\n");
1241            for (index, field) in node.fields.iter().enumerate() {
1242                if index > 0 {
1243                    out.push_str(",\n");
1244                }
1245                write_indent(out, level + 2);
1246                out.push_str("{\n");
1247                write_indent(out, level + 3);
1248                out.push_str("\"meta\": ");
1249                write_json_meta(&field.meta, out, level + 3);
1250                out.push_str(",\n");
1251                write_indent(out, level + 3);
1252                out.push_str("\"name\": ");
1253                write_json_string(&field.name, out);
1254                out.push_str(",\n");
1255                write_indent(out, level + 3);
1256                out.push_str("\"ty\": ");
1257                write_json_type(&field.ty, out, level + 3);
1258                out.push('\n');
1259                write_indent(out, level + 2);
1260                out.push('}');
1261            }
1262            out.push('\n');
1263            write_indent(out, level + 1);
1264            out.push_str("]\n");
1265            write_indent(out, level);
1266            out.push('}');
1267        }
1268        Item::Enum(node) => {
1269            out.push_str("{\n");
1270            write_indent(out, level + 1);
1271            out.push_str("\"kind\": \"Enum\",\n");
1272            write_indent(out, level + 1);
1273            out.push_str("\"meta\": ");
1274            write_json_meta(&node.meta, out, level + 1);
1275            out.push_str(",\n");
1276            write_indent(out, level + 1);
1277            out.push_str("\"name\": ");
1278            write_json_string(&node.name, out);
1279            out.push_str(",\n");
1280            write_indent(out, level + 1);
1281            out.push_str("\"variants\": [\n");
1282            for (index, variant) in node.variants.iter().enumerate() {
1283                if index > 0 {
1284                    out.push_str(",\n");
1285                }
1286                write_indent(out, level + 2);
1287                out.push_str("{\n");
1288                write_indent(out, level + 3);
1289                out.push_str("\"meta\": ");
1290                write_json_meta(&variant.meta, out, level + 3);
1291                out.push_str(",\n");
1292                write_indent(out, level + 3);
1293                out.push_str("\"name\": ");
1294                write_json_string(&variant.name, out);
1295                out.push('\n');
1296                write_indent(out, level + 2);
1297                out.push('}');
1298            }
1299            out.push('\n');
1300            write_indent(out, level + 1);
1301            out.push_str("]\n");
1302            write_indent(out, level);
1303            out.push('}');
1304        }
1305        Item::Fn(node) => {
1306            out.push_str("{\n");
1307            write_indent(out, level + 1);
1308            out.push_str("\"kind\": \"Fn\",\n");
1309            write_indent(out, level + 1);
1310            out.push_str("\"meta\": ");
1311            write_json_meta(&node.meta, out, level + 1);
1312            out.push_str(",\n");
1313            write_indent(out, level + 1);
1314            out.push_str("\"name\": ");
1315            write_json_string(&node.name, out);
1316            out.push_str(",\n");
1317            write_indent(out, level + 1);
1318            out.push_str("\"params\": [\n");
1319            for (index, param) in node.params.iter().enumerate() {
1320                if index > 0 {
1321                    out.push_str(",\n");
1322                }
1323                write_indent(out, level + 2);
1324                out.push_str("{\n");
1325                write_indent(out, level + 3);
1326                out.push_str("\"meta\": ");
1327                write_json_meta(&param.meta, out, level + 3);
1328                out.push_str(",\n");
1329                write_indent(out, level + 3);
1330                out.push_str("\"name\": ");
1331                write_json_string(&param.name, out);
1332                out.push_str(",\n");
1333                write_indent(out, level + 3);
1334                out.push_str("\"ty\": ");
1335                write_json_type(&param.ty, out, level + 3);
1336                out.push('\n');
1337                write_indent(out, level + 2);
1338                out.push('}');
1339            }
1340            out.push('\n');
1341            write_indent(out, level + 1);
1342            out.push_str("],\n");
1343            write_indent(out, level + 1);
1344            out.push_str("\"ret_ty\": ");
1345            match &node.ret_ty {
1346                Some(ret_ty) => write_json_type(ret_ty, out, level + 1),
1347                None => out.push_str("null"),
1348            }
1349            out.push_str(",\n");
1350            write_indent(out, level + 1);
1351            out.push_str("\"body\": ");
1352            write_json_block(&node.body, out, level + 1);
1353            out.push('\n');
1354            write_indent(out, level);
1355            out.push('}');
1356        }
1357        Item::Doc(node) => {
1358            out.push_str("{\n");
1359            write_indent(out, level + 1);
1360            out.push_str("\"kind\": \"Doc\",\n");
1361            write_indent(out, level + 1);
1362            out.push_str("\"meta\": ");
1363            write_json_meta(&node.meta, out, level + 1);
1364            out.push_str(",\n");
1365            write_indent(out, level + 1);
1366            out.push_str("\"text\": ");
1367            write_json_string(&node.text, out);
1368            out.push('\n');
1369            write_indent(out, level);
1370            out.push('}');
1371        }
1372        Item::Comment(node) => {
1373            out.push_str("{\n");
1374            write_indent(out, level + 1);
1375            out.push_str("\"kind\": \"Comment\",\n");
1376            write_indent(out, level + 1);
1377            out.push_str("\"meta\": ");
1378            write_json_meta(&node.meta, out, level + 1);
1379            out.push_str(",\n");
1380            write_indent(out, level + 1);
1381            out.push_str("\"text\": ");
1382            write_json_string(&node.text, out);
1383            out.push('\n');
1384            write_indent(out, level);
1385            out.push('}');
1386        }
1387    }
1388}
1389
1390fn write_file_json(file: &File, out: &mut String, level: usize) {
1391    out.push_str("{\n");
1392    write_indent(out, level + 1);
1393    out.push_str("\"items\": [\n");
1394    for (index, item) in file.items.iter().enumerate() {
1395        if index > 0 {
1396            out.push_str(",\n");
1397        }
1398        write_indent(out, level + 2);
1399        write_json_item(item, out, level + 2);
1400    }
1401    out.push('\n');
1402    write_indent(out, level + 1);
1403    out.push_str("]\n");
1404    write_indent(out, level);
1405    out.push('}');
1406}