text: "use strict";
  
  (() => {
    function tokenise(str) {
      const tokens = [];
      const re = {
        "float": /^-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/,
        "integer": /^-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/,
        "identifier": /^[A-Z_a-z][0-9A-Z_a-z-]*/,
        "string": /^"[^"]*"/,
        "whitespace": /^(?:[\t\n\r ]+|[\t\n\r ]*((\/\/.*|\/\*(.|\n|\r)*?\*\/)[\t\n\r ]*))+/,
        "other": /^[^\t\n\r 0-9A-Z_a-z]/
      };
      const types = ["float", "integer", "identifier", "string", "whitespace", "other"];
      while (str.length > 0) {
        let matched = false;
        for (const type of types) {
          str = str.replace(re[type], tok => {
            tokens.push({ type, value: tok });
            matched = true;
            return "";
          });
          if (matched) break;
        }
        if (matched) continue;
        throw new Error("Token stream not progressing");
      }
      return tokens;
    };
  
    class WebIDLParseError {
      constructor(str, line, input, tokens) {
        this.message = str;
        this.line = line;
        this.input = input;
        this.tokens = tokens;
      }
  
      toString() {
        return `${this.message}, line ${this.line} (tokens: '${this.input}')\n${JSON.stringify(this.tokens, null, 4)}`;
      }
    }
  
    function parse(tokens, opt) {
      let line = 1;
      tokens = tokens.slice();
      const names = new Map();
  
      const FLOAT = "float";
      const INT = "integer";
      const ID = "identifier";
      const STR = "string";
      const OTHER = "other";
  
      const EMPTY_OPERATION = Object.freeze({
        type: "operation",
        getter: false,
        setter: false,
        deleter: false,
        "static": false,
        stringifier: false
      });
  
      function error(str) {
        let tok = "";
        let numTokens = 0;
        const maxTokens = 5;
        while (numTokens < maxTokens && tokens.length > numTokens) {
          tok += tokens[numTokens].value;
          numTokens++;
        }
        throw new WebIDLParseError(str, line, tok, tokens.slice(0, maxTokens));
      };
  
      function sanitize_name(name, type) {
        if (names.has(name)) {
          error(`The name "${name}" of type "${names.get(name)}" is already seen`);
        }
        names.set(name, type);
        return name;
      }
  
      let last_token = null;
  
      function consume(type, value) {
        if (!tokens.length || tokens[0].type !== type) return;
        if (typeof value === "undefined" || tokens[0].value === value) {
          last_token = tokens.shift();
          if (type === ID) last_token.value = last_token.value.replace(/^_/, "");
          return last_token;
        }
      };
  
      function ws() {
        if (!tokens.length) return;
        if (tokens[0].type === "whitespace") {
          const t = tokens.shift();
          t.value.replace(/\n/g, m => {
            line++;
            return m;
          });
          return t;
        }
      };
  
      function all_ws(store, pea) { // pea == post extended attribute, tpea = same for types
        const t = { type: "whitespace", value: "" };
        while (true) {
          const w = ws();
          if (!w) break;
          t.value += w.value;
        }
        if (t.value.length > 0) {
          if (store) {
            let w = t.value;
            const re = {
              "ws": /^([\t\n\r ]+)/,
              "line-comment": /^\/\/(.*)\r?\n?/,
              "multiline-comment": /^\/\*((?:.|\n|\r)*?)\*\//
            };
            const wsTypes = [];
            for (const k in re) wsTypes.push(k);
            while (w.length) {
              let matched = false;
              for (const type of wsTypes) {
                w = w.replace(re[type], (tok, m1) => {
                  store.push({ type: type + (pea ? ("-" + pea) : ""), value: m1 });
                  matched = true;
                  return "";
                });
                if (matched) break;
              }
              if (matched) continue;
              throw new Error("Surprising white space construct."); // this shouldn't happen
            }
          }
          return t;
        }
      };
  
      function integer_type() {
        let ret = "";
        all_ws();
        if (consume(ID, "unsigned")) ret = "unsigned ";
        all_ws();
        if (consume(ID, "short")) return ret + "short";
        if (consume(ID, "long")) {
          ret += "long";
          all_ws();
          if (consume(ID, "long")) return ret + " long";
          return ret;
        }
        if (ret) error("Failed to parse integer type");
      };
  
      function float_type() {
        let ret = "";
        all_ws();
        if (consume(ID, "unrestricted")) ret = "unrestricted ";
        all_ws();
        if (consume(ID, "float")) return ret + "float";
        if (consume(ID, "double")) return ret + "double";
        if (ret) error("Failed to parse float type");
      };
  
      function primitive_type() {
        const num_type = integer_type() || float_type();
        if (num_type) return num_type;
        all_ws();
        if (consume(ID, "boolean")) return "boolean";
        if (consume(ID, "byte")) return "byte";
        if (consume(ID, "octet")) return "octet";
      };
  
      function const_value() {
        if (consume(ID, "true")) return { type: "boolean", value: true };
        if (consume(ID, "false")) return { type: "boolean", value: false };
        if (consume(ID, "null")) return { type: "null" };
        if (consume(ID, "Infinity")) return { type: "Infinity", negative: false };
        if (consume(ID, "NaN")) return { type: "NaN" };
        const ret = consume(FLOAT) || consume(INT);
        if (ret) return { type: "number", value: ret.value };
        const tok = consume(OTHER, "-");
        if (tok) {
          if (consume(ID, "Infinity")) return { type: "Infinity", negative: true };
          else tokens.unshift(tok);
        }
      };
  
      function type_suffix(obj) {
        while (true) {
          all_ws();
          if (consume(OTHER, "?")) {
            if (obj.nullable) error("Can't nullable more than once");
            obj.nullable = true;
          } else return;
        }
      };
  
      function single_type() {
        const prim = primitive_type();
        const ret = { sequence: false, generic: null, nullable: false, union: false };
        let name;
        let value;
        if (prim) {
          ret.idlType = prim;
        } else if (name = consume(ID)) {
          value = name.value;
          all_ws();
          // Generic types
          if (consume(OTHER, "<")) {
            // backwards compat
            if (value === "sequence") {
              ret.sequence = true;
            }
            ret.generic = value;
            const types = [];
            do {
              all_ws();
              types.push(type_with_extended_attributes() || error("Error parsing generic type " + value));
              all_ws();
            }
            while (consume(OTHER, ","));
            if (value === "sequence") {
              if (types.length !== 1) error("A sequence must have exactly one subtype");
            } else if (value === "record") {
              if (types.length !== 2) error("A record must have exactly two subtypes");
              if (!/^(DOMString|USVString|ByteString)$/.test(types[0].idlType)) {
                error("Record key must be DOMString, USVString, or ByteString");
              }
              if (types[0].extAttrs) error("Record key cannot have extended attribute");
            } else if (value === "Promise") {
              if (types[0].extAttrs) error("Promise type cannot have extended attribute");
            }
            ret.idlType = types.length === 1 ? types[0] : types;
            all_ws();
            if (!consume(OTHER, ">")) error("Unterminated generic type " + value);
            type_suffix(ret);
            return ret;
          } else {
            ret.idlType = value;
          }
        } else {
          return;
        }
        type_suffix(ret);
        if (ret.nullable && ret.idlType === "any") error("Type any cannot be made nullable");
        return ret;
      };
  
      function union_type() {
        all_ws();
        if (!consume(OTHER, "(")) return;
        const ret = { sequence: false, generic: null, nullable: false, union: true, idlType: [] };
        const fst = type_with_extended_attributes() || error("Union type with no content");
        ret.idlType.push(fst);
        while (true) {
          all_ws();
          if (!consume(ID, "or")) break;
          const typ = type_with_extended_attributes() || error("No type after 'or' in union type");
          ret.idlType.push(typ);
        }
        if (!consume(OTHER, ")")) error("Unterminated union type");
        type_suffix(ret);
        return ret;
      };
  
      function type() {
        return single_type() || union_type();
      };
  
      function type_with_extended_attributes() {
        const extAttrs = extended_attrs();
        const ret = single_type() || union_type();
        if (extAttrs.length && ret) ret.extAttrs = extAttrs;
        return ret;
      };
  
      function argument(store) {
        const ret = { optional: false, variadic: false };
        ret.extAttrs = extended_attrs(store);
        all_ws(store, "pea");
        const opt_token = consume(ID, "optional");
        if (opt_token) {
          ret.optional = true;
          all_ws();
        }
        ret.idlType = type_with_extended_attributes();
        if (!ret.idlType) {
          if (opt_token) tokens.unshift(opt_token);
          return;
        }
        const type_token = last_token;
        if (!ret.optional) {
          all_ws();
          if (tokens.length >= 3 &&
            tokens[0].type === "other" && tokens[0].value === "." &&
            tokens[1].type === "other" && tokens[1].value === "." &&
            tokens[2].type === "other" && tokens[2].value === "."
          ) {
            tokens.shift();
            tokens.shift();
            tokens.shift();
            ret.variadic = true;
          }
        }
        all_ws();
        const name = consume(ID);
        if (!name) {
          if (opt_token) tokens.unshift(opt_token);
          tokens.unshift(type_token);
          return;
        }
        ret.name = name.value;
        if (ret.optional) {
          all_ws();
          const dflt = default_();
          if (typeof dflt !== "undefined") {
            ret["default"] = dflt;
          }
        }
        return ret;
      };
  
      function argument_list(store) {
        const ret = [];
        const arg = argument(store ? ret : null);
        if (!arg) return;
        ret.push(arg);
        while (true) {
          all_ws(store ? ret : null);
          if (!consume(OTHER, ",")) return ret;
          const nxt = argument(store ? ret : null) || error("Trailing comma in arguments list");
          ret.push(nxt);
        }
      };
  
      function simple_extended_attr(store) {
        all_ws();
        const name = consume(ID);
        if (!name) return;
        const ret = {
          name: name.value,
          "arguments": null,
          type: "extended-attribute",
          rhs: null
        };
        all_ws();
        const eq = consume(OTHER, "=");
        if (eq) {
          all_ws();
          ret.rhs = consume(ID) ||
            consume(FLOAT) ||
            consume(INT) ||
            consume(STR);
          if (!ret.rhs && consume(OTHER, "(")) {
            // [Exposed=(Window,Worker)]
            const rhs_list = [];
            const id = consume(ID);
            if (id) {
              rhs_list.push(id.value);
            }
            identifiers(rhs_list);
            consume(OTHER, ")") || error("Unexpected token in extended attribute argument list or type pair");
            ret.rhs = {
              type: "identifier-list",
              value: rhs_list
            };
          }
          if (!ret.rhs) return error("No right hand side to extended attribute assignment");
        }
        all_ws();
        if (consume(OTHER, "(")) {
          let args, pair;
          // [Constructor(DOMString str)]
          if (args = argument_list(store)) {
            ret["arguments"] = args;
          }
          // [Constructor()]
          else {
            ret["arguments"] = [];
          }
          all_ws();
          consume(OTHER, ")") || error("Unexpected token in extended attribute argument list");
        }
        return ret;
      };
  
      // Note: we parse something simpler than the official syntax. It's all that ever
      // seems to be used
      function extended_attrs(store) {
        const eas = [];
        all_ws(store);
        if (!consume(OTHER, "[")) return eas;
        eas[0] = simple_extended_attr(store) || error("Extended attribute with not content");
        all_ws();
        while (consume(OTHER, ",")) {
          if (eas.length) {
            eas.push(simple_extended_attr(store));
          } else {
            eas.push(simple_extended_attr(store) || error("Trailing comma in extended attribute"));
          }
        }
        all_ws();
        consume(OTHER, "]") || error("No end of extended attribute");
        return eas;
      };
  
      function default_() {
        all_ws();
        if (consume(OTHER, "=")) {
          all_ws();
          const def = const_value();
          if (def) {
            return def;
          } else if (consume(OTHER, "[")) {
            if (!consume(OTHER, "]")) error("Default sequence value must be empty");
            return { type: "sequence", value: [] };
          } else {
            const str = consume(STR) || error("No value for default");
            str.value = str.value.replace(/^"/, "").replace(/"$/, "");
            return str;
          }
        }
      };
  
      function const_(store) {
        all_ws(store, "pea");
        if (!consume(ID, "const")) return;
        const ret = { type: "const", nullable: false };
        all_ws();
        let typ = primitive_type();
        if (!typ) {
          typ = consume(ID) || error("No type for const");
          typ = typ.value;
        }
        ret.idlType = typ;
        all_ws();
        if (consume(OTHER, "?")) {
          ret.nullable = true;
          all_ws();
        }
        const name = consume(ID) || error("No name for const");
        ret.name = name.value;
        all_ws();
        consume(OTHER, "=") || error("No value assignment for const");
        all_ws();
        const cnt = const_value();
        if (cnt) ret.value = cnt;
        else error("No value for const");
        all_ws();
        consume(OTHER, ";") || error("Unterminated const");
        return ret;
      };
  
      function inheritance() {
        all_ws();
        if (consume(OTHER, ":")) {
          all_ws();
          const inh = consume(ID) || error("No type in inheritance");
          return inh.value;
        }
      };
  
      function operation_rest(ret, store) {
        all_ws();
        if (!ret) ret = {};
        const name = consume(ID);
        ret.name = name ? name.value : null;
        all_ws();
        consume(OTHER, "(") || error("Invalid operation");
        ret["arguments"] = argument_list(store) || [];
        all_ws();
        consume(OTHER, ")") || error("Unterminated operation");
        all_ws();
        consume(OTHER, ";") || error("Unterminated operation");
        return ret;
      };
  
      function callback(store) {
        all_ws(store, "pea");
        let ret;
        if (!consume(ID, "callback")) return;
        all_ws();
        const tok = consume(ID, "interface");
        if (tok) {
          ret = interface_rest();
          ret.type = "callback interface";
          return ret;
        }
        const name = consume(ID) || error("No name for callback");
        ret = { type: "callback", name: sanitize_name(name.value, "callback") };
        all_ws();
        consume(OTHER, "=") || error("No assignment in callback");
        all_ws();
        ret.idlType = return_type();
        all_ws();
        consume(OTHER, "(") || error("No arguments in callback");
        ret["arguments"] = argument_list(store) || [];
        all_ws();
        consume(OTHER, ")") || error("Unterminated callback");
        all_ws();
        consume(OTHER, ";") || error("Unterminated callback");
        return ret;
      };
  
      function attribute(store) {
        all_ws(store, "pea");
        const grabbed = [];
        const ret = {
          type: "attribute",
          "static": false,
          stringifier: false,
          inherit: false,
          readonly: false
        };
        const w = all_ws();
        if (w) grabbed.push(w);
        if (consume(ID, "inherit")) {
          if (ret["static"] || ret.stringifier) error("Cannot have a static or stringifier inherit");
          ret.inherit = true;
          grabbed.push(last_token);
          const w = all_ws();
          if (w) grabbed.push(w);
        }
        if (consume(ID, "readonly")) {
          ret.readonly = true;
          grabbed.push(last_token);
          const w = all_ws();
          if (w) grabbed.push(w);
        }
        const rest = attribute_rest(ret);
        if (!rest) {
          tokens = grabbed.concat(tokens);
        }
        return rest;
      };
  
      function attribute_rest(ret) {
        if (!consume(ID, "attribute")) {
          return;
        }
        all_ws();
        ret.idlType = type_with_extended_attributes() || error("No type in attribute");
        if (ret.idlType.sequence) error("Attributes cannot accept sequence types");
        if (ret.idlType.generic === "record") error("Attributes cannot accept record types");
        all_ws();
        const name = consume(ID) || error("No name in attribute");
        ret.name = name.value;
        all_ws();
        consume(OTHER, ";") || error("Unterminated attribute");
        return ret;
      };
  
      function return_type() {
        const typ = type();
        if (!typ) {
          if (consume(ID, "void")) {
            return "void";
          } else error("No return type");
        }
        return typ;
      };
  
      function operation(store) {
        all_ws(store, "pea");
        const ret = Object.assign({}, EMPTY_OPERATION);
        while (true) {
          all_ws();
          if (consume(ID, "getter")) ret.getter = true;
          else if (consume(ID, "setter")) ret.setter = true;
          else if (consume(ID, "deleter")) ret.deleter = true;
          else break;
        }
        if (ret.getter || ret.setter || ret.deleter) {
          all_ws();
          ret.idlType = return_type();
          operation_rest(ret, store);
          return ret;
        }
        ret.idlType = return_type();
        all_ws();
        if (consume(ID, "iterator")) {
          all_ws();
          ret.type = "iterator";
          if (consume(ID, "object")) {
            ret.iteratorObject = "object";
          } else if (consume(OTHER, "=")) {
            all_ws();
            var name = consume(ID) || error("No right hand side in iterator");
            ret.iteratorObject = name.value;
          }
          all_ws();
          consume(OTHER, ";") || error("Unterminated iterator");
          return ret;
        } else {
          operation_rest(ret, store);
          return ret;
        }
      };
  
      function static_member(store) {
        all_ws(store, "pea");
        if (!consume(ID, "static")) return;
        all_ws();
        return noninherited_attribute(store, "static") ||
          regular_operation(store, "static") ||
          error("No body in static member");
      }
  
      function stringifier(store) {
        all_ws(store, "pea");
        if (!consume(ID, "stringifier")) return;
        all_ws();
        if (consume(OTHER, ";")) {
          return Object.assign({}, EMPTY_OPERATION, { stringifier: true });
        }
        return noninherited_attribute(store, "stringifier") ||
          regular_operation(store, "stringifier") ||
          error("Unterminated stringifier");
      }
  
      function identifiers(arr) {
        while (true) {
          all_ws();
          if (consume(OTHER, ",")) {
            all_ws();
            const name = consume(ID) || error("Trailing comma in identifiers list");
            arr.push(name.value);
          } else break;
        }
      };
  
      function iterable_type() {
        if (consume(ID, "iterable")) return "iterable";
        else if (consume(ID, "legacyiterable")) return "legacyiterable";
        else if (consume(ID, "maplike")) return "maplike";
        else if (consume(ID, "setlike")) return "setlike";
        else return;
      };
  
      function readonly_iterable_type() {
        if (consume(ID, "maplike")) return "maplike";
        else if (consume(ID, "setlike")) return "setlike";
        else return;
      };
  
      function iterable(store) {
        all_ws(store, "pea");
        const grabbed = [];
        const ret = { type: null, idlType: null, readonly: false };
        if (consume(ID, "readonly")) {
          ret.readonly = true;
          grabbed.push(last_token);
          var w = all_ws();
          if (w) grabbed.push(w);
        }
        const consumeItType = ret.readonly ? readonly_iterable_type : iterable_type;
  
        const ittype = consumeItType();
        if (!ittype) {
          tokens = grabbed.concat(tokens);
          return;
        }
  
        const secondTypeRequired = ittype === "maplike";
        const secondTypeAllowed = secondTypeRequired || ittype === "iterable";
        ret.type = ittype;
        if (ret.type !== 'maplike' && ret.type !== 'setlike')
          delete ret.readonly;
        all_ws();
        if (consume(OTHER, "<")) {
          ret.idlType = type_with_extended_attributes() || error(`Error parsing ${ittype} declaration`);
          all_ws();
          if (secondTypeAllowed) {
            let type2 = null;
            if (consume(OTHER, ",")) {
              all_ws();
              type2 = type_with_extended_attributes();
              all_ws();
            }
            if (type2)
              ret.idlType = [ret.idlType, type2];
            else if (secondTypeRequired)
              error(`Missing second type argument in ${ittype} declaration`);
          }
          if (!consume(OTHER, ">")) error(`Unterminated ${ittype} declaration`);
          all_ws();
          if (!consume(OTHER, ";")) error(`Missing semicolon after ${ittype} declaration`);
        } else
          error(`Error parsing ${ittype} declaration`);
  
        return ret;
      };
  
      function interface_rest(isPartial, store) {
        all_ws();
        const name = consume(ID) || error("No name for interface");
        const mems = [];
        const ret = {
          type: "interface",
          name: isPartial ? name.value : sanitize_name(name.value, "interface"),
          partial: false,
          members: mems
        };
        if (!isPartial) ret.inheritance = inheritance() || null;
        all_ws();
        consume(OTHER, "{") || error("Bodyless interface");
        while (true) {
          all_ws(store ? mems : null);
          if (consume(OTHER, "}")) {
            all_ws();
            consume(OTHER, ";") || error("Missing semicolon after interface");
            return ret;
          }
          const ea = extended_attrs(store ? mems : null);
          all_ws();
          const cnt = const_(store ? mems : null);
          if (cnt) {
            cnt.extAttrs = ea;
            ret.members.push(cnt);
            continue;
          }
          const mem = (opt.allowNestedTypedefs && typedef(store ? mems : null)) ||
            static_member(store ? mems : null) ||
            stringifier(store ? mems : null) ||
            iterable(store ? mems : null) ||
            attribute(store ? mems : null) ||
            operation(store ? mems : null) ||
            error("Unknown member");
          mem.extAttrs = ea;
          ret.members.push(mem);
        }
      };
  
      function mixin_rest(isPartial, store) {
        all_ws();
        if (!consume(ID, "mixin")) return;
        all_ws();
        const name = consume(ID) || error("No name for interface mixin");
        const mems = [];
        const ret = {
          type: "interface mixin",
          name: isPartial ? name.value : sanitize_name(name.value, "interface mixin"),
          partial: false,
          members: mems
        };
        all_ws();
        consume(OTHER, "{") || error("Bodyless interface mixin");
        while (true) {
          all_ws(store ? mems : null);
          if (consume(OTHER, "}")) {
            all_ws();
            consume(OTHER, ";") || error("Missing semicolon after interface mixin");
            return ret;
          }
          const ea = extended_attrs(store ? mems : null);
          all_ws();
          const cnt = const_(store ? mems : null);
          if (cnt) {
            cnt.extAttrs = ea;
            ret.members.push(cnt);
            continue;
          }
          const mem = stringifier(store ? mems : null) ||
            noninherited_attribute(store ? mems : null) ||
            regular_operation(store ? mems : null) ||
            error("Unknown member");
          mem.extAttrs = ea;
          ret.members.push(mem);
        }
      }
  
      function interface_(isPartial, store) {
        all_ws(isPartial ? null : store, "pea");
        if (!consume(ID, "interface")) return;
        return mixin_rest(isPartial, store) ||
          interface_rest(isPartial, store) ||
          error("Interface has no proper body");
      }
  
      function namespace(isPartial, store) {
        all_ws(isPartial ? null : store, "pea");
        if (!consume(ID, "namespace")) return;
        all_ws();
        const name = consume(ID) || error("No name for namespace");
        const mems = [];
        const ret = {
          type: "namespace",
          name: isPartial ? name.value : sanitize_name(name.value, "namespace"),
          partial: isPartial,
          members: mems
        };
        all_ws();
        consume(OTHER, "{") || error("Bodyless namespace");
        while (true) {
          all_ws(store ? mems : null);
          if (consume(OTHER, "}")) {
            all_ws();
            consume(OTHER, ";") || error("Missing semicolon after namespace");
            return ret;
          }
          const ea = extended_attrs(store ? mems : null);
          all_ws();
          const mem = noninherited_attribute(store ? mems : null) ||
            regular_operation(store ? mems : null) ||
            error("Unknown member");
          mem.extAttrs = ea;
          ret.members.push(mem);
        }
      }
  
      function noninherited_attribute(store, prefix) {
        const w = all_ws(store, "pea");
        const grabbed = [];
        const ret = {
          type: "attribute",
          "static": false,
          stringifier: false,
          inherit: false,
          readonly: false
        };
        if (prefix) {
          ret[prefix] = true;
        }
        if (w) grabbed.push(w);
        if (consume(ID, "readonly")) {
          ret.readonly = true;
          grabbed.push(last_token);
          const w = all_ws();
          if (w) grabbed.push(w);
        }
        const rest = attribute_rest(ret);
        if (!rest) {
          tokens = grabbed.concat(tokens);
        }
        return rest;
      }
  
      function regular_operation(store, prefix) {
        all_ws(store, "pea");
        const ret = Object.assign({}, EMPTY_OPERATION);
        if (prefix) {
          ret[prefix] = true;
        }
        ret.idlType = return_type();
        return operation_rest(ret, store);
      }
  
      function partial(store) {
        all_ws(store, "pea");
        if (!consume(ID, "partial")) return;
        const thing = dictionary(true, store) ||
          interface_(true, store) ||
          namespace(true, store) ||
          error("Partial doesn't apply to anything");
        thing.partial = true;
        return thing;
      };
  
      function dictionary(isPartial, store) {
        all_ws(isPartial ? null : store, "pea");
        if (!consume(ID, "dictionary")) return;
        all_ws();
        const name = consume(ID) || error("No name for dictionary");
        const mems = [];
        const ret = {
          type: "dictionary",
          name: isPartial ? name.value : sanitize_name(name.value, "dictionary"),
          partial: false,
          members: mems
        };
        if (!isPartial) ret.inheritance = inheritance() || null;
        all_ws();
        consume(OTHER, "{") || error("Bodyless dictionary");
        while (true) {
          all_ws(store ? mems : null);
          if (consume(OTHER, "}")) {
            all_ws();
            consume(OTHER, ";") || error("Missing semicolon after dictionary");
            return ret;
          }
          const ea = extended_attrs(store ? mems : null);
          all_ws(store ? mems : null, "pea");
          const required = consume(ID, "required");
          const typ = type_with_extended_attributes() || error("No type for dictionary member");
          all_ws();
          const name = consume(ID) || error("No name for dictionary member");
          const dflt = default_();
          if (required && dflt) error("Required member must not have a default");
          const member = {
            type: "field",
            name: name.value,
            required: !!required,
            idlType: typ,
            extAttrs: ea
          };
          if (typeof dflt !== "undefined") {
            member["default"] = dflt;
          }
          ret.members.push(member);
          all_ws();
          consume(OTHER, ";") || error("Unterminated dictionary member");
        }
      };
  
      function enum_(store) {
        all_ws(store, "pea");
        if (!consume(ID, "enum")) return;
        all_ws();
        const name = consume(ID) || error("No name for enum");
        const vals = [];
        const ret = {
          type: "enum",
          name: sanitize_name(name.value, "enum"),
          values: vals
        };
        all_ws();
        consume(OTHER, "{") || error("No curly for enum");
        let saw_comma = false;
        while (true) {
          all_ws(store ? vals : null);
          if (consume(OTHER, "}")) {
            all_ws();
            consume(OTHER, ";") || error("No semicolon after enum");
            return ret;
          }
          const val = consume(STR) || error("Unexpected value in enum");
          val.value = val.value.replace(/"/g, "");
          ret.values.push(val);
          all_ws(store ? vals : null);
          if (consume(OTHER, ",")) {
            if (store) vals.push({ type: "," });
            all_ws(store ? vals : null);
            saw_comma = true;
          } else {
            saw_comma = false;
          }
        }
      };
  
      function typedef(store) {
        all_ws(store, "pea");
        if (!consume(ID, "typedef")) return;
        const ret = {
          type: "typedef"
        };
        all_ws();
        ret.idlType = type_with_extended_attributes() || error("No type in typedef");
        all_ws();
        const name = consume(ID) || error("No name in typedef");
        ret.name = sanitize_name(name.value, "typedef");
        all_ws();
        consume(OTHER, ";") || error("Unterminated typedef");
        return ret;
      };
  
      function implements_(store) {
        all_ws(store, "pea");
        const target = consume(ID);
        if (!target) return;
        const w = all_ws();
        if (consume(ID, "implements")) {
          const ret = {
            type: "implements",
            target: target.value
          };
          all_ws();
          const imp = consume(ID) || error("Incomplete implements statement");
          ret["implements"] = imp.value;
          all_ws();
          consume(OTHER, ";") || error("No terminating ; for implements statement");
          return ret;
        } else {
          // rollback
          tokens.unshift(w);
          tokens.unshift(target);
        }
      };
  
      function includes(store) {
        all_ws(store, "pea");
        const target = consume(ID);
        if (!target) return;
        const w = all_ws();
        if (consume(ID, "includes")) {
          const ret = {
            type: "includes",
            target: target.value
          };
          all_ws();
          const imp = consume(ID) || error("Incomplete includes statement");
          ret["includes"] = imp.value;
          all_ws();
          consume(OTHER, ";") || error("No terminating ; for includes statement");
          return ret;
        } else {
          // rollback
          tokens.unshift(w);
          tokens.unshift(target);
        }
      };
  
      function definition(store) {
        return callback(store) ||
          interface_(false, store) ||
          partial(store) ||
          dictionary(false, store) ||
          enum_(store) ||
          typedef(store) ||
          implements_(store) ||
          includes(store) ||
          namespace(false, store);
      };
  
      function definitions(store) {
        if (!tokens.length) return [];
        const defs = [];
        while (true) {
          const ea = extended_attrs(store ? defs : null);
          const def = definition(store ? defs : null);
          if (!def) {
            if (ea.length) error("Stray extended attributes");
            break;
          }
          def.extAttrs = ea;
          defs.push(def);
        }
        return defs;
      };
      const res = definitions(opt.ws);
      if (tokens.length) error("Unrecognised tokens");
      return res;
    };
  
    const obj = {
      parse(str, opt) {
        if (!opt) opt = {};
        const tokens = tokenise(str);
        return parse(tokens, opt);
      }
    };
  
    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
      module.exports = obj;
    } else if (typeof define === 'function' && define.amd) {
      define([], () => obj);
    } else {
      (self || window).WebIDL2 = obj;
    }
  })();
  