rtlola_frontend/
tag_parser.rs1pub mod all;
11pub mod verbosity_parser;
12
13use std::collections::{HashMap, HashSet};
14
15use rtlola_hir::hir::StreamReference;
16use rtlola_reporting::{Diagnostic, RtLolaError};
17
18use super::RtLolaMir;
19
20pub trait TagParser: TagValidator {
22 type GlobalTags;
24 type LocalTags;
26
27 fn parse_global(
29 &self,
30 global_tags: &HashMap<String, Option<String>>,
31 mir: &RtLolaMir,
32 ) -> Result<Self::GlobalTags, RtLolaError>;
33
34 fn parse_local(
36 &self,
37 sr: StreamReference,
38 tags: &HashMap<String, Option<String>>,
39 mir: &RtLolaMir,
40 ) -> Result<Self::LocalTags, RtLolaError>;
41}
42
43pub trait TagValidator {
46 fn supported_tags<'a>(&self, mir: &'a RtLolaMir) -> (HashSet<&'a str>, HashSet<&'a str>);
48}
49
50#[derive(Debug)]
52pub struct ParseResult<GlobalTags, LocalTags> {
53 global_tags: GlobalTags,
55 local_tags: HashMap<StreamReference, LocalTags>,
57}
58
59impl<GlobalTags, LocalTags> ParseResult<GlobalTags, LocalTags> {
60 pub fn global_tags(&self) -> &GlobalTags {
62 &self.global_tags
63 }
64
65 pub fn local_tags(&self, sr: StreamReference) -> Option<&LocalTags> {
67 self.local_tags.get(&sr)
68 }
69}
70
71impl RtLolaMir {
72 pub fn parse_tags<T: TagParser>(
74 &self,
75 p: T,
76 ) -> Result<ParseResult<T::GlobalTags, T::LocalTags>, RtLolaError> {
77 let global_tags = p.parse_global(&self.global_tags, self)?;
78 let local_tags_iter = self
79 .all_streams()
80 .map(|sr| Ok((sr, p.parse_local(sr, self.stream(sr).tags(), self)?)));
81 let local_tags = RtLolaError::collect(local_tags_iter)?;
82
83 Ok(ParseResult {
84 global_tags,
85 local_tags,
86 })
87 }
88
89 pub fn validate_tags(&self, parser: &[&dyn TagValidator]) -> Result<(), RtLolaError> {
91 let global_keys = self
92 .global_tags
93 .keys()
94 .map(|k| k.as_str())
95 .collect::<HashSet<_>>();
96 let local_keys = self
97 .all_streams()
98 .flat_map(|sr| self.stream(sr).tags().keys().map(|k| k.as_str()))
99 .collect::<HashSet<_>>();
100 let (unused_gt, unused_lt) = parser.iter().fold(
101 (global_keys.clone(), local_keys.clone()),
102 |(mut gt, mut lt), p| {
103 let (pgt, plt) = p.supported_tags(self);
104 gt = > - &pgt;
105 lt = < - &plt;
106 (gt, lt)
107 },
108 );
109 if !unused_gt.is_empty() {
110 return Err(Diagnostic::error(&format!("Unused global tags: {unused_gt:?}")).into());
111 }
112 if !unused_lt.is_empty() {
113 return Err(Diagnostic::error(&format!("Unused local tags: {unused_lt:?}")).into());
114 }
115 Ok(())
116 }
117
118 pub fn all_global_tags(&self) -> HashSet<&str> {
120 self.global_tags.keys().map(|s| s.as_str()).collect()
121 }
122
123 pub fn all_local_tags(&self) -> HashSet<&str> {
125 self.all_streams()
126 .flat_map(|sr| self.stream(sr).tags().keys())
127 .map(|s| s.as_str())
128 .collect()
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use rtlola_parser::ParserConfig;
135
136 use super::all::{AllowAll, DenyAll};
137 use crate::parse;
138 use crate::tag_parser::verbosity_parser::{DebugParser, VerbosityParser};
139
140 #[test]
141 fn check_tags1() {
142 let spec = "
143 #![supported,unsupported]
144 input a : UInt64\n\
145 #[tag,unsupported=\"value\", unsupported2]
146 output b := a + 1\n\
147 trigger b > 10";
148 let mir = parse(&ParserConfig::for_string(spec.into())).unwrap();
149 assert!(mir.validate_tags(&[&AllowAll]).is_ok());
150 assert!(mir.validate_tags(&[&DenyAll]).is_err());
151 }
152
153 #[test]
154 fn check_tags_verbosity() {
155 let spec = "
156 input a : UInt64\n\
157 #[verbosity=\"public\"]
158 output b := a + 1\n\
159 #[warning]
160 trigger b > 10";
161 let mir = parse(&ParserConfig::for_string(spec.into())).unwrap();
162 mir.validate_tags(&[&VerbosityParser]).unwrap();
163 }
164
165 #[test]
166 fn check_tags_verbosity2() {
167 let spec = "
168 input a : UInt64\n\
169 #[verbosity=\"public\"]
170 output b := a + 1\n\
171 #[warnig] // <-- typo here
172 trigger b > 10";
173 let mir = parse(&ParserConfig::for_string(spec.into())).unwrap();
174 assert!(mir.validate_tags(&[&VerbosityParser]).is_err());
175 }
176
177 #[test]
178 fn check_tags_verbosity_and_debug() {
179 let spec = "
180 input a : UInt64\n\
181 #[verbosity=\"public\", debug]
182 output b := a + 1\n\
183 #[debug]
184 trigger b > 10";
185 let mir = parse(&ParserConfig::for_string(spec.into())).unwrap();
186 mir.validate_tags(&[&VerbosityParser, &DebugParser])
187 .unwrap();
188 }
189
190 #[test]
191 fn check_tags_verbosity_and_debug2() {
192 let spec = "
193 input a : UInt64\n\
194 #[verbosity=\"public\", debug]
195 output b := a + 1\n\
196 #[debg] // <-- typo here
197 trigger b > 10";
198 let mir = parse(&ParserConfig::for_string(spec.into())).unwrap();
199 assert!(mir
200 .validate_tags(&[&VerbosityParser, &DebugParser])
201 .is_err());
202 }
203}