1 // Copyright © 2024 Igalia S.L. 2 // SPDX-License-Identifier: MIT 3 4 use indexmap::IndexMap; 5 use roxmltree::Document; 6 use std::collections::HashMap; 7 8 /// A structure representing a bitset. 9 #[derive(Debug)] 10 pub struct Bitset<'a> { 11 pub name: &'a str, 12 pub displayname: Option<&'a str>, 13 pub extends: Option<&'a str>, 14 pub meta: HashMap<&'a str, &'a str>, 15 } 16 17 /// A structure representing a value in a bitset enum. 18 #[derive(Debug)] 19 pub struct BitSetEnumValue<'a> { 20 pub display: &'a str, 21 pub name: Option<&'a str>, 22 } 23 24 /// A structure representing a bitset enum. 25 #[derive(Debug)] 26 pub struct BitSetEnum<'a> { 27 pub name: &'a str, 28 pub values: Vec<BitSetEnumValue<'a>>, 29 } 30 31 /// A structure representing a bitset template. 32 #[derive(Debug)] 33 pub struct BitsetTemplate<'a> { 34 pub display: &'a str, 35 } 36 37 /// A structure representing an Instruction Set Architecture (ISA), 38 /// containing bitsets and enums. 39 pub struct ISA<'a> { 40 pub bitsets: IndexMap<&'a str, Bitset<'a>>, 41 pub enums: IndexMap<&'a str, BitSetEnum<'a>>, 42 pub templates: IndexMap<&'a str, BitsetTemplate<'a>>, 43 } 44 45 impl<'a> ISA<'a> { 46 /// Creates a new `ISA` by loading bitsets and enums from a parsed XML document. new(doc: &'a Document) -> Self47 pub fn new(doc: &'a Document) -> Self { 48 let mut isa = ISA { 49 bitsets: IndexMap::new(), 50 enums: IndexMap::new(), 51 templates: IndexMap::new(), 52 }; 53 54 isa.load_from_document(doc); 55 isa 56 } 57 58 /// Collects metadata for a given bitset by walking the `extends` chain in reverse order. collect_meta(&self, name: &'a str) -> HashMap<&'a str, &'a str>59 pub fn collect_meta(&self, name: &'a str) -> HashMap<&'a str, &'a str> { 60 let mut meta = HashMap::new(); 61 let mut chain = Vec::new(); 62 let mut current = Some(name); 63 64 // Gather the chain of bitsets 65 while let Some(item) = current { 66 if let Some(bitset) = self.bitsets.get(item) { 67 chain.push(bitset); 68 current = bitset.extends; 69 } else { 70 current = None; 71 } 72 } 73 74 // Collect metadata in reverse order so children's metadata overwrites their parent's. 75 for bitset in chain.into_iter().rev() { 76 meta.extend(&bitset.meta); 77 } 78 79 meta 80 } 81 82 /// Loads bitsets and enums from a parsed XML document into the `ISA`. load_from_document(&mut self, doc: &'a Document)83 fn load_from_document(&mut self, doc: &'a Document) { 84 doc.descendants() 85 .filter(|node| node.is_element() && node.has_tag_name("template")) 86 .for_each(|value| { 87 let name = value.attribute("name").unwrap(); 88 let display = value.text().unwrap(); 89 90 self.templates.insert(name, BitsetTemplate { display }); 91 }); 92 93 doc.descendants() 94 .filter(|node| node.is_element() && node.has_tag_name("enum")) 95 .for_each(|node| { 96 let values = node 97 .children() 98 .filter(|node| node.is_element() && node.has_tag_name("value")) 99 .map(|value| { 100 let display = value.attribute("display").unwrap(); 101 let name = value.attribute("name"); 102 103 BitSetEnumValue { display, name } 104 }) 105 .collect(); 106 107 let name = node.attribute("name").unwrap(); 108 109 self.enums.insert(name, BitSetEnum { name, values }); 110 }); 111 112 doc.descendants() 113 .filter(|node| node.is_element() && node.has_tag_name("bitset")) 114 .for_each(|node| { 115 let name = node.attribute("name").unwrap(); 116 let displayname = node.attribute("displayname"); 117 let extends = node.attribute("extends"); 118 let meta_nodes = node 119 .children() 120 .filter(|child| child.is_element() && child.has_tag_name("meta")); 121 122 // We can have multiple <meta> tags, which we need to combine. 123 let combined_meta: HashMap<_, _> = meta_nodes 124 .flat_map(|m| m.attributes()) 125 .map(|attr| (attr.name(), attr.value())) 126 .collect(); 127 128 self.bitsets.insert( 129 name, 130 Bitset { 131 name, 132 displayname, 133 extends, 134 meta: combined_meta, 135 }, 136 ); 137 }); 138 } 139 } 140 141 #[cfg(test)] 142 mod tests { 143 use super::*; 144 145 #[test] test_collect_meta()146 fn test_collect_meta() { 147 let mut isa = ISA { 148 bitsets: IndexMap::new(), 149 enums: IndexMap::new(), 150 templates: IndexMap::new(), 151 }; 152 isa.bitsets.insert( 153 "bitset1", 154 Bitset { 155 name: "bitset1", 156 extends: None, 157 meta: HashMap::from([("key1", "value1")]), 158 }, 159 ); 160 isa.bitsets.insert( 161 "bitset2", 162 Bitset { 163 name: "bitset2", 164 extends: Some("bitset1"), 165 meta: HashMap::from([("key2", "value2")]), 166 }, 167 ); 168 isa.bitsets.insert( 169 "bitset3", 170 Bitset { 171 name: "bitset3", 172 extends: Some("bitset2"), 173 meta: HashMap::from([("key3", "value3")]), 174 }, 175 ); 176 177 let meta = isa.collect_meta("bitset3"); 178 assert_eq!(meta.get("key1"), Some(&"value1")); 179 assert_eq!(meta.get("key2"), Some(&"value2")); 180 assert_eq!(meta.get("key3"), Some(&"value3")); 181 } 182 183 #[test] test_load_from_document()184 fn test_load_from_document() { 185 let xml_data = r#" 186 <isa> 187 <bitset name="bitset1"> 188 <meta key1="value1"/> 189 <meta key2="value2"/> 190 </bitset> 191 <bitset name="bitset2" extends="bitset1"/> 192 <enum name="enum1"> 193 <value display="val1" val="0"/> 194 <value display="val2" val="1"/> 195 </enum> 196 </isa> 197 "#; 198 199 let doc = Document::parse(xml_data).unwrap(); 200 let isa = ISA::new(&doc); 201 202 let bitset1 = isa.bitsets.get(&"bitset1").unwrap(); 203 assert_eq!(bitset1.name, "bitset1"); 204 assert_eq!(bitset1.meta.get("key1"), Some(&"value1")); 205 assert_eq!(bitset1.meta.get("key2"), Some(&"value2")); 206 207 let bitset2 = isa.bitsets.get(&"bitset2").unwrap(); 208 assert_eq!(bitset2.name, "bitset2"); 209 assert_eq!(bitset2.extends, Some("bitset1")); 210 211 let enum1 = isa.enums.get(&"enum1").unwrap(); 212 assert_eq!(enum1.name, "enum1"); 213 assert_eq!(enum1.values.len(), 2); 214 assert_eq!(enum1.values[0].display, "val1"); 215 assert_eq!(enum1.values[1].display, "val2"); 216 } 217 } 218