1 use spec::*; 2 use util::*; 3 4 use std::collections::{HashMap, HashSet}; 5 6 use itertools::Itertools; 7 8 /// A tool designed to replace all anonymous types in a specification 9 /// of the language by explicitly named types. 10 /// 11 /// Consider the following mini-specifications for JSON: 12 /// 13 /// ```idl 14 /// interface Value { 15 /// attribute (Object or String or Number or Array or Boolean)? value; 16 /// } 17 /// interface Object { 18 /// attribute FrozenArray<Property> properties; 19 /// } 20 /// interface Property { 21 /// attribute DOMString name; 22 /// attribute Value value; 23 /// } 24 /// interface Array { 25 /// attribute FrozenArray<Value?> items; 26 /// } 27 /// // ... Skipping definitions of String, Number, Boolean 28 /// ``` 29 /// 30 /// The deanonymizer will rewrite them as follows: 31 /// 32 /// ```idl 33 /// interface Value { // Deanonymized optional sum 34 /// attribute OptionalObjectOrStringOrNumberOrArrayOrBoolean value; 35 /// } 36 /// interface Object { // Deanonymized list 37 /// attribute ListOfProperty properties; 38 /// } 39 /// interface Property { // No change 40 /// attribute DOMString name; 41 /// attribute Value value; 42 /// } 43 /// interface Array { // Deanonymized list of options 44 /// attribute ListOfOptionalValue items; 45 /// } 46 /// // ... Skipping definitions of String, Number, Boolean 47 /// 48 /// typedef ObjectOrStringOrNumberOrArrayOrBoolean? OptionalObjectOrStringOrNumberOrArrayOrBoolean; 49 /// typedef (Object 50 /// or String 51 /// or Number 52 /// or Array 53 /// or Boolean) 54 /// ObjectOrStringOrNumberOrArrayOrBoolean; 55 /// typedef FrozenArray<Property> ListOfProperty; 56 /// typedef FrozenArray<OptionalValue> ListOfOptionalValue; 57 /// typedef Value? Optionalvalue; 58 /// ``` 59 /// 60 /// This deanonymization lets us cleanly define intermediate data structures and/or parsers 61 /// implementing the webidl specification. 62 pub struct TypeDeanonymizer { 63 builder: SpecBuilder, 64 65 /// When we encounter `typedef (A or B) C` 66 /// and `typedef (C or D) E`, we deanonymize into 67 /// `typedef (A or B or D) E`. 68 /// 69 /// This maintains the relationship that `E` (value) 70 /// contains `C` (key). 71 supersums_of: HashMap<NodeName, HashSet<NodeName>>, 72 } 73 impl TypeDeanonymizer { 74 /// Create an empty TypeDeanonymizer. new(spec: &Spec) -> Self75 pub fn new(spec: &Spec) -> Self { 76 let mut result = TypeDeanonymizer { 77 builder: SpecBuilder::new(), 78 supersums_of: HashMap::new(), 79 }; 80 let mut skip_name_map: HashMap<&FieldName, FieldName> = HashMap::new(); 81 82 // Copy field names 83 for (_, name) in spec.field_names() { 84 result.builder.import_field_name(name) 85 } 86 87 for (_, interface) in spec.interfaces_by_name() { 88 for field in interface.contents().fields() { 89 if field.is_lazy() { 90 let skip_name = result 91 .builder 92 .field_name(format!("{}_skip", field.name().to_str()).to_str()); 93 skip_name_map.insert(field.name(), skip_name); 94 } 95 } 96 } 97 98 // Copy and deanonymize interfaces. 99 for (name, interface) in spec.interfaces_by_name() { 100 result.builder.import_node_name(name); 101 // Collect interfaces to copy them into the `builder` 102 // and walk through their fields to deanonymize types. 103 104 let mut fields = vec![]; 105 106 // Copy other fields. 107 for field in interface.contents().fields() { 108 result.import_type(spec, field.type_(), None); 109 fields.push(field.clone()); 110 } 111 112 // Copy the declaration. 113 let mut declaration = result.builder.add_interface(name).unwrap(); 114 for field in fields.drain(..) { 115 // Create *_skip field just before the lazy field. 116 // See also tagged_tuple in write.rs. 117 if field.is_lazy() { 118 declaration.with_field( 119 skip_name_map.get(field.name()).unwrap(), 120 Type::offset().required(), 121 ); 122 } 123 declaration.with_field_laziness( 124 field.name(), 125 field.type_().clone(), 126 field.laziness(), 127 ); 128 } 129 130 if let Some(ref field_name) = interface.scoped_dictionary() { 131 declaration.with_scoped_dictionary(field_name); 132 } 133 } 134 // Copy and deanonymize typedefs 135 for (name, definition) in spec.typedefs_by_name() { 136 result.builder.import_node_name(name); 137 if result.builder.get_typedef(name).is_some() { 138 // Already imported by following links. 139 continue; 140 } 141 result.import_type(spec, &definition, Some(name.clone())); 142 } 143 // Copy and deanonymize string enums 144 for (name, definition) in spec.string_enums_by_name() { 145 result.builder.import_node_name(name); 146 let mut strings: Vec<_> = definition.strings().iter().collect(); 147 let mut declaration = result.builder.add_string_enum(name).unwrap(); 148 for string in strings.drain(..) { 149 declaration.with_string(&string); 150 } 151 } 152 debug!(target: "export_utils", "Names: {:?}", result.builder.names().keys().format(", ")); 153 154 result 155 } 156 supersums(&self) -> &HashMap<NodeName, HashSet<NodeName>>157 pub fn supersums(&self) -> &HashMap<NodeName, HashSet<NodeName>> { 158 &self.supersums_of 159 } 160 161 /// Convert into a new specification. into_spec(self, options: SpecOptions) -> Spec162 pub fn into_spec(self, options: SpecOptions) -> Spec { 163 self.builder.into_spec(options) 164 } 165 166 /// If `name` is the name of a (deanonymized) type, return the corresponding type. get_node_name(&self, name: &str) -> Option<NodeName>167 pub fn get_node_name(&self, name: &str) -> Option<NodeName> { 168 self.builder.get_node_name(name) 169 } 170 171 /// Returns `(sum, name)` where `sum` is `Some(names)` iff this type can be resolved to a sum of interfaces. import_type( &mut self, spec: &Spec, type_: &Type, public_name: Option<NodeName>, ) -> (Option<HashSet<NodeName>>, NodeName)172 fn import_type( 173 &mut self, 174 spec: &Spec, 175 type_: &Type, 176 public_name: Option<NodeName>, 177 ) -> (Option<HashSet<NodeName>>, NodeName) { 178 debug!(target: "export_utils", "import_type {:?} => {:?}", public_name, type_); 179 if type_.is_optional() { 180 let (_, spec_name) = self.import_typespec(spec, &type_.spec, None); 181 let my_name = match public_name { 182 None => self.builder.node_name(&format!("Optional{}", spec_name)), 183 Some(ref name) => name.clone(), 184 }; 185 let deanonymized = Type::named(&spec_name).optional().unwrap(); // Named types can always be made optional. 186 if let Some(ref mut typedef) = self.builder.add_typedef(&my_name) { 187 debug!(target: "export_utils", "import_type introduced {:?}", my_name); 188 typedef.with_type(deanonymized.clone()); 189 } else { 190 debug!(target: "export_utils", "import_type: Attempting to redefine typedef {name}", name = my_name.to_str()); 191 } 192 (None, my_name) 193 } else { 194 self.import_typespec(spec, &type_.spec, public_name) 195 } 196 } import_typespec( &mut self, spec: &Spec, type_spec: &TypeSpec, public_name: Option<NodeName>, ) -> (Option<HashSet<NodeName>>, NodeName)197 fn import_typespec( 198 &mut self, 199 spec: &Spec, 200 type_spec: &TypeSpec, 201 public_name: Option<NodeName>, 202 ) -> (Option<HashSet<NodeName>>, NodeName) { 203 debug!(target: "export_utils", "import_typespec {:?} => {:?}", public_name, type_spec); 204 match *type_spec { 205 TypeSpec::Boolean 206 | TypeSpec::Number 207 | TypeSpec::UnsignedLong 208 | TypeSpec::PropertyKey 209 | TypeSpec::IdentifierName 210 | TypeSpec::String 211 | TypeSpec::Offset 212 | TypeSpec::Void => { 213 if let Some(ref my_name) = public_name { 214 if let Some(ref mut typedef) = self.builder.add_typedef(&my_name) { 215 debug!(target: "export_utils", "import_typespec: Defining {name} (primitive)", name = my_name.to_str()); 216 typedef.with_type(type_spec.clone().required()); 217 } else { 218 debug!(target: "export_utils", "import_typespec: Attempting to redefine typedef {name}", name = my_name.to_str()); 219 } 220 } 221 // This is a workaround for typedefs in the webidl that are not truly typedefs. 222 // See https://github.com/Yoric/ecmascript-binary-ast/pull/1 223 let name = match *type_spec { 224 TypeSpec::PropertyKey => self.builder.node_name("PropertyKey"), 225 TypeSpec::IdentifierName => self.builder.node_name("IdentifierName"), 226 _ => self.builder.node_name(&format!("@@{:?}", type_spec)), 227 }; 228 (None, name) 229 } 230 TypeSpec::NamedType(ref link) => { 231 let resolved = spec.get_type_by_name(link) 232 .unwrap_or_else(|| panic!("While deanonymizing, could not find the definition of {} in the original spec.", link.to_str())); 233 let (sum, rewrite, primitive) = match resolved { 234 NamedType::StringEnum(_) => { 235 // - Can't use in a sum 236 // - No rewriting happened. 237 (None, None, None) 238 } 239 NamedType::Typedef(ref type_) => { 240 // - Might use in a sum. 241 // - Might be rewritten. 242 let (sum, name) = self.import_type(spec, type_, Some(link.clone())); 243 (sum, Some(name), type_.get_primitive(spec)) 244 } 245 NamedType::Interface(_) => { 246 // - May use in a sum. 247 // - If a rewriting takes place, it didn't change the names. 248 let sum = [link.clone()].iter().cloned().collect(); 249 (Some(sum), None, None) 250 } 251 }; 252 debug!(target: "export_utils", "import_typespec dealing with named type {}, public name {:?} => {:?}", 253 link, public_name, rewrite); 254 if let Some(ref my_name) = public_name { 255 // If we have a public name, alias it to `content` 256 if let Some(content) = rewrite { 257 let deanonymized = match primitive { 258 None 259 | Some(IsNullable { 260 is_nullable: true, .. 261 }) 262 | Some(IsNullable { 263 content: Primitive::Interface(_), 264 .. 265 }) => Type::named(&content).required(), 266 Some(IsNullable { 267 content: Primitive::String, 268 .. 269 }) => Type::string().required(), 270 Some(IsNullable { 271 content: Primitive::IdentifierName, 272 .. 273 }) => Type::identifier_name().required(), 274 Some(IsNullable { 275 content: Primitive::PropertyKey, 276 .. 277 }) => Type::property_key().required(), 278 Some(IsNullable { 279 content: Primitive::Number, 280 .. 281 }) => Type::number().required(), 282 Some(IsNullable { 283 content: Primitive::UnsignedLong, 284 .. 285 }) => Type::unsigned_long().required(), 286 Some(IsNullable { 287 content: Primitive::Boolean, 288 .. 289 }) => Type::bool().required(), 290 Some(IsNullable { 291 content: Primitive::Offset, 292 .. 293 }) => Type::offset().required(), 294 Some(IsNullable { 295 content: Primitive::Void, 296 .. 297 }) => Type::void().required(), 298 }; 299 debug!(target: "export_utils", "import_typespec aliasing {:?} => {:?}", 300 my_name, deanonymized); 301 if let Some(ref mut typedef) = self.builder.add_typedef(&my_name) { 302 debug!(target: "export_utils", "import_typespec: Defining {name} (name to content)", name = my_name.to_str()); 303 typedef.with_type(deanonymized.clone()); 304 } else { 305 debug!(target: "export_utils", "import_typespec: Attempting to redefine typedef {name}", name = my_name.to_str()); 306 } 307 } 308 // Also, don't forget to copy the typedef and alias `link` 309 let deanonymized = Type::named(link).required(); 310 if let Some(ref mut typedef) = self.builder.add_typedef(&my_name) { 311 debug!(target: "export_utils", "import_typespec: Defining {name} (name to link)", name = my_name.to_str()); 312 typedef.with_type(deanonymized.clone()); 313 } else { 314 debug!(target: "export_utils", "import_typespec: Attempting to redefine typedef {name}", name = my_name.to_str()); 315 } 316 } 317 (sum, link.clone()) 318 } 319 TypeSpec::Array { 320 ref contents, 321 ref supports_empty, 322 } => { 323 let (_, contents_name) = self.import_type(spec, contents, None); 324 let my_name = match public_name { 325 None => self.builder.node_name(&format!( 326 "{non_empty}ListOf{content}", 327 non_empty = if *supports_empty { "" } else { "NonEmpty" }, 328 content = contents_name.to_str() 329 )), 330 Some(ref name) => name.clone(), 331 }; 332 let deanonymized = if *supports_empty { 333 Type::named(&contents_name).array() 334 } else { 335 Type::named(&contents_name).non_empty_array() 336 }; 337 if let Some(ref mut typedef) = self.builder.add_typedef(&my_name) { 338 debug!(target: "export_utils", "import_typespec: Defining {name} (name to list)", 339 name = my_name.to_str()); 340 typedef.with_type(deanonymized.clone()); 341 } else { 342 debug!(target: "export_utils", "import_typespec: Attempting to redefine typedef {name}", name = my_name.to_str()); 343 } 344 (None, my_name) 345 } 346 TypeSpec::TypeSum(ref sum) => { 347 let mut full_sum = HashSet::new(); 348 let mut names = vec![]; 349 let mut subsums = vec![]; 350 for sub_type in sum.types() { 351 let (sub_sum, name) = self.import_typespec(spec, sub_type, None); 352 let mut sub_sum = sub_sum.unwrap_or_else( 353 || panic!("While treating {:?}, attempting to create a sum containing {}, which isn't an interface or a sum of interfaces", type_spec, name) 354 ); 355 if sub_sum.len() > 1 { 356 // The subtype is itself a sum. 357 subsums.push(name.clone()) 358 } 359 names.push(name); 360 for item in sub_sum.drain() { 361 full_sum.insert(item); 362 } 363 } 364 let my_name = match public_name { 365 None => self.builder.node_name(&format!( 366 "{}", 367 names.into_iter().sorted().into_iter().format("Or") 368 )), 369 Some(ref name) => name.clone(), 370 }; 371 for subsum_name in subsums { 372 // So, `my_name` is a superset of `subsum_name`. 373 let supersum_entry = self 374 .supersums_of 375 .entry(subsum_name.clone()) 376 .or_insert_with(|| HashSet::new()); 377 supersum_entry.insert(my_name.clone()); 378 } 379 let sum: Vec<_> = full_sum.iter().map(Type::named).collect(); 380 let deanonymized = Type::sum(&sum).required(); 381 if let Some(ref mut typedef) = self.builder.add_typedef(&my_name) { 382 debug!(target: "export_utils", "import_typespec: Defining {name} (name to sum)", name = my_name.to_str()); 383 typedef.with_type(deanonymized.clone()); 384 } else { 385 debug!(target: "export_utils", "import_type: Attempting to redefine typedef {name}", name = my_name.to_str()); 386 } 387 (Some(full_sum), my_name) 388 } 389 } 390 } 391 } 392 393 /// Utility to give a name to a type or type spec. 394 pub struct TypeName; 395 impl TypeName { type_(type_: &Type) -> String396 pub fn type_(type_: &Type) -> String { 397 let spec_name = Self::type_spec(type_.spec()); 398 if type_.is_optional() { 399 format!("Optional{}", spec_name) 400 } else { 401 spec_name 402 } 403 } 404 type_spec(spec: &TypeSpec) -> String405 pub fn type_spec(spec: &TypeSpec) -> String { 406 match *spec { 407 TypeSpec::Array { 408 ref contents, 409 supports_empty: false, 410 } => format!("NonEmptyListOf{}", Self::type_(contents)), 411 TypeSpec::Array { 412 ref contents, 413 supports_empty: true, 414 } => format!("ListOf{}", Self::type_(contents)), 415 TypeSpec::NamedType(ref name) => name.to_string().clone(), 416 TypeSpec::Offset => "_Offset".to_string(), 417 TypeSpec::Boolean => "_Bool".to_string(), 418 TypeSpec::Number => "_Number".to_string(), 419 TypeSpec::UnsignedLong => "_UnsignedLong".to_string(), 420 TypeSpec::String => "_String".to_string(), 421 TypeSpec::Void => "_Void".to_string(), 422 TypeSpec::IdentifierName => "IdentifierName".to_string(), 423 TypeSpec::PropertyKey => "PropertyKey".to_string(), 424 TypeSpec::TypeSum(ref sum) => format!( 425 "{}", 426 sum.types() 427 .iter() 428 .map(Self::type_spec) 429 .sorted() 430 .into_iter() 431 .format("Or") 432 ), 433 } 434 } 435 } 436 437 /// Export a type specification as webidl. 438 /// 439 /// Designed for generating documentation. 440 pub struct ToWebidl; 441 impl ToWebidl { 442 /// Export a TypeSpec. spec(spec: &TypeSpec, prefix: &str, indent: &str) -> Option<String>443 pub fn spec(spec: &TypeSpec, prefix: &str, indent: &str) -> Option<String> { 444 let result = match *spec { 445 TypeSpec::Offset => { 446 return None; 447 } 448 TypeSpec::Array { 449 ref contents, 450 ref supports_empty, 451 } => match Self::type_(&*contents, prefix, indent) { 452 None => { 453 return None; 454 } 455 Some(description) => format!( 456 "{emptiness}FrozenArray<{}>", 457 description, 458 emptiness = if *supports_empty { "" } else { "[NonEmpty] " } 459 ), 460 }, 461 TypeSpec::Boolean => "bool".to_string(), 462 TypeSpec::String => "string".to_string(), 463 TypeSpec::PropertyKey => "[PropertyKey] string".to_string(), 464 TypeSpec::IdentifierName => "[IdentifierName] string".to_string(), 465 TypeSpec::Number => "number".to_string(), 466 TypeSpec::UnsignedLong => "unsigned long".to_string(), 467 TypeSpec::NamedType(ref name) => name.to_str().to_string(), 468 TypeSpec::TypeSum(ref sum) => format!( 469 "({})", 470 sum.types() 471 .iter() 472 .filter_map(|x| Self::spec(x, "", indent)) 473 .format(" or ") 474 ), 475 TypeSpec::Void => "void".to_string(), 476 }; 477 Some(result) 478 } 479 480 /// Export a Type type_(type_: &Type, prefix: &str, indent: &str) -> Option<String>481 pub fn type_(type_: &Type, prefix: &str, indent: &str) -> Option<String> { 482 let pretty_type = Self::spec(type_.spec(), prefix, indent); 483 match pretty_type { 484 None => None, 485 Some(pretty_type) => Some(format!( 486 "{}{}", 487 pretty_type, 488 if type_.is_optional() { "?" } else { "" } 489 )), 490 } 491 } 492 493 /// Export an Interface interface(interface: &Interface, prefix: &str, indent: &str) -> String494 pub fn interface(interface: &Interface, prefix: &str, indent: &str) -> String { 495 let mut result = format!( 496 "{prefix} interface {name} : Node {{\n", 497 prefix = prefix, 498 name = interface.name().to_str() 499 ); 500 { 501 let prefix = format!("{prefix}{indent}", prefix = prefix, indent = indent); 502 for field in interface.contents().fields() { 503 match Self::type_(field.type_(), &prefix, indent) { 504 None => 505 /* generated field, ignore */ 506 {} 507 Some(description) => { 508 if let Some(ref doc) = field.doc() { 509 result.push_str(&format!( 510 "{prefix}// {doc}\n", 511 prefix = prefix, 512 doc = doc 513 )); 514 } 515 result.push_str(&format!( 516 "{prefix}{description} {name};\n", 517 prefix = prefix, 518 name = field.name().to_str(), 519 description = description 520 )); 521 if field.doc().is_some() { 522 result.push_str("\n"); 523 } 524 } 525 } 526 } 527 } 528 result.push_str(&format!("{prefix} }}\n", prefix = prefix)); 529 result 530 } 531 } 532