michael@0: michael@0: michael@0: (function () { michael@0: var tokenise = function (str) { michael@0: var tokens = [] michael@0: , re = { michael@0: "float": /^-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/ michael@0: , "integer": /^-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/ michael@0: , "identifier": /^[A-Z_a-z][0-9A-Z_a-z]*/ michael@0: , "string": /^"[^"]*"/ michael@0: , "whitespace": /^(?:[\t\n\r ]+|[\t\n\r ]*((\/\/.*|\/\*(.|\n|\r)*?\*\/)[\t\n\r ]*))+/ michael@0: , "other": /^[^\t\n\r 0-9A-Z_a-z]/ michael@0: } michael@0: , types = [] michael@0: ; michael@0: for (var k in re) types.push(k); michael@0: while (str.length > 0) { michael@0: var matched = false; michael@0: for (var i = 0, n = types.length; i < n; i++) { michael@0: var type = types[i]; michael@0: str = str.replace(re[type], function (tok) { michael@0: tokens.push({ type: type, value: tok }); michael@0: matched = true; michael@0: return ""; michael@0: }); michael@0: if (matched) break; michael@0: } michael@0: if (matched) continue; michael@0: throw new Error("Token stream not progressing"); michael@0: } michael@0: return tokens; michael@0: }; michael@0: michael@0: var parse = function (tokens) { michael@0: var line = 1; michael@0: tokens = tokens.slice(); michael@0: michael@0: var FLOAT = "float" michael@0: , INT = "integer" michael@0: , ID = "identifier" michael@0: , STR = "string" michael@0: , OTHER = "other" michael@0: ; michael@0: michael@0: var WebIDLParseError = function (str, line, input, tokens) { michael@0: this.message = str; michael@0: this.line = line; michael@0: this.input = input; michael@0: this.tokens = tokens; michael@0: }; michael@0: WebIDLParseError.prototype.toString = function () { michael@0: return this.message + ", line " + this.line + " (tokens: '" + this.input + "')\n" + michael@0: JSON.stringify(this.tokens, null, 4); michael@0: }; michael@0: michael@0: var error = function (str) { michael@0: var tok = "", numTokens = 0, maxTokens = 5; michael@0: while (numTokens < maxTokens && tokens.length > numTokens) { michael@0: tok += tokens[numTokens].value; michael@0: numTokens++; michael@0: } michael@0: throw new WebIDLParseError(str, line, tok, tokens.slice(0, 5)); michael@0: }; michael@0: michael@0: var last_token = null; michael@0: michael@0: var consume = function (type, value) { michael@0: if (!tokens.length || tokens[0].type !== type) return; michael@0: if (typeof value === "undefined" || tokens[0].value === value) { michael@0: last_token = tokens.shift(); michael@0: if (type === ID) last_token.value = last_token.value.replace(/^_/, ""); michael@0: return last_token; michael@0: } michael@0: }; michael@0: michael@0: var ws = function () { michael@0: if (!tokens.length) return; michael@0: if (tokens[0].type === "whitespace") { michael@0: var t = tokens.shift(); michael@0: t.value.replace(/\n/g, function (m) { line++; return m; }); michael@0: return t; michael@0: } michael@0: }; michael@0: michael@0: var all_ws = function () { michael@0: var t = { type: "whitespace", value: "" }; michael@0: while (true) { michael@0: var w = ws(); michael@0: if (!w) break; michael@0: t.value += w.value; michael@0: } michael@0: if (t.value.length > 0) return t; michael@0: }; michael@0: michael@0: var integer_type = function () { michael@0: var ret = ""; michael@0: all_ws(); michael@0: if (consume(ID, "unsigned")) ret = "unsigned "; michael@0: all_ws(); michael@0: if (consume(ID, "short")) return ret + "short"; michael@0: if (consume(ID, "long")) { michael@0: ret += "long"; michael@0: all_ws(); michael@0: if (consume(ID, "long")) return ret + " long"; michael@0: return ret; michael@0: } michael@0: if (ret) error("Failed to parse integer type"); michael@0: }; michael@0: michael@0: var float_type = function () { michael@0: var ret = ""; michael@0: all_ws(); michael@0: if (consume(ID, "unrestricted")) ret = "unrestricted "; michael@0: all_ws(); michael@0: if (consume(ID, "float")) return ret + "float"; michael@0: if (consume(ID, "double")) return ret + "double"; michael@0: if (ret) error("Failed to parse float type"); michael@0: }; michael@0: michael@0: var primitive_type = function () { michael@0: var num_type = integer_type() || float_type(); michael@0: if (num_type) return num_type; michael@0: all_ws(); michael@0: if (consume(ID, "boolean")) return "boolean"; michael@0: if (consume(ID, "byte")) return "byte"; michael@0: if (consume(ID, "octet")) return "octet"; michael@0: }; michael@0: michael@0: var const_value = function () { michael@0: if (consume(ID, "true")) return { type: "boolean", value: true }; michael@0: if (consume(ID, "false")) return { type: "boolean", value: false }; michael@0: if (consume(ID, "null")) return { type: "null" }; michael@0: if (consume(ID, "Infinity")) return { type: "Infinity", negative: false }; michael@0: if (consume(ID, "NaN")) return { type: "NaN" }; michael@0: var ret = consume(FLOAT) || consume(INT); michael@0: if (ret) return { type: "number", value: 1 * ret.value }; michael@0: var tok = consume(OTHER, "-"); michael@0: if (tok) { michael@0: if (consume(ID, "Infinity")) return { type: "Infinity", negative: true }; michael@0: else tokens.unshift(tok); michael@0: } michael@0: }; michael@0: michael@0: var type_suffix = function (obj) { michael@0: while (true) { michael@0: all_ws(); michael@0: if (consume(OTHER, "?")) { michael@0: if (obj.nullable) error("Can't nullable more than once"); michael@0: obj.nullable = true; michael@0: } michael@0: else if (consume(OTHER, "[")) { michael@0: all_ws(); michael@0: consume(OTHER, "]") || error("Unterminated array type"); michael@0: if (!obj.array) obj.array = 1; michael@0: else obj.array++; michael@0: } michael@0: else return; michael@0: } michael@0: }; michael@0: michael@0: var single_type = function () { michael@0: var prim = primitive_type() michael@0: , ret = { sequence: false, nullable: false, array: false, union: false } michael@0: ; michael@0: if (prim) { michael@0: ret.idlType = prim; michael@0: } michael@0: else if (consume(ID, "sequence")) { michael@0: all_ws(); michael@0: if (!consume(OTHER, "<")) { michael@0: ret.idlType = "sequence"; michael@0: } michael@0: else { michael@0: ret.sequence = true; michael@0: ret.idlType = type() || error("Error parsing sequence type"); michael@0: all_ws(); michael@0: if (!consume(OTHER, ">")) error("Unterminated sequence"); michael@0: all_ws(); michael@0: if (consume(OTHER, "?")) ret.nullable = true; michael@0: return ret; michael@0: } michael@0: } michael@0: else { michael@0: var name = consume(ID); michael@0: if (!name) return; michael@0: ret.idlType = name.value; michael@0: } michael@0: type_suffix(ret); michael@0: if (ret.nullable && ret.idlType === "any") error("Type any cannot be made nullable"); michael@0: return ret; michael@0: }; michael@0: michael@0: var union_type = function () { michael@0: all_ws(); michael@0: if (!consume(OTHER, "(")) return; michael@0: var ret = { sequence: false, nullable: false, array: false, union: true, idlType: [] }; michael@0: var fst = type() || error("Union type with no content"); michael@0: ret.idlType.push(fst); michael@0: while (true) { michael@0: all_ws(); michael@0: if (!consume(ID, "or")) break; michael@0: var typ = type() || error("No type after 'or' in union type"); michael@0: ret.idlType.push(typ); michael@0: } michael@0: if (!consume(OTHER, ")")) error("Unterminated union type"); michael@0: type_suffix(ret); michael@0: return ret; michael@0: }; michael@0: michael@0: var type = function () { michael@0: return single_type() || union_type(); michael@0: }; michael@0: michael@0: var argument = function () { michael@0: var ret = { optional: false, variadic: false }; michael@0: ret.extAttrs = extended_attrs(); michael@0: all_ws(); michael@0: if (consume(ID, "optional")) { michael@0: ret.optional = true; michael@0: all_ws(); michael@0: } michael@0: ret.idlType = type(); michael@0: if (!ret.idlType) return; michael@0: if (!ret.optional) { michael@0: all_ws(); michael@0: if (tokens.length >= 3 && michael@0: tokens[0].type === "other" && tokens[0].value === "." && michael@0: tokens[1].type === "other" && tokens[1].value === "." && michael@0: tokens[2].type === "other" && tokens[2].value === "." michael@0: ) { michael@0: tokens.shift(); michael@0: tokens.shift(); michael@0: tokens.shift(); michael@0: ret.variadic = true; michael@0: } michael@0: } michael@0: all_ws(); michael@0: var name = consume(ID) || error("No name in argument"); michael@0: ret.name = name.value; michael@0: if (ret.optional) { michael@0: all_ws(); michael@0: ret["default"] = default_(); michael@0: } michael@0: return ret; michael@0: }; michael@0: michael@0: var argument_list = function () { michael@0: var arg = argument(), ret = []; michael@0: if (!arg) return ret; michael@0: ret.push(arg); michael@0: while (true) { michael@0: all_ws(); michael@0: if (!consume(OTHER, ",")) return ret; michael@0: all_ws(); michael@0: var nxt = argument() || error("Trailing comma in arguments list"); michael@0: ret.push(nxt); michael@0: } michael@0: }; michael@0: michael@0: var simple_extended_attr = function () { michael@0: all_ws(); michael@0: var name = consume(ID); michael@0: if (!name) return; michael@0: var ret = { michael@0: name: name.value michael@0: , "arguments": null michael@0: }; michael@0: all_ws(); michael@0: var eq = consume(OTHER, "="); michael@0: if (eq) { michael@0: all_ws(); michael@0: ret.rhs = consume(ID); michael@0: if (!ret.rhs) return error("No right hand side to extended attribute assignment"); michael@0: } michael@0: all_ws(); michael@0: if (consume(OTHER, "(")) { michael@0: ret["arguments"] = argument_list(); michael@0: all_ws(); michael@0: consume(OTHER, ")") || error("Unclosed argument in extended attribute"); michael@0: } michael@0: return ret; michael@0: }; michael@0: michael@0: // Note: we parse something simpler than the official syntax. It's all that ever michael@0: // seems to be used michael@0: var extended_attrs = function () { michael@0: var eas = []; michael@0: all_ws(); michael@0: if (!consume(OTHER, "[")) return eas; michael@0: eas[0] = simple_extended_attr() || error("Extended attribute with not content"); michael@0: all_ws(); michael@0: while (consume(OTHER, ",")) { michael@0: all_ws(); michael@0: eas.push(simple_extended_attr() || error("Trailing comma in extended attribute")); michael@0: all_ws(); michael@0: } michael@0: consume(OTHER, "]") || error("No end of extended attribute"); michael@0: return eas; michael@0: }; michael@0: michael@0: var default_ = function () { michael@0: all_ws(); michael@0: if (consume(OTHER, "=")) { michael@0: all_ws(); michael@0: var def = const_value(); michael@0: if (def) { michael@0: return def; michael@0: } michael@0: else { michael@0: var str = consume(STR) || error("No value for default"); michael@0: str.value = str.value.replace(/^"/, "").replace(/"$/, ""); michael@0: return str; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: var const_ = function () { michael@0: all_ws(); michael@0: if (!consume(ID, "const")) return; michael@0: var ret = { type: "const", nullable: false }; michael@0: all_ws(); michael@0: var typ = primitive_type(); michael@0: if (!typ) { michael@0: typ = consume(ID) || error("No type for const"); michael@0: typ = typ.value; michael@0: } michael@0: ret.idlType = typ; michael@0: all_ws(); michael@0: if (consume(OTHER, "?")) { michael@0: ret.nullable = true; michael@0: all_ws(); michael@0: } michael@0: var name = consume(ID) || error("No name for const"); michael@0: ret.name = name.value; michael@0: all_ws(); michael@0: consume(OTHER, "=") || error("No value assignment for const"); michael@0: all_ws(); michael@0: var cnt = const_value(); michael@0: if (cnt) ret.value = cnt; michael@0: else error("No value for const"); michael@0: all_ws(); michael@0: consume(OTHER, ";") || error("Unterminated const"); michael@0: return ret; michael@0: }; michael@0: michael@0: var inheritance = function () { michael@0: all_ws(); michael@0: if (consume(OTHER, ":")) { michael@0: all_ws(); michael@0: var inh = consume(ID) || error ("No type in inheritance"); michael@0: return inh.value; michael@0: } michael@0: }; michael@0: michael@0: var operation_rest = function (ret) { michael@0: all_ws(); michael@0: if (!ret) ret = {}; michael@0: var name = consume(ID); michael@0: ret.name = name ? name.value : null; michael@0: all_ws(); michael@0: consume(OTHER, "(") || error("Invalid operation"); michael@0: ret["arguments"] = argument_list(); michael@0: all_ws(); michael@0: consume(OTHER, ")") || error("Unterminated operation"); michael@0: all_ws(); michael@0: consume(OTHER, ";") || error("Unterminated operation"); michael@0: return ret; michael@0: }; michael@0: michael@0: var callback = function () { michael@0: all_ws(); michael@0: var ret; michael@0: if (!consume(ID, "callback")) return; michael@0: all_ws(); michael@0: var tok = consume(ID, "interface"); michael@0: if (tok) { michael@0: tokens.unshift(tok); michael@0: ret = interface_(); michael@0: ret.type = "callback interface"; michael@0: return ret; michael@0: } michael@0: var name = consume(ID) || error("No name for callback"); michael@0: ret = { type: "callback", name: name.value }; michael@0: all_ws(); michael@0: consume(OTHER, "=") || error("No assignment in callback"); michael@0: all_ws(); michael@0: ret.idlType = return_type(); michael@0: all_ws(); michael@0: consume(OTHER, "(") || error("No arguments in callback"); michael@0: ret["arguments"] = argument_list(); michael@0: all_ws(); michael@0: consume(OTHER, ")") || error("Unterminated callback"); michael@0: all_ws(); michael@0: consume(OTHER, ";") || error("Unterminated callback"); michael@0: return ret; michael@0: }; michael@0: michael@0: var attribute = function () { michael@0: all_ws(); michael@0: var grabbed = [] michael@0: , ret = { michael@0: type: "attribute" michael@0: , "static": false michael@0: , stringifier: false michael@0: , inherit: false michael@0: , readonly: false michael@0: }; michael@0: if (consume(ID, "static")) { michael@0: ret["static"] = true; michael@0: grabbed.push(last_token); michael@0: } michael@0: else if (consume(ID, "stringifier")) { michael@0: ret.stringifier = true; michael@0: grabbed.push(last_token); michael@0: } michael@0: var w = all_ws(); michael@0: if (w) grabbed.push(w); michael@0: if (consume(ID, "inherit")) { michael@0: if (ret["static"] || ret.stringifier) error("Cannot have a static or stringifier inherit"); michael@0: ret.inherit = true; michael@0: grabbed.push(last_token); michael@0: var w = all_ws(); michael@0: if (w) grabbed.push(w); michael@0: } michael@0: if (consume(ID, "readonly")) { michael@0: ret.readonly = true; michael@0: grabbed.push(last_token); michael@0: var w = all_ws(); michael@0: if (w) grabbed.push(w); michael@0: } michael@0: if (!consume(ID, "attribute")) { michael@0: tokens = grabbed.concat(tokens); michael@0: return; michael@0: } michael@0: all_ws(); michael@0: ret.idlType = type() || error("No type in attribute"); michael@0: if (ret.idlType.sequence) error("Attributes cannot accept sequence types"); michael@0: all_ws(); michael@0: var name = consume(ID) || error("No name in attribute"); michael@0: ret.name = name.value; michael@0: all_ws(); michael@0: consume(OTHER, ";") || error("Unterminated attribute"); michael@0: return ret; michael@0: }; michael@0: michael@0: var return_type = function () { michael@0: var typ = type(); michael@0: if (!typ) { michael@0: if (consume(ID, "void")) { michael@0: return "void"; michael@0: } michael@0: else error("No return type"); michael@0: } michael@0: return typ; michael@0: }; michael@0: michael@0: var operation = function () { michael@0: all_ws(); michael@0: var ret = { michael@0: type: "operation" michael@0: , getter: false michael@0: , setter: false michael@0: , creator: false michael@0: , deleter: false michael@0: , legacycaller: false michael@0: , "static": false michael@0: , stringifier: false michael@0: }; michael@0: while (true) { michael@0: all_ws(); michael@0: if (consume(ID, "getter")) ret.getter = true; michael@0: else if (consume(ID, "setter")) ret.setter = true; michael@0: else if (consume(ID, "creator")) ret.creator = true; michael@0: else if (consume(ID, "deleter")) ret.deleter = true; michael@0: else if (consume(ID, "legacycaller")) ret.legacycaller = true; michael@0: else break; michael@0: } michael@0: if (ret.getter || ret.setter || ret.creator || ret.deleter || ret.legacycaller) { michael@0: all_ws(); michael@0: ret.idlType = return_type(); michael@0: operation_rest(ret); michael@0: return ret; michael@0: } michael@0: if (consume(ID, "static")) { michael@0: ret["static"] = true; michael@0: ret.idlType = return_type(); michael@0: operation_rest(ret); michael@0: return ret; michael@0: } michael@0: else if (consume(ID, "stringifier")) { michael@0: ret.stringifier = true; michael@0: all_ws(); michael@0: if (consume(OTHER, ";")) return ret; michael@0: ret.idlType = return_type(); michael@0: operation_rest(ret); michael@0: return ret; michael@0: } michael@0: ret.idlType = return_type(); michael@0: all_ws(); michael@0: if (consume(ID, "iterator")) { michael@0: all_ws(); michael@0: ret.type = "iterator"; michael@0: if (consume(ID, "object")) { michael@0: ret.iteratorObject = "object"; michael@0: } michael@0: else if (consume(OTHER, "=")) { michael@0: all_ws(); michael@0: var name = consume(ID) || error("No right hand side in iterator"); michael@0: ret.iteratorObject = name.value; michael@0: } michael@0: all_ws(); michael@0: consume(OTHER, ";") || error("Unterminated iterator"); michael@0: return ret; michael@0: } michael@0: else { michael@0: operation_rest(ret); michael@0: return ret; michael@0: } michael@0: }; michael@0: michael@0: var identifiers = function (arr) { michael@0: while (true) { michael@0: all_ws(); michael@0: if (consume(OTHER, ",")) { michael@0: all_ws(); michael@0: var name = consume(ID) || error("Trailing comma in identifiers list"); michael@0: arr.push(name.value); michael@0: } michael@0: else break; michael@0: } michael@0: }; michael@0: michael@0: var serialiser = function () { michael@0: all_ws(); michael@0: if (!consume(ID, "serializer")) return; michael@0: var ret = { type: "serializer" }; michael@0: all_ws(); michael@0: if (consume(OTHER, "=")) { michael@0: all_ws(); michael@0: if (consume(OTHER, "{")) { michael@0: ret.patternMap = true; michael@0: all_ws(); michael@0: var id = consume(ID); michael@0: if (id && id.value === "getter") { michael@0: ret.names = ["getter"]; michael@0: } michael@0: else if (id && id.value === "inherit") { michael@0: ret.names = ["inherit"]; michael@0: identifiers(ret.names); michael@0: } michael@0: else if (id) { michael@0: ret.names = [id.value]; michael@0: identifiers(ret.names); michael@0: } michael@0: else { michael@0: ret.names = []; michael@0: } michael@0: all_ws(); michael@0: consume(OTHER, "}") || error("Unterminated serializer pattern map"); michael@0: } michael@0: else if (consume(OTHER, "[")) { michael@0: ret.patternList = true; michael@0: all_ws(); michael@0: var id = consume(ID); michael@0: if (id && id.value === "getter") { michael@0: ret.names = ["getter"]; michael@0: } michael@0: else if (id) { michael@0: ret.names = [id.value]; michael@0: identifiers(ret.names); michael@0: } michael@0: else { michael@0: ret.names = []; michael@0: } michael@0: all_ws(); michael@0: consume(OTHER, "]") || error("Unterminated serializer pattern list"); michael@0: } michael@0: else { michael@0: var name = consume(ID) || error("Invalid serializer"); michael@0: ret.name = name.value; michael@0: } michael@0: all_ws(); michael@0: consume(OTHER, ";") || error("Unterminated serializer"); michael@0: return ret; michael@0: } michael@0: else if (consume(OTHER, ";")) { michael@0: // noop, just parsing michael@0: } michael@0: else { michael@0: ret.idlType = return_type(); michael@0: all_ws(); michael@0: ret.operation = operation_rest(); michael@0: } michael@0: return ret; michael@0: }; michael@0: michael@0: var interface_ = function (isPartial) { michael@0: all_ws(); michael@0: if (!consume(ID, "interface")) return; michael@0: all_ws(); michael@0: var name = consume(ID) || error("No name for interface"); michael@0: var ret = { michael@0: type: "interface" michael@0: , name: name.value michael@0: , partial: false michael@0: , members: [] michael@0: }; michael@0: if (!isPartial) ret.inheritance = inheritance() || null; michael@0: all_ws(); michael@0: consume(OTHER, "{") || error("Bodyless interface"); michael@0: while (true) { michael@0: all_ws(); michael@0: if (consume(OTHER, "}")) { michael@0: all_ws(); michael@0: consume(OTHER, ";") || error("Missing semicolon after interface"); michael@0: return ret; michael@0: } michael@0: var ea = extended_attrs(); michael@0: all_ws(); michael@0: var cnt = const_(); michael@0: if (cnt) { michael@0: cnt.extAttrs = ea; michael@0: ret.members.push(cnt); michael@0: continue; michael@0: } michael@0: var mem = serialiser() || attribute() || operation() || error("Unknown member"); michael@0: mem.extAttrs = ea; michael@0: ret.members.push(mem); michael@0: } michael@0: }; michael@0: michael@0: var partial = function () { michael@0: all_ws(); michael@0: if (!consume(ID, "partial")) return; michael@0: var thing = dictionary(true) || interface_(true) || error("Partial doesn't apply to anything"); michael@0: thing.partial = true; michael@0: return thing; michael@0: }; michael@0: michael@0: var dictionary = function (isPartial) { michael@0: all_ws(); michael@0: if (!consume(ID, "dictionary")) return; michael@0: all_ws(); michael@0: var name = consume(ID) || error("No name for dictionary"); michael@0: var ret = { michael@0: type: "dictionary" michael@0: , name: name.value michael@0: , partial: false michael@0: , members: [] michael@0: }; michael@0: if (!isPartial) ret.inheritance = inheritance() || null; michael@0: all_ws(); michael@0: consume(OTHER, "{") || error("Bodyless dictionary"); michael@0: while (true) { michael@0: all_ws(); michael@0: if (consume(OTHER, "}")) { michael@0: all_ws(); michael@0: consume(OTHER, ";") || error("Missing semicolon after dictionary"); michael@0: return ret; michael@0: } michael@0: var ea = extended_attrs(); michael@0: all_ws(); michael@0: var typ = type() || error("No type for dictionary member"); michael@0: all_ws(); michael@0: var name = consume(ID) || error("No name for dictionary member"); michael@0: ret.members.push({ michael@0: type: "field" michael@0: , name: name.value michael@0: , idlType: typ michael@0: , extAttrs: ea michael@0: , "default": default_() michael@0: }); michael@0: all_ws(); michael@0: consume(OTHER, ";") || error("Unterminated dictionary member"); michael@0: } michael@0: }; michael@0: michael@0: var exception = function () { michael@0: all_ws(); michael@0: if (!consume(ID, "exception")) return; michael@0: all_ws(); michael@0: var name = consume(ID) || error("No name for exception"); michael@0: var ret = { michael@0: type: "exception" michael@0: , name: name.value michael@0: , members: [] michael@0: }; michael@0: ret.inheritance = inheritance() || null; michael@0: all_ws(); michael@0: consume(OTHER, "{") || error("Bodyless exception"); michael@0: while (true) { michael@0: all_ws(); michael@0: if (consume(OTHER, "}")) { michael@0: all_ws(); michael@0: consume(OTHER, ";") || error("Missing semicolon after exception"); michael@0: return ret; michael@0: } michael@0: var ea = extended_attrs(); michael@0: all_ws(); michael@0: var cnt = const_(); michael@0: if (cnt) { michael@0: cnt.extAttrs = ea; michael@0: ret.members.push(cnt); michael@0: } michael@0: else { michael@0: var typ = type(); michael@0: all_ws(); michael@0: var name = consume(ID); michael@0: all_ws(); michael@0: if (!typ || !name || !consume(OTHER, ";")) error("Unknown member in exception body"); michael@0: ret.members.push({ michael@0: type: "field" michael@0: , name: name.value michael@0: , idlType: typ michael@0: , extAttrs: ea michael@0: }); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: var enum_ = function () { michael@0: all_ws(); michael@0: if (!consume(ID, "enum")) return; michael@0: all_ws(); michael@0: var name = consume(ID) || error("No name for enum"); michael@0: var ret = { michael@0: type: "enum" michael@0: , name: name.value michael@0: , values: [] michael@0: }; michael@0: all_ws(); michael@0: consume(OTHER, "{") || error("No curly for enum"); michael@0: var saw_comma = false; michael@0: while (true) { michael@0: all_ws(); michael@0: if (consume(OTHER, "}")) { michael@0: all_ws(); michael@0: if (saw_comma) error("Trailing comma in enum"); michael@0: consume(OTHER, ";") || error("No semicolon after enum"); michael@0: return ret; michael@0: } michael@0: var val = consume(STR) || error("Unexpected value in enum"); michael@0: ret.values.push(val.value.replace(/"/g, "")); michael@0: all_ws(); michael@0: if (consume(OTHER, ",")) { michael@0: all_ws(); michael@0: saw_comma = true; michael@0: } michael@0: else { michael@0: saw_comma = false; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: var typedef = function () { michael@0: all_ws(); michael@0: if (!consume(ID, "typedef")) return; michael@0: var ret = { michael@0: type: "typedef" michael@0: }; michael@0: all_ws(); michael@0: ret.typeExtAttrs = extended_attrs(); michael@0: all_ws(); michael@0: ret.idlType = type() || error("No type in typedef"); michael@0: all_ws(); michael@0: var name = consume(ID) || error("No name in typedef"); michael@0: ret.name = name.value; michael@0: all_ws(); michael@0: consume(OTHER, ";") || error("Unterminated typedef"); michael@0: return ret; michael@0: }; michael@0: michael@0: var implements_ = function () { michael@0: all_ws(); michael@0: var target = consume(ID); michael@0: if (!target) return; michael@0: var w = all_ws(); michael@0: if (consume(ID, "implements")) { michael@0: var ret = { michael@0: type: "implements" michael@0: , target: target.value michael@0: }; michael@0: all_ws(); michael@0: var imp = consume(ID) || error("Incomplete implements statement"); michael@0: ret["implements"] = imp.value; michael@0: all_ws(); michael@0: consume(OTHER, ";") || error("No terminating ; for implements statement"); michael@0: return ret; michael@0: } michael@0: else { michael@0: // rollback michael@0: tokens.unshift(w); michael@0: tokens.unshift(target); michael@0: } michael@0: }; michael@0: michael@0: var definition = function () { michael@0: return callback() || michael@0: interface_() || michael@0: partial() || michael@0: dictionary() || michael@0: exception() || michael@0: enum_() || michael@0: typedef() || michael@0: implements_() michael@0: ; michael@0: }; michael@0: michael@0: var definitions = function () { michael@0: if (!tokens.length) return []; michael@0: var defs = []; michael@0: while (true) { michael@0: var ea = extended_attrs() michael@0: , def = definition(); michael@0: if (!def) { michael@0: if (ea.length) error("Stray extended attributes"); michael@0: break; michael@0: } michael@0: def.extAttrs = ea; michael@0: defs.push(def); michael@0: } michael@0: return defs; michael@0: }; michael@0: var res = definitions(); michael@0: if (tokens.length) error("Unrecognised tokens"); michael@0: return res; michael@0: }; michael@0: michael@0: var obj = { michael@0: parse: function (str) { michael@0: var tokens = tokenise(str); michael@0: return parse(tokens); michael@0: } michael@0: }; michael@0: if (typeof module !== "undefined" && module.exports) { michael@0: module.exports = obj; michael@0: } michael@0: else { michael@0: window.WebIDL2 = obj; michael@0: } michael@0: }());