trustfall_core/frontend/
tags.rs1use std::{
2 collections::{BTreeMap, BTreeSet},
3 fmt::Debug,
4};
5
6use super::util::ComponentPath;
7use crate::{
8 ir::{FieldRef, Vid},
9 util::{BTreeMapOccupiedError, BTreeMapTryInsertExt},
10};
11
12#[derive(Debug, Default)]
13pub(super) struct TagHandler<'a> {
14 tags: BTreeMap<&'a str, TagEntry<'a>>,
15 used_tags: BTreeSet<&'a str>,
16 component_imported_tags: Vec<(Vid, Vec<FieldRef>)>,
17}
18
19#[derive(Debug, Clone)]
20pub(super) struct TagEntry<'a> {
21 pub(super) name: &'a str,
22 pub(super) field: FieldRef,
23 pub(super) path: ComponentPath,
24}
25
26impl<'a> TagEntry<'a> {
27 fn new(name: &'a str, field: FieldRef, path: ComponentPath) -> Self {
28 Self { name, field, path }
29 }
30}
31
32impl<'a> TagHandler<'a> {
33 #[inline]
34 pub(super) fn new() -> Self {
35 Default::default()
36 }
37
38 pub(super) fn register_tag(
39 &mut self,
40 name: &'a str,
41 field: FieldRef,
42 path: &ComponentPath,
43 ) -> Result<(), BTreeMapOccupiedError<'_, &'a str, TagEntry<'a>>> {
44 self.tags.insert_or_error(name, TagEntry::new(name, field, path.clone()))?;
45
46 Ok(())
47 }
48
49 pub(super) fn begin_subcomponent(&mut self, component_root: Vid) {
50 self.component_imported_tags.push((component_root, vec![]));
51 }
52
53 pub(super) fn end_subcomponent(&mut self, component_root: Vid) -> Vec<FieldRef> {
54 let (expected_vid, external_tags) = self.component_imported_tags.pop().unwrap();
55 assert_eq!(expected_vid, component_root);
56 external_tags
57 }
58
59 pub(super) fn reference_tag(
60 &mut self,
61 name: &str,
62 use_path: &ComponentPath,
63 use_vid: Vid,
64 ) -> Result<&TagEntry<'_>, TagLookupError> {
65 let entry =
66 self.tags.get(name).ok_or_else(|| TagLookupError::UndefinedTag(name.to_string()))?;
67
68 if entry.path.is_parent(use_path) {
69 if entry.field.defined_at() > use_vid {
70 return Err(TagLookupError::TagUsedBeforeDefinition(name.to_string()));
71 }
72
73 if &entry.path != use_path {
74 let importing_component_root = use_path[entry.path.len()];
77
78 let (component_root, imported_tags) =
81 self.component_imported_tags.get_mut(entry.path.len() - 1).unwrap();
82 assert_eq!(*component_root, importing_component_root);
83 imported_tags.push(entry.field.clone());
84 }
85
86 self.used_tags.insert(entry.name);
87 Ok(entry)
88 } else {
89 Err(TagLookupError::TagDefinedInsideFold(name.to_string()))
92 }
93 }
94
95 pub(super) fn finish(self) -> Result<(), BTreeSet<&'a str>> {
96 let unused_tags: BTreeSet<_> =
97 self.tags.keys().copied().filter(|x| !self.used_tags.contains(x)).collect();
98 if unused_tags.is_empty() {
99 Ok(())
100 } else {
101 Err(unused_tags)
102 }
103 }
104}
105
106pub(super) enum TagLookupError {
107 UndefinedTag(String),
108 TagUsedBeforeDefinition(String),
109 TagDefinedInsideFold(String),
110}