Bug 1437004 - Introducing BinSource parser generator;r?froydnj,jorendorff draft
authorDavid Teller <dteller@mozilla.com>
Thu, 05 Apr 2018 15:21:17 +0200
changeset 778391 81b8d0f399b60d776aa08dd551158b19a30b4610
parent 778390 501aa0f9a7bcde0beb0f68d4a686dbc883dd3bc4
child 778962 36200c288240879c6f4dd7e2366f6dec5adcef2a
child 778963 b489e4d8bdbcd1d5a2822cab183cfb2ad9eb02f8
push id105484
push userdteller@mozilla.com
push dateFri, 06 Apr 2018 09:27:53 +0000
reviewersfroydnj, jorendorff
bugs1437004, 1439645
milestone61.0a1
Bug 1437004 - Introducing BinSource parser generator;r?froydnj,jorendorff This crate contains a parser generator as a Rust crate. The parser generator is used to generate BinSource-auto.h, BinSource-auto.cpp, BinToken.h. As of this changeset, to limit yak shaving, the parser generator is not part of the build system. Making it part of the build system is delegated to bug 1439645. MozReview-Commit-ID: 1lODDSIsz8W
js/src/frontend/binsource/Cargo.toml
js/src/frontend/binsource/README.md
js/src/frontend/binsource/src/main.rs
python/mozbuild/mozbuild/vendor_rust.py
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/binsource/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "binsource"
+version = "0.1.0"
+authors = ["David Teller <D.O.Teller@gmail.com>"]
+
+[dependencies]
+binjs_meta = "^0.3.6"
+clap = "^2"
+env_logger = "^0.5.6"
+itertools = "^0.7.6"
+log = "0.4.1"
+yaml-rust = "^0.4"
+webidl = "^0.6.0"
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/binsource/README.md
@@ -0,0 +1,19 @@
+A parser generator used to generate the following files:
+
+- js/src/frontend/BinSource-auto.h
+- js/src/frontend/BinSource-auto.cpp
+- js/src/frontent/BinToken.h
+
+from the following files:
+
+- js/src/frontend/BinSource.webidl_ (specifications of BinAST)
+- js/src/frontend/BinSource.yaml (parser generator driver)
+
+To use it:
+```sh
+$ cd $(topsrcdir)/js/src/frontend/binsource
+% cargo run -- $(topsrcdir)/js/src/frontend/BinSource.webidl_ $(topsrcdir)/js/src/frontend/BinSource.yaml \
+      --out-class $(topsrcdir)/js/src/frontend/BinSource-auto.h    \
+      --out-impl $(topsrcdir)/js/src/frontend/BinSource-auto.cpp   \
+      --out-token $(topsrcdir)/js/src/frontend/BinToken.h
+```
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/binsource/src/main.rs
@@ -0,0 +1,1276 @@
+extern crate binjs_meta;
+extern crate clap;
+extern crate env_logger;
+extern crate itertools;
+#[macro_use] extern crate log;
+extern crate webidl;
+extern crate yaml_rust;
+
+use binjs_meta::export::{ ToWebidl, TypeDeanonymizer, TypeName };
+use binjs_meta::import::Importer;
+use binjs_meta::spec::*;
+use binjs_meta::util:: { Reindentable, ToCases, ToStr };
+
+use std::collections::{ HashMap, HashSet };
+use std::fs::*;
+use std::io::{ Read, Write };
+
+use clap::{ App, Arg };
+
+use itertools::Itertools;
+
+/// Rules for generating the code for parsing a single field
+/// of a node.
+///
+/// Extracted from the yaml file.
+#[derive(Clone, Default)]
+struct FieldRules {
+    /// Declaring the variable to hold the contents of that field.
+    declare: Option<String>,
+
+    /// Replace the declaration and assignation.
+    replace: Option<String>,
+
+    /// Things to add before the field, typically for checking invariants.
+    before_field: Option<String>,
+
+    /// Things to add after the field, typically for checking invariants.
+    after_field: Option<String>,
+
+    /// Things to add before the field, as part of a block, typically for
+    /// putting guard values on the stack.
+    block_before_field: Option<String>,
+
+    /// Things to add before the field, as part of a block, typically for
+    /// cleanup.
+    block_after_field: Option<String>,
+}
+
+/// Rules for generating the code for parsing a full node
+/// of a node.
+///
+/// Extracted from the yaml file.
+#[derive(Clone, Default)]
+struct NodeRules {
+    /// This node inherits from another node.
+    inherits: Option<NodeName>,
+
+    /// Override the result type for the method.
+    type_ok: Option<String>,
+
+    /// Stuff to add at start.
+    init: Option<String>,
+
+    /// How to append to a list. Used only for lists.
+    append: Option<String>,
+
+    /// Custom per-field treatment. Used only for interfaces.
+    by_field: HashMap<FieldName, FieldRules>,
+
+    /// How to build the result, eventually.
+    build_result: Option<String>,
+}
+
+/// Rules for generating entire files.
+///
+/// Extracted from the yaml file.
+#[derive(Default)]
+struct GlobalRules {
+    /// Header to add at the start of the .cpp file.
+    cpp_header: Option<String>,
+
+    /// Header to add at the end of the .cpp file.
+    cpp_footer: Option<String>,
+
+    /// Header to add at the start of the .hpp file
+    /// defining the class data/methods.
+    hpp_class_header: Option<String>,
+
+    /// Header to add at the start of the .hpp file.
+    /// defining the tokens.
+    hpp_tokens_header: Option<String>,
+
+    /// Footer to add at the start of the .hpp file.
+    /// defining the tokens.
+    hpp_tokens_footer: Option<String>,
+
+    /// Documentation for the `BinKind` class enum.
+    hpp_tokens_kind_doc: Option<String>,
+
+    /// Documentation for the `BinField` class enum.
+    hpp_tokens_field_doc: Option<String>,
+
+    /// Per-node rules.
+    per_node: HashMap<NodeName, NodeRules>,
+}
+impl GlobalRules {
+    fn new(syntax: &Spec, yaml: &yaml_rust::yaml::Yaml) -> Self {
+        let rules = yaml.as_hash()
+            .expect("Rules are not a dictionary");
+
+        let mut cpp_header = None;
+        let mut cpp_footer = None;
+        let mut hpp_class_header = None;
+        let mut hpp_tokens_header = None;
+        let mut hpp_tokens_footer = None;
+        let mut hpp_tokens_kind_doc = None;
+        let mut hpp_tokens_field_doc = None;
+        let mut per_node = HashMap::new();
+
+        for (node_key, node_entries) in rules.iter() {
+            let node_key = node_key.as_str()
+                .expect("Could not convert node_key to string");
+
+            match node_key {
+                "cpp" => {
+                    update_rule(&mut cpp_header, &node_entries["header"])
+                        .unwrap_or_else(|_| panic!("Rule cpp.header must be a string"));
+                    update_rule(&mut cpp_footer, &node_entries["footer"])
+                        .unwrap_or_else(|_| panic!("Rule cpp.footer must be a string"));
+                    continue;
+                }
+                "hpp" => {
+                    update_rule(&mut hpp_class_header, &node_entries["class"]["header"])
+                        .unwrap_or_else(|_| panic!("Rule hpp.class.header must be a string"));
+                    update_rule(&mut hpp_tokens_header, &node_entries["tokens"]["header"])
+                        .unwrap_or_else(|_| panic!("Rule hpp.tokens.header must be a string"));
+                    update_rule(&mut hpp_tokens_footer, &node_entries["tokens"]["footer"])
+                        .unwrap_or_else(|_| panic!("Rule hpp.tokens.footer must be a string"));
+                    update_rule(&mut hpp_tokens_kind_doc, &node_entries["tokens"]["kind"]["doc"])
+                        .unwrap_or_else(|_| panic!("Rule hpp.tokens.kind.doc must be a string"));
+                    update_rule(&mut hpp_tokens_field_doc, &node_entries["tokens"]["field"]["doc"])
+                        .unwrap_or_else(|_| panic!("Rule hpp.tokens.field.doc must be a string"));
+                    continue;
+                }
+                _ => {}
+            }
+
+
+            let node_name = syntax.get_node_name(&node_key)
+                .unwrap_or_else(|| panic!("Unknown node name {}", node_key));
+
+            let hash = node_entries.as_hash()
+                .unwrap_or_else(|| panic!("Node {} isn't a dictionary"));
+
+            let mut node_rule = NodeRules::default();
+            for (node_item_key, node_item_entry) in hash {
+                let as_string = node_item_key.as_str()
+                    .unwrap_or_else(|| panic!("Keys for rule {} must be strings", node_key));
+                match as_string {
+                    "inherits" => {
+                        let name = node_item_entry.as_str()
+                            .unwrap_or_else(|| panic!("Rule {}.{} must be a string", node_key, as_string));
+                        let inherits = syntax.get_node_name(name)
+                            .unwrap_or_else(|| panic!("Unknown node name {}", node_key));
+                        node_rule.inherits = Some(inherits).cloned();
+                    }
+                    "init" => {
+                        update_rule(&mut node_rule.init, node_item_entry)
+                            .unwrap_or_else(|()| panic!("Rule {}.{} must be a string", node_key, as_string));
+                    }
+                    "build" => {
+                        update_rule(&mut node_rule.build_result, node_item_entry)
+                            .unwrap_or_else(|()| panic!("Rule {}.{} must be a string", node_key, as_string));
+                    }
+                    "append" => {
+                        update_rule(&mut node_rule.append, node_item_entry)
+                            .unwrap_or_else(|()| panic!("Rule {}.{} must be a string", node_key, as_string));
+                    }
+                    "type-ok" => {
+                        update_rule(&mut node_rule.type_ok, node_item_entry)
+                            .unwrap_or_else(|()| panic!("Rule {}.{} must be a string", node_key, as_string));
+                    }
+                    "fields" => {
+                        let fields = node_item_entry.as_hash()
+                            .unwrap_or_else(|| panic!("Rule {}.fields must be a hash, got {:?}", node_key, node_entries["fields"]));
+                        for (field_key, field_entry) in fields {
+                            let field_key = field_key.as_str()
+                                .unwrap_or_else(|| panic!("In rule {}, field entries must be field names",
+                                    node_key))
+                                .to_string();
+                            let field_name = syntax.get_field_name(&field_key)
+                                .unwrap_or_else(|| panic!("In rule {}, can't find field {}",
+                                    node_key,
+                                    field_key));
+
+                            let mut field_rule = FieldRules::default();
+                            for (field_config_key, field_config_entry) in field_entry.as_hash()
+                                .unwrap_or_else(|| panic!("Rule {}.fields.{} must be a hash", node_key, field_key))
+                            {
+                                let field_config_key = field_config_key.as_str()
+                                    .expect("Expected a string as a key");
+                                match field_config_key
+                                {
+                                    "block" => {
+                                        update_rule(&mut field_rule.declare, &field_config_entry["declare"])
+                                            .unwrap_or_else(|()| panic!("Rule {}.fields.{}.{}.{} must be a string", node_key, field_key, field_config_key, "declare"));
+
+                                        update_rule(&mut field_rule.replace, &field_config_entry["replace"])
+                                            .unwrap_or_else(|()| panic!("Rule {}.fields.{}.{}.{} must be a string", node_key, field_key, field_config_key, "replace"));
+
+                                        update_rule(&mut field_rule.block_before_field, &field_config_entry["before"])
+                                            .unwrap_or_else(|()| panic!("Rule {}.fields.{}.{}.{} must be a string", node_key, field_key, field_config_key, "before"));
+
+                                        update_rule(&mut field_rule.block_after_field, &field_config_entry["after"])
+                                            .unwrap_or_else(|()| panic!("Rule {}.fields.{}.{}.{} must be a string", node_key, field_key, field_config_key, "after"));
+                                    }
+                                    "before" => {
+                                        update_rule(&mut field_rule.before_field, &field_config_entry)
+                                            .unwrap_or_else(|()| panic!("Rule {}.fields.{}.{} must be a string", node_key, field_key, field_config_key));
+                                    }
+                                    "after" => {
+                                        update_rule(&mut field_rule.after_field, &field_config_entry)
+                                            .unwrap_or_else(|()| panic!("Rule {}.fields.{}.{} must be a string", node_key, field_key, field_config_key));
+                                    }
+                                    _ => {
+                                        panic!("Unexpected {}.fields.{}.{}", node_key, field_key, field_config_key)
+                                    }
+                                }
+                            }
+                            node_rule.by_field.insert(field_name.clone(), field_rule);
+                        }
+                    }
+                    _ => panic!("Unexpected node_item_key {}.{}", node_key, as_string)
+                }
+            }
+
+            per_node.insert(node_name.clone(), node_rule);
+        }
+
+        Self {
+            cpp_header,
+            cpp_footer,
+            hpp_class_header,
+            hpp_tokens_header,
+            hpp_tokens_footer,
+            hpp_tokens_kind_doc,
+            hpp_tokens_field_doc,
+            per_node,
+        }
+    }
+    fn get(&self, name: &NodeName) -> NodeRules {
+        let mut rules = self.per_node.get(name)
+            .cloned()
+            .unwrap_or_default();
+        if let Some(ref parent) = rule.inherits {
+            let NodeRules {
+                inherits: _,
+                type_ok,
+                init,
+                append,
+                by_field,
+                build_result,
+            } = self.get(parent);
+            if rules.type_ok.is_none() {
+                rules.type_ok = type_ok;
+            }
+            if rules.init.is_none() {
+                rules.init = init;
+            }
+            if rules.append.is_none() {
+                rules.append = append;
+            }
+            if rules.build_result.is_none() {
+                rules.build_result = build_result;
+            }
+            for (key, value) in by_field {
+                rules.by_field.entry(key)
+                    .or_insert(value);
+            }
+        }
+        rules
+    }
+}
+
+/// The inforamtion used to generate a list parser.
+struct ListParserData {
+    /// Name of the node.
+    name: NodeName,
+
+    /// If `true`, supports empty lists.
+    supports_empty: bool,
+
+    /// Name of the elements in the list.
+    elements: NodeName,
+}
+
+/// The inforamtion used to generate a parser for an optional data structure.
+struct OptionParserData {
+    /// Name of the node.
+    name: NodeName,
+
+    /// Name of the element that may be contained.
+    elements: NodeName,
+}
+
+/// The actual exporter.
+struct CPPExporter {
+    /// The syntax to export.
+    syntax: Spec,
+
+    /// Rules, as specified in yaml.
+    rules: GlobalRules,
+
+    /// All parsers of lists.
+    list_parsers_to_generate: Vec<ListParserData>,
+
+    /// All parsers of options.
+    option_parsers_to_generate: Vec<OptionParserData>,
+}
+
+impl CPPExporter {
+    fn new(syntax: Spec, rules: GlobalRules) -> Self {
+        let mut list_parsers_to_generate = vec![];
+        let mut option_parsers_to_generate = vec![];
+        for (parser_node_name, typedef) in syntax.typedefs_by_name() {
+            if typedef.is_optional() {
+                let content_name = TypeName::type_spec(typedef.spec());
+                let content_node_name = syntax.get_node_name(&content_name)
+                    .unwrap_or_else(|| panic!("While generating an option parser, could not find node name {}", content_name))
+                    .clone();
+                debug!(target: "generate_spidermonkey", "CPPExporter::new adding optional typedef {:?} => {:?} => {:?}",
+                    parser_node_name,
+                    content_name,
+                    content_node_name);
+                option_parsers_to_generate.push(OptionParserData {
+                    name: parser_node_name.clone(),
+                    elements: content_node_name
+                });
+            } else if let TypeSpec::Array { ref contents, ref supports_empty } = *typedef.spec() {
+                let content_name = TypeName::type_(&**contents); // FIXME: Wait, do we have an implementation of type names in two places?
+                let content_node_name = syntax.get_node_name(&content_name)
+                    .unwrap_or_else(|| panic!("While generating an array parser, could not find node name {}", content_name))
+                    .clone();
+                list_parsers_to_generate.push(ListParserData {
+                    name: parser_node_name.clone(),
+                    supports_empty: *supports_empty,
+                    elements: content_node_name
+                });
+            }
+        }
+        list_parsers_to_generate.sort_by(|a, b| str::cmp(a.name.to_str(), b.name.to_str()));
+        option_parsers_to_generate.sort_by(|a, b| str::cmp(a.name.to_str(), b.name.to_str()));
+
+        CPPExporter {
+            syntax,
+            rules,
+            list_parsers_to_generate,
+            option_parsers_to_generate,
+        }
+    }
+
+// ----- Generating the header
+
+    /// Get the type representing a success for parsing this node.
+    fn get_type_ok(&self, name: &NodeName, default: &str) -> String {
+        let rules_for_this_interface = self.rules.get(name);
+        // If the override is provided, use it.
+        if let Some(ref type_ok) = rules_for_this_interface.type_ok {
+            return type_ok.to_string()
+        }
+        default.to_string()
+    }
+
+    fn get_method_signature(&self, name: &NodeName, default_type_ok: &str, prefix: &str, args: &str) -> String {
+        let type_ok = self.get_type_ok(name, default_type_ok);
+        let kind = name.to_class_cases();
+        format!("    JS::Result<{type_ok}> parse{prefix}{kind}({args});\n",
+            prefix = prefix,
+            type_ok = type_ok,
+            kind = kind,
+            args = args,
+        )
+    }
+
+    fn get_method_definition_start(&self, name: &NodeName, default_type_ok: &str, prefix: &str, args: &str) -> String {
+        let type_ok = self.get_type_ok(name, default_type_ok);
+        let kind = name.to_class_cases();
+        format!("JS::Result<{type_ok}>\nBinASTParser::parse{prefix}{kind}({args})",
+            prefix = prefix,
+            type_ok = type_ok,
+            kind = kind,
+            args = args,
+        )
+    }
+
+
+    /// Declaring enums for kinds and fields.
+    fn export_declare_kinds_and_fields_enums(&self, buffer: &mut String) {
+        buffer.push_str(&self.rules.hpp_tokens_header.reindent(""));
+
+        buffer.push_str("\n\n");
+        if self.rules.hpp_tokens_kind_doc.is_some() {
+            buffer.push_str(&self.rules.hpp_tokens_kind_doc.reindent(""));
+        }
+
+        let node_names = self.syntax.node_names()
+            .keys()
+            .sorted();
+        buffer.push_str(&format!("\n#define FOR_EACH_BIN_KIND(F) \\\n{nodes}\n\n",
+            nodes = node_names.iter()
+                .map(|name| format!("    F({name}, {name})",
+                    name = name))
+                .format(" \\\n")));
+        buffer.push_str("
+enum class BinKind {
+#define EMIT_ENUM(name, _) name,
+    FOR_EACH_BIN_KIND(EMIT_ENUM)
+#undef EMIT_ENUM
+};
+");
+
+        buffer.push_str(&format!("\n// The number of distinct values of BinKind.\nconst size_t BINKIND_LIMIT = {};\n", self.syntax.node_names().len()));
+        buffer.push_str("\n\n");
+        if self.rules.hpp_tokens_field_doc.is_some() {
+            buffer.push_str(&self.rules.hpp_tokens_field_doc.reindent(""));
+        }
+
+        let field_names = self.syntax.field_names()
+            .keys()
+            .sorted();
+        buffer.push_str(&format!("\n#define FOR_EACH_BIN_FIELD(F) \\\n{nodes}\n\n",
+            nodes = field_names.iter()
+                .map(|name| format!("    F({enum_name}, {spec_name})",
+                    spec_name = name,
+                    enum_name = name.to_cpp_enum_case()))
+                .format(" \\\n")));
+        buffer.push_str("
+enum class BinField {
+#define EMIT_ENUM(name, _) name,
+    FOR_EACH_BIN_FIELD(EMIT_ENUM)
+#undef EMIT_ENUM
+};
+");
+        buffer.push_str(&format!("\n// The number of distinct values of BinField.\nconst size_t BINFIELD_LIMIT = {};\n", self.syntax.field_names().len()));
+
+        buffer.push_str(&self.rules.hpp_tokens_footer.reindent(""));
+        buffer.push_str("\n");
+    }
+
+    /// Declare string enums
+    fn export_declare_string_enums_classes(&self, buffer: &mut String) {
+        buffer.push_str("\n\n// ----- Declaring string enums (by lexicographical order)\n");
+        let string_enums_by_name = self.syntax.string_enums_by_name()
+            .iter()
+            .sorted_by(|a, b| str::cmp(a.0.to_str(), b.0.to_str()));
+        for (name, enum_) in string_enums_by_name {
+            let rendered_cases = enum_.strings()
+                .iter()
+                .map(|str| format!("{case:<20}      /* \"{original}\" */",
+                    case = str.to_cpp_enum_case(),
+                    original = str))
+                .format(",\n    ");
+            let rendered = format!("enum class {name} {{\n    {cases}\n}};\n\n",
+                cases = rendered_cases,
+                name = name.to_class_cases());
+            buffer.push_str(&rendered);
+        }
+    }
+
+    fn export_declare_sums_of_interface_methods(&self, buffer: &mut String) {
+        let sums_of_interfaces = self.syntax.resolved_sums_of_interfaces_by_name()
+            .iter()
+            .sorted_by(|a, b| a.0.cmp(&b.0));
+        buffer.push_str("\n\n// ----- Sums of interfaces (by lexicographical order)\n");
+        buffer.push_str("// Implementations are autogenerated\n");
+        buffer.push_str("// `ParseNode*` may never be nullptr\n");
+        for &(ref name, _) in &sums_of_interfaces {
+            let rendered = self.get_method_signature(name, "ParseNode*", "", "");
+            buffer.push_str(&rendered.reindent(""));
+        }
+        for (name, _) in sums_of_interfaces {
+            let rendered = self.get_method_signature(name, "ParseNode*", "Sum", "const size_t start, const BinKind kind, const BinFields& fields");
+            buffer.push_str(&rendered.reindent(""));
+        }
+    }
+
+    fn export_declare_single_interface_methods(&self, buffer: &mut String) {
+        buffer.push_str("\n\n// ----- Interfaces (by lexicographical order)\n");
+        buffer.push_str("// Implementations are autogenerated\n");
+        buffer.push_str("// `ParseNode*` may never be nullptr\n");
+        let interfaces_by_name = self.syntax.interfaces_by_name()
+            .iter()
+            .sorted_by(|a, b| str::cmp(a.0.to_str(), b.0.to_str()));
+
+        let mut outer_parsers = Vec::with_capacity(interfaces_by_name.len());
+        let mut inner_parsers = Vec::with_capacity(interfaces_by_name.len());
+
+        for &(name, _) in &interfaces_by_name {
+            let outer = self.get_method_signature(name, "ParseNode*", "", "");
+            let inner = self.get_method_signature(name, "ParseNode*", "Interface", "const size_t start, const BinKind kind, const BinFields& fields");
+            outer_parsers.push(outer.reindent(""));
+            inner_parsers.push(inner.reindent(""));
+        }
+
+        for parser in outer_parsers.drain(..) {
+            buffer.push_str(&parser);
+            buffer.push_str("\n");
+        }
+
+        for parser in inner_parsers.drain(..) {
+            buffer.push_str(&parser);
+            buffer.push_str("\n");
+        }
+    }
+
+    fn export_declare_string_enums_methods(&self, buffer: &mut String) {
+        buffer.push_str("\n\n// ----- String enums (by lexicographical order)\n");
+        buffer.push_str("// Implementations are autogenerated\n");
+        let string_enums_by_name = self.syntax.string_enums_by_name()
+            .iter()
+            .sorted_by(|a, b| str::cmp(a.0.to_str(), b.0.to_str()));
+        for (kind, _) in string_enums_by_name {
+            let type_ok = format!("BinASTParser::{kind}", kind = kind.to_class_cases());
+            let rendered = self.get_method_signature(kind, &type_ok, "", "");
+            buffer.push_str(&rendered.reindent(""));
+            buffer.push_str("\n");
+        }
+    }
+
+    fn export_declare_list_methods(&self, buffer: &mut String) {
+        buffer.push_str("\n\n// ----- Lists (by lexicographical order)\n");
+        buffer.push_str("// Implementations are autogenerated\n");
+        for parser in &self.list_parsers_to_generate {
+            let rendered = self.get_method_signature(&parser.name, "ParseNode*", "", "");
+            buffer.push_str(&rendered.reindent(""));
+            buffer.push_str("\n");
+        }
+    }
+
+    fn export_declare_option_methods(&self, buffer: &mut String) {
+        buffer.push_str("\n\n// ----- Default values (by lexicographical order)\n");
+        buffer.push_str("// Implementations are autogenerated\n");
+        for parser in &self.option_parsers_to_generate {
+            let rendered = self.get_method_signature(&parser.name, "ParseNode*", "", "");
+            buffer.push_str(&rendered.reindent(""));
+            buffer.push_str("\n");
+        }
+    }
+
+    fn generate_autogenerated_warning(&self) -> String {
+        let warning = format!("// This file was autogenerated by binjs_generate_spidermonkey,
+// please DO NOT EDIT BY HAND.
+");
+        warning
+    }
+
+    /// Generate C++ headers for SpiderMonkey
+    fn to_spidermonkey_token_hpp(&self) -> String {
+        let mut buffer = String::new();
+
+        buffer.push_str(&self.generate_autogenerated_warning());
+
+        self.export_declare_kinds_and_fields_enums(&mut buffer);
+
+        buffer.push_str("\n");
+        buffer
+    }
+    fn to_spidermonkey_class_hpp(&self) -> String {
+        let mut buffer = String::new();
+
+        buffer.push_str(&self.generate_autogenerated_warning());
+
+        buffer.push_str(&self.rules.hpp_class_header.reindent(""));
+        buffer.push_str("\n");
+
+        self.export_declare_string_enums_classes(&mut buffer);
+        self.export_declare_sums_of_interface_methods(&mut buffer);
+        self.export_declare_single_interface_methods(&mut buffer);
+        self.export_declare_string_enums_methods(&mut buffer);
+        self.export_declare_list_methods(&mut buffer);
+        self.export_declare_option_methods(&mut buffer);
+
+        buffer.push_str("\n");
+        buffer
+    }
+}
+
+impl CPPExporter {
+    /// Generate implementation of a single typesum.
+    fn generate_implement_sum(&self, buffer: &mut String, name: &NodeName, nodes: &HashSet<NodeName>) {
+        // Generate comments (FIXME: We should use the actual webidl, not the resolved sum)
+        let nodes = nodes.iter()
+            .sorted();
+        let kind = name.to_class_cases();
+        let rendered_bnf = format!("/*\n{name} ::= {nodes}\n*/",
+            nodes = nodes.iter()
+                .format("\n    "),
+            name = name.to_str());
+
+        // Generate outer method
+        buffer.push_str(&format!("{bnf}
+{first_line}
+{{
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+    const auto start = tokenizer_->offset();
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+
+    MOZ_TRY_DECL(result, parseSum{kind}(start, kind, fields));
+
+    TRY(guard.done());
+    return result;
+}}\n",
+                bnf = rendered_bnf,
+                kind = kind,
+                first_line = self.get_method_definition_start(name, "ParseNode*", "", "")
+        ));
+
+        // Generate inner method
+        let mut buffer_cases = String::new();
+        for node in nodes {
+            buffer_cases.push_str(&format!("
+      case BinKind::{kind}:
+        MOZ_TRY_VAR(result, parseInterface{kind}(start, kind, fields));
+        break;",
+                kind = node.to_class_cases()));
+        }
+        buffer.push_str(&format!("\n{first_line}
+{{
+    {type_ok} result;
+    switch(kind) {{{cases}
+      default:
+        return raiseInvalidKind(\"{kind}\", kind);
+    }}
+    return result;
+}}
+
+",
+            kind = kind,
+            cases = buffer_cases,
+            first_line = self.get_method_definition_start(name, "ParseNode*", "Sum", "const size_t start, const BinKind kind, const BinFields& fields"),
+            type_ok = self.get_type_ok(name, "ParseNode*")
+        ));
+    }
+
+    /// Generate the implementation of a single list parser
+    fn generate_implement_list(&self, buffer: &mut String, parser: &ListParserData) {
+        let rules_for_this_list = self.rules.get(&parser.name);
+
+        // Warn if some rules are unused.
+        for &(condition, name) in &[
+            (rules_for_this_list.build_result.is_some(), "build:"),
+            (rules_for_this_list.type_ok.is_some(), "type-ok:"),
+            (rules_for_this_list.by_field.len() > 0, "fields:"),
+        ] {
+            if condition {
+                warn!("In {}, rule `{}` was specified but is ignored.", parser.name, name);
+            }
+        }
+
+        let kind = parser.name.to_class_cases();
+        let first_line = self.get_method_definition_start(&parser.name, "ParseNode*", "", "");
+
+        let init = match rules_for_this_list.init {
+            Some(str) => str.reindent("    "),
+            None => {
+                // We cannot generate the method if we don't know how to initialize the list.
+                let rendered = format!("
+{first_line}
+{{
+    return raiseError(\"FIXME: Not implemented yet ({kind})\");
+}}\n",
+                    first_line = first_line,
+                    kind = kind,
+                );
+                buffer.push_str(&rendered);
+                return;
+            }
+        };
+        let append = match rules_for_this_list.append {
+            Some(str) => format!("{}", str.reindent("        ")),
+            None => "        result->appendWithoutOrderAssumption(item);".to_string()
+        };
+
+
+        let rendered = format!("\n{first_line}
+{{
+    uint32_t length;
+    AutoList guard(*tokenizer_);
+
+    const auto start = tokenizer_->offset();
+    TRY(tokenizer_->enterList(length, guard));{empty_check}
+{init}
+
+    for (uint32_t i = 0; i < length; ++i) {{
+        MOZ_TRY_DECL(item, parse{inner}());
+{append}
+    }}
+
+    TRY(guard.done());
+    return result;
+}}\n",
+            first_line = first_line,
+            empty_check =
+                if parser.supports_empty {
+                    "".to_string()
+                } else {
+                    format!("\n    if (length == 0)\n         return raiseEmpty(\"{kind}\");\n",
+                        kind = kind)
+                },
+            inner = parser.elements.to_class_cases(),
+            init = init,
+            append = append);
+        buffer.push_str(&rendered);
+    }
+
+    fn generate_implement_option(&self, buffer: &mut String, parser: &OptionParserData) {
+        debug!(target: "generate_spidermonkey", "Implementing optional value {} backed by {}",
+            parser.name.to_str(), parser.elements.to_str());
+
+        let rules_for_this_node = self.rules.get(&parser.name);
+
+        // Warn if some rules are unused.
+        for &(condition, name) in &[
+            (rules_for_this_node.build_result.is_some(), "build:"),
+            (rules_for_this_node.append.is_some(), "append:"),
+            (rules_for_this_node.by_field.len() > 0, "fields:"),
+        ] {
+            if condition {
+                warn!("In {}, rule `{}` was specified but is ignored.", parser.name, name);
+            }
+        }
+
+        let type_ok = self.get_type_ok(&parser.name, "ParseNode*");
+        let default_value =
+            if type_ok == "Ok" {
+                "Ok()"
+            } else {
+                "nullptr"
+            }.to_string();
+
+        // At this stage, thanks to deanonymization, `contents`
+        // is something like `OptionalFooBar`.
+        let named_implementation =
+            if let Some(NamedType::Typedef(ref typedef)) = self.syntax.get_type_by_name(&parser.name) {
+                assert!(typedef.is_optional());
+                if let TypeSpec::NamedType(ref named) = *typedef.spec() {
+                    self.syntax.get_type_by_name(named)
+                        .unwrap_or_else(|| panic!("Internal error: Could not find type {}, which should have been generated.", named.to_str()))
+                } else {
+                    panic!("Internal error: In {}, type {:?} should have been a named type",
+                        parser.name.to_str(),
+                        typedef);
+                }
+            } else {
+                panic!("Internal error: In {}, there should be a type with that name",
+                    parser.name.to_str());
+            };
+        match named_implementation {
+            NamedType::Interface(_) => {
+                buffer.push_str(&format!("{first_line}
+{{
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    {type_ok} result;
+    if (kind == BinKind::{null}) {{
+        result = {default_value};
+    }} else {{
+        const auto start = tokenizer_->offset();
+        MOZ_TRY_VAR(result, parseInterface{contents}(start, kind, fields));
+    }}
+    TRY(guard.done());
+
+    return result;
+}}
+
+",
+                    first_line = self.get_method_definition_start(&parser.name, "ParseNode*", "", ""),
+                    null = self.syntax.get_null_name().to_str(),
+                    contents = parser.elements.to_class_cases(),
+                    type_ok = type_ok,
+                    default_value = default_value,
+                ));
+            }
+            NamedType::Typedef(ref type_) => {
+                match type_.spec() {
+                    &TypeSpec::TypeSum(_) => {
+                buffer.push_str(&format!("{first_line}
+{{
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    {type_ok} result;
+    if (kind == BinKind::_Null) {{
+        result = {default_value};
+    }} else {{
+        const auto start = tokenizer_->offset();
+        MOZ_TRY_VAR(result, parseSum{contents}(start, kind, fields));
+    }}
+    TRY(guard.done());
+
+    return result;
+}}
+
+",
+                            first_line = self.get_method_definition_start(&parser.name, "ParseNode*", "", ""),
+                            contents = parser.elements.to_class_cases(),
+                            type_ok = type_ok,
+                            default_value = default_value,
+                        ));
+                    }
+                    &TypeSpec::String => {
+                        let build_result = rules_for_this_node.init.reindent("    ");
+
+                        buffer.push_str(&format!("{first_line}
+{{
+    RootedAtom string(cx_);
+    MOZ_TRY(readMaybeString(&string));
+
+{build}
+
+    {return_}
+}}
+
+",
+                            first_line = self.get_method_definition_start(&parser.name, "ParseNode*", "", ""),
+                            build = build_result,
+                            return_ = if build_result.len() == 0 {
+                                format!("return raiseError(\"FIXME: Not implemented yet ({kind})\");\n",
+                                    kind = parser.name.to_str())
+                            } else {
+                                "return result;".to_string()
+                            }
+                        ));
+                    }
+                    _else => unimplemented!("{:?}", _else)
+                }
+            }
+            NamedType::StringEnum(_) => {
+                unimplemented!()
+            }
+        }
+    }
+
+    fn generate_implement_interface(&self, buffer: &mut String, name: &NodeName, interface: &Interface) {
+        let rules_for_this_interface = self.rules.get(name);
+
+        for &(condition, rule_name) in &[
+            (rules_for_this_interface.append.is_some(), "build:"),
+        ] {
+            if condition {
+                warn!("In {}, rule `{}` was specified but is ignored.", name, rule_name);
+            }
+        }
+
+        // Generate comments
+        let comment = format!("\n/*\n{}*/\n", ToWebidl::interface(interface, "", "    "));
+        buffer.push_str(&comment);
+
+        // Generate public method
+        let kind = name.to_class_cases();
+        buffer.push_str(&format!("{first_line}
+{{
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    const auto start = tokenizer_->offset();
+
+    MOZ_TRY_DECL(result, parseInterface{kind}(start, kind, fields));
+    TRY(guard.done());
+
+    return result;
+}}
+
+",
+            first_line = self.get_method_definition_start(name, "ParseNode*", "", ""),
+            kind = kind
+        ));
+
+        // Generate aux method
+        let number_of_fields = interface.contents().fields().len();
+        let first_line = self.get_method_definition_start(name, "ParseNode*", "Interface", "const size_t start, const BinKind kind, const BinFields& fields");
+
+        let fields_type_list = format!("{{ {} }}", interface.contents()
+            .fields()
+            .iter()
+            .map(|field| format!("BinField::{}", field.name().to_cpp_enum_case()))
+            .format(", "));
+
+        let mut fields_implem = String::new();
+        for field in interface.contents().fields() {
+            let rules_for_this_field = rules_for_this_interface.by_field.get(field.name())
+                .cloned()
+                .unwrap_or_default();
+            let needs_block = rules_for_this_field.block_before_field.is_some() || rules_for_this_field.block_after_field.is_some();
+
+            let var_name = field.name().to_cpp_field_case();
+            let (decl_var, parse_var) = match field.type_().get_primitive(&self.syntax) {
+                Some(IsNullable { is_nullable: false, content: Primitive::Number }) => {
+                    if needs_block {
+                        (Some(format!("double {var_name};", var_name = var_name)),
+                            Some(format!("MOZ_TRY_VAR({var_name}, readNumber());", var_name = var_name)))
+                    } else {
+                        (None,
+                            Some(format!("MOZ_TRY_DECL({var_name}, readNumber());", var_name = var_name)))
+                    }
+                }
+                Some(IsNullable { is_nullable: false, content: Primitive::Boolean }) => {
+                    if needs_block {
+                        (Some(format!("bool {var_name};", var_name = var_name)),
+                        Some(format!("MOZ_TRY_VAR({var_name}, readBool());", var_name = var_name)))
+                    } else {
+                        (None,
+                        Some(format!("MOZ_TRY_DECL({var_name}, readBool());", var_name = var_name)))
+                    }
+                }
+                Some(IsNullable { content: Primitive::Void, .. }) => {
+                    warn!("Internal error: We shouldn't have any `void` types at this stage.");
+                    (Some(format!("// Skipping void field {}", field.name().to_str())),
+                        None)
+                }
+                Some(IsNullable { is_nullable: false, content: Primitive::String }) => {
+                    (Some(format!("RootedAtom {var_name}(cx_);", var_name = var_name)),
+                        Some(format!("MOZ_TRY(readString(&{var_name}));", var_name = var_name)))
+                }
+                Some(IsNullable { content: Primitive::Interface(ref interface), ..})
+                    if &self.get_type_ok(interface.name(), "?") == "Ok" =>
+                {
+                    // Special case: `Ok` means that we shouldn't bind the return value.
+                    let typename = TypeName::type_(field.type_());
+                    (None,
+                        Some(format!("MOZ_TRY(parse{typename}());",
+                            typename = typename)))
+                }
+                _else => {
+                    let typename = TypeName::type_(field.type_());
+                    if needs_block {
+                        (Some(format!("{typename} {var_name};",
+                            var_name = var_name,
+                            typename = typename)),
+                            Some(format!("MOZ_TRY_VAR({var_name}, parse{typename}());",
+                            var_name = var_name,
+                            typename = typename)
+                        ))
+                    } else {
+                        (None,
+                            Some(format!("MOZ_TRY_DECL({var_name}, parse{typename}());",
+                            var_name = var_name,
+                            typename = typename)))
+                    }
+                }
+            };
+
+            let rendered = {
+                if rules_for_this_field.replace.is_some() {
+                    for &(condition, rule_name) in &[
+                        (rules_for_this_field.before_field.is_some(), "before:"),
+                        (rules_for_this_field.after_field.is_some(), "after:"),
+                        (rules_for_this_field.declare.is_some(), "declare:"),
+                    ] {
+                        if condition {
+                            warn!("In {}, rule `{}` was specified but is ignored because `replace:` is also specified.", name, rule_name);
+                        }
+                    }
+                    rules_for_this_field.replace.reindent("    ")
+                        .newline()
+                } else {
+                    let before_field = rules_for_this_field.before_field.reindent("    ");
+                    let after_field = rules_for_this_field.after_field.reindent("    ");
+                    let decl_var = if rules_for_this_field.declare.is_some() {
+                        rules_for_this_field.declare.reindent("    ")
+                    } else {
+                        decl_var.reindent("    ")
+                    };
+                    if needs_block {
+                        let parse_var = parse_var.reindent("        ");
+                        format!("{before_field}
+{decl_var}
+    {{
+{block_before_field}
+{parse_var}
+{block_after_field}
+    }}
+{after_field}",
+                            before_field = before_field.reindent("    "),
+                            decl_var = decl_var.reindent("    "),
+                            parse_var = parse_var.reindent("        "),
+                            after_field = after_field.reindent("    "),
+                            block_before_field = rules_for_this_field.block_before_field.reindent("        "),
+                            block_after_field = rules_for_this_field.block_after_field.reindent("        "))
+                    } else {
+                        // We have a before_field and an after_field. This will create newlines
+                        // for them.
+                        format!("
+{before_field}
+{decl_var}
+{parse_var}
+{after_field}
+",
+                            before_field = before_field.reindent("    "),
+                            decl_var = decl_var.reindent("    "),
+                            parse_var = parse_var.reindent("    "),
+                            after_field = after_field.reindent("    "))
+                    }
+                }
+            };
+            fields_implem.push_str(&rendered);
+        }
+
+        let init = rules_for_this_interface.init.reindent("    ");
+        let build_result = rules_for_this_interface.build_result.reindent("    ");
+
+        if build_result == "" {
+            buffer.push_str(&format!("{first_line}
+{{
+    return raiseError(\"FIXME: Not implemented yet ({})\");
+}}
+
+",
+                kind = kind.to_str(),
+                first_line = first_line,
+            ));
+        } else {
+            let check_fields = if number_of_fields == 0 {
+                format!("MOZ_TRY(checkFields0(kind, fields));")
+            } else {
+                format!("MOZ_TRY(checkFields(kind, fields, {fields_type_list}));",
+                    fields_type_list = fields_type_list)
+            };
+            buffer.push_str(&format!("{first_line}
+{{
+    MOZ_ASSERT(kind == BinKind::{kind});
+    CheckRecursionLimit(cx_);
+
+    {check_fields}
+{pre}{fields_implem}
+{post}return result;
+}}
+
+",
+                check_fields = check_fields,
+                fields_implem = fields_implem,
+                pre = init,
+                post = build_result,
+                kind = kind,
+                first_line = first_line,
+            ));
+        }
+    }
+
+    /// Generate C++ code for SpiderMonkey
+    fn to_spidermonkey_cpp(&self) -> String {
+        let mut buffer = String::new();
+
+        buffer.push_str(&self.generate_autogenerated_warning());
+
+        // 0. Header
+        buffer.push_str(&self.rules.cpp_header.reindent(""));
+        buffer.push_str("\n");
+
+        // 1. Typesums
+        buffer.push_str("\n\n// ----- Sums of interfaces (autogenerated, by lexicographical order)\n");
+        buffer.push_str("// Sums of sums are flattened.\n");
+
+        let sums_of_interfaces = self.syntax.resolved_sums_of_interfaces_by_name()
+            .iter()
+            .sorted_by(|a, b| a.0.cmp(&b.0));
+
+        for (name, nodes) in sums_of_interfaces {
+            self.generate_implement_sum(&mut buffer, name, nodes);
+        }
+
+        // 2. Single interfaces
+        buffer.push_str("\n\n// ----- Interfaces (autogenerated, by lexicographical order)\n");
+        buffer.push_str("// When fields have a non-trivial type, implementation is deanonymized and delegated to another parser.\n");
+        let interfaces_by_name = self.syntax.interfaces_by_name()
+            .iter()
+            .sorted_by(|a, b| str::cmp(a.0.to_str(), b.0.to_str()));
+
+        for (name, interface) in interfaces_by_name {
+            self.generate_implement_interface(&mut buffer, name, interface);
+        }
+
+        // 3. String Enums
+        buffer.push_str("\n\n// ----- String enums (autogenerated, by lexicographical order)\n");
+        {
+            let string_enums_by_name = self.syntax.string_enums_by_name()
+                .iter()
+                .sorted_by(|a, b| str::cmp(a.0.to_str(), b.0.to_str()));
+            for (kind, enum_) in string_enums_by_name {
+                let convert = format!("{cases}
+
+    return raiseInvalidEnum(\"{kind}\", chars);",
+                    kind = kind,
+                    cases = enum_.strings()
+                        .iter()
+                        .map(|string| {
+                            format!("    if (chars == \"{string}\")
+        return {kind}::{variant};",
+                                string = string,
+                                kind = kind,
+                                variant = string.to_cpp_enum_case()
+                            )
+                        })
+                        .format("\n")
+                );
+
+                let rendered_doc = format!("/*\nenum {kind} {{\n{cases}\n}};\n*/\n",
+                    kind = kind,
+                    cases = enum_.strings()
+                            .iter()
+                            .map(|s| format!("    \"{}\"", s))
+                            .format(",\n")
+                );
+                buffer.push_str(&format!("{rendered_doc}{first_line}
+{{
+    // Unoptimized implementation.
+    Chars chars(cx_);
+    MOZ_TRY(readString(chars));
+
+{convert}
+}}
+
+",
+                    rendered_doc = rendered_doc,
+                    convert = convert,
+                    first_line = self.get_method_definition_start(kind, &format!("BinASTParser::{kind}", kind = kind), "", "")
+                ));
+            }
+        }
+
+        // 4. Lists
+        buffer.push_str("\n\n// ----- Lists (autogenerated, by lexicographical order)\n");
+        for parser in &self.list_parsers_to_generate {
+            self.generate_implement_list(&mut buffer, parser);
+        }
+
+        // 5. Optional values
+        buffer.push_str("\n\n    // ----- Default values (by lexicographical order)\n");
+        for parser in &self.option_parsers_to_generate {
+            self.generate_implement_option(&mut buffer, parser);
+        }
+
+        buffer.push_str("\n");
+        buffer.push_str(&self.rules.cpp_footer.reindent(""));
+        buffer.push_str("\n");
+
+        buffer
+    }
+}
+
+fn update_rule(rule: &mut Option<String>, entry: &yaml_rust::Yaml) -> Result<Option<()>, ()> {
+    if entry.is_badvalue() {
+        return Ok(None)
+    } else if let Some(as_str) = entry.as_str() {
+        *rule = Some(as_str.to_string());
+        Ok(Some(()))
+    } else {
+        Err(())
+    }
+}
+
+
+fn main() {
+    env_logger::init();
+
+    let matches = App::new("BinAST C++ parser generator")
+        .author("David Teller, <dteller@mozilla.com>")
+        .about("Converts an webidl syntax definition and a yaml set of rules into the C++ source code of a parser.")
+        .args(&[
+            Arg::with_name("INPUT.webidl")
+                .required(true)
+                .help("Input webidl file to use. Must be a webidl source file."),
+            Arg::with_name("INPUT.yaml")
+                .required(true)
+                .help("Input rules file to use. Must be a yaml source file."),
+            Arg::with_name("OUT_HEADER_CLASS_FILE")
+                .long("out-class")
+                .required(true)
+                .takes_value(true)
+                .help("Output header file (.h, designed to be included from within the class file)"),
+            Arg::with_name("OUT_TOKEN_FILE")
+                .long("out-token")
+                .required(true)
+                .takes_value(true)
+                .help("Output token file (.h)"),
+            Arg::with_name("OUT_IMPL_FILE")
+                .long("out-impl")
+                .required(true)
+                .takes_value(true)
+                .help("Output implementation file (.cpp)"),
+        ])
+    .get_matches();
+
+    let source_path = matches.value_of("INPUT.webidl")
+        .expect("Expected INPUT.webidl");
+
+    let mut file = File::open(source_path)
+        .expect("Could not open source");
+    let mut source = String::new();
+    file.read_to_string(&mut source)
+        .expect("Could not read source");
+
+    println!("...parsing webidl");
+    let ast = webidl::parse_string(&source)
+        .expect("Could not parse source");
+
+    println!("...verifying grammar");
+    let mut builder = Importer::import(&ast);
+    let fake_root = builder.node_name("");
+    let null = builder.node_name("_Null");
+    builder.add_interface(&null)
+        .unwrap();
+    let syntax = builder.into_spec(SpecOptions {
+        root: &fake_root,
+        null: &null,
+    });
+
+    let deanonymizer = TypeDeanonymizer::new(&syntax);
+    let syntax_options = SpecOptions {
+        root: &fake_root,
+        null: &null,
+    };
+    let new_syntax = deanonymizer.into_spec(syntax_options);
+
+    let rules_source_path = matches.value_of("INPUT.yaml").unwrap();
+    println!("...generating rules");
+    let mut file = File::open(rules_source_path)
+        .expect("Could not open rules");
+    let mut data = String::new();
+    file.read_to_string(&mut data)
+        .expect("Could not read rules");
+
+    let yaml = yaml_rust::YamlLoader::load_from_str(&data)
+        .expect("Could not parse rules");
+    assert_eq!(yaml.len(), 1);
+
+    let global_rules = GlobalRules::new(&new_syntax, &yaml[0]);
+    let exporter = CPPExporter::new(new_syntax, global_rules);
+
+    let write_to = |description, arg, data: &String| {
+        let dest_path = matches.value_of(arg)
+            .unwrap();
+        println!("...exporting {description}: {path}",
+            description = description,
+            path = dest_path);
+        let mut dest = File::create(&dest_path)
+            .unwrap_or_else(|e| panic!("Could not create {description} at {path}: {error}",
+                            description = description,
+                            path = dest_path,
+                            error = e));
+        dest.write_all(data.as_bytes())
+            .unwrap_or_else(|e| panic!("Could not write {description} at {path}: {error}",
+                            description = description,
+                            path = dest_path,
+                            error = e));
+    };
+
+    write_to("C++ class header code", "OUT_HEADER_CLASS_FILE",
+        &exporter.to_spidermonkey_class_hpp());
+    write_to("C++ token header code", "OUT_TOKEN_FILE",
+        &exporter.to_spidermonkey_token_hpp());
+    write_to("C++ token implementation code", "OUT_IMPL_FILE",
+        &exporter.to_spidermonkey_cpp());
+
+    println!("...done");
+}
--- a/python/mozbuild/mozbuild/vendor_rust.py
+++ b/python/mozbuild/mozbuild/vendor_rust.py
@@ -134,42 +134,58 @@ Please commit or stash these changes bef
         # list and that multiple entries can be separated by '/'.  We
         # choose to list all combinations instead for the sake of
         # completeness and because some entries below obviously do not
         # conform to the format prescribed in the documentation.
         #
         # It is insufficient to have additions to this whitelist reviewed
         # solely by a build peer; any additions must be checked by somebody
         # competent to review licensing minutiae.
-        LICENSE_WHITELIST = [
+
+        # Licenses for code used at runtime. Please see the above comment before
+        # adding anything to this list.
+        RUNTIME_LICENSE_WHITELIST = [
             'Apache-2.0',
             'Apache-2.0 / MIT',
             'Apache-2.0/MIT',
             'Apache-2 / MIT',
-            'BSD-3-Clause', # bindgen (only used at build time)
             'CC0-1.0',
             'ISC',
             'ISC/Apache-2.0',
             'MIT',
             'MIT / Apache-2.0',
             'MIT/Apache-2.0',
             'MIT OR Apache-2.0',
             'MPL-2.0',
             'Unlicense/MIT',
         ]
 
+        # Licenses for code used at build time (e.g. code generators). Please see the above
+        # comments before adding anything to this list.
+        BUILDTIME_LICENSE_WHITELIST = {
+            'BSD-2-Clause': [
+                'Inflector',
+            ],
+            'BSD-3-Clause': [
+                'adler32',
+                'bindgen',
+                'fuchsia-zircon',
+                'fuchsia-zircon-sys',
+            ]
+        }
+
         # This whitelist should only be used for packages that use a
         # license-file and for which the license-file entry has been
         # reviewed.  The table is keyed by package names and maps to the
         # sha256 hash of the license file that we reviewed.
         #
         # As above, it is insufficient to have additions to this whitelist
         # reviewed solely by a build peer; any additions must be checked by
         # somebody competent to review licensing minutiae.
-        LICENSE_FILE_PACKAGE_WHITELIST = {
+        RUNTIME_LICENSE_FILE_PACKAGE_WHITELIST = {
             # MIT
             'deque': '6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb',
         }
 
         LICENSE_LINE_RE = re.compile(r'\s*license\s*=\s*"([^"]+)"')
         LICENSE_FILE_LINE_RE = re.compile(r'\s*license[-_]file\s*=\s*"([^"]+)"')
 
         def check_package(package):
@@ -202,30 +218,41 @@ Please commit or stash these changes bef
                              'package {} provides too many licenses'.format(package))
                     return False
 
                 if license_matches:
                     license = license_matches[0].group(1)
                     self.log(logging.DEBUG, 'package_license', {},
                              'has license {}'.format(license))
 
-                    if license not in LICENSE_WHITELIST:
-                        self.log(logging.ERROR, 'package_license_error', {},
+                    if license not in RUNTIME_LICENSE_WHITELIST:
+                        if license not in BUILDTIME_LICENSE_WHITELIST:
+                            self.log(logging.ERROR, 'package_license_error', {},
                                  '''Package {} has a non-approved license: {}.
 
 Please request license review on the package's license.  If the package's license
 is approved, please add it to the whitelist of suitable licenses.
 '''.format(package, license))
-                        return False
+                            return False
+                        elif package not in BUILDTIME_LICENSE_WHITELIST[license]:
+                            self.log(logging.ERROR, 'package_license_error', {},
+                                 '''Package {} has a license that is approved for build-time dependencies: {}
+but the package itself is not whitelisted as being a build-time only package.
+
+If your package is build-time only, please add it to the whitelist of build-time
+only packages. Otherwise, you need to request license review on the package's license.
+If the package's license is approved, please add it to the whitelist of suitable licenses.
+'''.format(package, license))
+                            return False
                 else:
                     license_file = license_file_matches[0].group(1)
                     self.log(logging.DEBUG, 'package_license_file', {},
                              'has license-file {}'.format(license_file))
 
-                    if package not in LICENSE_FILE_PACKAGE_WHITELIST:
+                    if package not in RUNTIME_LICENSE_FILE_PACKAGE_WHITELIST:
                         self.log(logging.ERROR, 'package_license_file_unknown', {},
                                  '''Package {} has an unreviewed license file: {}.
 
 Please request review on the provided license; if approved, the package can be added
 to the whitelist of packages whose licenses are suitable.
 '''.format(package, license_file))
                         return False