Skip to main content

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}