mirror of
https://github.com/servo/servo.git
synced 2025-07-01 20:43:39 +01:00
1104 lines
34 KiB
JavaScript
1104 lines
34 KiB
JavaScript
"use strict";
|
|
|
|
(() => {
|
|
// These regular expressions use the sticky flag so they will only match at
|
|
// the current location (ie. the offset of lastIndex).
|
|
const tokenRe = {
|
|
// This expression uses a lookahead assertion to catch false matches
|
|
// against integers early.
|
|
"float": /-?(?=[0-9]*\.|[0-9]+[eE])(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/y,
|
|
"integer": /-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/y,
|
|
"identifier": /[A-Z_a-z][0-9A-Z_a-z-]*/y,
|
|
"string": /"[^"]*"/y,
|
|
"whitespace": /[\t\n\r ]+/y,
|
|
"comment": /((\/(\/.*|\*([^*]|\*[^\/])*\*\/)[\t\n\r ]*)+)/y,
|
|
"other": /[^\t\n\r 0-9A-Z_a-z]/y
|
|
};
|
|
|
|
function attemptTokenMatch(str, type, re, lastIndex, tokens) {
|
|
re.lastIndex = lastIndex;
|
|
const result = re.exec(str);
|
|
if (result) {
|
|
tokens.push({ type, value: result[0] });
|
|
return re.lastIndex;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function tokenise(str) {
|
|
const tokens = [];
|
|
let lastIndex = 0;
|
|
while (lastIndex < str.length) {
|
|
const nextChar = str.charAt(lastIndex);
|
|
let result = -1;
|
|
if (/[-0-9.]/.test(nextChar)) {
|
|
result = attemptTokenMatch(str, "float", tokenRe.float, lastIndex,
|
|
tokens);
|
|
if (result === -1) {
|
|
result = attemptTokenMatch(str, "integer", tokenRe.integer, lastIndex,
|
|
tokens);
|
|
}
|
|
if (result === -1) {
|
|
// '-' and '.' can also match "other".
|
|
result = attemptTokenMatch(str, "other", tokenRe.other,
|
|
lastIndex, tokens);
|
|
}
|
|
} else if (/[A-Z_a-z]/.test(nextChar)) {
|
|
result = attemptTokenMatch(str, "identifier", tokenRe.identifier,
|
|
lastIndex, tokens);
|
|
} else if (nextChar === '"') {
|
|
result = attemptTokenMatch(str, "string", tokenRe.string,
|
|
lastIndex, tokens);
|
|
if (result === -1) {
|
|
// '"' can also match "other".
|
|
result = attemptTokenMatch(str, "other", tokenRe.other,
|
|
lastIndex, tokens);
|
|
}
|
|
} else if (/[\t\n\r ]/.test(nextChar)) {
|
|
result = attemptTokenMatch(str, "whitespace", tokenRe.whitespace,
|
|
lastIndex, tokens);
|
|
} else if (nextChar === '/') {
|
|
// The parser expects comments to be labelled as "whitespace".
|
|
result = attemptTokenMatch(str, "whitespace", tokenRe.comment,
|
|
lastIndex, tokens);
|
|
if (result === -1) {
|
|
// '/' can also match "other".
|
|
result = attemptTokenMatch(str, "other", tokenRe.other,
|
|
lastIndex, tokens);
|
|
}
|
|
} else {
|
|
result = attemptTokenMatch(str, "other", tokenRe.other,
|
|
lastIndex, tokens);
|
|
}
|
|
if (result === -1) {
|
|
throw new Error("Token stream not progressing");
|
|
}
|
|
lastIndex = result;
|
|
}
|
|
return tokens;
|
|
}
|
|
|
|
class WebIDLParseError {
|
|
constructor(str, line, input, tokens) {
|
|
this.message = str;
|
|
this.line = line;
|
|
this.input = input;
|
|
this.tokens = tokens;
|
|
}
|
|
|
|
toString() {
|
|
return `${this.message}, line ${this.line} (tokens: '${this.input}')\n${JSON.stringify(this.tokens, null, 4)}`;
|
|
}
|
|
}
|
|
|
|
function parse(tokens, opt) {
|
|
let line = 1;
|
|
tokens = tokens.slice();
|
|
const names = new Map();
|
|
let current = null;
|
|
|
|
const FLOAT = "float";
|
|
const INT = "integer";
|
|
const ID = "identifier";
|
|
const STR = "string";
|
|
const OTHER = "other";
|
|
|
|
const EMPTY_OPERATION = Object.freeze({
|
|
type: "operation",
|
|
getter: false,
|
|
setter: false,
|
|
deleter: false,
|
|
static: false,
|
|
stringifier: false
|
|
});
|
|
|
|
function error(str) {
|
|
let tok = "";
|
|
let numTokens = 0;
|
|
const maxTokens = 5;
|
|
while (numTokens < maxTokens && tokens.length > numTokens) {
|
|
tok += tokens[numTokens].value;
|
|
numTokens++;
|
|
}
|
|
|
|
let message;
|
|
if (current) {
|
|
message = `Got an error during or right after parsing \`${current.partial ? "partial " : ""}${current.type} ${current.name}\`: ${str}`
|
|
}
|
|
else {
|
|
// throwing before any valid definition
|
|
message = `Got an error before parsing any named definition: ${str}`;
|
|
}
|
|
|
|
throw new WebIDLParseError(message, line, tok, tokens.slice(0, maxTokens));
|
|
}
|
|
|
|
function sanitize_name(name, type) {
|
|
if (names.has(name)) {
|
|
error(`The name "${name}" of type "${names.get(name)}" is already seen`);
|
|
}
|
|
names.set(name, type);
|
|
return name;
|
|
}
|
|
|
|
let last_token = null;
|
|
|
|
function consume(type, value) {
|
|
if (!tokens.length || tokens[0].type !== type) return;
|
|
if (typeof value === "undefined" || tokens[0].value === value) {
|
|
last_token = tokens.shift();
|
|
if (type === ID && last_token.value.startsWith('_'))
|
|
last_token.value = last_token.value.substring(1);
|
|
return last_token;
|
|
}
|
|
}
|
|
|
|
function count(str, char) {
|
|
let total = 0;
|
|
for (let i = str.indexOf(char); i !== -1; i = str.indexOf(char, i + 1)) {
|
|
++total;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
function ws() {
|
|
if (!tokens.length) return;
|
|
if (tokens[0].type === "whitespace") {
|
|
const t = tokens.shift();
|
|
line += count(t.value, '\n');
|
|
return t;
|
|
}
|
|
}
|
|
|
|
const all_ws_re = {
|
|
"ws": /([\t\n\r ]+)/y,
|
|
"line-comment": /\/\/(.*)\r?\n?/y,
|
|
"multiline-comment": /\/\*((?:[^*]|\*[^/])*)\*\//y
|
|
};
|
|
function all_ws(store, pea) { // pea == post extended attribute, tpea = same for types
|
|
const t = { type: "whitespace", value: "" };
|
|
while (true) {
|
|
const w = ws();
|
|
if (!w) break;
|
|
t.value += w.value;
|
|
}
|
|
if (t.value.length > 0) {
|
|
if (store) {
|
|
let w = t.value;
|
|
let lastIndex = 0;
|
|
while (lastIndex < w.length) {
|
|
let matched = false;
|
|
// Servo doesn't support using "const" in this construction yet.
|
|
// See https://github.com/servo/servo/issues/20231.
|
|
// |type| can be made const once Servo supports it.
|
|
for (let type in all_ws_re) {
|
|
const re = all_ws_re[type];
|
|
re.lastIndex = lastIndex;
|
|
const result = re.exec(w);
|
|
if (result) {
|
|
store.push({ type: type + (pea ? ("-" + pea) : ""), value: result[1] });
|
|
matched = true;
|
|
lastIndex = re.lastIndex;
|
|
break;
|
|
}
|
|
}
|
|
if (!matched)
|
|
throw new Error("Surprising white space construct."); // this shouldn't happen
|
|
}
|
|
}
|
|
return t;
|
|
}
|
|
}
|
|
|
|
function integer_type() {
|
|
let ret = "";
|
|
all_ws();
|
|
if (consume(ID, "unsigned")) ret = "unsigned ";
|
|
all_ws();
|
|
if (consume(ID, "short")) return ret + "short";
|
|
if (consume(ID, "long")) {
|
|
ret += "long";
|
|
all_ws();
|
|
if (consume(ID, "long")) return ret + " long";
|
|
return ret;
|
|
}
|
|
if (ret) error("Failed to parse integer type");
|
|
}
|
|
|
|
function float_type() {
|
|
let ret = "";
|
|
all_ws();
|
|
if (consume(ID, "unrestricted")) ret = "unrestricted ";
|
|
all_ws();
|
|
if (consume(ID, "float")) return ret + "float";
|
|
if (consume(ID, "double")) return ret + "double";
|
|
if (ret) error("Failed to parse float type");
|
|
}
|
|
|
|
function primitive_type() {
|
|
const num_type = integer_type() || float_type();
|
|
if (num_type) return num_type;
|
|
all_ws();
|
|
if (consume(ID, "boolean")) return "boolean";
|
|
if (consume(ID, "byte")) return "byte";
|
|
if (consume(ID, "octet")) return "octet";
|
|
}
|
|
|
|
function const_value() {
|
|
if (consume(ID, "true")) return { type: "boolean", value: true };
|
|
if (consume(ID, "false")) return { type: "boolean", value: false };
|
|
if (consume(ID, "null")) return { type: "null" };
|
|
if (consume(ID, "Infinity")) return { type: "Infinity", negative: false };
|
|
if (consume(ID, "NaN")) return { type: "NaN" };
|
|
const ret = consume(FLOAT) || consume(INT);
|
|
if (ret) return { type: "number", value: ret.value };
|
|
const tok = consume(OTHER, "-");
|
|
if (tok) {
|
|
if (consume(ID, "Infinity")) return { type: "Infinity", negative: true };
|
|
else tokens.unshift(tok);
|
|
}
|
|
}
|
|
|
|
function type_suffix(obj) {
|
|
while (true) {
|
|
all_ws();
|
|
if (consume(OTHER, "?")) {
|
|
if (obj.nullable) error("Can't nullable more than once");
|
|
obj.nullable = true;
|
|
} else return;
|
|
}
|
|
}
|
|
|
|
function single_type(typeName) {
|
|
const prim = primitive_type();
|
|
const ret = { type: typeName || null, sequence: false, generic: null, nullable: false, union: false };
|
|
let name;
|
|
let value;
|
|
if (prim) {
|
|
ret.idlType = prim;
|
|
} else if (name = consume(ID)) {
|
|
value = name.value;
|
|
all_ws();
|
|
// Generic types
|
|
if (consume(OTHER, "<")) {
|
|
// backwards compat
|
|
if (value === "sequence") {
|
|
ret.sequence = true;
|
|
}
|
|
ret.generic = value;
|
|
const types = [];
|
|
do {
|
|
all_ws();
|
|
types.push(type_with_extended_attributes(typeName) || error("Error parsing generic type " + value));
|
|
all_ws();
|
|
}
|
|
while (consume(OTHER, ","));
|
|
if (value === "sequence") {
|
|
if (types.length !== 1) error("A sequence must have exactly one subtype");
|
|
} else if (value === "record") {
|
|
if (types.length !== 2) error("A record must have exactly two subtypes");
|
|
if (!/^(DOMString|USVString|ByteString)$/.test(types[0].idlType)) {
|
|
error("Record key must be DOMString, USVString, or ByteString");
|
|
}
|
|
if (types[0].extAttrs) error("Record key cannot have extended attribute");
|
|
} else if (value === "Promise") {
|
|
if (types[0].extAttrs) error("Promise type cannot have extended attribute");
|
|
}
|
|
ret.idlType = types.length === 1 ? types[0] : types;
|
|
all_ws();
|
|
if (!consume(OTHER, ">")) error("Unterminated generic type " + value);
|
|
type_suffix(ret);
|
|
return ret;
|
|
} else {
|
|
ret.idlType = value;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
type_suffix(ret);
|
|
if (ret.nullable && ret.idlType === "any") error("Type any cannot be made nullable");
|
|
return ret;
|
|
}
|
|
|
|
function union_type(typeName) {
|
|
all_ws();
|
|
if (!consume(OTHER, "(")) return;
|
|
const ret = { type: typeName || null, sequence: false, generic: null, nullable: false, union: true, idlType: [] };
|
|
const fst = type_with_extended_attributes() || error("Union type with no content");
|
|
ret.idlType.push(fst);
|
|
while (true) {
|
|
all_ws();
|
|
if (!consume(ID, "or")) break;
|
|
const typ = type_with_extended_attributes() || error("No type after 'or' in union type");
|
|
ret.idlType.push(typ);
|
|
}
|
|
if (!consume(OTHER, ")")) error("Unterminated union type");
|
|
type_suffix(ret);
|
|
return ret;
|
|
}
|
|
|
|
function type(typeName) {
|
|
return single_type(typeName) || union_type(typeName);
|
|
}
|
|
|
|
function type_with_extended_attributes(typeName) {
|
|
const extAttrs = extended_attrs();
|
|
const ret = single_type(typeName) || union_type(typeName);
|
|
if (extAttrs.length && ret) ret.extAttrs = extAttrs;
|
|
return ret;
|
|
}
|
|
|
|
function argument(store) {
|
|
const ret = { optional: false, variadic: false };
|
|
ret.extAttrs = extended_attrs(store);
|
|
all_ws(store, "pea");
|
|
const opt_token = consume(ID, "optional");
|
|
if (opt_token) {
|
|
ret.optional = true;
|
|
all_ws();
|
|
}
|
|
ret.idlType = type_with_extended_attributes("argument-type");
|
|
if (!ret.idlType) {
|
|
if (opt_token) tokens.unshift(opt_token);
|
|
return;
|
|
}
|
|
const type_token = last_token;
|
|
if (!ret.optional) {
|
|
all_ws();
|
|
if (tokens.length >= 3 &&
|
|
tokens[0].type === "other" && tokens[0].value === "." &&
|
|
tokens[1].type === "other" && tokens[1].value === "." &&
|
|
tokens[2].type === "other" && tokens[2].value === "."
|
|
) {
|
|
tokens.shift();
|
|
tokens.shift();
|
|
tokens.shift();
|
|
ret.variadic = true;
|
|
}
|
|
}
|
|
all_ws();
|
|
const name = consume(ID);
|
|
if (!name) {
|
|
if (opt_token) tokens.unshift(opt_token);
|
|
tokens.unshift(type_token);
|
|
return;
|
|
}
|
|
ret.name = name.value;
|
|
if (ret.optional) {
|
|
all_ws();
|
|
const dflt = default_();
|
|
if (typeof dflt !== "undefined") {
|
|
ret["default"] = dflt;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function argument_list(store) {
|
|
const ret = [];
|
|
const arg = argument(store ? ret : null);
|
|
if (!arg) return;
|
|
ret.push(arg);
|
|
while (true) {
|
|
all_ws(store ? ret : null);
|
|
if (!consume(OTHER, ",")) return ret;
|
|
const nxt = argument(store ? ret : null) || error("Trailing comma in arguments list");
|
|
ret.push(nxt);
|
|
}
|
|
}
|
|
|
|
function simple_extended_attr(store) {
|
|
all_ws();
|
|
const name = consume(ID);
|
|
if (!name) return;
|
|
const ret = {
|
|
name: name.value,
|
|
"arguments": null,
|
|
type: "extended-attribute",
|
|
rhs: null
|
|
};
|
|
all_ws();
|
|
const eq = consume(OTHER, "=");
|
|
if (eq) {
|
|
all_ws();
|
|
ret.rhs = consume(ID) ||
|
|
consume(FLOAT) ||
|
|
consume(INT) ||
|
|
consume(STR);
|
|
if (!ret.rhs && consume(OTHER, "(")) {
|
|
// [Exposed=(Window,Worker)]
|
|
const rhs_list = [];
|
|
const id = consume(ID);
|
|
if (id) {
|
|
rhs_list.push(id.value);
|
|
}
|
|
identifiers(rhs_list);
|
|
consume(OTHER, ")") || error("Unexpected token in extended attribute argument list or type pair");
|
|
ret.rhs = {
|
|
type: "identifier-list",
|
|
value: rhs_list
|
|
};
|
|
}
|
|
if (!ret.rhs) return error("No right hand side to extended attribute assignment");
|
|
}
|
|
all_ws();
|
|
if (consume(OTHER, "(")) {
|
|
let args, pair;
|
|
// [Constructor(DOMString str)]
|
|
if (args = argument_list(store)) {
|
|
ret["arguments"] = args;
|
|
}
|
|
// [Constructor()]
|
|
else {
|
|
ret["arguments"] = [];
|
|
}
|
|
all_ws();
|
|
consume(OTHER, ")") || error("Unexpected token in extended attribute argument list");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Note: we parse something simpler than the official syntax. It's all that ever
|
|
// seems to be used
|
|
function extended_attrs(store) {
|
|
const eas = [];
|
|
all_ws(store);
|
|
if (!consume(OTHER, "[")) return eas;
|
|
eas[0] = simple_extended_attr(store) || error("Extended attribute with not content");
|
|
all_ws();
|
|
while (consume(OTHER, ",")) {
|
|
if (eas.length) {
|
|
eas.push(simple_extended_attr(store));
|
|
} else {
|
|
eas.push(simple_extended_attr(store) || error("Trailing comma in extended attribute"));
|
|
}
|
|
}
|
|
all_ws();
|
|
consume(OTHER, "]") || error("No end of extended attribute");
|
|
return eas;
|
|
}
|
|
|
|
function default_() {
|
|
all_ws();
|
|
if (consume(OTHER, "=")) {
|
|
all_ws();
|
|
const def = const_value();
|
|
if (def) {
|
|
return def;
|
|
} else if (consume(OTHER, "[")) {
|
|
if (!consume(OTHER, "]")) error("Default sequence value must be empty");
|
|
return { type: "sequence", value: [] };
|
|
} else {
|
|
const str = consume(STR) || error("No value for default");
|
|
str.value = str.value.slice(1, -1);
|
|
return str;
|
|
}
|
|
}
|
|
}
|
|
|
|
function const_(store) {
|
|
all_ws(store, "pea");
|
|
if (!consume(ID, "const")) return;
|
|
const ret = { type: "const", nullable: false };
|
|
all_ws();
|
|
let typ = primitive_type();
|
|
if (!typ) {
|
|
typ = consume(ID) || error("No type for const");
|
|
typ = typ.value;
|
|
}
|
|
ret.idlType = { type: "const-type", idlType: typ };
|
|
all_ws();
|
|
if (consume(OTHER, "?")) {
|
|
ret.nullable = true;
|
|
all_ws();
|
|
}
|
|
const name = consume(ID) || error("No name for const");
|
|
ret.name = name.value;
|
|
all_ws();
|
|
consume(OTHER, "=") || error("No value assignment for const");
|
|
all_ws();
|
|
const cnt = const_value();
|
|
if (cnt) ret.value = cnt;
|
|
else error("No value for const");
|
|
all_ws();
|
|
consume(OTHER, ";") || error("Unterminated const");
|
|
return ret;
|
|
}
|
|
|
|
function inheritance() {
|
|
all_ws();
|
|
if (consume(OTHER, ":")) {
|
|
all_ws();
|
|
const inh = consume(ID) || error("No type in inheritance");
|
|
return inh.value;
|
|
}
|
|
}
|
|
|
|
function operation_rest(ret, store) {
|
|
all_ws();
|
|
if (!ret) ret = {};
|
|
const name = consume(ID);
|
|
ret.name = name ? name.value : null;
|
|
all_ws();
|
|
consume(OTHER, "(") || error("Invalid operation");
|
|
ret["arguments"] = argument_list(store) || [];
|
|
all_ws();
|
|
consume(OTHER, ")") || error("Unterminated operation");
|
|
all_ws();
|
|
consume(OTHER, ";") || error("Unterminated operation");
|
|
return ret;
|
|
}
|
|
|
|
function callback(store) {
|
|
all_ws(store, "pea");
|
|
let ret;
|
|
if (!consume(ID, "callback")) return;
|
|
all_ws();
|
|
const tok = consume(ID, "interface");
|
|
if (tok) {
|
|
ret = interface_rest(false, store, "callback interface");
|
|
return ret;
|
|
}
|
|
const name = consume(ID) || error("No name for callback");
|
|
ret = current = { type: "callback", name: sanitize_name(name.value, "callback") };
|
|
all_ws();
|
|
consume(OTHER, "=") || error("No assignment in callback");
|
|
all_ws();
|
|
ret.idlType = return_type();
|
|
all_ws();
|
|
consume(OTHER, "(") || error("No arguments in callback");
|
|
ret["arguments"] = argument_list(store) || [];
|
|
all_ws();
|
|
consume(OTHER, ")") || error("Unterminated callback");
|
|
all_ws();
|
|
consume(OTHER, ";") || error("Unterminated callback");
|
|
return ret;
|
|
}
|
|
|
|
function attribute(store) {
|
|
all_ws(store, "pea");
|
|
const grabbed = [];
|
|
const ret = {
|
|
type: "attribute",
|
|
static: false,
|
|
stringifier: false,
|
|
inherit: false,
|
|
readonly: false
|
|
};
|
|
const w = all_ws();
|
|
if (w) grabbed.push(w);
|
|
if (consume(ID, "inherit")) {
|
|
if (ret.static || ret.stringifier) error("Cannot have a static or stringifier inherit");
|
|
ret.inherit = true;
|
|
grabbed.push(last_token);
|
|
const w = all_ws();
|
|
if (w) grabbed.push(w);
|
|
}
|
|
if (consume(ID, "readonly")) {
|
|
ret.readonly = true;
|
|
grabbed.push(last_token);
|
|
const w = all_ws();
|
|
if (w) grabbed.push(w);
|
|
}
|
|
const rest = attribute_rest(ret);
|
|
if (!rest) {
|
|
tokens = grabbed.concat(tokens);
|
|
}
|
|
return rest;
|
|
}
|
|
|
|
function attribute_rest(ret) {
|
|
if (!consume(ID, "attribute")) {
|
|
return;
|
|
}
|
|
all_ws();
|
|
ret.idlType = type_with_extended_attributes("attribute-type") || error("No type in attribute");
|
|
if (ret.idlType.sequence) error("Attributes cannot accept sequence types");
|
|
if (ret.idlType.generic === "record") error("Attributes cannot accept record types");
|
|
all_ws();
|
|
const name = consume(ID) || error("No name in attribute");
|
|
ret.name = name.value;
|
|
all_ws();
|
|
consume(OTHER, ";") || error("Unterminated attribute");
|
|
return ret;
|
|
}
|
|
|
|
function return_type() {
|
|
const typ = type("return-type");
|
|
if (!typ) {
|
|
if (consume(ID, "void")) {
|
|
return "void";
|
|
} else error("No return type");
|
|
}
|
|
return typ;
|
|
}
|
|
|
|
function operation(store) {
|
|
all_ws(store, "pea");
|
|
const ret = Object.assign({}, EMPTY_OPERATION);
|
|
while (true) {
|
|
all_ws();
|
|
if (consume(ID, "getter")) ret.getter = true;
|
|
else if (consume(ID, "setter")) ret.setter = true;
|
|
else if (consume(ID, "deleter")) ret.deleter = true;
|
|
else break;
|
|
}
|
|
if (ret.getter || ret.setter || ret.deleter) {
|
|
all_ws();
|
|
ret.idlType = return_type();
|
|
operation_rest(ret, store);
|
|
return ret;
|
|
}
|
|
ret.idlType = return_type();
|
|
all_ws();
|
|
operation_rest(ret, store);
|
|
return ret;
|
|
}
|
|
|
|
function static_member(store) {
|
|
all_ws(store, "pea");
|
|
if (!consume(ID, "static")) return;
|
|
all_ws();
|
|
return noninherited_attribute(store, "static") ||
|
|
regular_operation(store, "static") ||
|
|
error("No body in static member");
|
|
}
|
|
|
|
function stringifier(store) {
|
|
all_ws(store, "pea");
|
|
if (!consume(ID, "stringifier")) return;
|
|
all_ws();
|
|
if (consume(OTHER, ";")) {
|
|
return Object.assign({}, EMPTY_OPERATION, { stringifier: true });
|
|
}
|
|
return noninherited_attribute(store, "stringifier") ||
|
|
regular_operation(store, "stringifier") ||
|
|
error("Unterminated stringifier");
|
|
}
|
|
|
|
function identifiers(arr) {
|
|
while (true) {
|
|
all_ws();
|
|
if (consume(OTHER, ",")) {
|
|
all_ws();
|
|
const name = consume(ID) || error("Trailing comma in identifiers list");
|
|
arr.push(name.value);
|
|
} else break;
|
|
}
|
|
}
|
|
|
|
function iterable_type() {
|
|
if (consume(ID, "iterable")) return "iterable";
|
|
else if (consume(ID, "legacyiterable")) return "legacyiterable";
|
|
else if (consume(ID, "maplike")) return "maplike";
|
|
else if (consume(ID, "setlike")) return "setlike";
|
|
else return;
|
|
}
|
|
|
|
function readonly_iterable_type() {
|
|
if (consume(ID, "maplike")) return "maplike";
|
|
else if (consume(ID, "setlike")) return "setlike";
|
|
else return;
|
|
}
|
|
|
|
function iterable(store) {
|
|
all_ws(store, "pea");
|
|
const grabbed = [];
|
|
const ret = { type: null, idlType: null, readonly: false };
|
|
if (consume(ID, "readonly")) {
|
|
ret.readonly = true;
|
|
grabbed.push(last_token);
|
|
var w = all_ws();
|
|
if (w) grabbed.push(w);
|
|
}
|
|
const consumeItType = ret.readonly ? readonly_iterable_type : iterable_type;
|
|
|
|
const ittype = consumeItType();
|
|
if (!ittype) {
|
|
tokens = grabbed.concat(tokens);
|
|
return;
|
|
}
|
|
|
|
const secondTypeRequired = ittype === "maplike";
|
|
const secondTypeAllowed = secondTypeRequired || ittype === "iterable";
|
|
ret.type = ittype;
|
|
if (ret.type !== 'maplike' && ret.type !== 'setlike')
|
|
delete ret.readonly;
|
|
all_ws();
|
|
if (consume(OTHER, "<")) {
|
|
ret.idlType = [type_with_extended_attributes()] || error(`Error parsing ${ittype} declaration`);
|
|
all_ws();
|
|
if (secondTypeAllowed) {
|
|
if (consume(OTHER, ",")) {
|
|
all_ws();
|
|
ret.idlType.push(type_with_extended_attributes());
|
|
all_ws();
|
|
}
|
|
else if (secondTypeRequired)
|
|
error(`Missing second type argument in ${ittype} declaration`);
|
|
}
|
|
if (!consume(OTHER, ">")) error(`Unterminated ${ittype} declaration`);
|
|
all_ws();
|
|
if (!consume(OTHER, ";")) error(`Missing semicolon after ${ittype} declaration`);
|
|
} else
|
|
error(`Error parsing ${ittype} declaration`);
|
|
|
|
return ret;
|
|
}
|
|
|
|
function interface_rest(isPartial, store, typeName = "interface") {
|
|
all_ws();
|
|
const name = consume(ID) || error("No name for interface");
|
|
const mems = [];
|
|
const ret = current = {
|
|
type: typeName,
|
|
name: isPartial ? name.value : sanitize_name(name.value, "interface"),
|
|
partial: isPartial,
|
|
members: mems
|
|
};
|
|
if (!isPartial) ret.inheritance = inheritance() || null;
|
|
all_ws();
|
|
consume(OTHER, "{") || error("Bodyless interface");
|
|
while (true) {
|
|
all_ws(store ? mems : null);
|
|
if (consume(OTHER, "}")) {
|
|
all_ws();
|
|
consume(OTHER, ";") || error("Missing semicolon after interface");
|
|
return ret;
|
|
}
|
|
const ea = extended_attrs(store ? mems : null);
|
|
all_ws();
|
|
const cnt = const_(store ? mems : null);
|
|
if (cnt) {
|
|
cnt.extAttrs = ea;
|
|
ret.members.push(cnt);
|
|
continue;
|
|
}
|
|
const mem = (opt.allowNestedTypedefs && typedef(store ? mems : null)) ||
|
|
static_member(store ? mems : null) ||
|
|
stringifier(store ? mems : null) ||
|
|
iterable(store ? mems : null) ||
|
|
attribute(store ? mems : null) ||
|
|
operation(store ? mems : null) ||
|
|
error("Unknown member");
|
|
mem.extAttrs = ea;
|
|
ret.members.push(mem);
|
|
}
|
|
}
|
|
|
|
function mixin_rest(isPartial, store) {
|
|
all_ws();
|
|
if (!consume(ID, "mixin")) return;
|
|
all_ws();
|
|
const name = consume(ID) || error("No name for interface mixin");
|
|
const mems = [];
|
|
const ret = current = {
|
|
type: "interface mixin",
|
|
name: isPartial ? name.value : sanitize_name(name.value, "interface mixin"),
|
|
partial: isPartial,
|
|
members: mems
|
|
};
|
|
all_ws();
|
|
consume(OTHER, "{") || error("Bodyless interface mixin");
|
|
while (true) {
|
|
all_ws(store ? mems : null);
|
|
if (consume(OTHER, "}")) {
|
|
all_ws();
|
|
consume(OTHER, ";") || error("Missing semicolon after interface mixin");
|
|
return ret;
|
|
}
|
|
const ea = extended_attrs(store ? mems : null);
|
|
all_ws();
|
|
const cnt = const_(store ? mems : null);
|
|
if (cnt) {
|
|
cnt.extAttrs = ea;
|
|
ret.members.push(cnt);
|
|
continue;
|
|
}
|
|
const mem = stringifier(store ? mems : null) ||
|
|
noninherited_attribute(store ? mems : null) ||
|
|
regular_operation(store ? mems : null) ||
|
|
error("Unknown member");
|
|
mem.extAttrs = ea;
|
|
ret.members.push(mem);
|
|
}
|
|
}
|
|
|
|
function interface_(isPartial, store) {
|
|
all_ws(isPartial ? null : store, "pea");
|
|
if (!consume(ID, "interface")) return;
|
|
return mixin_rest(isPartial, store) ||
|
|
interface_rest(isPartial, store) ||
|
|
error("Interface has no proper body");
|
|
}
|
|
|
|
function namespace(isPartial, store) {
|
|
all_ws(isPartial ? null : store, "pea");
|
|
if (!consume(ID, "namespace")) return;
|
|
all_ws();
|
|
const name = consume(ID) || error("No name for namespace");
|
|
const mems = [];
|
|
const ret = current = {
|
|
type: "namespace",
|
|
name: isPartial ? name.value : sanitize_name(name.value, "namespace"),
|
|
partial: isPartial,
|
|
members: mems
|
|
};
|
|
all_ws();
|
|
consume(OTHER, "{") || error("Bodyless namespace");
|
|
while (true) {
|
|
all_ws(store ? mems : null);
|
|
if (consume(OTHER, "}")) {
|
|
all_ws();
|
|
consume(OTHER, ";") || error("Missing semicolon after namespace");
|
|
return ret;
|
|
}
|
|
const ea = extended_attrs(store ? mems : null);
|
|
all_ws();
|
|
const mem = noninherited_attribute(store ? mems : null) ||
|
|
regular_operation(store ? mems : null) ||
|
|
error("Unknown member");
|
|
mem.extAttrs = ea;
|
|
ret.members.push(mem);
|
|
}
|
|
}
|
|
|
|
function noninherited_attribute(store, prefix) {
|
|
const w = all_ws(store, "pea");
|
|
const grabbed = [];
|
|
const ret = {
|
|
type: "attribute",
|
|
static: false,
|
|
stringifier: false,
|
|
inherit: false,
|
|
readonly: false
|
|
};
|
|
if (prefix) {
|
|
ret[prefix] = true;
|
|
}
|
|
if (w) grabbed.push(w);
|
|
if (consume(ID, "readonly")) {
|
|
ret.readonly = true;
|
|
grabbed.push(last_token);
|
|
const w = all_ws();
|
|
if (w) grabbed.push(w);
|
|
}
|
|
const rest = attribute_rest(ret);
|
|
if (!rest) {
|
|
tokens = grabbed.concat(tokens);
|
|
}
|
|
return rest;
|
|
}
|
|
|
|
function regular_operation(store, prefix) {
|
|
all_ws(store, "pea");
|
|
const ret = Object.assign({}, EMPTY_OPERATION);
|
|
if (prefix) {
|
|
ret[prefix] = true;
|
|
}
|
|
ret.idlType = return_type();
|
|
return operation_rest(ret, store);
|
|
}
|
|
|
|
function partial(store) {
|
|
all_ws(store, "pea");
|
|
if (!consume(ID, "partial")) return;
|
|
const thing = dictionary(true, store) ||
|
|
interface_(true, store) ||
|
|
namespace(true, store) ||
|
|
error("Partial doesn't apply to anything");
|
|
return thing;
|
|
}
|
|
|
|
function dictionary(isPartial, store) {
|
|
all_ws(isPartial ? null : store, "pea");
|
|
if (!consume(ID, "dictionary")) return;
|
|
all_ws();
|
|
const name = consume(ID) || error("No name for dictionary");
|
|
const mems = [];
|
|
const ret = current = {
|
|
type: "dictionary",
|
|
name: isPartial ? name.value : sanitize_name(name.value, "dictionary"),
|
|
partial: isPartial,
|
|
members: mems
|
|
};
|
|
if (!isPartial) ret.inheritance = inheritance() || null;
|
|
all_ws();
|
|
consume(OTHER, "{") || error("Bodyless dictionary");
|
|
while (true) {
|
|
all_ws(store ? mems : null);
|
|
if (consume(OTHER, "}")) {
|
|
all_ws();
|
|
consume(OTHER, ";") || error("Missing semicolon after dictionary");
|
|
return ret;
|
|
}
|
|
const ea = extended_attrs(store ? mems : null);
|
|
all_ws(store ? mems : null, "pea");
|
|
const required = consume(ID, "required");
|
|
const typ = type_with_extended_attributes("dictionary-type") || error("No type for dictionary member");
|
|
all_ws();
|
|
const name = consume(ID) || error("No name for dictionary member");
|
|
const dflt = default_();
|
|
if (required && dflt) error("Required member must not have a default");
|
|
const member = {
|
|
type: "field",
|
|
name: name.value,
|
|
required: !!required,
|
|
idlType: typ,
|
|
extAttrs: ea
|
|
};
|
|
if (typeof dflt !== "undefined") {
|
|
member["default"] = dflt;
|
|
}
|
|
ret.members.push(member);
|
|
all_ws();
|
|
consume(OTHER, ";") || error("Unterminated dictionary member");
|
|
}
|
|
}
|
|
|
|
function enum_(store) {
|
|
all_ws(store, "pea");
|
|
if (!consume(ID, "enum")) return;
|
|
all_ws();
|
|
const name = consume(ID) || error("No name for enum");
|
|
const vals = [];
|
|
const ret = current = {
|
|
type: "enum",
|
|
name: sanitize_name(name.value, "enum"),
|
|
values: vals
|
|
};
|
|
all_ws();
|
|
consume(OTHER, "{") || error("No curly for enum");
|
|
let saw_comma = false;
|
|
while (true) {
|
|
all_ws(store ? vals : null);
|
|
if (consume(OTHER, "}")) {
|
|
all_ws();
|
|
consume(OTHER, ";") || error("No semicolon after enum");
|
|
return ret;
|
|
}
|
|
const val = consume(STR) || error("Unexpected value in enum");
|
|
val.value = val.value.slice(1, -1);
|
|
ret.values.push(val);
|
|
all_ws(store ? vals : null);
|
|
if (consume(OTHER, ",")) {
|
|
if (store) vals.push({ type: "," });
|
|
all_ws(store ? vals : null);
|
|
saw_comma = true;
|
|
} else {
|
|
saw_comma = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
function typedef(store) {
|
|
all_ws(store, "pea");
|
|
if (!consume(ID, "typedef")) return;
|
|
const ret = {
|
|
type: "typedef"
|
|
};
|
|
all_ws();
|
|
ret.idlType = type_with_extended_attributes("typedef-type") || error("No type in typedef");
|
|
all_ws();
|
|
const name = consume(ID) || error("No name in typedef");
|
|
ret.name = sanitize_name(name.value, "typedef");
|
|
current = ret;
|
|
all_ws();
|
|
consume(OTHER, ";") || error("Unterminated typedef");
|
|
return ret;
|
|
}
|
|
|
|
function implements_(store) {
|
|
all_ws(store, "pea");
|
|
const target = consume(ID);
|
|
if (!target) return;
|
|
const w = all_ws();
|
|
if (consume(ID, "implements")) {
|
|
const ret = {
|
|
type: "implements",
|
|
target: target.value
|
|
};
|
|
all_ws();
|
|
const imp = consume(ID) || error("Incomplete implements statement");
|
|
ret["implements"] = imp.value;
|
|
all_ws();
|
|
consume(OTHER, ";") || error("No terminating ; for implements statement");
|
|
return ret;
|
|
} else {
|
|
// rollback
|
|
tokens.unshift(w);
|
|
tokens.unshift(target);
|
|
}
|
|
}
|
|
|
|
function includes(store) {
|
|
all_ws(store, "pea");
|
|
const target = consume(ID);
|
|
if (!target) return;
|
|
const w = all_ws();
|
|
if (consume(ID, "includes")) {
|
|
const ret = {
|
|
type: "includes",
|
|
target: target.value
|
|
};
|
|
all_ws();
|
|
const imp = consume(ID) || error("Incomplete includes statement");
|
|
ret["includes"] = imp.value;
|
|
all_ws();
|
|
consume(OTHER, ";") || error("No terminating ; for includes statement");
|
|
return ret;
|
|
} else {
|
|
// rollback
|
|
tokens.unshift(w);
|
|
tokens.unshift(target);
|
|
}
|
|
}
|
|
|
|
function definition(store) {
|
|
return callback(store) ||
|
|
interface_(false, store) ||
|
|
partial(store) ||
|
|
dictionary(false, store) ||
|
|
enum_(store) ||
|
|
typedef(store) ||
|
|
implements_(store) ||
|
|
includes(store) ||
|
|
namespace(false, store);
|
|
}
|
|
|
|
function definitions(store) {
|
|
if (!tokens.length) return [];
|
|
const defs = [];
|
|
while (true) {
|
|
const ea = extended_attrs(store ? defs : null);
|
|
const def = definition(store ? defs : null);
|
|
if (!def) {
|
|
if (ea.length) error("Stray extended attributes");
|
|
break;
|
|
}
|
|
def.extAttrs = ea;
|
|
defs.push(def);
|
|
}
|
|
return defs;
|
|
}
|
|
const res = definitions(opt.ws);
|
|
if (tokens.length) error("Unrecognised tokens");
|
|
return res;
|
|
}
|
|
|
|
const obj = {
|
|
parse(str, opt) {
|
|
if (!opt) opt = {};
|
|
const tokens = tokenise(str);
|
|
return parse(tokens, opt);
|
|
}
|
|
};
|
|
|
|
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
|
module.exports = obj;
|
|
} else if (typeof define === 'function' && define.amd) {
|
|
define([], () => obj);
|
|
} else {
|
|
(self || window).WebIDL2 = obj;
|
|
}
|
|
})();
|