export const errorConstructors = new Set([ "Error", "TypeError", "RangeError", "SyntaxError", "ReferenceError", "EvalError", "URIError", "AggregateError", ]) export const valueConstructors = new Set(["Date", "RegExp", "Map", "Set", "URL", "URLSearchParams"]) export const compoundOperators = new Set(["+=", "-=", "*=", "/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", ">>>="]) const ErrorBrand: unique symbol = Symbol("codemode.error") export const createErrorValue = (name: string, message: string): SafeObject => { const value = Object.assign(Object.create(null) as SafeObject, { name, message }) Object.defineProperty(value, ErrorBrand, { value: name }) return value } export const createAggregateErrorValue = (errors: Array, message: string): SafeObject => Object.assign(createErrorValue("AggregateError", message), { errors }) export const errorBrandName = (value: unknown): string | undefined => value !== null && typeof value === "object" ? ((value as Record)[ErrorBrand] as string | undefined) : undefined export const boundedData = (value: unknown, label: string): unknown => copyIn(value, label, true) export const coerceToString = (value: unknown): string => { if (value === null) return "null" if (value === undefined) return "undefined" if (value instanceof CodeModeDate) return Number.isFinite(value.time) ? new Date(value.time).toISOString() : "Invalid Date" if (value instanceof CodeModeRegExp) return `/${value.regex.source}/${value.regex.flags}` if (value instanceof CodeModeMap) return "[object Map]" if (value instanceof CodeModeSet) return "[object Set]" if (value instanceof CodeModeURL) return value.url.href if (value instanceof CodeModeURLSearchParams) return value.params.toString() if (errorBrandName(value) !== undefined) { // Match Error.prototype.toString: "name: message", or just one when the other is empty. const error = value as { name?: unknown; message?: unknown } const name = typeof error.name === "string" ? error.name : "Error" const message = typeof error.message === "string" ? error.message : "" if (message === "") return name if (name === "") return message return `${name}: ${message}` } if (typeof value === "object") { return Array.isArray(value) ? value.map((item) => (item === null || item === undefined ? "" : coerceToString(item))).join(",") : "[object Object]" } return String(value) } export const coerceToNumber = (value: unknown): number => { if (value instanceof CodeModeDate) return value.time if (isCodeModeValue(value)) return Number.NaN // Arrays coerce through our own string coercion: host Number(array) joins with host // ToPrimitive, which throws on the null-prototype objects the interpreter produces. if (Array.isArray(value)) return Number(coerceToString(value)) return value !== null && typeof value === "object" ? Number.NaN : Number(value) } export const invokeCoercion = (ref: CoercionFunction, args: Array, node: AstNode): unknown => { // Native: Number() is 0 and String() is "", unlike their undefined-argument forms; the // other coercers match native through the undefined-argument path below. if (args.length === 0) { if (ref.name === "Number") return 0 if (ref.name === "String") return "" } const raw = args[0] // Error values are plain SafeObjects; the boundedData path below would strip their brand. if (ref.name === "String" && errorBrandName(raw) !== undefined) return coerceToString(raw) if (isCodeModeValue(raw)) { if (ref.name === "Boolean") return true if (ref.name === "Number") return coerceToNumber(raw) if (ref.name === "String") return coerceToString(raw) if (ref.name === "isFinite") return Number.isFinite(coerceToNumber(raw)) if (ref.name === "isNaN") return Number.isNaN(coerceToNumber(raw)) if (ref.name === "parseInt") return parseInt(coerceToString(raw)) return parseFloat(coerceToString(raw)) } const value = boundedData(raw, `${ref.name} input`) if (ref.name === "Number") return coerceToNumber(value) if (ref.name === "Boolean") return Boolean(value) if (ref.name === "isFinite") return Number.isFinite(coerceToNumber(value)) if (ref.name === "isNaN") return Number.isNaN(coerceToNumber(value)) if (ref.name === "parseInt") { const radix = args[1] if (radix !== undefined && typeof radix !== "number") { throw new InterpreterRuntimeError("parseInt expects a numeric radix.", node) } return parseInt(coerceToString(value), radix) } if (ref.name === "parseFloat") return parseFloat(coerceToString(value)) return coerceToString(value) } import { type AstNode, CoercionFunction, InterpreterRuntimeError } from "../interpreter/model.js" import { copyIn, type SafeObject } from "../tool-runtime.js" import { isCodeModeValue, CodeModeDate, CodeModeMap, CodeModeRegExp, CodeModeSet, CodeModeURL, CodeModeURLSearchParams, } from "../values.js"