webidl2js
Version:
Auto-generates class structures for WebIDL specifications
370 lines (341 loc) • 12.5 kB
JavaScript
;
const Overloads = require("./overloads");
const Types = require("./types");
const utils = require("./utils");
function isOrIncludes(ctx, parent, predicate) {
parent = Types.resolveType(ctx, parent);
return predicate(parent) || (parent.union && parent.idlType.some(predicate));
}
function generateVarConversion(ctx, overload, i, parent, errPrefix, targetIdx = i) {
const requires = new utils.RequiresMap(ctx);
const idlType = overload.typeList[i];
// Always (try to) force-convert dictionaries
const isDefaultedDictionary = overload.operation.arguments[i].default &&
overload.operation.arguments[i].default.type === "dictionary";
if (isDefaultedDictionary && !ctx.dictionaries.has(idlType.idlType)) {
throw new Error(`The parameter ${overload.operation.arguments[i].name} was defaulted to {}, but no dictionary ` +
`named ${idlType.idlType} exists`);
}
const optional = overload.optionalityList[i] === "optional" && !isDefaultedDictionary;
let str = `{ let curArg = arguments[${targetIdx}];`;
if (optional) {
str += `
if (curArg !== undefined) {
`;
}
const msg = typeof targetIdx === "string" ?
`"${errPrefix}parameter " + (${targetIdx} + 1)` :
`"${errPrefix}parameter ${i + 1}"`;
const conv = Types.generateTypeConversion(ctx, "curArg", idlType, [], parent.name, msg);
requires.merge(conv.requires);
str += conv.body;
if (optional) {
str += "}";
const defaultValue = overload.operation.arguments[i].default;
if (defaultValue) {
str += `
else {
curArg = ${utils.getDefault(defaultValue)};
}
`;
}
}
str += "args.push(curArg);";
str += "}";
return {
requires,
body: str
};
}
module.exports.generateAsyncIteratorArgConversions = (ctx, idl, parent, errPrefix) => {
const requires = new utils.RequiresMap(ctx);
let str = "const args = [];";
for (let i = 0; i < idl.arguments.length; ++i) {
const idlArg = idl.arguments[i];
if (!idlArg.optional) {
throw new Error("All async iterable arguments must be optional");
}
const isDefaultedDictionary = idlArg.default && idlArg.default.type === "dictionary";
if (isDefaultedDictionary && !ctx.dictionaries.has(idlArg.idlType.idlType)) {
throw new Error(`The dictionary ${idlArg.idlType.idlType} was referenced in an argument list, but doesn't exist`);
}
const msg = `"${errPrefix}parameter ${i + 1}"`;
const conv = Types.generateTypeConversion(ctx, `args[${i}]`, idlArg.idlType, [], parent.name, msg);
requires.merge(conv.requires);
if (isDefaultedDictionary) {
str += `args[${i}] = arguments[${i}];${conv.body}`;
} else {
str += `
if (arguments[${i}] !== undefined) {
args[${i}] = arguments[${i}];${conv.body}
}
`;
if (idlArg.default) {
str += `
else {
args[${i}] = ${utils.getDefault(idlArg.default)};
}
`;
} else {
str += `
else {
args[${i}] = undefined;
}
`;
}
}
}
return {
requires,
body: str
};
};
module.exports.generateOverloadConversions = function (ctx, typeOfOp, name, parent, errPrefix) {
const requires = new utils.RequiresMap(ctx);
const ops = Overloads.getOperations(typeOfOp, name, parent);
const argLengths = Overloads.getEffectiveOverloads(typeOfOp, name, 0, parent).map(o => o.typeList.length);
const maxArgs = Math.max(...argLengths);
let str = "";
if (maxArgs > 0) {
const minArgs = Math.min(...argLengths);
if (minArgs > 0) {
const plural = minArgs > 1 ? "s" : "";
str += `
if (arguments.length < ${minArgs}) {
throw new globalObject.TypeError(\`${errPrefix}${minArgs} argument${plural} required, but only \${arguments.length} present.\`);
}
`;
}
str += "const args = [];";
const switchCases = [];
// Special case: when the operation isn't overloaded, always try to convert to the maximum number of args.
for (let numArgs = ops.length === 1 ? maxArgs : minArgs; numArgs <= maxArgs; numArgs++) {
// for (let numArgs = minArgs; numArgs <= maxArgs; numArgs++) {
const S = Overloads.getEffectiveOverloads(typeOfOp, name, numArgs, parent)
.filter(o => o.typeList.length === numArgs);
if (S.length === 0) {
switchCases.push(`
throw new globalObject.TypeError(\`${errPrefix}only \${arguments.length} arguments present.\`);
`);
continue;
}
let d = -1;
if (S.length > 1) {
d = Overloads.distinguishingArgumentIndex(ctx, S);
}
let caseSrc = "";
let i = 0;
for (; i < d; i++) {
const conv = generateVarConversion(ctx, S[0], i, parent, errPrefix);
requires.merge(conv.requires);
caseSrc += conv.body;
}
if (i === d) {
caseSrc += "{";
caseSrc += `let curArg = arguments[${d}];`;
const possibilities = [];
const optionals = S.filter(o => o.optionalityList[d] === "optional");
if (optionals.length) {
possibilities.push(`
if (curArg === undefined) {
${continued(optionals[0], i)}
}
`);
}
const nullables = S.filter(o => {
return isOrIncludes(ctx, o.typeList[d], t => t.nullable || ctx.dictionaries.has(t.idlType));
});
if (nullables.length) {
possibilities.push(`
if (curArg === null || curArg === undefined) {
${continued(nullables[0], i)}
}
`);
}
const interfaceTypes = new Map();
for (const o of S) {
const type = Types.resolveType(ctx, o.typeList[d]);
if (ctx.interfaces.has(type.idlType)) {
interfaceTypes.set(type.idlType, o);
} else if (type.union) {
for (const child of type.idlType) {
if (ctx.interfaces.has(child.idlType)) {
interfaceTypes.set(child.idlType, o);
}
}
}
}
for (const [iface, overload] of interfaceTypes) {
let fn;
// Avoid requiring the interface itself
if (iface !== parent.name) {
fn = `${iface}.is`;
requires.addRelative(iface);
} else {
fn = "exports.is";
}
possibilities.push(`
if (${fn}(curArg)) {
${continued(overload, i)}
}
`);
}
const arrayBuffers = S.filter(o => isOrIncludes(ctx, o.typeList[d], t => t.idlType === "ArrayBuffer"));
if (arrayBuffers.length) {
possibilities.push(`
if (utils.isArrayBuffer(curArg)) {
${continued(arrayBuffers[0], i)}
}
`);
}
const sharedArrayBuffers = S.filter(o => {
return isOrIncludes(ctx, o.typeList[d], t => t.idlType === "SharedArrayBuffer");
});
if (sharedArrayBuffers.length) {
possibilities.push(`
if (utils.isSharedArrayBuffer(curArg)) {
${continued(sharedArrayBuffers[0], i)}
}
`);
}
const arrayBufferViews = new Map();
for (const o of S) {
const type = Types.resolveType(ctx, o.typeList[d]);
if (Types.arrayBufferViewTypes.has(type.idlType)) {
arrayBufferViews.set(type.idlType, o);
} else if (type.union) {
for (const child of type.idlType) {
if (Types.arrayBufferViewTypes.has(child.idlType)) {
arrayBufferViews.set(child.idlType, o);
}
}
}
}
if (arrayBufferViews.size) {
// Special case for all ArrayBufferView types.
if (arrayBufferViews.size === Types.arrayBufferViewTypes.size &&
new Set(arrayBufferViews.values()).size === 1) {
possibilities.push(`
if (ArrayBuffer.isView(curArg)) {
${continued(arrayBufferViews.get("Uint8Array"), i)}
}
`);
} else {
for (const [type, overload] of arrayBufferViews) {
possibilities.push(`
if (ArrayBuffer.isView(curArg) && curArg instanceof ${type}) {
${continued(overload, i)}
}
`);
}
}
}
const callables = S.filter(o => {
return isOrIncludes(ctx, o.typeList[d], t => ["Function", "VoidFunction"].includes(t.idlType));
});
if (callables.length) {
possibilities.push(`
if (typeof curArg === "function") {
${continued(callables[0], i)}
}
`);
}
const iterables = S.filter(o => {
return isOrIncludes(ctx, o.typeList[d], t => ["sequence", "FrozenArray"].includes(t.generic));
});
if (iterables.length) {
possibilities.push(`
if (utils.isObject(curArg) && typeof curArg[Symbol.iterator] === "function") {
${continued(iterables[0], i)}
}
`);
}
const objects = S.filter(o => isOrIncludes(ctx, o.typeList[d], t => t.idlType === "object"));
if (objects.length) {
possibilities.push(`
if (utils.isObject(curArg)) {
${continued(objects[0], i)}
}
`);
}
const booleans = S.filter(o => isOrIncludes(ctx, o.typeList[d], t => t.idlType === "boolean"));
if (booleans.length) {
possibilities.push(`
if (typeof curArg === "boolean") {
${continued(booleans[0], i)}
}
`);
}
const numerics = S.filter(o => isOrIncludes(ctx, o.typeList[d], t => Types.numericTypes.has(t.idlType)));
if (numerics.length) {
possibilities.push(`
if (typeof curArg === "number") {
${continued(numerics[0], i)}
}
`);
}
const strings = S.filter(o => {
return isOrIncludes(ctx, o.typeList[d], t => {
return Types.stringTypes.has(t.idlType) || ctx.enumerations.has(t.idlType);
});
});
const any = S.filter(o => isOrIncludes(ctx, o.typeList[d], t => t.idlType === "any"));
if (strings.length) {
possibilities.push(`{ ${continued(strings[0], i)} }`);
} else if (numerics.length) {
possibilities.push(`{ ${continued(numerics[0], i)} }`);
} else if (booleans.length) {
possibilities.push(`{ ${continued(booleans[0], i)} }`);
} else if (any.length) {
possibilities.push(`{ ${continued(any[0], i)} }`);
} else {
possibilities.push(`throw new globalObject.TypeError("${errPrefix}No such overload");`);
}
caseSrc += possibilities.join(" else ");
caseSrc += "}";
} else {
// Branch taken when S.length === 1.
caseSrc += continued(S[0], i);
}
switchCases.push(caseSrc);
function continued(overload, idx) {
let continuedStr = "";
for (; idx < numArgs; idx++) {
let targetIdx = idx;
if (overload.optionalityList[idx] === "variadic" && numArgs === maxArgs && idx === numArgs - 1) {
continuedStr += `for (let i = ${idx}; i < arguments.length; i++)`;
targetIdx = "i";
}
const conv = generateVarConversion(ctx, overload, idx, parent, errPrefix, targetIdx);
requires.merge(conv.requires);
continuedStr += conv.body;
}
return continuedStr;
}
}
if (switchCases.length === 1) {
str += switchCases[0];
} else {
str += "switch (arguments.length) {";
let lastBody;
for (let i = 0; i < switchCases.length - 1; i++) {
if (lastBody !== undefined && switchCases[i] !== lastBody) {
str += `${lastBody}break;`;
}
str += `case ${minArgs + i}:`;
lastBody = switchCases[i];
}
if (lastBody !== undefined && switchCases[switchCases.length - 1] !== lastBody) {
str += `${lastBody}break;`;
}
str += "default:";
str += switchCases[switchCases.length - 1];
str += "}";
}
}
return {
requires,
body: str,
hasArgs: maxArgs > 0
};
};