• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"use strict";
2
3(() => {
4  // These regular expressions use the sticky flag so they will only match at
5  // the current location (ie. the offset of lastIndex).
6  const tokenRe = {
7    // This expression uses a lookahead assertion to catch false matches
8    // against integers early.
9    "float": /-?(?=[0-9]*\.|[0-9]+[eE])(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/y,
10    "integer": /-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/y,
11    "identifier": /_?[A-Za-z][0-9A-Z_a-z-]*/y,
12    "string": /"[^"]*"/y,
13    "whitespace": /[\t\n\r ]+/y,
14    "comment": /((\/(\/.*|\*([^*]|\*[^\/])*\*\/)[\t\n\r ]*)+)/y,
15    "other": /[^\t\n\r 0-9A-Za-z]/y
16  };
17
18  const stringTypes = [
19    "ByteString",
20    "DOMString",
21    "USVString"
22  ];
23
24  const argumentNameKeywords = [
25    "attribute",
26    "callback",
27    "const",
28    "deleter",
29    "dictionary",
30    "enum",
31    "getter",
32    "includes",
33    "inherit",
34    "interface",
35    "iterable",
36    "maplike",
37    "namespace",
38    "partial",
39    "required",
40    "setlike",
41    "setter",
42    "static",
43    "stringifier",
44    "typedef",
45    "unrestricted"
46  ];
47
48  const nonRegexTerminals = [
49    "FrozenArray",
50    "Infinity",
51    "NaN",
52    "Promise",
53    "boolean",
54    "byte",
55    "double",
56    "false",
57    "float",
58    "implements",
59    "legacyiterable",
60    "long",
61    "mixin",
62    "null",
63    "octet",
64    "optional",
65    "or",
66    "readonly",
67    "record",
68    "sequence",
69    "short",
70    "true",
71    "unsigned",
72    "void"
73  ].concat(argumentNameKeywords, stringTypes);
74
75  const punctuations = [
76    "(",
77    ")",
78    ",",
79    "-Infinity",
80    "...",
81    ":",
82    ";",
83    "<",
84    "=",
85    ">",
86    "?",
87    "[",
88    "]",
89    "{",
90    "}"
91  ];
92
93  function tokenise(str) {
94    const tokens = [];
95    let lastIndex = 0;
96    let trivia = "";
97    while (lastIndex < str.length) {
98      const nextChar = str.charAt(lastIndex);
99      let result = -1;
100
101      if (/[\t\n\r ]/.test(nextChar)) {
102        result = attemptTokenMatch("whitespace", { noFlushTrivia: true });
103      } else if (nextChar === '/') {
104        result = attemptTokenMatch("comment", { noFlushTrivia: true });
105      }
106
107      if (result !== -1) {
108        trivia += tokens.pop().value;
109      } else if (/[-0-9.]/.test(nextChar)) {
110        result = attemptTokenMatch("float");
111        if (result === -1) {
112          result = attemptTokenMatch("integer");
113        }
114      } else if (/[A-Z_a-z]/.test(nextChar)) {
115        result = attemptTokenMatch("identifier");
116        const token = tokens[tokens.length - 1];
117        if (result !== -1 && nonRegexTerminals.includes(token.value)) {
118          token.type = token.value;
119        }
120      } else if (nextChar === '"') {
121        result = attemptTokenMatch("string");
122      }
123
124      for (const punctuation of punctuations) {
125        if (str.startsWith(punctuation, lastIndex)) {
126          tokens.push({ type: punctuation, value: punctuation, trivia });
127          trivia = "";
128          lastIndex += punctuation.length;
129          result = lastIndex;
130          break;
131        }
132      }
133
134      // other as the last try
135      if (result === -1) {
136        result = attemptTokenMatch("other");
137      }
138      if (result === -1) {
139        throw new Error("Token stream not progressing");
140      }
141      lastIndex = result;
142    }
143    return tokens;
144
145    function attemptTokenMatch(type, { noFlushTrivia } = {}) {
146      const re = tokenRe[type];
147      re.lastIndex = lastIndex;
148      const result = re.exec(str);
149      if (result) {
150        tokens.push({ type, value: result[0], trivia });
151        if (!noFlushTrivia) {
152          trivia = "";
153        }
154        return re.lastIndex;
155      }
156      return -1;
157    }
158  }
159
160  class WebIDLParseError {
161    constructor(str, line, input, tokens) {
162      this.message = str;
163      this.line = line;
164      this.input = input;
165      this.tokens = tokens;
166    }
167
168    toString() {
169      const escapedInput = JSON.stringify(this.input);
170      const tokens = JSON.stringify(this.tokens, null, 4);
171      return `${this.message}, line ${this.line} (tokens: ${escapedInput})\n${tokens}`;
172    }
173  }
174
175  function parse(tokens) {
176    let line = 1;
177    tokens = tokens.slice();
178    const names = new Map();
179    let current = null;
180
181    const FLOAT = "float";
182    const INT = "integer";
183    const ID = "identifier";
184    const STR = "string";
185    const OTHER = "other";
186
187    const EMPTY_OPERATION = Object.freeze({
188      type: "operation",
189      getter: false,
190      setter: false,
191      deleter: false,
192      static: false,
193      stringifier: false
194    });
195
196    const EMPTY_IDLTYPE = Object.freeze({
197      generic: null,
198      nullable: false,
199      union: false,
200      idlType: null,
201      extAttrs: []
202    });
203
204    function error(str) {
205      const maxTokens = 5;
206      const tok = tokens
207        .slice(consume_position, consume_position + maxTokens)
208        .map(t => t.trivia + t.value).join("");
209      // Count newlines preceding the actual erroneous token
210      if (tokens.length) {
211        line += count(tokens[consume_position].trivia, "\n");
212      }
213
214      let message;
215      if (current) {
216        message = `Got an error during or right after parsing \`${current.partial ? "partial " : ""}${current.type} ${current.name}\`: ${str}`
217      }
218      else {
219        // throwing before any valid definition
220        message = `Got an error before parsing any named definition: ${str}`;
221      }
222
223      throw new WebIDLParseError(message, line, tok, tokens.slice(0, maxTokens));
224    }
225
226    function sanitize_name(name, type) {
227      if (names.has(name)) {
228        error(`The name "${name}" of type "${names.get(name)}" is already seen`);
229      }
230      names.set(name, type);
231      return name;
232    }
233
234    let consume_position = 0;
235
236    function probe(type) {
237      return tokens.length > consume_position && tokens[consume_position].type === type;
238    }
239
240    function consume(...candidates) {
241      // TODO: use const when Servo updates its JS engine
242      for (let type of candidates) {
243        if (!probe(type)) continue;
244        const token = tokens[consume_position];
245        consume_position++;
246        line += count(token.trivia, "\n");
247        return token;
248      }
249    }
250
251    function unescape(identifier) {
252      return identifier.startsWith('_') ? identifier.slice(1) : identifier;
253    }
254
255    function unconsume(position) {
256      while (consume_position > position) {
257        consume_position--;
258        line -= count(tokens[consume_position].trivia, "\n");
259      }
260    }
261
262    function count(str, char) {
263      let total = 0;
264      for (let i = str.indexOf(char); i !== -1; i = str.indexOf(char, i + 1)) {
265        ++total;
266      }
267      return total;
268    }
269
270    function integer_type() {
271      let ret = "";
272      if (consume("unsigned")) ret = "unsigned ";
273      if (consume("short")) return ret + "short";
274      if (consume("long")) {
275        ret += "long";
276        if (consume("long")) return ret + " long";
277        return ret;
278      }
279      if (ret) error("Failed to parse integer type");
280    }
281
282    function float_type() {
283      let ret = "";
284      if (consume("unrestricted")) ret = "unrestricted ";
285      if (consume("float")) return ret + "float";
286      if (consume("double")) return ret + "double";
287      if (ret) error("Failed to parse float type");
288    }
289
290    function primitive_type() {
291      const num_type = integer_type() || float_type();
292      if (num_type) return num_type;
293      if (consume("boolean")) return "boolean";
294      if (consume("byte")) return "byte";
295      if (consume("octet")) return "octet";
296    }
297
298    function const_value() {
299      if (consume("true")) return { type: "boolean", value: true };
300      if (consume("false")) return { type: "boolean", value: false };
301      if (consume("null")) return { type: "null" };
302      if (consume("Infinity")) return { type: "Infinity", negative: false };
303      if (consume("-Infinity")) return { type: "Infinity", negative: true };
304      if (consume("NaN")) return { type: "NaN" };
305      const ret = consume(FLOAT, INT);
306      if (ret) return { type: "number", value: ret.value };
307    }
308
309    function type_suffix(obj) {
310      obj.nullable = !!consume("?");
311      if (probe("?")) error("Can't nullable more than once");
312    }
313
314    function generic_type(typeName) {
315      const name = consume("FrozenArray", "Promise", "sequence", "record");
316      if (!name) {
317        return;
318      }
319      const ret = { generic: name.type };
320      consume("<") || error(`No opening bracket after ${name.type}`);
321      switch (name.type) {
322        case "Promise":
323          if (probe("[")) error("Promise type cannot have extended attribute");
324          ret.idlType = return_type(typeName);
325          break;
326        case "sequence":
327        case "FrozenArray":
328          ret.idlType = type_with_extended_attributes(typeName);
329          break;
330        case "record":
331          if (probe("[")) error("Record key cannot have extended attribute");
332          ret.idlType = [];
333          const keyType = consume(...stringTypes);
334          if (!keyType) error(`Record key must be a string type`);
335          ret.idlType.push(Object.assign({ type: typeName }, EMPTY_IDLTYPE, { idlType: keyType.value }));
336          consume(",") || error("Missing comma after record key type");
337          const valueType = type_with_extended_attributes(typeName) || error("Error parsing generic type record");
338          ret.idlType.push(valueType);
339          break;
340      }
341      if (!ret.idlType) error(`Error parsing generic type ${name.type}`);
342      consume(">") || error(`Missing closing bracket after ${name.type}`);
343      if (name.type === "Promise" && probe("?")) {
344        error("Promise type cannot be nullable");
345      }
346      type_suffix(ret);
347      return ret;
348    }
349
350    function single_type(typeName) {
351      const ret = Object.assign({ type: typeName || null }, EMPTY_IDLTYPE);
352      const generic = generic_type(typeName);
353      if (generic) {
354        return Object.assign(ret, generic);
355      }
356      const prim = primitive_type();
357      let name;
358      if (prim) {
359        ret.idlType = prim;
360      } else if (name = consume(ID, ...stringTypes)) {
361        ret.idlType = name.value;
362        if (probe("<")) error(`Unsupported generic type ${name.value}`);
363      } else {
364        return;
365      }
366      type_suffix(ret);
367      if (ret.nullable && ret.idlType === "any") error("Type any cannot be made nullable");
368      return ret;
369    }
370
371    function union_type(typeName) {
372      if (!consume("(")) return;
373      const ret = Object.assign({ type: typeName || null }, EMPTY_IDLTYPE, { union: true, idlType: [] });
374      do {
375        const typ = type_with_extended_attributes() || error("No type after open parenthesis or 'or' in union type");
376        ret.idlType.push(typ);
377      } while (consume("or"));
378      if (ret.idlType.length < 2) {
379        error("At least two types are expected in a union type but found less");
380      }
381      if (!consume(")")) error("Unterminated union type");
382      type_suffix(ret);
383      return ret;
384    }
385
386    function type(typeName) {
387      return single_type(typeName) || union_type(typeName);
388    }
389
390    function type_with_extended_attributes(typeName) {
391      const extAttrs = extended_attrs();
392      const ret = single_type(typeName) || union_type(typeName);
393      if (extAttrs.length && ret) ret.extAttrs = extAttrs;
394      return ret;
395    }
396
397    function argument() {
398      const start_position = consume_position;
399      const ret = { optional: false, variadic: false, default: null };
400      ret.extAttrs = extended_attrs();
401      const opt_token = consume("optional");
402      if (opt_token) {
403        ret.optional = true;
404      }
405      ret.idlType = type_with_extended_attributes("argument-type");
406      if (!ret.idlType) {
407        unconsume(start_position);
408        return;
409      }
410      if (!ret.optional && consume("...")) {
411        ret.variadic = true;
412      }
413      const name = consume(ID, ...argumentNameKeywords);
414      if (!name) {
415        unconsume(start_position);
416        return;
417      }
418      ret.name = unescape(name.value);
419      ret.escapedName = name.value;
420      if (ret.optional) {
421        ret.default = default_() || null;
422      }
423      return ret;
424    }
425
426    function argument_list() {
427      const ret = [];
428      const arg = argument();
429      if (!arg) return ret;
430      ret.push(arg);
431      while (true) {
432        if (!consume(",")) return ret;
433        const nxt = argument() || error("Trailing comma in arguments list");
434        ret.push(nxt);
435      }
436    }
437
438    function simple_extended_attr() {
439      const name = consume(ID);
440      if (!name) return;
441      const ret = {
442        name: name.value,
443        arguments: null,
444        type: "extended-attribute",
445        rhs: null
446      };
447      const eq = consume("=");
448      if (eq) {
449        ret.rhs = consume(ID, FLOAT, INT, STR);
450        if (ret.rhs) {
451          // No trivia exposure yet
452          ret.rhs.trivia = undefined;
453        }
454      }
455      if (consume("(")) {
456        if (eq && !ret.rhs) {
457          // [Exposed=(Window,Worker)]
458          ret.rhs = {
459            type: "identifier-list",
460            value: identifiers()
461          };
462        }
463        else {
464          // [NamedConstructor=Audio(DOMString src)] or [Constructor(DOMString str)]
465          ret.arguments = argument_list();
466        }
467        consume(")") || error("Unexpected token in extended attribute argument list");
468      }
469      if (eq && !ret.rhs) error("No right hand side to extended attribute assignment");
470      return ret;
471    }
472
473    // Note: we parse something simpler than the official syntax. It's all that ever
474    // seems to be used
475    function extended_attrs() {
476      const eas = [];
477      if (!consume("[")) return eas;
478      eas[0] = simple_extended_attr() || error("Extended attribute with not content");
479      while (consume(",")) {
480        eas.push(simple_extended_attr() || error("Trailing comma in extended attribute"));
481      }
482      consume("]") || error("No end of extended attribute");
483      return eas;
484    }
485
486    function default_() {
487      if (consume("=")) {
488        const def = const_value();
489        if (def) {
490          return def;
491        } else if (consume("[")) {
492          if (!consume("]")) error("Default sequence value must be empty");
493          return { type: "sequence", value: [] };
494        } else {
495          const str = consume(STR) || error("No value for default");
496          str.value = str.value.slice(1, -1);
497          // No trivia exposure yet
498          str.trivia = undefined;
499          return str;
500        }
501      }
502    }
503
504    function const_() {
505      if (!consume("const")) return;
506      const ret = { type: "const", nullable: false };
507      let typ = primitive_type();
508      if (!typ) {
509        typ = consume(ID) || error("No type for const");
510        typ = typ.value;
511      }
512      ret.idlType = Object.assign({ type: "const-type" }, EMPTY_IDLTYPE, { idlType: typ });
513      type_suffix(ret);
514      const name = consume(ID) || error("No name for const");
515      ret.name = name.value;
516      consume("=") || error("No value assignment for const");
517      const cnt = const_value();
518      if (cnt) ret.value = cnt;
519      else error("No value for const");
520      consume(";") || error("Unterminated const");
521      return ret;
522    }
523
524    function inheritance() {
525      if (consume(":")) {
526        const inh = consume(ID) || error("No type in inheritance");
527        return inh.value;
528      }
529    }
530
531    function operation_rest(ret) {
532      if (!ret) ret = {};
533      const name = consume(ID);
534      ret.name = name ? unescape(name.value) : null;
535      ret.escapedName = name ? name.value : null;
536      consume("(") || error("Invalid operation");
537      ret.arguments = argument_list();
538      consume(")") || error("Unterminated operation");
539      consume(";") || error("Unterminated operation");
540      return ret;
541    }
542
543    function callback() {
544      let ret;
545      if (!consume("callback")) return;
546      const tok = consume("interface");
547      if (tok) {
548        ret = interface_rest(false, "callback interface");
549        return ret;
550      }
551      const name = consume(ID) || error("No name for callback");
552      ret = current = { type: "callback", name: sanitize_name(name.value, "callback") };
553      consume("=") || error("No assignment in callback");
554      ret.idlType = return_type() || error("Missing return type");
555      consume("(") || error("No arguments in callback");
556      ret.arguments = argument_list();
557      consume(")") || error("Unterminated callback");
558      consume(";") || error("Unterminated callback");
559      return ret;
560    }
561
562    function attribute({ noInherit = false, readonly = false } = {}) {
563      const start_position = consume_position;
564      const ret = {
565        type: "attribute",
566        static: false,
567        stringifier: false,
568        inherit: false,
569        readonly: false
570      };
571      if (!noInherit && consume("inherit")) {
572        ret.inherit = true;
573      }
574      if (consume("readonly")) {
575        ret.readonly = true;
576      } else if (readonly && probe("attribute")) {
577        error("Attributes must be readonly in this context");
578      }
579      const rest = attribute_rest(ret);
580      if (!rest) {
581        unconsume(start_position);
582      }
583      return rest;
584    }
585
586    function attribute_rest(ret) {
587      if (!consume("attribute")) {
588        return;
589      }
590      ret.idlType = type_with_extended_attributes("attribute-type") || error("No type in attribute");
591      if (ret.idlType.generic === "sequence") error("Attributes cannot accept sequence types");
592      if (ret.idlType.generic === "record") error("Attributes cannot accept record types");
593      const name = consume(ID, "required") || error("No name in attribute");
594      ret.name = unescape(name.value);
595      ret.escapedName = name.value;
596      consume(";") || error("Unterminated attribute");
597      return ret;
598    }
599
600    function return_type(typeName) {
601      const typ = type(typeName || "return-type");
602      if (typ) {
603        return typ;
604      }
605      if (consume("void")) {
606        return Object.assign({ type: "return-type" }, EMPTY_IDLTYPE, { idlType: "void" });
607      }
608    }
609
610    function operation({ regular = false } = {}) {
611      const ret = Object.assign({}, EMPTY_OPERATION);
612      while (!regular) {
613        if (consume("getter")) ret.getter = true;
614        else if (consume("setter")) ret.setter = true;
615        else if (consume("deleter")) ret.deleter = true;
616        else break;
617      }
618      ret.idlType = return_type() || error("Missing return type");
619      operation_rest(ret);
620      return ret;
621    }
622
623    function static_member() {
624      if (!consume("static")) return;
625      const member = attribute({ noInherit: true }) ||
626        operation({ regular: true }) ||
627        error("No body in static member");
628      member.static = true;
629      return member;
630    }
631
632    function stringifier() {
633      if (!consume("stringifier")) return;
634      if (consume(";")) {
635        return Object.assign({}, EMPTY_OPERATION, { stringifier: true });
636      }
637      const member = attribute({ noInherit: true }) ||
638        operation({ regular: true }) ||
639        error("Unterminated stringifier");
640      member.stringifier = true;
641      return member;
642    }
643
644    function identifiers() {
645      const arr = [];
646      const id = consume(ID);
647      if (id) {
648        arr.push(id.value);
649      }
650      else error("Expected identifiers but not found");
651      while (true) {
652        if (consume(",")) {
653          const name = consume(ID) || error("Trailing comma in identifiers list");
654          arr.push(name.value);
655        } else break;
656      }
657      return arr;
658    }
659
660    function iterable_type() {
661      if (consume("iterable")) return "iterable";
662      else if (consume("legacyiterable")) return "legacyiterable";
663      else if (consume("maplike")) return "maplike";
664      else if (consume("setlike")) return "setlike";
665      else return;
666    }
667
668    function readonly_iterable_type() {
669      if (consume("maplike")) return "maplike";
670      else if (consume("setlike")) return "setlike";
671      else return;
672    }
673
674    function iterable() {
675      const start_position = consume_position;
676      const ret = { type: null, idlType: null, readonly: false };
677      if (consume("readonly")) {
678        ret.readonly = true;
679      }
680      const consumeItType = ret.readonly ? readonly_iterable_type : iterable_type;
681
682      const ittype = consumeItType();
683      if (!ittype) {
684        unconsume(start_position);
685        return;
686      }
687
688      const secondTypeRequired = ittype === "maplike";
689      const secondTypeAllowed = secondTypeRequired || ittype === "iterable";
690      ret.type = ittype;
691      if (ret.type !== 'maplike' && ret.type !== 'setlike')
692        delete ret.readonly;
693      if (consume("<")) {
694        ret.idlType = [type_with_extended_attributes()] || error(`Error parsing ${ittype} declaration`);
695        if (secondTypeAllowed) {
696          if (consume(",")) {
697            ret.idlType.push(type_with_extended_attributes());
698          }
699          else if (secondTypeRequired)
700            error(`Missing second type argument in ${ittype} declaration`);
701        }
702        if (!consume(">")) error(`Unterminated ${ittype} declaration`);
703        if (!consume(";")) error(`Missing semicolon after ${ittype} declaration`);
704      } else
705        error(`Error parsing ${ittype} declaration`);
706
707      return ret;
708    }
709
710    function interface_rest(isPartial, typeName = "interface") {
711      const name = consume(ID) || error("No name for interface");
712      const mems = [];
713      const ret = current = {
714        type: typeName,
715        name: isPartial ? name.value : sanitize_name(name.value, "interface"),
716        partial: isPartial,
717        members: mems
718      };
719      if (!isPartial) ret.inheritance = inheritance() || null;
720      consume("{") || error("Bodyless interface");
721      while (true) {
722        if (consume("}")) {
723          consume(";") || error("Missing semicolon after interface");
724          return ret;
725        }
726        const ea = extended_attrs();
727        const mem = const_() ||
728          static_member() ||
729          stringifier() ||
730          iterable() ||
731          attribute() ||
732          operation() ||
733          error("Unknown member");
734        mem.extAttrs = ea;
735        ret.members.push(mem);
736      }
737    }
738
739    function mixin_rest(isPartial) {
740      if (!consume("mixin")) return;
741      const name = consume(ID) || error("No name for interface mixin");
742      const mems = [];
743      const ret = current = {
744        type: "interface mixin",
745        name: isPartial ? name.value : sanitize_name(name.value, "interface mixin"),
746        partial: isPartial,
747        members: mems
748      };
749      consume("{") || error("Bodyless interface mixin");
750      while (true) {
751        if (consume("}")) {
752          consume(";") || error("Missing semicolon after interface mixin");
753          return ret;
754        }
755        const ea = extended_attrs();
756        const mem = const_() ||
757          stringifier() ||
758          attribute({ noInherit: true }) ||
759          operation({ regular: true }) ||
760          error("Unknown member");
761        mem.extAttrs = ea;
762        ret.members.push(mem);
763      }
764    }
765
766    function interface_(isPartial) {
767      if (!consume("interface")) return;
768      return mixin_rest(isPartial) ||
769        interface_rest(isPartial) ||
770        error("Interface has no proper body");
771    }
772
773    function namespace(isPartial) {
774      if (!consume("namespace")) return;
775      const name = consume(ID) || error("No name for namespace");
776      const mems = [];
777      const ret = current = {
778        type: "namespace",
779        name: isPartial ? name.value : sanitize_name(name.value, "namespace"),
780        partial: isPartial,
781        members: mems
782      };
783      consume("{") || error("Bodyless namespace");
784      while (true) {
785        if (consume("}")) {
786          consume(";") || error("Missing semicolon after namespace");
787          return ret;
788        }
789        const ea = extended_attrs();
790        const mem = attribute({ noInherit: true, readonly: true }) ||
791          operation({ regular: true }) ||
792          error("Unknown member");
793        mem.extAttrs = ea;
794        ret.members.push(mem);
795      }
796    }
797
798    function partial() {
799      if (!consume("partial")) return;
800      const thing = dictionary(true) ||
801        interface_(true) ||
802        namespace(true) ||
803        error("Partial doesn't apply to anything");
804      return thing;
805    }
806
807    function dictionary(isPartial) {
808      if (!consume("dictionary")) return;
809      const name = consume(ID) || error("No name for dictionary");
810      const mems = [];
811      const ret = current = {
812        type: "dictionary",
813        name: isPartial ? name.value : sanitize_name(name.value, "dictionary"),
814        partial: isPartial,
815        members: mems
816      };
817      if (!isPartial) ret.inheritance = inheritance() || null;
818      consume("{") || error("Bodyless dictionary");
819      while (true) {
820        if (consume("}")) {
821          consume(";") || error("Missing semicolon after dictionary");
822          return ret;
823        }
824        const ea = extended_attrs();
825        const required = consume("required");
826        const typ = type_with_extended_attributes("dictionary-type") || error("No type for dictionary member");
827        const name = consume(ID) || error("No name for dictionary member");
828        const dflt = default_() || null;
829        if (required && dflt) error("Required member must not have a default");
830        const member = {
831          type: "field",
832          name: unescape(name.value),
833          escapedName: name.value,
834          required: !!required,
835          idlType: typ,
836          extAttrs: ea,
837          default: dflt
838        };
839        ret.members.push(member);
840        consume(";") || error("Unterminated dictionary member");
841      }
842    }
843
844    function enum_() {
845      if (!consume("enum")) return;
846      const name = consume(ID) || error("No name for enum");
847      const vals = [];
848      const ret = current = {
849        type: "enum",
850        name: sanitize_name(name.value, "enum"),
851        values: vals
852      };
853      consume("{") || error("No curly for enum");
854      let value_expected = true;
855      while (true) {
856        if (consume("}")) {
857          if (!ret.values.length) error("No value in enum");
858          consume(";") || error("No semicolon after enum");
859          return ret;
860        }
861        else if (!value_expected) {
862          error("No comma between enum values");
863        }
864        const val = consume(STR) || error("Unexpected value in enum");
865        val.value = val.value.slice(1, -1);
866        // No trivia exposure yet
867        val.trivia = undefined;
868        ret.values.push(val);
869        value_expected = !!consume(",");
870      }
871    }
872
873    function typedef() {
874      if (!consume("typedef")) return;
875      const ret = {
876        type: "typedef"
877      };
878      ret.idlType = type_with_extended_attributes("typedef-type") || error("No type in typedef");
879      const name = consume(ID) || error("No name in typedef");
880      ret.name = sanitize_name(name.value, "typedef");
881      current = ret;
882      consume(";") || error("Unterminated typedef");
883      return ret;
884    }
885
886    function implements_() {
887      const start_position = consume_position;
888      const target = consume(ID);
889      if (!target) return;
890      if (consume("implements")) {
891        const ret = {
892          type: "implements",
893          target: target.value
894        };
895        const imp = consume(ID) || error("Incomplete implements statement");
896        ret.implements = imp.value;
897        consume(";") || error("No terminating ; for implements statement");
898        return ret;
899      } else {
900        // rollback
901        unconsume(start_position);
902      }
903    }
904
905    function includes() {
906      const start_position = consume_position;
907      const target = consume(ID);
908      if (!target) return;
909      if (consume("includes")) {
910        const ret = {
911          type: "includes",
912          target: target.value
913        };
914        const imp = consume(ID) || error("Incomplete includes statement");
915        ret.includes = imp.value;
916        consume(";") || error("No terminating ; for includes statement");
917        return ret;
918      } else {
919        // rollback
920        unconsume(start_position);
921      }
922    }
923
924    function definition() {
925      return callback() ||
926        interface_(false) ||
927        partial() ||
928        dictionary(false) ||
929        enum_() ||
930        typedef() ||
931        implements_() ||
932        includes() ||
933        namespace(false);
934    }
935
936    function definitions() {
937      if (!tokens.length) return [];
938      const defs = [];
939      while (true) {
940        const ea = extended_attrs();
941        const def = definition();
942        if (!def) {
943          if (ea.length) error("Stray extended attributes");
944          break;
945        }
946        def.extAttrs = ea;
947        defs.push(def);
948      }
949      return defs;
950    }
951    const res = definitions();
952    if (consume_position < tokens.length) error("Unrecognised tokens");
953    return res;
954  }
955
956  const obj = {
957    parse(str) {
958      const tokens = tokenise(str);
959      return parse(tokens);
960    }
961  };
962
963  if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
964    module.exports = obj;
965  } else if (typeof define === 'function' && define.amd) {
966    define([], () => obj);
967  } else {
968    (self || window).WebIDL2 = obj;
969  }
970})();
971