donet_core/
dckeyword.rs

1/*
2    This file is part of Donet.
3
4    Copyright © 2024 Max Rodriguez <[email protected]>
5
6    Donet is free software; you can redistribute it and/or modify
7    it under the terms of the GNU Affero General Public License,
8    as published by the Free Software Foundation, either version 3
9    of the License, or (at your option) any later version.
10
11    Donet is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14    GNU Affero General Public License for more details.
15
16    You should have received a copy of the GNU Affero General Public
17    License along with Donet. If not, see <https://www.gnu.org/licenses/>.
18*/
19
20//! Representation of arbitrary and historical
21//! keywords as defined in the DC file.
22
23use crate::hashgen::*;
24use multimap::MultiMap;
25
26/// This is a flag bitmask for historical keywords.
27/// Panda uses a C/C++ 'int' for this, which is stored
28/// as 4 bytes in modern 32-bit and 64-bit C/C++ compilers.
29pub type HistoricalFlag = i32;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct DCKeyword {
33    name: String,
34    // This flag is only kept for historical reasons, so we can
35    // preserve the DC file's hash code if no new flags are in use.
36    historical_flag: HistoricalFlag,
37}
38
39impl From<interim::DCKeyword> for DCKeyword {
40    fn from(value: interim::DCKeyword) -> Self {
41        Self {
42            name: value.name,
43            historical_flag: value.historical_flag,
44        }
45    }
46}
47
48impl std::fmt::Display for DCKeyword {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        write!(f, "keyword ")?;
51        f.write_str(&self.name)?;
52        writeln!(f, ";")
53    }
54}
55
56impl LegacyDCHash for DCKeyword {
57    fn generate_hash(&self, hashgen: &mut DCHashGenerator) {
58        hashgen.add_string(self.name.clone());
59    }
60}
61
62impl DCKeyword {
63    #[inline]
64    pub fn get_name(&self) -> String {
65        self.name.clone()
66    }
67
68    #[inline]
69    pub fn get_historical_flag(&self) -> HistoricalFlag {
70        self.historical_flag
71    }
72}
73
74/// A map of key/value pairs mapping keyword names to DCKeyword struct pointers.
75pub type KeywordName2Keyword<'dc> = MultiMap<String, &'dc DCKeyword>;
76
77/// Represents the two types of inputs that `DCKeywordList.has_keyword`
78/// accepts for looking up a Keyword. In Panda and Astron, the
79/// `has_keyword` method is overloaded instead.
80pub enum IdentifyKeyword {
81    ByStruct(DCKeyword),
82    ByName(String),
83}
84
85/// This is a list of [`DCKeyword`] structures, which represent
86/// communication keywords that may be set on a particular field.
87#[derive(Debug)]
88pub struct DCKeywordList<'dc> {
89    keywords: Vec<&'dc DCKeyword>,
90    kw_name_2_keyword: KeywordName2Keyword<'dc>,
91    flags: HistoricalFlag,
92}
93
94impl std::cmp::PartialEq for DCKeywordList<'_> {
95    fn eq(&self, other: &Self) -> bool {
96        let target_kw_map: KeywordName2Keyword = other._get_keywords_by_name_map();
97
98        // If our maps are different sizes, they are already not the same.
99        if self.kw_name_2_keyword.len() != target_kw_map.len() {
100            return false;
101        }
102
103        // Since MultiMap does not implement the Eq trait,
104        // we have to iterate through both maps and compare.
105        for key in self.kw_name_2_keyword.keys() {
106            if !target_kw_map.contains_key(key) {
107                return false;
108            }
109        }
110        true // no differences found
111    }
112}
113
114impl std::fmt::Display for DCKeywordList<'_> {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        for (i, kw) in self.keywords.iter().enumerate() {
117            // We do not call the fmt::Display impl of [`DCKeyword`] here,
118            // as that formats it as a declaration, not use in a field's
119            // keyword list. So, we just need to format the kw identifier.
120            f.write_str(&kw.name)?;
121
122            if i != self.keywords.len() - 1 {
123                write!(f, " ")?;
124            }
125        }
126        writeln!(f, ";")
127    }
128}
129
130impl LegacyDCHash for DCKeywordList<'_> {
131    fn generate_hash(&self, hashgen: &mut DCHashGenerator) {
132        if self.flags != !0 {
133            // All of the flags are historical flags only, so add just the flags
134            // bitmask to keep the hash code the same as it has historically been.
135            hashgen.add_int(self.flags);
136        } else {
137            hashgen.add_int(self.keywords.len().try_into().unwrap());
138
139            for keyword in &self.keywords {
140                keyword.generate_hash(hashgen);
141            }
142        }
143    }
144}
145
146impl<'dc> DCKeywordList<'dc> {
147    /// Returns the number of keywords in this keyword list.
148    pub fn get_num_keywords(&self) -> usize {
149        self.keywords.len()
150    }
151
152    /// Returns `true` if given keyword identifier or struct
153    /// is present in this keyword list.
154    pub fn has_keyword(&self, kw: IdentifyKeyword) -> bool {
155        match kw {
156            IdentifyKeyword::ByName(kw_id) => self.get_keyword_by_name(kw_id).is_some(),
157            IdentifyKeyword::ByStruct(kw_obj) => {
158                for keyword in &self.keywords {
159                    if **keyword == kw_obj {
160                        return true;
161                    }
162                }
163                false // no match found
164            }
165        }
166    }
167
168    /// Returns [`DCKeyword`] reference by index, wrapped in an Option.
169    pub fn get_keyword(&self, index: usize) -> Option<&'dc DCKeyword> {
170        self.keywords.get(index).copied()
171    }
172
173    /// Returns [`DCKeyword`] reference by given name, wrapped in an Option.
174    pub fn get_keyword_by_name(&self, name: String) -> Option<&'dc DCKeyword> {
175        self.kw_name_2_keyword.get(&name).copied()
176    }
177
178    /// Returns a clone of this object's keyword name map.
179    pub fn _get_keywords_by_name_map(&self) -> KeywordName2Keyword {
180        self.kw_name_2_keyword.clone()
181    }
182}
183
184/// Contains intermediate keyword structures and logic
185/// for semantic analysis as the keyword/lists is being built.
186pub(crate) mod interim {
187    use super::HistoricalFlag;
188    use crate::parser::ast;
189    use crate::parser::lexer::Span;
190    use multimap::MultiMap;
191    use std::rc::Rc;
192
193    #[derive(Debug)]
194    pub struct DCKeyword {
195        pub span: Span,
196        pub name: String,
197        pub historical_flag: HistoricalFlag,
198    }
199
200    impl From<ast::KeywordDefinition> for DCKeyword {
201        fn from(value: ast::KeywordDefinition) -> Self {
202            Self {
203                span: value.span,
204                name: value.identifier,
205                historical_flag: {
206                    if value.historical {
207                        // Sets the historical flag bitmask to the bitwise complement of 0
208                        // (!0 in Rust, or ~0 in C/C++), as if the keyword were not one
209                        // of the historically defined keywords.
210                        !0
211                    } else {
212                        0
213                    }
214                },
215            }
216        }
217    }
218
219    #[derive(Debug)]
220    pub struct DCKeywordList {
221        pub keywords: Vec<Rc<DCKeyword>>,
222        pub kw_name_2_keyword: MultiMap<String, Rc<DCKeyword>>,
223        pub flags: HistoricalFlag,
224    }
225
226    impl Default for DCKeywordList {
227        fn default() -> Self {
228            Self {
229                keywords: vec![],
230                kw_name_2_keyword: MultiMap::new(),
231                // Panda initializes its keyword list class with the flags bitmask
232                // set to 0 as a regular int (signed). But, it still confuses me why
233                // since a clear bitmask (no historical kw flags) is the bitwise complement of 0.
234                flags: 0_i32,
235            }
236        }
237    }
238
239    impl DCKeywordList {
240        pub fn add_keyword(&mut self, keyword: DCKeyword) -> Result<(), ()> {
241            let kw_name: String = keyword.name.clone(); // avoid moving 'name'
242
243            if self.kw_name_2_keyword.get(&kw_name).is_some() {
244                return Err(()); // keyword is already in our list!
245            }
246
247            // Mixes the bitmask of this keyword into our KW list flags bitmask.
248            self.flags |= keyword.historical_flag;
249
250            self.keywords.push(Rc::new(keyword));
251            self.kw_name_2_keyword
252                .insert(kw_name, self.keywords.last().unwrap().clone());
253            Ok(())
254        }
255
256        /// Overwrites the DCKeywords of this list with the target's DCKeywords.
257        pub fn copy_keywords(&mut self, target: &DCKeywordList) {
258            let target_kw_array: Vec<Rc<DCKeyword>> = target._get_keyword_list();
259            let target_kw_map: MultiMap<String, Rc<DCKeyword>> = target._get_keywords_by_name_map();
260
261            self.keywords = target_kw_array; // old vec will be dropped from memory
262            self.kw_name_2_keyword = target_kw_map;
263        }
264
265        /// Returns a clone of this object's keyword array.
266        pub fn _get_keyword_list(&self) -> Vec<Rc<DCKeyword>> {
267            self.keywords.clone()
268        }
269
270        /// Returns a clone of this object's keyword name map.
271        pub fn _get_keywords_by_name_map(&self) -> MultiMap<String, Rc<DCKeyword>> {
272            self.kw_name_2_keyword.clone()
273        }
274
275        /// Clears the DCKeywords array, keyword name map, and
276        /// historical flags bitmask from this [`DCKeywordList`] struct.
277        pub fn clear_keywords(&mut self) {
278            self.keywords.clear();
279            self.kw_name_2_keyword.clear();
280            self.flags = 0_i32;
281        }
282    }
283}