1use crate::{
2 CollectionStyle, Diagnostic, DiagnosticKind, NodeId, Span, YamlError, YamlEventKind,
3 YamlScalarStyle,
4};
5
6const NO_SEMANTIC_NODE: u32 = u32::MAX;
7const NO_PROPERTIES: u32 = u32::MAX;
8const EXPLICIT_START: u8 = 1 << 0;
9const EXPLICIT_END: u8 = 1 << 1;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum SemanticKind {
14 Document,
16 Mapping {
18 style: CollectionStyle,
20 },
21 Sequence {
23 style: CollectionStyle,
25 },
26 Scalar {
28 style: YamlScalarStyle,
30 },
31 Alias,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub(crate) struct SemanticNode {
37 pub(crate) kind: SemanticKind,
38 flags: u8,
39 padding: u8,
40 pub(crate) span_start: u32,
41 pub(crate) end_offset: u32,
42 property: u32,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub(crate) struct SemanticProperties {
47 pub(crate) tag: Option<Span>,
48 pub(crate) anchor: Option<Span>,
49 pub(crate) alias: Option<Span>,
50 pub(crate) content_indent: Option<u32>,
51}
52
53impl SemanticProperties {
54 pub(crate) const NONE: Self = Self {
55 tag: None,
56 anchor: None,
57 alias: None,
58 content_indent: None,
59 };
60
61 fn is_empty(self) -> bool {
62 self == Self::NONE
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67struct PropertyRecord {
68 properties: SemanticProperties,
69 document: NodeId,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73struct AnchorBinding {
74 name: Span,
75 target: NodeId,
76 document: NodeId,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80struct TagDirectiveBinding {
81 handle: Span,
82 prefix: Span,
83 document: NodeId,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub(crate) struct SemanticStore {
89 slots: Vec<u32>,
90 nodes: Vec<SemanticNode>,
91 properties: Vec<PropertyRecord>,
92 anchors: Vec<AnchorBinding>,
93 tag_directives: Vec<TagDirectiveBinding>,
94 pub(crate) documents: Vec<NodeId>,
95}
96
97impl SemanticStore {
98 fn insert(&mut self, cst: NodeId, node: SemanticNode) {
99 if self.slots.len() <= cst.as_usize() {
100 self.slots
101 .resize(cst.as_usize().saturating_add(1), NO_SEMANTIC_NODE);
102 }
103 let index = u32::try_from(self.nodes.len()).expect("semantic arena exceeds u32 capacity");
104 self.slots[cst.as_usize()] = index;
105 self.nodes.push(node);
106 }
107
108 fn close(&mut self, cst: NodeId, span: Span, explicit: Option<bool>) {
109 let index = self.slots[cst.as_usize()] as usize;
110 self.nodes[index].end_offset = span.end;
111 if let Some(explicit) = explicit {
112 self.nodes[index].set_flag(EXPLICIT_END, explicit);
113 }
114 }
115
116 pub(crate) fn get(&self, cst: NodeId) -> Option<&SemanticNode> {
117 let index = *self.slots.get(cst.as_usize())?;
118 (index != NO_SEMANTIC_NODE).then(|| &self.nodes[index as usize])
119 }
120
121 pub(crate) fn properties(&self, cst: NodeId) -> Option<SemanticProperties> {
122 let node = self.get(cst)?;
123 (node.property != NO_PROPERTIES).then(|| self.properties[node.property as usize].properties)
124 }
125
126 pub(crate) fn clear_tag(&mut self, cst: NodeId) {
127 let Some(property) = self.get(cst).map(|node| node.property) else {
128 return;
129 };
130 if property != NO_PROPERTIES {
131 self.properties[property as usize].properties.tag = None;
132 }
133 }
134
135 pub(crate) fn property_document(&self, cst: NodeId) -> Option<NodeId> {
136 let node = self.get(cst)?;
137 (node.property != NO_PROPERTIES).then(|| self.properties[node.property as usize].document)
138 }
139
140 pub(crate) fn anchors(&self) -> impl DoubleEndedIterator<Item = (Span, NodeId, NodeId)> + '_ {
141 self.anchors
142 .iter()
143 .map(|binding| (binding.name, binding.target, binding.document))
144 }
145
146 pub(crate) fn tag_directives(
147 &self,
148 document: NodeId,
149 ) -> impl Iterator<Item = (Span, Span)> + '_ {
150 self.tag_directives
151 .iter()
152 .filter(move |binding| binding.document == document)
153 .map(|binding| (binding.handle, binding.prefix))
154 }
155}
156
157pub(crate) struct SemanticBuilder {
158 store: SemanticStore,
159 open: Vec<OpenNode>,
160 error: Option<YamlError>,
161}
162
163impl SemanticBuilder {
164 pub(crate) fn with_capacity(cst_capacity: usize, semantic_capacity: usize) -> Self {
165 Self {
166 store: SemanticStore {
167 slots: Vec::with_capacity(cst_capacity),
168 nodes: Vec::with_capacity(semantic_capacity),
169 properties: Vec::new(),
170 anchors: Vec::new(),
171 tag_directives: Vec::new(),
172 documents: Vec::with_capacity(1),
173 },
174 open: Vec::with_capacity(8),
175 error: None,
176 }
177 }
178
179 pub(crate) fn push(
180 &mut self,
181 kind: YamlEventKind,
182 span: Span,
183 cst: Option<NodeId>,
184 properties: SemanticProperties,
185 ) {
186 if self.error.is_some() {
187 return;
188 }
189 if let Err(error) = self.try_push(kind, span, cst, properties) {
190 self.error = Some(error);
191 }
192 }
193
194 fn try_push(
195 &mut self,
196 kind: YamlEventKind,
197 span: Span,
198 cst: Option<NodeId>,
199 properties: SemanticProperties,
200 ) -> Result<(), YamlError> {
201 match kind {
202 YamlEventKind::StreamStart | YamlEventKind::StreamEnd => Ok(()),
203 YamlEventKind::DocumentStart { explicit } => {
204 let cst = required_cst(cst, span)?;
205 for directive in self
206 .store
207 .tag_directives
208 .iter_mut()
209 .rev()
210 .take_while(|directive| directive.document == NodeId(u32::MAX))
211 {
212 directive.document = cst;
213 }
214 self.store.documents.push(cst);
215 let property = self.insert_properties(cst, properties);
216 self.store.insert(
217 cst,
218 SemanticNode::new(SemanticKind::Document, span, explicit, property),
219 );
220 self.open.push(OpenNode::Document { cst, children: 0 });
221 Ok(())
222 }
223 YamlEventKind::MappingStart { style, .. } => {
224 let cst = required_cst(cst, span)?;
225 let property = self.insert_properties(cst, properties);
226 self.store.insert(
227 cst,
228 SemanticNode::new(SemanticKind::Mapping { style }, span, false, property),
229 );
230 self.open.push(OpenNode::Mapping {
231 cst,
232 waiting_for_value: false,
233 });
234 Ok(())
235 }
236 YamlEventKind::SequenceStart { style, .. } => {
237 let cst = required_cst(cst, span)?;
238 let property = self.insert_properties(cst, properties);
239 self.store.insert(
240 cst,
241 SemanticNode::new(SemanticKind::Sequence { style }, span, false, property),
242 );
243 self.open.push(OpenNode::Sequence { cst });
244 Ok(())
245 }
246 YamlEventKind::Scalar { style, .. } => {
247 let cst = required_cst(cst, span)?;
248 let property = self.insert_properties(cst, properties);
249 self.store.insert(
250 cst,
251 SemanticNode::new(SemanticKind::Scalar { style }, span, false, property),
252 );
253 self.attach_child(cst, span)
254 }
255 YamlEventKind::Alias { .. } => {
256 let cst = required_cst(cst, span)?;
257 let property = self.insert_properties(cst, properties);
258 self.store.insert(
259 cst,
260 SemanticNode::new(SemanticKind::Alias, span, false, property),
261 );
262 self.attach_child(cst, span)
263 }
264 YamlEventKind::MappingEnd => {
265 let Some(OpenNode::Mapping {
266 cst,
267 waiting_for_value,
268 }) = self.open.pop()
269 else {
270 return Err(structure_error("mismatched mapping end event", span));
271 };
272 if waiting_for_value {
273 return Err(structure_error(
274 "mapping entry does not contain a value",
275 span,
276 ));
277 }
278 self.store.close(cst, span, None);
279 self.attach_child(cst, span)
280 }
281 YamlEventKind::SequenceEnd => {
282 let Some(OpenNode::Sequence { cst }) = self.open.pop() else {
283 return Err(structure_error("mismatched sequence end event", span));
284 };
285 self.store.close(cst, span, None);
286 self.attach_child(cst, span)
287 }
288 YamlEventKind::DocumentEnd { explicit } => {
289 let Some(OpenNode::Document { cst, .. }) = self.open.pop() else {
290 return Err(structure_error("mismatched document end event", span));
291 };
292 self.store.close(cst, span, Some(explicit));
293 Ok(())
294 }
295 }
296 }
297
298 pub(crate) fn push_tag_directive(&mut self, handle: Span, prefix: Span) {
299 self.store.tag_directives.push(TagDirectiveBinding {
300 handle,
301 prefix,
302 document: NodeId(u32::MAX),
303 });
304 }
305
306 fn insert_properties(&mut self, target: NodeId, properties: SemanticProperties) -> u32 {
307 if properties.is_empty() {
308 return NO_PROPERTIES;
309 }
310 let document = self
311 .open
312 .iter()
313 .find_map(|node| match node {
314 OpenNode::Document { cst, .. } => Some(*cst),
315 _ => None,
316 })
317 .unwrap_or(target);
318 let index = u32::try_from(self.store.properties.len())
319 .expect("semantic property arena exceeds u32 capacity");
320 self.store.properties.push(PropertyRecord {
321 properties,
322 document,
323 });
324 if let Some(name) = properties.anchor {
325 self.store.anchors.push(AnchorBinding {
326 name,
327 target,
328 document,
329 });
330 }
331 index
332 }
333
334 fn attach_child(&mut self, _child: NodeId, span: Span) -> Result<(), YamlError> {
335 let Some(parent) = self.open.last_mut() else {
336 return Ok(());
337 };
338 match parent {
339 OpenNode::Document { children, .. } => {
340 *children += 1;
341 if *children > 1 {
342 return Err(structure_error(
343 "document contains multiple root nodes",
344 span,
345 ));
346 }
347 }
348 OpenNode::Mapping {
349 waiting_for_value, ..
350 } => {
351 *waiting_for_value = !*waiting_for_value;
352 }
353 OpenNode::Sequence { .. } => {}
354 }
355 Ok(())
356 }
357
358 pub(crate) fn finish(mut self, cst_len: usize) -> Result<SemanticStore, YamlError> {
359 if let Some(error) = self.error {
360 return Err(error);
361 }
362 if !self.open.is_empty() {
363 return Err(structure_error("unclosed semantic node", Span::empty(0)));
364 }
365 self.store.slots.resize(cst_len, NO_SEMANTIC_NODE);
366 Ok(self.store)
367 }
368}
369
370impl SemanticNode {
371 fn new(kind: SemanticKind, span: Span, explicit_start: bool, property: u32) -> Self {
372 Self {
373 kind,
374 flags: u8::from(explicit_start) * EXPLICIT_START,
375 padding: 0,
376 span_start: span.start,
377 end_offset: span.end,
378 property,
379 }
380 }
381
382 pub(crate) const fn explicit_start(self) -> bool {
383 self.flags & EXPLICIT_START != 0
384 }
385
386 pub(crate) const fn explicit_end(self) -> bool {
387 self.flags & EXPLICIT_END != 0
388 }
389
390 fn set_flag(&mut self, flag: u8, value: bool) {
391 if value {
392 self.flags |= flag;
393 } else {
394 self.flags &= !flag;
395 }
396 }
397}
398
399fn required_cst(cst: Option<NodeId>, span: Span) -> Result<NodeId, YamlError> {
400 cst.ok_or_else(|| structure_error("semantic node is missing its CST origin", span))
401}
402
403#[derive(Clone, Copy)]
404enum OpenNode {
405 Document {
406 cst: NodeId,
407 children: usize,
408 },
409 Mapping {
410 cst: NodeId,
411 waiting_for_value: bool,
412 },
413 Sequence {
414 cst: NodeId,
415 },
416}
417
418fn structure_error(message: &str, span: Span) -> YamlError {
419 YamlError::new(Diagnostic::new(DiagnosticKind::Semantic, message, span))
420}
421
422#[cfg(test)]
423mod tests {
424 use super::{SemanticBuilder, SemanticNode, SemanticProperties};
425 use crate::{CollectionStyle, NodeId, Span, YamlEventKind, YamlScalarStyle};
426
427 #[test]
428 fn direct_builder_rejects_dangling_mapping_value() {
429 let mut builder = SemanticBuilder::with_capacity(4, 4);
430 builder.push(
431 YamlEventKind::DocumentStart { explicit: false },
432 Span::empty(0),
433 Some(NodeId(0)),
434 SemanticProperties::NONE,
435 );
436 builder.push(
437 YamlEventKind::MappingStart {
438 style: CollectionStyle::Block,
439 tag: None,
440 anchor: None,
441 },
442 Span::empty(0),
443 Some(NodeId(1)),
444 SemanticProperties::NONE,
445 );
446 builder.push(
447 YamlEventKind::Scalar {
448 style: YamlScalarStyle::Plain,
449 value: String::new(),
450 tag: None,
451 anchor: None,
452 },
453 Span::empty(0),
454 Some(NodeId(2)),
455 SemanticProperties::NONE,
456 );
457 builder.push(
458 YamlEventKind::MappingEnd,
459 Span::empty(0),
460 None,
461 SemanticProperties::NONE,
462 );
463
464 let error = builder.finish(3).expect_err("mapping value is required");
465 assert!(error.to_string().contains("does not contain a value"));
466 }
467
468 #[test]
469 fn direct_builder_rejects_mismatched_collection_end() {
470 let mut builder = SemanticBuilder::with_capacity(2, 2);
471 builder.push(
472 YamlEventKind::SequenceStart {
473 style: CollectionStyle::Flow,
474 tag: None,
475 anchor: None,
476 },
477 Span::empty(0),
478 Some(NodeId(0)),
479 SemanticProperties::NONE,
480 );
481 builder.push(
482 YamlEventKind::MappingEnd,
483 Span::empty(1),
484 None,
485 SemanticProperties::NONE,
486 );
487
488 let error = builder.finish(1).expect_err("collection ends must match");
489 assert!(error.to_string().contains("mismatched mapping end"));
490 }
491
492 #[test]
493 fn direct_builder_rejects_multiple_document_roots() {
494 let mut builder = SemanticBuilder::with_capacity(3, 3);
495 builder.push(
496 YamlEventKind::DocumentStart { explicit: false },
497 Span::empty(0),
498 Some(NodeId(0)),
499 SemanticProperties::NONE,
500 );
501 for cst in [NodeId(1), NodeId(2)] {
502 builder.push(
503 YamlEventKind::Scalar {
504 style: YamlScalarStyle::Plain,
505 value: String::new(),
506 tag: None,
507 anchor: None,
508 },
509 Span::empty(0),
510 Some(cst),
511 SemanticProperties::NONE,
512 );
513 }
514
515 let error = builder.finish(3).expect_err("documents have one root");
516 assert!(error.to_string().contains("multiple root nodes"));
517 }
518
519 #[test]
520 fn semantic_record_is_at_most_sixteen_bytes() {
521 assert!(std::mem::size_of::<SemanticNode>() <= 16);
522 }
523
524 #[test]
525 fn undecorated_nodes_do_not_populate_sparse_arenas() {
526 let mut builder = SemanticBuilder::with_capacity(2, 2);
527 builder.push(
528 YamlEventKind::DocumentStart { explicit: false },
529 Span::empty(0),
530 Some(NodeId(0)),
531 SemanticProperties::NONE,
532 );
533 builder.push(
534 YamlEventKind::Scalar {
535 style: YamlScalarStyle::Plain,
536 value: String::new(),
537 tag: None,
538 anchor: None,
539 },
540 Span::empty(0),
541 Some(NodeId(1)),
542 SemanticProperties::NONE,
543 );
544 builder.push(
545 YamlEventKind::DocumentEnd { explicit: false },
546 Span::empty(0),
547 None,
548 SemanticProperties::NONE,
549 );
550
551 let store = builder.finish(2).expect("semantic structure closes");
552 assert!(store.properties.is_empty());
553 assert!(store.anchors.is_empty());
554 assert!(store.tag_directives.is_empty());
555 }
556}