temporalz/test/test262/polyfill.js
2026-02-12 01:26:36 +06:00

683 lines
24 KiB
JavaScript

// This polyfill is executed in a vm.Script context by test262-runner,
// so it must be synchronous and self-contained.
// The wasm bytes are injected as global.__TEMPORALZ_WASM_BYTES__ by the runner.
(function () {
if (!globalThis.__TEMPORALZ_WASM_BYTES__) {
throw new Error("WASM bytes not injected into test context");
}
const wasmBinary = globalThis.__TEMPORALZ_WASM_BYTES__;
const wasmModule = new WebAssembly.Module(wasmBinary);
const wasmInstance = new WebAssembly.Instance(wasmModule, {
env: {
console(ptr, len) {
const bytes = new Uint8Array(memory.buffer, ptr, len);
globalThis.console.log(decoder.decode(bytes));
},
},
});
const exports = wasmInstance.exports;
const memory = exports.memory;
const encoder = {
encode(str) {
const buf = [];
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
if (code < 0x80) {
buf.push(code);
} else if (code < 0x800) {
buf.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
} else if (code < 0xd800 || code >= 0xe000) {
buf.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
} else {
const code2 = str.charCodeAt(++i);
const codePoint = 0x10000 + (((code & 0x3ff) << 10) | (code2 & 0x3ff));
buf.push(
0xf0 | (codePoint >> 18),
0x80 | ((codePoint >> 12) & 0x3f),
0x80 | ((codePoint >> 6) & 0x3f),
0x80 | (codePoint & 0x3f)
);
}
}
return new Uint8Array(buf);
},
};
const decoder = {
decode(bytes) {
let str = "";
for (let i = 0; i < bytes.length; i++) {
const byte = bytes[i];
if (byte < 0x80) {
str += String.fromCharCode(byte);
} else if ((byte & 0xe0) === 0xc0) {
str += String.fromCharCode(((byte & 0x1f) << 6) | (bytes[++i] & 0x3f));
} else if ((byte & 0xf0) === 0xe0) {
str += String.fromCharCode(
((byte & 0x0f) << 12) | ((bytes[++i] & 0x3f) << 6) | (bytes[++i] & 0x3f)
);
} else if ((byte & 0xf8) === 0xf0) {
const codePoint =
((byte & 0x07) << 18) |
((bytes[++i] & 0x3f) << 12) |
((bytes[++i] & 0x3f) << 6) |
(bytes[++i] & 0x3f);
const high = ((codePoint - 0x10000) >> 10) + 0xd800;
const low = ((codePoint - 0x10000) & 0x3ff) + 0xdc00;
str += String.fromCharCode(high, low);
}
}
return str;
},
};
function readString(ptr, len) {
return decoder.decode(new Uint8Array(memory.buffer, ptr, len));
}
function lastError() {
const ptr = exports.temporalz_last_error_ptr();
const len = exports.temporalz_last_error_len();
if (!ptr || !len) return new Error("temporalz error");
const msg = readString(ptr, len);
exports.temporalz_last_error_clear();
if (msg.includes("Range")) return new RangeError(msg);
if (msg.includes("Type")) return new TypeError(msg);
return new Error(msg);
}
function requireHandle(handle) {
if (!handle) throw lastError();
return handle;
}
function allocString(text) {
const bytes = encoder.encode(text);
const ptr = exports.temporalz_alloc(bytes.length);
if (!ptr) throw lastError();
new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
return { ptr, len: bytes.length };
}
function takeString(packed) {
if (!packed) throw lastError();
const value = BigInt(packed);
const ptr = Number(value >> 32n);
const len = Number(value & 0xffffffffn);
const text = readString(ptr, len);
exports.temporalz_string_free(ptr, len);
return text;
}
function splitI128(value) {
const v = typeof value === "bigint" ? value : BigInt(value);
const mask = (1n << 64n) - 1n;
return { hi: v >> 64n, lo: v & mask };
}
function joinI128(hi, lo) {
const mask = (1n << 64n) - 1n;
return (BigInt(hi) << 64n) | (BigInt(lo) & mask);
}
const unitCodes = {
nanosecond: 1,
microsecond: 2,
millisecond: 3,
second: 4,
minute: 5,
hour: 6,
day: 7,
week: 8,
month: 9,
year: 10,
auto: 11,
};
const roundingModeCodes = {
ceil: 1,
floor: 2,
expand: 3,
trunc: 4,
halfCeil: 5,
halfFloor: 6,
halfExpand: 7,
halfTrunc: 8,
halfEven: 9,
};
function toUnitCode(value, name) {
if (value === undefined || value === null) return 255;
const code = unitCodes[value];
if (!code) throw new RangeError(`Invalid ${name}`);
return code;
}
function toRoundingModeCode(value) {
if (value === undefined || value === null) return 255;
const code = roundingModeCodes[value];
if (!code) throw new RangeError("Invalid roundingMode");
return code;
}
function toIntegerBigInt(value, name) {
if (typeof value === "bigint") return value;
const number = Number(value);
if (!isFiniteNumber(number)) throw new RangeError(`${name} must be finite`);
if (!Number.isInteger(number)) throw new RangeError(`${name} must be an integer`);
return BigInt(number);
}
function toNumber(value, name) {
const number = Number(value);
if (!isFiniteNumber(number)) throw new RangeError(`${name} must be finite`);
return number;
}
function toInteger(value, name) {
const number = Number(value);
if (!isFiniteNumber(number)) throw new RangeError(`${name} must be finite`);
if (!Number.isInteger(number)) throw new RangeError(`${name} must be an integer`);
return number;
}
function isFiniteNumber(value) {
return value === value && value !== Infinity && value !== -Infinity;
}
function unimplemented(name) {
return function () {
throw new Error(`${name} is not implemented yet`);
};
}
class Instant {
constructor(handle) {
this._handle = handle;
}
static _fromHandle(handle) {
return new Instant(handle);
}
static fromEpochMilliseconds(epochMs) {
const number = Number(epochMs);
if (!isFiniteNumber(number)) throw new RangeError("epochMilliseconds must be finite");
const handle = exports.temporalz_instant_from_epoch_milliseconds(number);
return Instant._fromHandle(requireHandle(handle));
}
static fromEpochNanoseconds(epochNs) {
const parts = splitI128(epochNs);
const handle = exports.temporalz_instant_from_epoch_nanoseconds_parts(
parts.hi,
parts.lo
);
return Instant._fromHandle(requireHandle(handle));
}
static from(value) {
if (value instanceof Instant) return value;
if (typeof value === "string") {
const text = allocString(value);
const handle = exports.temporalz_instant_from_utf8(text.ptr, text.len);
exports.temporalz_free(text.ptr, text.len);
return Instant._fromHandle(requireHandle(handle));
}
if (value && typeof value === "object") {
if (value.epochNanoseconds !== undefined) {
return Instant.fromEpochNanoseconds(value.epochNanoseconds);
}
if (value.epochMilliseconds !== undefined) {
return Instant.fromEpochMilliseconds(value.epochMilliseconds);
}
}
throw new TypeError("Instant.from expects a string or object");
}
get epochMilliseconds() {
return exports.temporalz_instant_epoch_milliseconds(this._handle);
}
get epochNanoseconds() {
const hi = exports.temporalz_instant_epoch_nanoseconds_hi(this._handle);
const lo = exports.temporalz_instant_epoch_nanoseconds_lo(this._handle);
return joinI128(hi, lo);
}
toString() {
return takeString(exports.temporalz_instant_to_string(this._handle));
}
toJSON() {
return this.toString();
}
add(durationLike) {
const dur = Duration.from(durationLike);
const handle = exports.temporalz_instant_add(this._handle, dur._handle);
return Instant._fromHandle(requireHandle(handle));
}
subtract(durationLike) {
const dur = Duration.from(durationLike);
const handle = exports.temporalz_instant_subtract(this._handle, dur._handle);
return Instant._fromHandle(requireHandle(handle));
}
round(options) {
if (!options || typeof options !== "object") {
throw new TypeError("round options must be an object");
}
const smallestUnit = toUnitCode(options.smallestUnit, "smallestUnit");
if (smallestUnit === 255) throw new RangeError("smallestUnit is required");
const roundingMode = toRoundingModeCode(options.roundingMode);
let roundingIncrement = 0;
if (options.roundingIncrement !== undefined) {
const inc = Number(options.roundingIncrement);
if (!isFiniteNumber(inc) || !Number.isInteger(inc) || inc <= 0) {
throw new RangeError("Invalid roundingIncrement");
}
roundingIncrement = inc;
}
const handle = exports.temporalz_instant_round(
this._handle,
smallestUnit,
roundingMode,
roundingIncrement
);
return Instant._fromHandle(requireHandle(handle));
}
equals(other) {
const rhs = Instant.from(other);
return exports.temporalz_instant_equals(this._handle, rhs._handle) === 1;
}
toLocaleString() {
return this.toString();
}
valueOf() {
throw new TypeError("Cannot convert Temporal.Instant to a number");
}
static compare(a, b) {
const left = Instant.from(a);
const right = Instant.from(b);
return Number(exports.temporalz_instant_compare(left._handle, right._handle));
}
}
const plainDateHandleToken = Symbol("PlainDateHandle");
class PlainDate {
constructor(year, month, day) {
if (year === plainDateHandleToken) {
this._handle = month;
return;
}
const yearValue = toInteger(year ?? 0, "year");
const monthValue = toInteger(month ?? 0, "month");
const dayValue = toInteger(day ?? 0, "day");
const handle = exports.temporalz_plain_date_init(yearValue, monthValue, dayValue);
this._handle = requireHandle(handle);
}
static _fromHandle(handle) {
return new PlainDate(plainDateHandleToken, handle);
}
static from(value) {
if (value instanceof PlainDate) return value;
if (typeof value === "string") {
const text = allocString(value);
const handle = exports.temporalz_plain_date_from_utf8(text.ptr, text.len);
exports.temporalz_free(text.ptr, text.len);
return PlainDate._fromHandle(requireHandle(handle));
}
if (value === null || typeof value !== "object") {
throw new TypeError("PlainDate.from expects a string or object");
}
const year = toInteger(value.year, "year");
const month = toInteger(value.month, "month");
const day = toInteger(value.day, "day");
const handle = exports.temporalz_plain_date_init(year, month, day);
return PlainDate._fromHandle(requireHandle(handle));
}
toString() {
return takeString(exports.temporalz_plain_date_to_string(this._handle));
}
}
const durationHandleToken = Symbol("DurationHandle");
class Duration {
constructor(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds
) {
if (years === durationHandleToken) {
this._handle = months;
return;
}
const yearsValue = toIntegerBigInt(years ?? 0, "years");
const monthsValue = toIntegerBigInt(months ?? 0, "months");
const weeksValue = toIntegerBigInt(weeks ?? 0, "weeks");
const daysValue = toIntegerBigInt(days ?? 0, "days");
const hoursValue = toIntegerBigInt(hours ?? 0, "hours");
const minutesValue = toIntegerBigInt(minutes ?? 0, "minutes");
const secondsValue = toIntegerBigInt(seconds ?? 0, "seconds");
const millisecondsValue = toIntegerBigInt(milliseconds ?? 0, "milliseconds");
const microsecondsValue = toNumber(microseconds ?? 0, "microseconds");
const nanosecondsValue = toNumber(nanoseconds ?? 0, "nanoseconds");
const created = exports.temporalz_duration_init(
yearsValue,
monthsValue,
weeksValue,
daysValue,
hoursValue,
minutesValue,
secondsValue,
millisecondsValue,
microsecondsValue,
nanosecondsValue
);
this._handle = requireHandle(created);
}
static _fromHandle(handle) {
return new Duration(durationHandleToken, handle);
}
static from(value) {
if (value instanceof Duration) return value;
if (typeof value === "string") {
const text = allocString(value);
const handle = exports.temporalz_duration_from_utf8(text.ptr, text.len);
exports.temporalz_free(text.ptr, text.len);
return Duration._fromHandle(requireHandle(handle));
}
if (value === null || typeof value !== "object") {
throw new TypeError("Duration.from expects a string or object");
}
const fields = [
"years",
"months",
"weeks",
"days",
"hours",
"minutes",
"seconds",
"milliseconds",
"microseconds",
"nanoseconds",
];
let mask = 0;
const values = [0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0, 0];
fields.forEach((field, index) => {
if (value[field] !== undefined) {
mask |= 1 << index;
if (field === "microseconds" || field === "nanoseconds") {
values[index] = toNumber(value[field], field);
} else {
values[index] = toIntegerBigInt(value[field], field);
}
}
});
const handle = exports.temporalz_duration_from_parts(
mask,
values[0],
values[1],
values[2],
values[3],
values[4],
values[5],
values[6],
values[7],
values[8],
values[9]
);
return Duration._fromHandle(requireHandle(handle));
}
static compare(a, b, options) {
const left = Duration.from(a);
const right = Duration.from(b);
return Duration.compareWithOptions(left, right, options);
}
static compareWithOptions(left, right, options) {
if (options !== undefined && (options === null || typeof options !== "object")) {
throw new TypeError("options must be an object");
}
const relativeTo = toRelativeTo(options);
if (!relativeTo && (hasYearMonthWeek(left) || hasYearMonthWeek(right))) {
throw new RangeError("relativeTo is required for calendar units");
}
if (relativeTo) {
return Number(
exports.temporalz_duration_compare_plain_date(
left._handle,
right._handle,
relativeTo._handle
)
);
}
return Number(exports.temporalz_duration_compare(left._handle, right._handle));
}
add(other) {
const rhs = Duration.from(other);
const handle = exports.temporalz_duration_add(this._handle, rhs._handle);
return Duration._fromHandle(requireHandle(handle));
}
subtract(other) {
const rhs = Duration.from(other);
const handle = exports.temporalz_duration_subtract(this._handle, rhs._handle);
return Duration._fromHandle(requireHandle(handle));
}
abs() {
const handle = exports.temporalz_duration_abs(this._handle);
return Duration._fromHandle(requireHandle(handle));
}
negated() {
const handle = exports.temporalz_duration_negated(this._handle);
return Duration._fromHandle(requireHandle(handle));
}
round(options = {}) {
if (options === undefined) options = {};
if (options === null || typeof options !== "object") {
throw new TypeError("round options must be an object");
}
const relativeTo = toRelativeTo(options);
if (!relativeTo && hasCalendarUnits(this)) {
throw new RangeError("relativeTo is required for calendar units");
}
const smallestUnit = toUnitCode(options.smallestUnit, "smallestUnit");
const largestUnit = toUnitCode(options.largestUnit, "largestUnit");
const roundingMode = toRoundingModeCode(options.roundingMode);
let roundingIncrement = 0;
if (options.roundingIncrement !== undefined) {
const inc = Number(options.roundingIncrement);
if (!Number.isFinite(inc) || !Number.isInteger(inc) || inc <= 0) {
throw new RangeError("Invalid roundingIncrement");
}
roundingIncrement = inc;
}
const handle = relativeTo
? exports.temporalz_duration_round_plain_date(
this._handle,
smallestUnit,
largestUnit,
roundingMode,
roundingIncrement,
relativeTo._handle
)
: exports.temporalz_duration_round(
this._handle,
smallestUnit,
largestUnit,
roundingMode,
roundingIncrement
);
return Duration._fromHandle(requireHandle(handle));
}
total(options) {
if (!options || typeof options !== "object") {
throw new TypeError("total options must be an object");
}
const relativeTo = toRelativeTo(options);
if (!relativeTo && hasCalendarUnits(this)) {
throw new RangeError("relativeTo is required for calendar units");
}
const unit = toUnitCode(options.unit, "unit");
if (unit === 255) throw new RangeError("Invalid unit");
const result = relativeTo
? exports.temporalz_duration_total_plain_date(
this._handle,
unit,
relativeTo._handle
)
: exports.temporalz_duration_total(this._handle, unit);
if (!Number.isFinite(result)) throw lastError();
return result;
}
get sign() {
return Number(exports.temporalz_duration_sign(this._handle));
}
get blank() {
return exports.temporalz_duration_blank(this._handle) === 1;
}
toString() {
return takeString(exports.temporalz_duration_to_string(this._handle));
}
toJSON() {
return this.toString();
}
toLocaleString() {
return this.toString();
}
valueOf() {
throw new TypeError("Cannot convert Temporal.Duration to a number");
}
get years() {
return Number(exports.temporalz_duration_years(this._handle));
}
get months() {
return Number(exports.temporalz_duration_months(this._handle));
}
get weeks() {
return Number(exports.temporalz_duration_weeks(this._handle));
}
get days() {
return Number(exports.temporalz_duration_days(this._handle));
}
get hours() {
return Number(exports.temporalz_duration_hours(this._handle));
}
get minutes() {
return Number(exports.temporalz_duration_minutes(this._handle));
}
get seconds() {
return Number(exports.temporalz_duration_seconds(this._handle));
}
get milliseconds() {
return Number(exports.temporalz_duration_milliseconds(this._handle));
}
get microseconds() {
return Number(exports.temporalz_duration_microseconds(this._handle));
}
get nanoseconds() {
return Number(exports.temporalz_duration_nanoseconds(this._handle));
}
}
function hasCalendarUnits(duration) {
return (
duration.years !== 0 ||
duration.months !== 0 ||
duration.weeks !== 0 ||
duration.days !== 0
);
}
function hasYearMonthWeek(duration) {
return duration.years !== 0 || duration.months !== 0 || duration.weeks !== 0;
}
function toRelativeTo(options) {
if (!options || options.relativeTo === undefined) return null;
const rel = options.relativeTo;
if (rel instanceof PlainDate) return rel;
if (typeof rel === "string") return PlainDate.from(rel);
if (rel && typeof rel === "object") return PlainDate.from(rel);
throw new TypeError("Invalid relativeTo");
}
const Temporal = {
Instant,
Duration,
Now: {
instant: unimplemented("Temporal.Now.instant"),
plainDateISO: unimplemented("Temporal.Now.plainDateISO"),
plainDateTimeISO: unimplemented("Temporal.Now.plainDateTimeISO"),
plainTimeISO: unimplemented("Temporal.Now.plainTimeISO"),
timeZoneId: unimplemented("Temporal.Now.timeZoneId"),
zonedDateTimeISO: unimplemented("Temporal.Now.zonedDateTimeISO"),
},
PlainDate,
PlainTime: unimplemented("Temporal.PlainTime"),
PlainDateTime: unimplemented("Temporal.PlainDateTime"),
PlainYearMonth: unimplemented("Temporal.PlainYearMonth"),
PlainMonthDay: unimplemented("Temporal.PlainMonthDay"),
ZonedDateTime: unimplemented("Temporal.ZonedDateTime"),
};
globalThis.Temporal = Temporal;
})();