333 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			333 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | "use strict"; | ||
|  | 
 | ||
|  | function _(message, opts) { | ||
|  |     return `${opts && opts.context ? opts.context : "Value"} ${message}.`; | ||
|  | } | ||
|  | 
 | ||
|  | function type(V) { | ||
|  |     if (V === null) { | ||
|  |         return "Null"; | ||
|  |     } | ||
|  |     switch (typeof V) { | ||
|  |         case "undefined": | ||
|  |             return "Undefined"; | ||
|  |         case "boolean": | ||
|  |             return "Boolean"; | ||
|  |         case "number": | ||
|  |             return "Number"; | ||
|  |         case "string": | ||
|  |             return "String"; | ||
|  |         case "symbol": | ||
|  |             return "Symbol"; | ||
|  |         case "object": | ||
|  |             // Falls through
 | ||
|  |         case "function": | ||
|  |             // Falls through
 | ||
|  |         default: | ||
|  |             // Per ES spec, typeof returns an implemention-defined value that is not any of the existing ones for
 | ||
|  |             // uncallable non-standard exotic objects. Yet Type() which the Web IDL spec depends on returns Object for
 | ||
|  |             // such cases. So treat the default case as an object.
 | ||
|  |             return "Object"; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | // Round x to the nearest integer, choosing the even integer if it lies halfway between two.
 | ||
|  | function evenRound(x) { | ||
|  |     // There are four cases for numbers with fractional part being .5:
 | ||
|  |     //
 | ||
|  |     // case |     x     | floor(x) | round(x) | expected | x <> 0 | x % 1 | x & 1 |   example
 | ||
|  |     //   1  |  2n + 0.5 |  2n      |  2n + 1  |  2n      |   >    |  0.5  |   0   |  0.5 ->  0
 | ||
|  |     //   2  |  2n + 1.5 |  2n + 1  |  2n + 2  |  2n + 2  |   >    |  0.5  |   1   |  1.5 ->  2
 | ||
|  |     //   3  | -2n - 0.5 | -2n - 1  | -2n      | -2n      |   <    | -0.5  |   0   | -0.5 ->  0
 | ||
|  |     //   4  | -2n - 1.5 | -2n - 2  | -2n - 1  | -2n - 2  |   <    | -0.5  |   1   | -1.5 -> -2
 | ||
|  |     // (where n is a non-negative integer)
 | ||
|  |     //
 | ||
|  |     // Branch here for cases 1 and 4
 | ||
|  |     if ((x > 0 && (x % 1) === +0.5 && (x & 1) === 0) || | ||
|  |         (x < 0 && (x % 1) === -0.5 && (x & 1) === 1)) { | ||
|  |         return censorNegativeZero(Math.floor(x)); | ||
|  |     } | ||
|  | 
 | ||
|  |     return censorNegativeZero(Math.round(x)); | ||
|  | } | ||
|  | 
 | ||
|  | function integerPart(n) { | ||
|  |     return censorNegativeZero(Math.trunc(n)); | ||
|  | } | ||
|  | 
 | ||
|  | function sign(x) { | ||
|  |     return x < 0 ? -1 : 1; | ||
|  | } | ||
|  | 
 | ||
|  | function modulo(x, y) { | ||
|  |     // https://tc39.github.io/ecma262/#eqn-modulo
 | ||
|  |     // Note that http://stackoverflow.com/a/4467559/3191 does NOT work for large modulos
 | ||
|  |     const signMightNotMatch = x % y; | ||
|  |     if (sign(y) !== sign(signMightNotMatch)) { | ||
|  |         return signMightNotMatch + y; | ||
|  |     } | ||
|  |     return signMightNotMatch; | ||
|  | } | ||
|  | 
 | ||
|  | function censorNegativeZero(x) { | ||
|  |     return x === 0 ? 0 : x; | ||
|  | } | ||
|  | 
 | ||
|  | function createIntegerConversion(bitLength, typeOpts) { | ||
|  |     const isSigned = !typeOpts.unsigned; | ||
|  | 
 | ||
|  |     let lowerBound; | ||
|  |     let upperBound; | ||
|  |     if (bitLength === 64) { | ||
|  |         upperBound = Math.pow(2, 53) - 1; | ||
|  |         lowerBound = !isSigned ? 0 : -Math.pow(2, 53) + 1; | ||
|  |     } else if (!isSigned) { | ||
|  |         lowerBound = 0; | ||
|  |         upperBound = Math.pow(2, bitLength) - 1; | ||
|  |     } else { | ||
|  |         lowerBound = -Math.pow(2, bitLength - 1); | ||
|  |         upperBound = Math.pow(2, bitLength - 1) - 1; | ||
|  |     } | ||
|  | 
 | ||
|  |     const twoToTheBitLength = Math.pow(2, bitLength); | ||
|  |     const twoToOneLessThanTheBitLength = Math.pow(2, bitLength - 1); | ||
|  | 
 | ||
|  |     return (V, opts) => { | ||
|  |         if (opts === undefined) { | ||
|  |             opts = {}; | ||
|  |         } | ||
|  | 
 | ||
|  |         let x = +V; | ||
|  |         x = censorNegativeZero(x); // Spec discussion ongoing: https://github.com/heycam/webidl/issues/306
 | ||
|  | 
 | ||
|  |         if (opts.enforceRange) { | ||
|  |             if (!Number.isFinite(x)) { | ||
|  |                 throw new TypeError(_("is not a finite number", opts)); | ||
|  |             } | ||
|  | 
 | ||
|  |             x = integerPart(x); | ||
|  | 
 | ||
|  |             if (x < lowerBound || x > upperBound) { | ||
|  |                 throw new TypeError(_( | ||
|  |                     `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, opts)); | ||
|  |             } | ||
|  | 
 | ||
|  |             return x; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (!Number.isNaN(x) && opts.clamp) { | ||
|  |             x = Math.min(Math.max(x, lowerBound), upperBound); | ||
|  |             x = evenRound(x); | ||
|  |             return x; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (!Number.isFinite(x) || x === 0) { | ||
|  |             return 0; | ||
|  |         } | ||
|  |         x = integerPart(x); | ||
|  | 
 | ||
|  |         // Math.pow(2, 64) is not accurately representable in JavaScript, so try to avoid these per-spec operations if
 | ||
|  |         // possible. Hopefully it's an optimization for the non-64-bitLength cases too.
 | ||
|  |         if (x >= lowerBound && x <= upperBound) { | ||
|  |             return x; | ||
|  |         } | ||
|  | 
 | ||
|  |         // These will not work great for bitLength of 64, but oh well. See the README for more details.
 | ||
|  |         x = modulo(x, twoToTheBitLength); | ||
|  |         if (isSigned && x >= twoToOneLessThanTheBitLength) { | ||
|  |             return x - twoToTheBitLength; | ||
|  |         } | ||
|  |         return x; | ||
|  |     }; | ||
|  | } | ||
|  | 
 | ||
|  | exports.any = V => { | ||
|  |     return V; | ||
|  | }; | ||
|  | 
 | ||
|  | exports.void = function () { | ||
|  |     return undefined; | ||
|  | }; | ||
|  | 
 | ||
|  | exports.boolean = function (val) { | ||
|  |     return !!val; | ||
|  | }; | ||
|  | 
 | ||
|  | exports.byte = createIntegerConversion(8, { unsigned: false }); | ||
|  | exports.octet = createIntegerConversion(8, { unsigned: true }); | ||
|  | 
 | ||
|  | exports.short = createIntegerConversion(16, { unsigned: false }); | ||
|  | exports["unsigned short"] = createIntegerConversion(16, { unsigned: true }); | ||
|  | 
 | ||
|  | exports.long = createIntegerConversion(32, { unsigned: false }); | ||
|  | exports["unsigned long"] = createIntegerConversion(32, { unsigned: true }); | ||
|  | 
 | ||
|  | exports["long long"] = createIntegerConversion(64, { unsigned: false }); | ||
|  | exports["unsigned long long"] = createIntegerConversion(64, { unsigned: true }); | ||
|  | 
 | ||
|  | exports.double = (V, opts) => { | ||
|  |     const x = +V; | ||
|  | 
 | ||
|  |     if (!Number.isFinite(x)) { | ||
|  |         throw new TypeError(_("is not a finite floating-point value", opts)); | ||
|  |     } | ||
|  | 
 | ||
|  |     return x; | ||
|  | }; | ||
|  | 
 | ||
|  | exports["unrestricted double"] = V => { | ||
|  |     const x = +V; | ||
|  | 
 | ||
|  |     return x; | ||
|  | }; | ||
|  | 
 | ||
|  | exports.float = (V, opts) => { | ||
|  |     const x = +V; | ||
|  | 
 | ||
|  |     if (!Number.isFinite(x)) { | ||
|  |         throw new TypeError(_("is not a finite floating-point value", opts)); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (Object.is(x, -0)) { | ||
|  |         return x; | ||
|  |     } | ||
|  | 
 | ||
|  |     const y = Math.fround(x); | ||
|  | 
 | ||
|  |     if (!Number.isFinite(y)) { | ||
|  |         throw new TypeError(_("is outside the range of a single-precision floating-point value", opts)); | ||
|  |     } | ||
|  | 
 | ||
|  |     return y; | ||
|  | }; | ||
|  | 
 | ||
|  | exports["unrestricted float"] = V => { | ||
|  |     const x = +V; | ||
|  | 
 | ||
|  |     if (isNaN(x)) { | ||
|  |         return x; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (Object.is(x, -0)) { | ||
|  |         return x; | ||
|  |     } | ||
|  | 
 | ||
|  |     return Math.fround(x); | ||
|  | }; | ||
|  | 
 | ||
|  | exports.DOMString = function (V, opts) { | ||
|  |     if (opts === undefined) { | ||
|  |         opts = {}; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (opts.treatNullAsEmptyString && V === null) { | ||
|  |         return ""; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (typeof V === "symbol") { | ||
|  |         throw new TypeError(_("is a symbol, which cannot be converted to a string", opts)); | ||
|  |     } | ||
|  | 
 | ||
|  |     return String(V); | ||
|  | }; | ||
|  | 
 | ||
|  | exports.ByteString = (V, opts) => { | ||
|  |     const x = exports.DOMString(V, opts); | ||
|  |     let c; | ||
|  |     for (let i = 0; (c = x.codePointAt(i)) !== undefined; ++i) { | ||
|  |         if (c > 255) { | ||
|  |             throw new TypeError(_("is not a valid ByteString", opts)); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     return x; | ||
|  | }; | ||
|  | 
 | ||
|  | exports.USVString = (V, opts) => { | ||
|  |     const S = exports.DOMString(V, opts); | ||
|  |     const n = S.length; | ||
|  |     const U = []; | ||
|  |     for (let i = 0; i < n; ++i) { | ||
|  |         const c = S.charCodeAt(i); | ||
|  |         if (c < 0xD800 || c > 0xDFFF) { | ||
|  |             U.push(String.fromCodePoint(c)); | ||
|  |         } else if (0xDC00 <= c && c <= 0xDFFF) { | ||
|  |             U.push(String.fromCodePoint(0xFFFD)); | ||
|  |         } else if (i === n - 1) { | ||
|  |             U.push(String.fromCodePoint(0xFFFD)); | ||
|  |         } else { | ||
|  |             const d = S.charCodeAt(i + 1); | ||
|  |             if (0xDC00 <= d && d <= 0xDFFF) { | ||
|  |                 const a = c & 0x3FF; | ||
|  |                 const b = d & 0x3FF; | ||
|  |                 U.push(String.fromCodePoint((2 << 15) + ((2 << 9) * a) + b)); | ||
|  |                 ++i; | ||
|  |             } else { | ||
|  |                 U.push(String.fromCodePoint(0xFFFD)); | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     return U.join(""); | ||
|  | }; | ||
|  | 
 | ||
|  | exports.object = (V, opts) => { | ||
|  |     if (type(V) !== "Object") { | ||
|  |         throw new TypeError(_("is not an object", opts)); | ||
|  |     } | ||
|  | 
 | ||
|  |     return V; | ||
|  | }; | ||
|  | 
 | ||
|  | // Not exported, but used in Function and VoidFunction.
 | ||
|  | 
 | ||
|  | // Neither Function nor VoidFunction is defined with [TreatNonObjectAsNull], so
 | ||
|  | // handling for that is omitted.
 | ||
|  | function convertCallbackFunction(V, opts) { | ||
|  |     if (typeof V !== "function") { | ||
|  |         throw new TypeError(_("is not a function", opts)); | ||
|  |     } | ||
|  |     return V; | ||
|  | } | ||
|  | 
 | ||
|  | [ | ||
|  |     Error, | ||
|  |     ArrayBuffer, // The IsDetachedBuffer abstract operation is not exposed in JS
 | ||
|  |     DataView, Int8Array, Int16Array, Int32Array, Uint8Array, | ||
|  |     Uint16Array, Uint32Array, Uint8ClampedArray, Float32Array, Float64Array | ||
|  | ].forEach(func => { | ||
|  |     const name = func.name; | ||
|  |     const article = /^[AEIOU]/.test(name) ? "an" : "a"; | ||
|  |     exports[name] = (V, opts) => { | ||
|  |         if (!(V instanceof func)) { | ||
|  |             throw new TypeError(_(`is not ${article} ${name} object`, opts)); | ||
|  |         } | ||
|  | 
 | ||
|  |         return V; | ||
|  |     }; | ||
|  | }); | ||
|  | 
 | ||
|  | // Common definitions
 | ||
|  | 
 | ||
|  | exports.ArrayBufferView = (V, opts) => { | ||
|  |     if (!ArrayBuffer.isView(V)) { | ||
|  |         throw new TypeError(_("is not a view on an ArrayBuffer object", opts)); | ||
|  |     } | ||
|  | 
 | ||
|  |     return V; | ||
|  | }; | ||
|  | 
 | ||
|  | exports.BufferSource = (V, opts) => { | ||
|  |     if (!(ArrayBuffer.isView(V) || V instanceof ArrayBuffer)) { | ||
|  |         throw new TypeError(_("is not an ArrayBuffer object or a view on one", opts)); | ||
|  |     } | ||
|  | 
 | ||
|  |     return V; | ||
|  | }; | ||
|  | 
 | ||
|  | exports.DOMTimeStamp = exports["unsigned long long"]; | ||
|  | 
 | ||
|  | exports.Function = convertCallbackFunction; | ||
|  | 
 | ||
|  | exports.VoidFunction = convertCallbackFunction; |