tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WebIDLParser.js (33933B)


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