rtlola_frontend/
tag_parser.rs

1//! This module provides a parser to interpret the annotated tags of a specification
2//! as a custom type.
3//!
4//! Each parser should implement the [TagParser] trait.
5//! The main entry points for applying the parser is the [RtLolaMir::parse_tags] method,
6//! which receives a list of parsers and returns a list of parse results.
7//! The [RtLolaMir::validate_tags] method allows for validating that all annotated tags are
8//! validated by any parser. This allows for typos to be detected.
9
10pub 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
20/// Represents a parser supporting a subset of all tags annotated to the specification
21pub trait TagParser: TagValidator {
22    /// The type representing the result of parsing global tags
23    type GlobalTags;
24    /// The type representing the result of parsing local tags (on streams)
25    type LocalTags;
26
27    /// Parses the global tags to `Self::GlobalTags`
28    fn parse_global(
29        &self,
30        global_tags: &HashMap<String, Option<String>>,
31        mir: &RtLolaMir,
32    ) -> Result<Self::GlobalTags, RtLolaError>;
33
34    /// Parses the tags annotated to a stream to `Self::LocalTags`
35    fn parse_local(
36        &self,
37        sr: StreamReference,
38        tags: &HashMap<String, Option<String>>,
39        mir: &RtLolaMir,
40    ) -> Result<Self::LocalTags, RtLolaError>;
41}
42
43/// Specifies for a [TagParser] the tag-keys that are validated by the Parser.
44/// All other keys are ignored.
45pub trait TagValidator {
46    /// Returns the global/local tags that are supported by the parser.
47    fn supported_tags<'a>(&self, mir: &'a RtLolaMir) -> (HashSet<&'a str>, HashSet<&'a str>);
48}
49
50/// The result after applying a [TagParser] to a specification
51#[derive(Debug)]
52pub struct ParseResult<GlobalTags, LocalTags> {
53    /// The representation of the global tags
54    global_tags: GlobalTags,
55    /// A mapping from streams to the corresponding representation of the local tags
56    local_tags: HashMap<StreamReference, LocalTags>,
57}
58
59impl<GlobalTags, LocalTags> ParseResult<GlobalTags, LocalTags> {
60    /// Returns the representation returned by the parser for the global tags
61    pub fn global_tags(&self) -> &GlobalTags {
62        &self.global_tags
63    }
64
65    /// Returns the representation returned by the parser for the given stream (if it exists)
66    pub fn local_tags(&self, sr: StreamReference) -> Option<&LocalTags> {
67        self.local_tags.get(&sr)
68    }
69}
70
71impl RtLolaMir {
72    /// Checks and applies the given parser to the specification to return a list of [ParseResult]'s.
73    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    /// Checks whether the specification contains tags that are not handled by any parser
90    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 = &gt - &pgt;
105                lt = &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    /// Returns the keys of all global tags in the specification
119    pub fn all_global_tags(&self) -> HashSet<&str> {
120        self.global_tags.keys().map(|s| s.as_str()).collect()
121    }
122
123    /// Returns the keys of all local tags used anywhere in the specification
124    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}