Bug 1893067 - Add actions key to glean urlbar metrics. r=mak,urlbar-reviewers
[gecko.git] / dom / imptests / WebIDLParser.js
blob103a7f48bd87c65b6e81ace5b37f7fabb3c2b51e
3 (function () {
4     var tokenise = function (str) {
5         var tokens = []
6         ,   re = {
7                 "float":        /^-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/
8             ,   "integer":      /^-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/
9             ,   "identifier":   /^[A-Z_a-z][0-9A-Z_a-z]*/
10             ,   "string":       /^"[^"]*"/
11             ,   "whitespace":   /^(?:[\t\n\r ]+|[\t\n\r ]*((\/\/.*|\/\*(.|\n|\r)*?\*\/)[\t\n\r ]*))+/
12             ,   "other":        /^[^\t\n\r 0-9A-Z_a-z]/
13             }
14         ,   types = []
15         ;
16         for (var k in re) types.push(k);
17         while (str.length > 0) {
18             var matched = false;
19             for (var i = 0, n = types.length; i < n; i++) {
20                 var type = types[i];
21                 str = str.replace(re[type], function (tok) {
22                     tokens.push({ type: type, value: tok });
23                     matched = true;
24                     return "";
25                 });
26                 if (matched) break;
27             }
28             if (matched) continue;
29             throw new Error("Token stream not progressing");
30         }
31         return tokens;
32     };
33     
34     var parse = function (tokens, opt) {
35         var line = 1;
36         tokens = tokens.slice();
37         
38         var FLOAT = "float"
39         ,   INT = "integer"
40         ,   ID = "identifier"
41         ,   STR = "string"
42         ,   OTHER = "other"
43         ;
44         
45         var WebIDLParseError = function (str, line, input, tokens) {
46             this.message = str;
47             this.line = line;
48             this.input = input;
49             this.tokens = tokens;
50         };
51         WebIDLParseError.prototype.toString = function () {
52             return this.message + ", line " + this.line + " (tokens: '" + this.input + "')\n" +
53                    JSON.stringify(this.tokens, null, 4);
54         };
55         
56         var error = function (str) {
57             var tok = "", numTokens = 0, maxTokens = 5;
58             while (numTokens < maxTokens && tokens.length > numTokens) {
59                 tok += tokens[numTokens].value;
60                 numTokens++;
61             }
62             throw new WebIDLParseError(str, line, tok, tokens.slice(0, 5));
63         };
64         
65         var last_token = null;
66         
67         var consume = function (type, value) {
68             if (!tokens.length || tokens[0].type !== type) return;
69             if (typeof value === "undefined" || tokens[0].value === value) {
70                  last_token = tokens.shift();
71                  if (type === ID) last_token.value = last_token.value.replace(/^_/, "");
72                  return last_token;
73              }
74         };
75         
76         var ws = function () {
77             if (!tokens.length) return;
78             if (tokens[0].type === "whitespace") {
79                 var t = tokens.shift();
80                 t.value.replace(/\n/g, function (m) { line++; return m; });
81                 return t;
82             }
83         };
84         
85         var all_ws = function (store, pea) { // pea == post extended attribute, tpea = same for types
86             var t = { type: "whitespace", value: "" };
87             while (true) {
88                 var w = ws();
89                 if (!w) break;
90                 t.value += w.value;
91             }
92             if (t.value.length > 0) {
93                 if (store) {
94                     var w = t.value
95                     ,   re = {
96                             "ws":                   /^([\t\n\r ]+)/
97                         ,   "line-comment":         /^\/\/(.*)\n?/m
98                         ,   "multiline-comment":    /^\/\*((?:.|\n|\r)*?)\*\//
99                         }
100                     ,   wsTypes = []
101                     ;
102                     for (var k in re) wsTypes.push(k);
103                     while (w.length) {
104                         var matched = false;
105                         for (var i = 0, n = wsTypes.length; i < n; i++) {
106                             var type = wsTypes[i];
107                             w = w.replace(re[type], function (tok, m1) {
108                                 store.push({ type: type + (pea ? ("-" + pea) : ""), value: m1 });
109                                 matched = true;
110                                 return "";
111                             });
112                             if (matched) break;
113                         }
114                         if (matched) continue;
115                         throw new Error("Surprising white space construct."); // this shouldn't happen
116                     }
117                 }
118                 return t;
119             }
120         };
121         
122         var integer_type = function () {
123             var ret = "";
124             all_ws();
125             if (consume(ID, "unsigned")) ret = "unsigned ";
126             all_ws();
127             if (consume(ID, "short")) return ret + "short";
128             if (consume(ID, "long")) {
129                 ret += "long";
130                 all_ws();
131                 if (consume(ID, "long")) return ret + " long";
132                 return ret;
133             }
134             if (ret) error("Failed to parse integer type");
135         };
136         
137         var float_type = function () {
138             var ret = "";
139             all_ws();
140             if (consume(ID, "unrestricted")) ret = "unrestricted ";
141             all_ws();
142             if (consume(ID, "float")) return ret + "float";
143             if (consume(ID, "double")) return ret + "double";
144             if (ret) error("Failed to parse float type");
145         };
146         
147         var primitive_type = function () {
148             var num_type = integer_type() || float_type();
149             if (num_type) return num_type;
150             all_ws();
151             if (consume(ID, "boolean")) return "boolean";
152             if (consume(ID, "byte")) return "byte";
153             if (consume(ID, "octet")) return "octet";
154         };
155         
156         var const_value = function () {
157             if (consume(ID, "true")) return { type: "boolean", value: true };
158             if (consume(ID, "false")) return { type: "boolean", value: false };
159             if (consume(ID, "null")) return { type: "null" };
160             if (consume(ID, "Infinity")) return { type: "Infinity", negative: false };
161             if (consume(ID, "NaN")) return { type: "NaN" };
162             var ret = consume(FLOAT) || consume(INT);
163             if (ret) return { type: "number", value: 1 * ret.value };
164             var tok = consume(OTHER, "-");
165             if (tok) {
166                 if (consume(ID, "Infinity")) return { type: "Infinity", negative: true };
167                 else tokens.unshift(tok);
168             }
169         };
170         
171         var type_suffix = function (obj) {
172             while (true) {
173                 all_ws();
174                 if (consume(OTHER, "?")) {
175                     if (obj.nullable) error("Can't nullable more than once");
176                     obj.nullable = true;
177                 }
178                 else if (consume(OTHER, "[")) {
179                     all_ws();
180                     consume(OTHER, "]") || error("Unterminated array type");
181                     if (!obj.array) {
182                         obj.array = 1;
183                         obj.nullableArray = [obj.nullable];
184                     }
185                     else {
186                         obj.array++;
187                         obj.nullableArray.push(obj.nullable);
188                     }
189                     obj.nullable = false;
190                 }
191                 else return;
192             }
193         };
194         
195         var single_type = function () {
196             var prim = primitive_type()
197             ,   ret = { sequence: false, generic: null, nullable: false, array: false, union: false }
198             ,   name
199             ,   value
200             ;
201             if (prim) {
202                 ret.idlType = prim;
203             }
204             else if (name = consume(ID)) {
205                 value = name.value;
206                 all_ws();
207                 // Generic types
208                 if (consume(OTHER, "<")) {
209                     // backwards compat
210                     if (value === "sequence") {
211                         ret.sequence = true;
212                     }
213                     ret.generic = value;
214                     ret.idlType = type() || error("Error parsing generic type " + value);
215                     all_ws();
216                     if (!consume(OTHER, ">")) error("Unterminated generic type " + value);
217                     all_ws();
218                     if (consume(OTHER, "?")) ret.nullable = true;
219                     return ret;
220                 }
221                 else {
222                     ret.idlType = value;
223                 }
224             }
225             else {
226                 return;
227             }
228             type_suffix(ret);
229             if (ret.nullable && !ret.array && ret.idlType === "any") error("Type any cannot be made nullable");
230             return ret;
231         };
232         
233         var union_type = function () {
234             all_ws();
235             if (!consume(OTHER, "(")) return;
236             var ret = { sequence: false, generic: null, nullable: false, array: false, union: true, idlType: [] };
237             var fst = type() || error("Union type with no content");
238             ret.idlType.push(fst);
239             while (true) {
240                 all_ws();
241                 if (!consume(ID, "or")) break;
242                 var typ = type() || error("No type after 'or' in union type");
243                 ret.idlType.push(typ);
244             }
245             if (!consume(OTHER, ")")) error("Unterminated union type");
246             type_suffix(ret);
247             return ret;
248         };
249         
250         var type = function () {
251             return single_type() || union_type();
252         };
253         
254         var argument = function (store) {
255             var ret = { optional: false, variadic: false };
256             ret.extAttrs = extended_attrs(store);
257             all_ws(store, "pea");
258             var opt_token = consume(ID, "optional");
259             if (opt_token) {
260                 ret.optional = true;
261                 all_ws();
262             }
263             ret.idlType = type();
264             if (!ret.idlType) {
265                 if (opt_token) tokens.unshift(opt_token);
266                 return;
267             }
268             var type_token = last_token;
269             if (!ret.optional) {
270                 all_ws();
271                 if (tokens.length >= 3 &&
272                     tokens[0].type === "other" && tokens[0].value === "." &&
273                     tokens[1].type === "other" && tokens[1].value === "." &&
274                     tokens[2].type === "other" && tokens[2].value === "."
275                     ) {
276                     tokens.shift();
277                     tokens.shift();
278                     tokens.shift();
279                     ret.variadic = true;
280                 }
281             }
282             all_ws();
283             var name = consume(ID);
284             if (!name) {
285                 if (opt_token) tokens.unshift(opt_token);
286                 tokens.unshift(type_token);
287                 return;
288             }
289             ret.name = name.value;
290             if (ret.optional) {
291                 all_ws();
292                 ret["default"] = default_();
293             }
294             return ret;
295         };
296         
297         var argument_list = function (store) {
298             var ret = []
299             ,   arg = argument(store ? ret : null)
300             ;
301             if (!arg) return;
302             ret.push(arg);
303             while (true) {
304                 all_ws(store ? ret : null);
305                 if (!consume(OTHER, ",")) return ret;
306                 var nxt = argument(store ? ret : null) || error("Trailing comma in arguments list");
307                 ret.push(nxt);
308             }
309         };
310         
311         var type_pair = function () {
312             all_ws();
313             var k = type();
314             if (!k) return;
315             all_ws()
316             if (!consume(OTHER, ",")) return;
317             all_ws();
318             var v = type();
319             if (!v) return;
320             return [k, v];
321         };
322         
323         var simple_extended_attr = function (store) {
324             all_ws();
325             var name = consume(ID);
326             if (!name) return;
327             var ret = {
328                 name: name.value
329             ,   "arguments": null
330             };
331             all_ws();
332             var eq = consume(OTHER, "=");
333             if (eq) {
334                 all_ws();
335                 ret.rhs = consume(ID);
336                 if (!ret.rhs) return error("No right hand side to extended attribute assignment");
337             }
338             all_ws();
339             if (consume(OTHER, "(")) {
340                 var args, pair;
341                 // [Constructor(DOMString str)]
342                 if (args = argument_list(store)) {
343                     ret["arguments"] = args;
344                 }
345                 // [MapClass(DOMString, DOMString)]
346                 else if (pair = type_pair()) {
347                     ret.typePair = pair;
348                 }
349                 // [Constructor()]
350                 else {
351                     ret["arguments"] = [];
352                 }
353                 all_ws();
354                 consume(OTHER, ")") || error("Unexpected token in extended attribute argument list or type pair");
355             }
356             return ret;
357         };
358         
359         // Note: we parse something simpler than the official syntax. It's all that ever
360         // seems to be used
361         var extended_attrs = function (store) {
362             var eas = [];
363             all_ws(store);
364             if (!consume(OTHER, "[")) return eas;
365             eas[0] = simple_extended_attr(store) || error("Extended attribute with not content");
366             all_ws();
367             while (consume(OTHER, ",")) {
368                 eas.push(simple_extended_attr(store) || error("Trailing comma in extended attribute"));
369                 all_ws();
370             }
371             consume(OTHER, "]") || error("No end of extended attribute");
372             return eas;
373         };
374         
375         var default_ = function () {
376             all_ws();
377             if (consume(OTHER, "=")) {
378                 all_ws();
379                 var def = const_value();
380                 if (def) {
381                     return def;
382                 }
383                 else {
384                     var str = consume(STR) || error("No value for default");
385                     str.value = str.value.replace(/^"/, "").replace(/"$/, "");
386                     return str;
387                 }
388             }
389         };
390         
391         var const_ = function (store) {
392             all_ws(store, "pea");
393             if (!consume(ID, "const")) return;
394             var ret = { type: "const", nullable: false };
395             all_ws();
396             var typ = primitive_type();
397             if (!typ) {
398                 typ = consume(ID) || error("No type for const");
399                 typ = typ.value;
400             }
401             ret.idlType = typ;
402             all_ws();
403             if (consume(OTHER, "?")) {
404                 ret.nullable = true;
405                 all_ws();
406             }
407             var name = consume(ID) || error("No name for const");
408             ret.name = name.value;
409             all_ws();
410             consume(OTHER, "=") || error("No value assignment for const");
411             all_ws();
412             var cnt = const_value();
413             if (cnt) ret.value = cnt;
414             else error("No value for const");
415             all_ws();
416             consume(OTHER, ";") || error("Unterminated const");
417             return ret;
418         };
419         
420         var inheritance = function () {
421             all_ws();
422             if (consume(OTHER, ":")) {
423                 all_ws();
424                 var inh = consume(ID) || error ("No type in inheritance");
425                 return inh.value;
426             }
427         };
428         
429         var operation_rest = function (ret, store) {
430             all_ws();
431             if (!ret) ret = {};
432             var name = consume(ID);
433             ret.name = name ? name.value : null;
434             all_ws();
435             consume(OTHER, "(") || error("Invalid operation");
436             ret["arguments"] = argument_list(store) || [];
437             all_ws();
438             consume(OTHER, ")") || error("Unterminated operation");
439             all_ws();
440             consume(OTHER, ";") || error("Unterminated operation");
441             return ret;
442         };
443         
444         var callback = function (store) {
445             all_ws(store, "pea");
446             var ret;
447             if (!consume(ID, "callback")) return;
448             all_ws();
449             var tok = consume(ID, "interface");
450             if (tok) {
451                 tokens.unshift(tok);
452                 ret = interface_();
453                 ret.type = "callback interface";
454                 return ret;
455             }
456             var name = consume(ID) || error("No name for callback");
457             ret = { type: "callback", name: name.value };
458             all_ws();
459             consume(OTHER, "=") || error("No assignment in callback");
460             all_ws();
461             ret.idlType = return_type();
462             all_ws();
463             consume(OTHER, "(") || error("No arguments in callback");
464             ret["arguments"] = argument_list(store) || [];
465             all_ws();
466             consume(OTHER, ")") || error("Unterminated callback");
467             all_ws();
468             consume(OTHER, ";") || error("Unterminated callback");
469             return ret;
470         };
472         var attribute = function (store) {
473             all_ws(store, "pea");
474             var grabbed = []
475             ,   ret = {
476                 type:           "attribute"
477             ,   "static":       false
478             ,   stringifier:    false
479             ,   inherit:        false
480             ,   readonly:       false
481             };
482             if (consume(ID, "static")) {
483                 ret["static"] = true;
484                 grabbed.push(last_token);
485             }
486             else if (consume(ID, "stringifier")) {
487                 ret.stringifier = true;
488                 grabbed.push(last_token);
489             }
490             var w = all_ws();
491             if (w) grabbed.push(w);
492             if (consume(ID, "inherit")) {
493                 if (ret["static"] || ret.stringifier) error("Cannot have a static or stringifier inherit");
494                 ret.inherit = true;
495                 grabbed.push(last_token);
496                 var w = all_ws();
497                 if (w) grabbed.push(w);
498             }
499             if (consume(ID, "readonly")) {
500                 ret.readonly = true;
501                 grabbed.push(last_token);
502                 var w = all_ws();
503                 if (w) grabbed.push(w);
504             }
505             if (!consume(ID, "attribute")) {
506                 tokens = grabbed.concat(tokens);
507                 return;
508             }
509             all_ws();
510             ret.idlType = type() || error("No type in attribute");
511             if (ret.idlType.sequence) error("Attributes cannot accept sequence types");
512             all_ws();
513             var name = consume(ID) || error("No name in attribute");
514             ret.name = name.value;
515             all_ws();
516             consume(OTHER, ";") || error("Unterminated attribute");
517             return ret;
518         };
519         
520         var return_type = function () {
521             var typ = type();
522             if (!typ) {
523                 if (consume(ID, "void")) {
524                     return "void";
525                 }
526                 else error("No return type");
527             }
528             return typ;
529         };
530         
531         var operation = function (store) {
532             all_ws(store, "pea");
533             var ret = {
534                 type:           "operation"
535             ,   getter:         false
536             ,   setter:         false
537             ,   creator:        false
538             ,   deleter:        false
539             ,   legacycaller:   false
540             ,   "static":       false
541             ,   stringifier:    false
542             };
543             while (true) {
544                 all_ws();
545                 if (consume(ID, "getter")) ret.getter = true;
546                 else if (consume(ID, "setter")) ret.setter = true;
547                 else if (consume(ID, "creator")) ret.creator = true;
548                 else if (consume(ID, "deleter")) ret.deleter = true;
549                 else if (consume(ID, "legacycaller")) ret.legacycaller = true;
550                 else break;
551             }
552             if (ret.getter || ret.setter || ret.creator || ret.deleter || ret.legacycaller) {
553                 all_ws();
554                 ret.idlType = return_type();
555                 operation_rest(ret, store);
556                 return ret;
557             }
558             if (consume(ID, "static")) {
559                 ret["static"] = true;
560                 ret.idlType = return_type();
561                 operation_rest(ret, store);
562                 return ret;
563             }
564             else if (consume(ID, "stringifier")) {
565                 ret.stringifier = true;
566                 all_ws();
567                 if (consume(OTHER, ";")) return ret;
568                 ret.idlType = return_type();
569                 operation_rest(ret, store);
570                 return ret;
571             }
572             ret.idlType = return_type();
573             all_ws();
574             if (consume(ID, "iterator")) {
575                 all_ws();
576                 ret.type = "iterator";
577                 if (consume(ID, "object")) {
578                     ret.iteratorObject = "object";
579                 }
580                 else if (consume(OTHER, "=")) {
581                     all_ws();
582                     var name = consume(ID) || error("No right hand side in iterator");
583                     ret.iteratorObject = name.value;
584                 }
585                 all_ws();
586                 consume(OTHER, ";") || error("Unterminated iterator");
587                 return ret;
588             }
589             else {
590                 operation_rest(ret, store);
591                 return ret;
592             }
593         };
594         
595         var identifiers = function (arr) {
596             while (true) {
597                 all_ws();
598                 if (consume(OTHER, ",")) {
599                     all_ws();
600                     var name = consume(ID) || error("Trailing comma in identifiers list");
601                     arr.push(name.value);
602                 }
603                 else break;
604             }
605         };
606         
607         var serialiser = function (store) {
608             all_ws(store, "pea");
609             if (!consume(ID, "serializer")) return;
610             var ret = { type: "serializer" };
611             all_ws();
612             if (consume(OTHER, "=")) {
613                 all_ws();
614                 if (consume(OTHER, "{")) {
615                     ret.patternMap = true;
616                     all_ws();
617                     var id = consume(ID);
618                     if (id && id.value === "getter") {
619                         ret.names = ["getter"];
620                     }
621                     else if (id && id.value === "inherit") {
622                         ret.names = ["inherit"];
623                         identifiers(ret.names);
624                     }
625                     else if (id) {
626                         ret.names = [id.value];
627                         identifiers(ret.names);
628                     }
629                     else {
630                         ret.names = [];
631                     }
632                     all_ws();
633                     consume(OTHER, "}") || error("Unterminated serializer pattern map");
634                 }
635                 else if (consume(OTHER, "[")) {
636                     ret.patternList = true;
637                     all_ws();
638                     var id = consume(ID);
639                     if (id && id.value === "getter") {
640                         ret.names = ["getter"];
641                     }
642                     else if (id) {
643                         ret.names = [id.value];
644                         identifiers(ret.names);
645                     }
646                     else {
647                         ret.names = [];
648                     }
649                     all_ws();
650                     consume(OTHER, "]") || error("Unterminated serializer pattern list");
651                 }
652                 else {
653                     var name = consume(ID) || error("Invalid serializer");
654                     ret.name = name.value;
655                 }
656                 all_ws();
657                 consume(OTHER, ";") || error("Unterminated serializer");
658                 return ret;
659             }
660             else if (consume(OTHER, ";")) {
661                 // noop, just parsing
662             }
663             else {
664                 ret.idlType = return_type();
665                 all_ws();
666                 ret.operation = operation_rest(null, store);
667             }
668             return ret;
669         };
670         
671         var interface_ = function (isPartial, store) {
672             all_ws(isPartial ? null : store, "pea");
673             if (!consume(ID, "interface")) return;
674             all_ws();
675             var name = consume(ID) || error("No name for interface");
676             var mems = []
677             ,   ret = {
678                 type:   "interface"
679             ,   name:   name.value
680             ,   partial:    false
681             ,   members:    mems
682             };
683             if (!isPartial) ret.inheritance = inheritance() || null;
684             all_ws();
685             consume(OTHER, "{") || error("Bodyless interface");
686             while (true) {
687                 all_ws(store ? mems : null);
688                 if (consume(OTHER, "}")) {
689                     all_ws();
690                     consume(OTHER, ";") || error("Missing semicolon after interface");
691                     return ret;
692                 }
693                 var ea = extended_attrs(store ? mems : null);
694                 all_ws();
695                 var cnt = const_(store ? mems : null);
696                 if (cnt) {
697                     cnt.extAttrs = ea;
698                     ret.members.push(cnt);
699                     continue;
700                 }
701                 var mem = serialiser(store ? mems : null) ||
702                           attribute(store ? mems : null) ||
703                           operation(store ? mems : null) ||
704                           error("Unknown member");
705                 mem.extAttrs = ea;
706                 ret.members.push(mem);
707             }
708         };
709         
710         var partial = function (store) {
711             all_ws(store, "pea");
712             if (!consume(ID, "partial")) return;
713             var thing = dictionary(true, store) ||
714                         interface_(true, store) ||
715                         error("Partial doesn't apply to anything");
716             thing.partial = true;
717             return thing;
718         };
719         
720         var dictionary = function (isPartial, store) {
721             all_ws(isPartial ? null : store, "pea");
722             if (!consume(ID, "dictionary")) return;
723             all_ws();
724             var name = consume(ID) || error("No name for dictionary");
725             var mems = []
726             ,   ret = {
727                 type:   "dictionary"
728             ,   name:   name.value
729             ,   partial:    false
730             ,   members:    mems
731             };
732             if (!isPartial) ret.inheritance = inheritance() || null;
733             all_ws();
734             consume(OTHER, "{") || error("Bodyless dictionary");
735             while (true) {
736                 all_ws(store ? mems : null);
737                 if (consume(OTHER, "}")) {
738                     all_ws();
739                     consume(OTHER, ";") || error("Missing semicolon after dictionary");
740                     return ret;
741                 }
742                 var ea = extended_attrs(store ? mems : null);
743                 all_ws(store ? mems : null, "pea");
744                 var typ = type() || error("No type for dictionary member");
745                 all_ws();
746                 var name = consume(ID) || error("No name for dictionary member");
747                 ret.members.push({
748                     type:       "field"
749                 ,   name:       name.value
750                 ,   idlType:    typ
751                 ,   extAttrs:   ea
752                 ,   "default":  default_()
753                 });
754                 all_ws();
755                 consume(OTHER, ";") || error("Unterminated dictionary member");
756             }
757         };
758         
759         var exception = function (store) {
760             all_ws(store, "pea");
761             if (!consume(ID, "exception")) return;
762             all_ws();
763             var name = consume(ID) || error("No name for exception");
764             var mems = []
765             ,   ret = {
766                 type:   "exception"
767             ,   name:   name.value
768             ,   members:    mems
769             };
770             ret.inheritance = inheritance() || null;
771             all_ws();
772             consume(OTHER, "{") || error("Bodyless exception");
773             while (true) {
774                 all_ws(store ? mems : null);
775                 if (consume(OTHER, "}")) {
776                     all_ws();
777                     consume(OTHER, ";") || error("Missing semicolon after exception");
778                     return ret;
779                 }
780                 var ea = extended_attrs(store ? mems : null);
781                 all_ws(store ? mems : null, "pea");
782                 var cnt = const_();
783                 if (cnt) {
784                     cnt.extAttrs = ea;
785                     ret.members.push(cnt);
786                 }
787                 else {
788                     var typ = type();
789                     all_ws();
790                     var name = consume(ID);
791                     all_ws();
792                     if (!typ || !name || !consume(OTHER, ";")) error("Unknown member in exception body");
793                     ret.members.push({
794                         type:       "field"
795                     ,   name:       name.value
796                     ,   idlType:    typ
797                     ,   extAttrs:   ea
798                     });
799                 }
800             }
801         };
802         
803         var enum_ = function (store) {
804             all_ws(store, "pea");
805             if (!consume(ID, "enum")) return;
806             all_ws();
807             var name = consume(ID) || error("No name for enum");
808             var vals = []
809             ,   ret = {
810                 type:   "enum"
811             ,   name:   name.value
812             ,   values: vals
813             };
814             all_ws();
815             consume(OTHER, "{") || error("No curly for enum");
816             var saw_comma = false;
817             while (true) {
818                 all_ws(store ? vals : null);
819                 if (consume(OTHER, "}")) {
820                     all_ws();
821                     if (saw_comma) error("Trailing comma in enum");
822                     consume(OTHER, ";") || error("No semicolon after enum");
823                     return ret;
824                 }
825                 var val = consume(STR) || error("Unexpected value in enum");
826                 ret.values.push(val.value.replace(/"/g, ""));
827                 all_ws(store ? vals : null);
828                 if (consume(OTHER, ",")) {
829                     if (store) vals.push({ type: "," });
830                     all_ws(store ? vals : null);
831                     saw_comma = true;
832                 }
833                 else {
834                     saw_comma = false;
835                 }
836             }
837         };
838         
839         var typedef = function (store) {
840             all_ws(store, "pea");
841             if (!consume(ID, "typedef")) return;
842             var ret = {
843                 type:   "typedef"
844             };
845             all_ws();
846             ret.typeExtAttrs = extended_attrs();
847             all_ws(store, "tpea");
848             ret.idlType = type() || error("No type in typedef");
849             all_ws();
850             var name = consume(ID) || error("No name in typedef");
851             ret.name = name.value;
852             all_ws();
853             consume(OTHER, ";") || error("Unterminated typedef");
854             return ret;
855         };
856         
857         var implements_ = function (store) {
858             all_ws(store, "pea");
859             var target = consume(ID);
860             if (!target) return;
861             var w = all_ws();
862             if (consume(ID, "implements")) {
863                 var ret = {
864                     type:   "implements"
865                 ,   target: target.value
866                 };
867                 all_ws();
868                 var imp = consume(ID) || error("Incomplete implements statement");
869                 ret["implements"] = imp.value;
870                 all_ws();
871                 consume(OTHER, ";") || error("No terminating ; for implements statement");
872                 return ret;
873             }
874             else {
875                 // rollback
876                 tokens.unshift(w);
877                 tokens.unshift(target);
878             }
879         };
880         
881         var definition = function (store) {
882             return  callback(store)             ||
883                     interface_(false, store)    ||
884                     partial(store)              ||
885                     dictionary(false, store)    ||
886                     exception(store)            ||
887                     enum_(store)                ||
888                     typedef(store)              ||
889                     implements_(store)
890                     ;
891         };
892         
893         var definitions = function (store) {
894             if (!tokens.length) return [];
895             var defs = [];
896             while (true) {
897                 var ea = extended_attrs(store ? defs : null)
898                 ,   def = definition(store ? defs : null);
899                 if (!def) {
900                     if (ea.length) error("Stray extended attributes");
901                     break;
902                 }
903                 def.extAttrs = ea;
904                 defs.push(def);
905             }
906             return defs;
907         };
908         var res = definitions(opt.ws);
909         if (tokens.length) error("Unrecognised tokens");
910         return res;
911     };
913     var inNode = typeof module !== "undefined" && module.exports
914     ,   obj = {
915             parse:  function (str, opt) {
916                 if (!opt) opt = {};
917                 var tokens = tokenise(str);
918                 return parse(tokens, opt);
919             }
920     };
922     if (inNode) module.exports = obj;
923     else        self.WebIDL2 = obj;
924 }());