Skip to main content

binjs_meta/
export.rs

1use spec::*;
2use util::*;
3
4use std::collections::{HashMap, HashSet};
5
6use 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.
62pub 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}
73impl TypeDeanonymizer {
74    /// Create an empty TypeDeanonymizer.
75    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
157    pub fn supersums(&self) -> &HashMap<NodeName, HashSet<NodeName>> {
158        &self.supersums_of
159    }
160
161    /// Convert into a new specification.
162    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.
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.
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    }
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.
394pub struct TypeName;
395impl TypeName {
396    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
405    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.
440pub struct ToWebidl;
441impl ToWebidl {
442    /// Export a TypeSpec.
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
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
494    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}