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