asdf_yaml/node.rs
1//! Nodes in an ASDF YAML document.
2
3use crate::tag::Tag;
4
5/// An index into a [`Document`](crate::Document)'s node arena.
6///
7/// Nodes are addressed by index rather than by reference so that aliases can
8/// genuinely share a target, and so that a handle to a node (which the C API
9/// exposes as `asdf_value_t`) stays valid while the document is mutated.
10#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
11pub struct NodeId(pub(crate) u32);
12
13impl NodeId {
14 /// The raw arena index.
15 pub fn index(self) -> usize {
16 self.0 as usize
17 }
18}
19
20/// How a scalar was written in the source document.
21///
22/// This is preserved because ASDF's type resolution depends on it: a quoted
23/// `"123"` is a string, an unquoted `123` is an integer.
24#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
25pub enum ScalarStyle {
26 /// Unquoted.
27 #[default]
28 Plain,
29 /// Wrapped in `'`.
30 SingleQuoted,
31 /// Wrapped in `"`.
32 DoubleQuoted,
33 /// A `|` block scalar.
34 Literal,
35 /// A `>` block scalar.
36 Folded,
37}
38
39impl ScalarStyle {
40 /// Whether this style forces the scalar to be a string regardless of
41 /// its content.
42 ///
43 /// Mirrors libasdf: a scalar is a string if it is explicitly quoted, or
44 /// uses a literal or folded representation.
45 pub fn is_quoted(self) -> bool {
46 !matches!(self, ScalarStyle::Plain)
47 }
48}
49
50/// Whether a collection was written inline or as an indented block.
51#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
52pub enum CollectionStyle {
53 /// Let the emitter choose.
54 #[default]
55 Auto,
56 /// `{...}` / `[...]`.
57 Flow,
58 /// Indented block notation.
59 Block,
60}
61
62/// A key/value pair in a mapping.
63///
64/// Keys are nodes rather than strings so that complex keys round-trip, even
65/// though ASDF trees use string keys almost exclusively.
66#[derive(Clone, Debug, PartialEq)]
67pub struct Entry {
68 /// The key node.
69 pub key: NodeId,
70 /// The value node.
71 pub value: NodeId,
72}
73
74/// The payload of a node.
75#[derive(Clone, Debug, PartialEq)]
76pub enum NodeData {
77 /// A scalar, kept in its source representation. Type resolution happens on
78 /// demand rather than at parse time, so that the raw text is never lost.
79 Scalar {
80 /// The scalar text, with escapes already processed by the parser.
81 value: String,
82 /// How it was written.
83 style: ScalarStyle,
84 },
85 /// An ordered list of nodes.
86 Sequence {
87 /// The items.
88 items: Vec<NodeId>,
89 /// How it was written.
90 style: CollectionStyle,
91 },
92 /// An ordered list of key/value pairs.
93 Mapping {
94 /// The entries, in document order.
95 entries: Vec<Entry>,
96 /// How it was written.
97 style: CollectionStyle,
98 },
99 /// A reference to an anchored node elsewhere in the document.
100 ///
101 /// Kept distinct from its target so that the alias survives a round trip
102 /// as `*anchor` rather than being expanded into a copy.
103 Alias(NodeId),
104}
105
106/// A node in the document tree.
107#[derive(Clone, Debug, PartialEq)]
108pub struct Node {
109 /// The node's explicit tag, if it carried one.
110 pub tag: Option<Tag>,
111 /// The anchor name this node was defined under, if any.
112 pub anchor: Option<String>,
113 /// The node's payload.
114 pub data: NodeData,
115 /// Byte range in the source document, when the node was parsed rather
116 /// than constructed.
117 pub span: Option<Span>,
118}
119
120/// A byte range in the source document.
121#[derive(Clone, Copy, PartialEq, Eq, Debug)]
122pub struct Span {
123 /// Byte offset of the first byte.
124 pub start: usize,
125 /// Byte offset one past the last byte.
126 pub end: usize,
127}
128
129impl Node {
130 /// A node with the given payload and nothing else set.
131 pub fn new(data: NodeData) -> Self {
132 Self { tag: None, anchor: None, data, span: None }
133 }
134
135 /// A plain scalar node.
136 pub fn scalar(value: impl Into<String>) -> Self {
137 Self::new(NodeData::Scalar { value: value.into(), style: ScalarStyle::Plain })
138 }
139
140 /// A scalar node with an explicit style.
141 pub fn scalar_styled(value: impl Into<String>, style: ScalarStyle) -> Self {
142 Self::new(NodeData::Scalar { value: value.into(), style })
143 }
144
145 /// An empty sequence node.
146 pub fn sequence() -> Self {
147 Self::new(NodeData::Sequence { items: Vec::new(), style: CollectionStyle::Auto })
148 }
149
150 /// An empty mapping node.
151 pub fn mapping() -> Self {
152 Self::new(NodeData::Mapping { entries: Vec::new(), style: CollectionStyle::Auto })
153 }
154
155 /// Attach a tag, builder-style.
156 pub fn with_tag(mut self, tag: Tag) -> Self {
157 self.tag = Some(tag);
158 self
159 }
160
161 /// Whether this node is a scalar.
162 pub fn is_scalar(&self) -> bool {
163 matches!(self.data, NodeData::Scalar { .. })
164 }
165
166 /// Whether this node is a sequence.
167 pub fn is_sequence(&self) -> bool {
168 matches!(self.data, NodeData::Sequence { .. })
169 }
170
171 /// Whether this node is a mapping.
172 pub fn is_mapping(&self) -> bool {
173 matches!(self.data, NodeData::Mapping { .. })
174 }
175
176 /// Whether this node is an alias to another node.
177 pub fn is_alias(&self) -> bool {
178 matches!(self.data, NodeData::Alias(_))
179 }
180
181 /// The scalar text, if this is a scalar.
182 pub fn as_str(&self) -> Option<&str> {
183 match &self.data {
184 NodeData::Scalar { value, .. } => Some(value),
185 _ => None,
186 }
187 }
188
189 /// The scalar style, if this is a scalar.
190 pub fn scalar_style(&self) -> Option<ScalarStyle> {
191 match &self.data {
192 NodeData::Scalar { style, .. } => Some(*style),
193 _ => None,
194 }
195 }
196}