1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use crate::ByteSpan;
use crate::SourceMap;
use crate::SourceSpan;
/// Append the source text for `span` to `sink` by slicing
/// directly from `source` via byte offsets (zero-copy,
/// lossless).
pub(crate) fn append_span_source_slice(
span: ByteSpan,
sink: &mut String,
source: &str,
) {
let start = span.start as usize;
let end = span.end as usize;
debug_assert!(
start <= end,
"append_span_source_slice: inverted span \
(start {start} > end {end})",
);
debug_assert!(
end <= source.len(),
"append_span_source_slice: span byte range \
{}..{} exceeds source length {}",
start,
end,
source.len(),
);
sink.push_str(&source[start..end]);
}
/// Trait implemented by all AST node types. Provides source
/// reconstruction and span access methods.
///
/// All AST node types implement this trait via
/// `#[inherent] impl AstNode`, giving each node both inherent
/// methods (no trait import needed) and a trait bound for generic
/// utilities (error formatters, linters, etc.).
///
/// # Source Reconstruction Modes
///
/// - **Source-slice mode (fast, lossless):** When `source` is
/// `Some(s)`, [`append_source`](AstNode::append_source) slices
/// `&s[span.start.byte_offset..span.end.byte_offset]`. This is
/// the common path for string-based token sources. Zero
/// allocation.
///
/// - **Synthetic-formatting mode (slower, lossy):** When `source`
/// is `None`, [`append_source`](AstNode::append_source) walks the
/// AST and emits keywords, names, values, and punctuation with
/// standard spacing. The output is semantically equivalent but not
/// formatting-identical.
///
/// # Span Access
///
/// Every AST node carries a [`ByteSpan`] recording its
/// byte-offset range in the source text.
/// [`byte_span()`](AstNode::byte_span) exposes this uniformly
/// across all node types, and
/// [`source_span()`](AstNode::source_span) resolves it to
/// line/column coordinates on demand via a [`SourceMap`].
pub trait AstNode {
/// Append this node's source representation to `sink`.
///
/// When `source` is `Some(s)`, slices the original source text
/// directly via byte offsets (zero-copy, lossless). When
/// `source` is `None`, reconstructs from semantic data with
/// standard formatting (lossy but semantically equivalent).
fn append_source(
&self,
sink: &mut String,
source: Option<&str>,
);
/// Returns this node's byte-offset span within the source
/// text.
///
/// The returned [`ByteSpan`] is a compact `[start, end)`
/// byte range that can be resolved to line/column positions
/// via [`source_span()`](AstNode::source_span) or
/// [`ByteSpan::resolve()`].
fn byte_span(&self) -> ByteSpan;
/// Resolves this node's position to line/column coordinates
/// using the given [`SourceMap`].
///
/// Returns [`None`] if the byte offsets cannot be resolved
/// (e.g. the node was synthetically constructed without
/// valid span data).
fn source_span(
&self,
source_map: &SourceMap,
) -> Option<SourceSpan> {
self.byte_span().resolve(source_map)
}
/// Return this node as a source string.
///
/// Convenience wrapper around
/// [`append_source`](AstNode::append_source).
fn to_source(
&self,
source: Option<&str>,
) -> String {
let mut s = String::new();
self.append_source(&mut s, source);
s
}
}