From 6b3ea3d69b69ce51f9f8d041e2f3015af142515d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E8=B1=AA?= <504595380@qq.com> Date: Fri, 27 Oct 2023 16:43:33 +0800 Subject: [PATCH] example: add deno console --- boa_examples/scripts/console.js | 22 + boa_examples/scripts/deno/00_core.js | 393 +++ boa_examples/scripts/deno/01_console.js | 3688 +++++++++++++++++++++++ boa_examples/src/bin/deno_console.rs | 35 + 4 files changed, 4138 insertions(+) create mode 100644 boa_examples/scripts/console.js create mode 100644 boa_examples/scripts/deno/00_core.js create mode 100644 boa_examples/scripts/deno/01_console.js create mode 100644 boa_examples/src/bin/deno_console.rs diff --git a/boa_examples/scripts/console.js b/boa_examples/scripts/console.js new file mode 100644 index 00000000000..3acbd6c4916 --- /dev/null +++ b/boa_examples/scripts/console.js @@ -0,0 +1,22 @@ +console.log(1, "a", false, true, Promise.resolve(1), Symbol(1)); + +console.log(new Set([1, 2, 3, 4])); + +console.log( + new Map([ + [1, 2], + ["a", "b"], + [false, true], + ]), +); + +let a = [1]; +a[1] = a; +console.log(a); + +let b = { a: [1, 2, "a"] }; +b["b"] = b; + +console.log(b); + +console.log(new Error("console error")); diff --git a/boa_examples/scripts/deno/00_core.js b/boa_examples/scripts/deno/00_core.js new file mode 100644 index 00000000000..6bfee64e260 --- /dev/null +++ b/boa_examples/scripts/deno/00_core.js @@ -0,0 +1,393 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// https://github.com/denoland/deno/blob/main/ext/node/polyfills/internal/primordials.mjs +// https://github.com/denoland/deno/blob/main/ext/node/polyfills/internal_binding/util.ts + +const ALL_PROPERTIES = 0; +const ONLY_WRITABLE = 1; +const ONLY_ENUMERABLE = 2; +const ONLY_CONFIGURABLE = 4; +const ONLY_ENUM_WRITABLE = 6; +const SKIP_STRINGS = 8; +const SKIP_SYMBOLS = 16; +const isNumericLookup = {}; +function isArrayIndex(value) { + switch (typeof value) { + case "number": + return value >= 0 && (value | 0) === value; + case "string": { + const result = isNumericLookup[value]; + if (result !== void 0) { + return result; + } + const length = value.length; + if (length === 0) { + return (isNumericLookup[value] = false); + } + let ch = 0; + let i = 0; + for (; i < length; ++i) { + ch = value.charCodeAt(i); + if ((i === 0 && ch === 48 && length > 1) || ch < 48 || ch > 57) { + return (isNumericLookup[value] = false); + } + } + return (isNumericLookup[value] = true); + } + default: + return false; + } +} +function getOwnNonIndexProperties(obj, filter) { + let allProperties = [ + ...Object.getOwnPropertyNames(obj), + ...Object.getOwnPropertySymbols(obj), + ]; + if (Array.isArray(obj)) { + allProperties = allProperties.filter((k) => !isArrayIndex(k)); + } + if (filter === ALL_PROPERTIES) { + return allProperties; + } + const result = []; + for (const key of allProperties) { + const desc = Object.getOwnPropertyDescriptor(obj, key); + if (desc === void 0) { + continue; + } + if (filter & ONLY_WRITABLE && !desc.writable) { + continue; + } + if (filter & ONLY_ENUMERABLE && !desc.enumerable) { + continue; + } + if (filter & ONLY_CONFIGURABLE && !desc.configurable) { + continue; + } + if (filter & SKIP_STRINGS && typeof key === "string") { + continue; + } + if (filter & SKIP_SYMBOLS && typeof key === "symbol") { + continue; + } + result.push(key); + } + return result; +} + +const internals = {}; +const primordials = {}; + +primordials.ArrayBufferPrototypeGetByteLength = (that) => { + if (!ArrayBuffer.isView(that)) { + throw new Error(); + } + that.byteLength; +}; +primordials.ArrayPrototypePushApply = (that, ...args) => that.push(...args); +primordials.MapPrototypeGetSize = (that) => that.size; +primordials.RegExpPrototypeSymbolReplace = (that, ...args) => + RegExp.prototype[Symbol.replace].call(that, ...args); +primordials.SafeArrayIterator = class SafeArrayIterator { + constructor(array) { + this.array = [...array]; + this.index = 0; + } + + next() { + if (this.index < this.array.length) { + return { value: this.array[this.index++], done: false }; + } else { + return { done: true }; + } + } + + [Symbol.iterator]() { + return this; + } +}; +primordials.SafeMap = Map; +primordials.SafeMapIterator = class SafeMapIterator { + get [Symbol.toStringTag]() { + return "Map Iterator"; + } + constructor(map) { + this.map = map; + this.keys = Array.from(map.keys()); + this.index = 0; + } + + next() { + if (this.index < this.keys.length) { + const key = this.keys[this.index]; + const value = this.map.get(key); + this.index++; + return { value: [key, value], done: false }; + } else { + return { done: true }; + } + } + + [Symbol.iterator]() { + return this; + } +}; +primordials.SafeRegExp = RegExp; +primordials.SafeSet = Set; +primordials.SafeSetIterator = class SafeSetIterator { + get [Symbol.toStringTag]() { + return "Set Iterator"; + } + constructor(set) { + this.set = set; + this.values = Array.from(set); + this.index = 0; + } + + next() { + if (this.index < this.values.length) { + const value = this.values[this.index]; + this.index++; + return { value, done: false }; + } else { + return { done: true }; + } + } + + [Symbol.iterator]() { + return this; + } +}; +primordials.SafeStringIterator = class SafeStringIterator { + get [Symbol.toStringTag]() { + return "String Iterator"; + } + constructor(str) { + this.str = str; + this.index = 0; + } + + next() { + if (this.index < this.str.length) { + const char = this.str[this.index]; + this.index++; + return { value: char, done: false }; + } else { + return { done: true }; + } + } + + [Symbol.iterator]() { + return this; + } +}; +primordials.SetPrototypeGetSize = (that) => that.size; +primordials.SymbolPrototypeGetDescription = (that) => that.description; +primordials.TypedArrayPrototypeGetByteLength = (that) => that.byteLength; +primordials.TypedArrayPrototypeGetLength = (that) => that.length; +primordials.TypedArrayPrototypeGetSymbolToStringTag = (that) => { + if (ArrayBuffer.isView(that)) { + return that[Symbol.toStringTag]; + } +}; +primordials.ObjectPrototype = Object.prototype; +primordials.ObjectPrototypeIsPrototypeOf = (that, ...args) => + Object.prototype.isPrototypeOf.call(that, ...args); +primordials.ObjectPrototypePropertyIsEnumerable = (that, ...args) => + Object.prototype.propertyIsEnumerable.call(that, ...args); +primordials.ObjectPrototypeToString = (that, ...args) => + Object.prototype.toString.call(that, ...args); +primordials.ObjectAssign = (...args) => Object.assign(...args); +primordials.ObjectGetOwnPropertyDescriptor = (...args) => + Object.getOwnPropertyDescriptor(...args); +primordials.ObjectGetOwnPropertyNames = (...args) => + Object.getOwnPropertyNames(...args); +primordials.ObjectGetOwnPropertySymbols = (...args) => + Object.getOwnPropertySymbols(...args); +primordials.ObjectHasOwn = (...args) => Object.hasOwn(...args); +primordials.ObjectIs = (...args) => Object.is(...args); +primordials.ObjectCreate = (...args) => Object.create(...args); +primordials.ObjectDefineProperty = (...args) => Object.defineProperty(...args); +primordials.ObjectFreeze = (...args) => Object.freeze(...args); +primordials.ObjectGetPrototypeOf = (...args) => Object.getPrototypeOf(...args); +primordials.ObjectSetPrototypeOf = (...args) => Object.setPrototypeOf(...args); +primordials.ObjectKeys = (...args) => Object.keys(...args); +primordials.ObjectFromEntries = (...args) => Object.fromEntries(...args); +primordials.ObjectValues = (...args) => Object.values(...args); +primordials.FunctionPrototypeBind = (that, ...args) => + Function.prototype.bind.call(that, ...args); +primordials.FunctionPrototypeCall = (that, ...args) => + Function.prototype.call.call(that, ...args); +primordials.FunctionPrototypeToString = (that, ...args) => + Function.prototype.toString.call(that, ...args); +primordials.Array = Array; +primordials.ArrayPrototypeFill = (that, ...args) => + Array.prototype.fill.call(that, ...args); +primordials.ArrayPrototypeFind = (that, ...args) => + Array.prototype.find.call(that, ...args); +primordials.ArrayPrototypePop = (that, ...args) => + Array.prototype.pop.call(that, ...args); +primordials.ArrayPrototypePush = (that, ...args) => + Array.prototype.push.call(that, ...args); +primordials.ArrayPrototypeShift = (that, ...args) => + Array.prototype.shift.call(that, ...args); +primordials.ArrayPrototypeUnshift = (that, ...args) => + Array.prototype.unshift.call(that, ...args); +primordials.ArrayPrototypeSlice = (that, ...args) => + Array.prototype.slice.call(that, ...args); +primordials.ArrayPrototypeSort = (that, ...args) => + Array.prototype.sort.call(that, ...args); +primordials.ArrayPrototypeSplice = (that, ...args) => + Array.prototype.splice.call(that, ...args); +primordials.ArrayPrototypeIncludes = (that, ...args) => + Array.prototype.includes.call(that, ...args); +primordials.ArrayPrototypeJoin = (that, ...args) => + Array.prototype.join.call(that, ...args); +primordials.ArrayPrototypeForEach = (that, ...args) => + Array.prototype.forEach.call(that, ...args); +primordials.ArrayPrototypeFilter = (that, ...args) => + Array.prototype.filter.call(that, ...args); +primordials.ArrayPrototypeMap = (that, ...args) => + Array.prototype.map.call(that, ...args); +primordials.ArrayPrototypeReduce = (that, ...args) => + Array.prototype.reduce.call(that, ...args); +primordials.ArrayIsArray = (...args) => Array.isArray(...args); +primordials.Number = Number; +primordials.NumberPrototypeToString = (that, ...args) => + Number.prototype.toString.call(that, ...args); +primordials.NumberPrototypeValueOf = (that, ...args) => + Number.prototype.valueOf.call(that, ...args); +primordials.NumberIsInteger = (...args) => Number.isInteger(...args); +primordials.NumberParseInt = (...args) => Number.parseInt(...args); +primordials.Boolean = Boolean; +primordials.BooleanPrototypeValueOf = (that, ...args) => + Boolean.prototype.valueOf.call(that, ...args); +primordials.String = String; +primordials.StringPrototypeCharCodeAt = (that, ...args) => + String.prototype.charCodeAt.call(that, ...args); +primordials.StringPrototypeCodePointAt = (that, ...args) => + String.prototype.codePointAt.call(that, ...args); +primordials.StringPrototypeEndsWith = (that, ...args) => + String.prototype.endsWith.call(that, ...args); +primordials.StringPrototypeIncludes = (that, ...args) => + String.prototype.includes.call(that, ...args); +primordials.StringPrototypeIndexOf = (that, ...args) => + String.prototype.indexOf.call(that, ...args); +primordials.StringPrototypeLastIndexOf = (that, ...args) => + String.prototype.lastIndexOf.call(that, ...args); +primordials.StringPrototypeMatch = (that, ...args) => + String.prototype.match.call(that, ...args); +primordials.StringPrototypeNormalize = (that, ...args) => + String.prototype.normalize.call(that, ...args); +primordials.StringPrototypePadEnd = (that, ...args) => + String.prototype.padEnd.call(that, ...args); +primordials.StringPrototypePadStart = (that, ...args) => + String.prototype.padStart.call(that, ...args); +primordials.StringPrototypeRepeat = (that, ...args) => + String.prototype.repeat.call(that, ...args); +primordials.StringPrototypeReplace = (that, ...args) => + String.prototype.replace.call(that, ...args); +primordials.StringPrototypeReplaceAll = (that, ...args) => + String.prototype.replaceAll.call(that, ...args); +primordials.StringPrototypeSlice = (that, ...args) => + String.prototype.slice.call(that, ...args); +primordials.StringPrototypeSplit = (that, ...args) => + String.prototype.split.call(that, ...args); +primordials.StringPrototypeStartsWith = (that, ...args) => + String.prototype.startsWith.call(that, ...args); +primordials.StringPrototypeTrim = (that, ...args) => + String.prototype.trim.call(that, ...args); +primordials.StringPrototypeToLowerCase = (that, ...args) => + String.prototype.toLowerCase.call(that, ...args); +primordials.StringPrototypeValueOf = (that, ...args) => + String.prototype.valueOf.call(that, ...args); +primordials.Symbol = Symbol; +primordials.SymbolPrototypeToString = (that, ...args) => + Symbol.prototype.toString.call(that, ...args); +primordials.SymbolPrototypeValueOf = (that, ...args) => + Symbol.prototype.valueOf.call(that, ...args); +primordials.SymbolFor = (...args) => Symbol.for(...args); +primordials.SymbolHasInstance = Symbol.hasInstance; +primordials.SymbolIterator = Symbol.iterator; +primordials.SymbolToStringTag = Symbol.toStringTag; +primordials.DatePrototype = Date.prototype; +primordials.DatePrototypeToISOString = (that, ...args) => + Date.prototype.toISOString.call(that, ...args); +primordials.DatePrototypeGetTime = (that, ...args) => + Date.prototype.getTime.call(that, ...args); +primordials.DateNow = (...args) => Date.now(...args); +primordials.RegExpPrototypeExec = (that, ...args) => + RegExp.prototype.exec.call(that, ...args); +primordials.RegExpPrototypeToString = (that, ...args) => + RegExp.prototype.toString.call(that, ...args); +primordials.RegExpPrototypeTest = (that, ...args) => + RegExp.prototype.test.call(that, ...args); +primordials.Error = Error; +primordials.ErrorPrototype = Error.prototype; +primordials.ErrorPrototypeToString = (that, ...args) => + Error.prototype.toString.call(that, ...args); +primordials.ErrorCaptureStackTrace = (...args) => + Error.captureStackTrace(...args); +primordials.AggregateErrorPrototype = AggregateError.prototype; +primordials.MathAbs = (...args) => Math.abs(...args); +primordials.MathFloor = (...args) => Math.floor(...args); +primordials.MathMax = (...args) => Math.max(...args); +primordials.MathMin = (...args) => Math.min(...args); +primordials.MathRound = (...args) => Math.round(...args); +primordials.MathSqrt = (...args) => Math.sqrt(...args); +primordials.ArrayBufferIsView = (...args) => ArrayBuffer.isView(...args); +primordials.Uint8Array = Uint8Array; +primordials.MapPrototype = Map.prototype; +primordials.MapPrototypeGet = (that, ...args) => + Map.prototype.get.call(that, ...args); +primordials.MapPrototypeSet = (that, ...args) => + Map.prototype.set.call(that, ...args); +primordials.MapPrototypeHas = (that, ...args) => + Map.prototype.has.call(that, ...args); +primordials.MapPrototypeDelete = (that, ...args) => + Map.prototype.delete.call(that, ...args); +primordials.MapPrototypeEntries = (that, ...args) => + Map.prototype.entries.call(that, ...args); +primordials.MapPrototypeForEach = (that, ...args) => + Map.prototype.forEach.call(that, ...args); +primordials.BigIntPrototypeValueOf = (that, ...args) => + BigInt.prototype.valueOf.call(that, ...args); +primordials.SetPrototype = Set.prototype; +primordials.SetPrototypeHas = (that, ...args) => + Set.prototype.has.call(that, ...args); +primordials.SetPrototypeAdd = (that, ...args) => + Set.prototype.add.call(that, ...args); +primordials.SetPrototypeValues = (that, ...args) => + Set.prototype.values.call(that, ...args); +primordials.WeakMapPrototypeHas = (that, ...args) => + WeakMap.prototype.has.call(that, ...args); +primordials.WeakSetPrototypeHas = (that, ...args) => + WeakSet.prototype.has.call(that, ...args); +primordials.Proxy = Proxy; +primordials.ReflectGet = (...args) => Reflect.get(...args); +primordials.ReflectGetOwnPropertyDescriptor = (...args) => + Reflect.getOwnPropertyDescriptor(...args); +primordials.ReflectGetPrototypeOf = (...args) => + Reflect.getPrototypeOf(...args); +primordials.ReflectHas = (...args) => Reflect.has(...args); +primordials.ReflectOwnKeys = (...args) => Reflect.ownKeys(...args); + +const ops = { + op_get_non_index_property_names: getOwnNonIndexProperties, + op_get_constructor_name(v) { + return Object.prototype.toString.call(v).slice(8, -1); + }, +}; + +globalThis.Deno = { + core: { + ops, + getProxyDetails() { + return null; + }, + }, +}; + +globalThis.__bootstrap = { + internals, + primordials, +}; diff --git a/boa_examples/scripts/deno/01_console.js b/boa_examples/scripts/deno/01_console.js new file mode 100644 index 00000000000..4f2d7fb4a52 --- /dev/null +++ b/boa_examples/scripts/deno/01_console.js @@ -0,0 +1,3688 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// https://github.com/denoland/deno/blob/main/ext/console/01_console.js + +const core = globalThis.Deno.core; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { + AggregateErrorPrototype, + Array, + ArrayBufferIsView, + ArrayBufferPrototypeGetByteLength, + ArrayIsArray, + ArrayPrototypeFill, + ArrayPrototypeFilter, + ArrayPrototypeFind, + ArrayPrototypeForEach, + ArrayPrototypeIncludes, + ArrayPrototypeJoin, + ArrayPrototypeMap, + ArrayPrototypePop, + ArrayPrototypePush, + ArrayPrototypePushApply, + ArrayPrototypeReduce, + ArrayPrototypeShift, + ArrayPrototypeSlice, + ArrayPrototypeSort, + ArrayPrototypeSplice, + ArrayPrototypeUnshift, + BigIntPrototypeValueOf, + Boolean, + BooleanPrototypeValueOf, + DateNow, + DatePrototype, + DatePrototypeGetTime, + DatePrototypeToISOString, + Error, + ErrorCaptureStackTrace, + ErrorPrototype, + ErrorPrototypeToString, + FunctionPrototypeBind, + FunctionPrototypeCall, + FunctionPrototypeToString, + MapPrototype, + MapPrototypeDelete, + MapPrototypeEntries, + MapPrototypeForEach, + MapPrototypeGet, + MapPrototypeGetSize, + MapPrototypeHas, + MapPrototypeSet, + MathAbs, + MathFloor, + MathMax, + MathMin, + MathRound, + MathSqrt, + Number, + NumberIsInteger, + NumberParseInt, + NumberPrototypeToString, + NumberPrototypeValueOf, + ObjectAssign, + ObjectCreate, + ObjectDefineProperty, + ObjectFreeze, + ObjectFromEntries, + ObjectGetOwnPropertyDescriptor, + ObjectGetOwnPropertyNames, + ObjectGetOwnPropertySymbols, + ObjectGetPrototypeOf, + ObjectHasOwn, + ObjectIs, + ObjectKeys, + ObjectPrototype, + ObjectPrototypeIsPrototypeOf, + ObjectPrototypePropertyIsEnumerable, + ObjectPrototypeToString, + ObjectSetPrototypeOf, + ObjectValues, + Proxy, + ReflectGet, + ReflectGetOwnPropertyDescriptor, + ReflectGetPrototypeOf, + ReflectHas, + ReflectOwnKeys, + RegExpPrototypeExec, + RegExpPrototypeSymbolReplace, + RegExpPrototypeTest, + RegExpPrototypeToString, + SafeArrayIterator, + SafeMap, + SafeMapIterator, + SafeRegExp, + SafeSet, + SafeSetIterator, + SafeStringIterator, + SetPrototype, + SetPrototypeAdd, + SetPrototypeHas, + SetPrototypeGetSize, + SetPrototypeValues, + String, + StringPrototypeCharCodeAt, + StringPrototypeCodePointAt, + StringPrototypeEndsWith, + StringPrototypeIncludes, + StringPrototypeIndexOf, + StringPrototypeLastIndexOf, + StringPrototypeMatch, + StringPrototypeNormalize, + StringPrototypePadEnd, + StringPrototypePadStart, + StringPrototypeRepeat, + StringPrototypeReplace, + StringPrototypeReplaceAll, + StringPrototypeSlice, + StringPrototypeSplit, + StringPrototypeStartsWith, + StringPrototypeToLowerCase, + StringPrototypeTrim, + StringPrototypeValueOf, + Symbol, + SymbolFor, + SymbolHasInstance, + SymbolIterator, + SymbolPrototypeGetDescription, + SymbolPrototypeToString, + SymbolPrototypeValueOf, + SymbolToStringTag, + TypedArrayPrototypeGetByteLength, + TypedArrayPrototypeGetLength, + TypedArrayPrototypeGetSymbolToStringTag, + Uint8Array, + WeakMapPrototypeHas, + WeakSetPrototypeHas, + isNaN, +} = primordials; + +let noColor = false; + +function setNoColor(value) { + noColor = value; +} + +function getNoColor() { + return noColor; +} + +function assert(cond, msg = "Assertion failed.") { + if (!cond) { + throw new AssertionError(msg); + } +} + +// Don't use 'blue' not visible on cmd.exe +const styles = { + special: "cyan", + number: "yellow", + bigint: "yellow", + boolean: "yellow", + undefined: "grey", + null: "bold", + string: "green", + symbol: "green", + date: "magenta", + // "name": intentionally not styling + // TODO(BridgeAR): Highlight regular expressions properly. + regexp: "red", + module: "underline", + internalError: "red", +}; + +const defaultFG = 39; +const defaultBG = 49; + +// Set Graphics Rendition https://en.wikipedia.org/wiki/ANSI_escape_code#graphics +// Each color consists of an array with the color code as first entry and the +// reset code as second entry. +const colors = { + reset: [0, 0], + bold: [1, 22], + dim: [2, 22], // Alias: faint + italic: [3, 23], + underline: [4, 24], + blink: [5, 25], + // Swap foreground and background colors + inverse: [7, 27], // Alias: swapcolors, swapColors + hidden: [8, 28], // Alias: conceal + strikethrough: [9, 29], // Alias: strikeThrough, crossedout, crossedOut + doubleunderline: [21, 24], // Alias: doubleUnderline + black: [30, defaultFG], + red: [31, defaultFG], + green: [32, defaultFG], + yellow: [33, defaultFG], + blue: [34, defaultFG], + magenta: [35, defaultFG], + cyan: [36, defaultFG], + white: [37, defaultFG], + bgBlack: [40, defaultBG], + bgRed: [41, defaultBG], + bgGreen: [42, defaultBG], + bgYellow: [43, defaultBG], + bgBlue: [44, defaultBG], + bgMagenta: [45, defaultBG], + bgCyan: [46, defaultBG], + bgWhite: [47, defaultBG], + framed: [51, 54], + overlined: [53, 55], + gray: [90, defaultFG], // Alias: grey, blackBright + redBright: [91, defaultFG], + greenBright: [92, defaultFG], + yellowBright: [93, defaultFG], + blueBright: [94, defaultFG], + magentaBright: [95, defaultFG], + cyanBright: [96, defaultFG], + whiteBright: [97, defaultFG], + bgGray: [100, defaultBG], // Alias: bgGrey, bgBlackBright + bgRedBright: [101, defaultBG], + bgGreenBright: [102, defaultBG], + bgYellowBright: [103, defaultBG], + bgBlueBright: [104, defaultBG], + bgMagentaBright: [105, defaultBG], + bgCyanBright: [106, defaultBG], + bgWhiteBright: [107, defaultBG], +}; + +function defineColorAlias(target, alias) { + ObjectDefineProperty(colors, alias, { + get() { + return this[target]; + }, + set(value) { + this[target] = value; + }, + configurable: true, + enumerable: false, + }); +} + +defineColorAlias("gray", "grey"); +defineColorAlias("gray", "blackBright"); +defineColorAlias("bgGray", "bgGrey"); +defineColorAlias("bgGray", "bgBlackBright"); +defineColorAlias("dim", "faint"); +defineColorAlias("strikethrough", "crossedout"); +defineColorAlias("strikethrough", "strikeThrough"); +defineColorAlias("strikethrough", "crossedOut"); +defineColorAlias("hidden", "conceal"); +defineColorAlias("inverse", "swapColors"); +defineColorAlias("inverse", "swapcolors"); +defineColorAlias("doubleunderline", "doubleUnderline"); + +// https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.bytelength +let _getSharedArrayBufferByteLength; + +function getSharedArrayBufferByteLength(value) { + // TODO(kt3k): add SharedArrayBuffer to primordials + _getSharedArrayBufferByteLength ??= ObjectGetOwnPropertyDescriptor( + // deno-lint-ignore prefer-primordials + SharedArrayBuffer.prototype, + "byteLength", + ).get; + + return FunctionPrototypeCall(_getSharedArrayBufferByteLength, value); +} + +function isObjectLike(value) { + return value !== null && typeof value === "object"; +} + +function isAnyArrayBuffer(value) { + return isArrayBuffer(value) || isSharedArrayBuffer(value); +} + +function isArgumentsObject(value) { + return ( + isObjectLike(value) && + value[SymbolToStringTag] === undefined && + ObjectPrototypeToString(value) === "[object Arguments]" + ); +} + +function isArrayBuffer(value) { + try { + ArrayBufferPrototypeGetByteLength(value); + return true; + } catch { + return false; + } +} + +function isAsyncFunction(value) { + return ( + typeof value === "function" && value[SymbolToStringTag] === "AsyncFunction" + ); +} + +function isAsyncGeneratorFunction(value) { + return ( + typeof value === "function" && + value[SymbolToStringTag] === "AsyncGeneratorFunction" + ); +} + +function isBooleanObject(value) { + if (!isObjectLike(value)) { + return false; + } + + try { + BooleanPrototypeValueOf(value); + return true; + } catch { + return false; + } +} + +function isBoxedPrimitive(value) { + return ( + isBooleanObject(value) || + isStringObject(value) || + isNumberObject(value) || + isSymbolObject(value) || + isBigIntObject(value) + ); +} + +function isDataView(value) { + return ( + ArrayBufferIsView(value) && + TypedArrayPrototypeGetSymbolToStringTag(value) === undefined + ); +} + +function isTypedArray(value) { + return TypedArrayPrototypeGetSymbolToStringTag(value) !== undefined; +} + +function isGeneratorFunction(value) { + return ( + typeof value === "function" && + value[SymbolToStringTag] === "GeneratorFunction" + ); +} + +function isMap(value) { + try { + MapPrototypeGetSize(value); + return true; + } catch { + return false; + } +} + +function isMapIterator(value) { + return isObjectLike(value) && value[SymbolToStringTag] === "Map Iterator"; +} + +function isModuleNamespaceObject(value) { + return isObjectLike(value) && value[SymbolToStringTag] === "Module"; +} + +function isNativeError(value) { + return ( + isObjectLike(value) && + value[SymbolToStringTag] === undefined && + ObjectPrototypeToString(value) === "[object Error]" + ); +} + +function isNumberObject(value) { + if (!isObjectLike(value)) { + return false; + } + + try { + NumberPrototypeValueOf(value); + return true; + } catch { + return false; + } +} + +function isBigIntObject(value) { + if (!isObjectLike(value)) { + return false; + } + + try { + BigIntPrototypeValueOf(value); + return true; + } catch { + return false; + } +} + +function isPromise(value) { + return isObjectLike(value) && value[SymbolToStringTag] === "Promise"; +} +function isRegExp(value) { + return ( + isObjectLike(value) && + value[SymbolToStringTag] === undefined && + ObjectPrototypeToString(value) === "[object RegExp]" + ); +} + +function isSet(value) { + try { + SetPrototypeGetSize(value); + return true; + } catch { + return false; + } +} + +function isSetIterator(value) { + return isObjectLike(value) && value[SymbolToStringTag] === "Set Iterator"; +} + +function isSharedArrayBuffer(value) { + try { + getSharedArrayBufferByteLength(value); + return true; + } catch { + return false; + } +} + +function isStringObject(value) { + if (!isObjectLike(value)) { + return false; + } + + try { + StringPrototypeValueOf(value); + return true; + } catch { + return false; + } +} + +function isSymbolObject(value) { + if (!isObjectLike(value)) { + return false; + } + + try { + SymbolPrototypeValueOf(value); + return true; + } catch { + return false; + } +} + +function isWeakMap(value) { + try { + WeakMapPrototypeHas(value, null); + return true; + } catch { + return false; + } +} + +function isWeakSet(value) { + try { + WeakSetPrototypeHas(value, null); + return true; + } catch { + return false; + } +} + +const kObjectType = 0; +const kArrayType = 1; +const kArrayExtrasType = 2; + +const kMinLineLength = 16; + +// Constants to map the iterator state. +const kWeak = 0; +const kIterator = 1; +const kMapEntries = 2; + +// Escaped control characters (plus the single quote and the backslash). Use +// empty strings to fill up unused entries. +// deno-fmt-ignore +const meta = [ + "\\x00", + "\\x01", + "\\x02", + "\\x03", + "\\x04", + "\\x05", + "\\x06", + "\\x07", // x07 + "\\b", + "\\t", + "\\n", + "\\x0B", + "\\f", + "\\r", + "\\x0E", + "\\x0F", // x0F + "\\x10", + "\\x11", + "\\x12", + "\\x13", + "\\x14", + "\\x15", + "\\x16", + "\\x17", // x17 + "\\x18", + "\\x19", + "\\x1A", + "\\x1B", + "\\x1C", + "\\x1D", + "\\x1E", + "\\x1F", // x1F + "", + "", + "", + "", + "", + "", + "", + "\\'", + "", + "", + "", + "", + "", + "", + "", + "", // x2F + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // x3F + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // x4F + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "\\\\", + "", + "", + "", // x5F + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // x6F + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "\\x7F", // x7F + "\\x80", + "\\x81", + "\\x82", + "\\x83", + "\\x84", + "\\x85", + "\\x86", + "\\x87", // x87 + "\\x88", + "\\x89", + "\\x8A", + "\\x8B", + "\\x8C", + "\\x8D", + "\\x8E", + "\\x8F", // x8F + "\\x90", + "\\x91", + "\\x92", + "\\x93", + "\\x94", + "\\x95", + "\\x96", + "\\x97", // x97 + "\\x98", + "\\x99", + "\\x9A", + "\\x9B", + "\\x9C", + "\\x9D", + "\\x9E", + "\\x9F", // x9F +]; + +// https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot +const isUndetectableObject = (v) => typeof v === "undefined" && v !== undefined; + +const strEscapeSequencesReplacer = new SafeRegExp( + "[\x00-\x1f\x27\x5c\x7f-\x9f]", + "g", +); + +const keyStrRegExp = new SafeRegExp("^[a-zA-Z_][a-zA-Z_0-9]*$"); +const numberRegExp = new SafeRegExp("^(0|[1-9][0-9]*)$"); + +// TODO(wafuwafu13): Figure out +const escapeFn = (str) => meta[StringPrototypeCharCodeAt(str, 0)]; + +function stylizeNoColor(str) { + return str; +} + +// node custom inspect symbol +const nodeCustomInspectSymbol = SymbolFor("nodejs.util.inspect.custom"); + +// This non-unique symbol is used to support op_crates, ie. +// in extensions/web we don't want to depend on public +// Symbol.for("Deno.customInspect") symbol defined in the public API. +// Internal only, shouldn't be used by users. +const privateCustomInspect = SymbolFor("Deno.privateCustomInspect"); + +function getUserOptions(ctx, isCrossContext) { + const ret = { + stylize: ctx.stylize, + showHidden: ctx.showHidden, + depth: ctx.depth, + colors: ctx.colors, + customInspect: ctx.customInspect, + showProxy: ctx.showProxy, + maxArrayLength: ctx.maxArrayLength, + maxStringLength: ctx.maxStringLength, + breakLength: ctx.breakLength, + compact: ctx.compact, + sorted: ctx.sorted, + getters: ctx.getters, + numericSeparator: ctx.numericSeparator, + ...ctx.userOptions, + }; + + // Typically, the target value will be an instance of `Object`. If that is + // *not* the case, the object may come from another vm.Context, and we want + // to avoid passing it objects from this Context in that case, so we remove + // the prototype from the returned object itself + the `stylize()` function, + // and remove all other non-primitives, including non-primitive user options. + if (isCrossContext) { + ObjectSetPrototypeOf(ret, null); + for (const key of new SafeArrayIterator(ObjectKeys(ret))) { + if ( + (typeof ret[key] === "object" || typeof ret[key] === "function") && + ret[key] !== null + ) { + delete ret[key]; + } + } + ret.stylize = ObjectSetPrototypeOf((value, flavour) => { + let stylized; + try { + stylized = `${ctx.stylize(value, flavour)}`; + } catch { + // Continue regardless of error. + } + + if (typeof stylized !== "string") return value; + // `stylized` is a string as it should be, which is safe to pass along. + return stylized; + }, null); + } + + return ret; +} + +// Note: using `formatValue` directly requires the indentation level to be +// corrected by setting `ctx.indentationLvL += diff` and then to decrease the +// value afterwards again. +function formatValue(ctx, value, recurseTimes, typedArray) { + // Primitive types cannot have properties. + if ( + typeof value !== "object" && + typeof value !== "function" && + !isUndetectableObject(value) + ) { + return formatPrimitive(ctx.stylize, value, ctx); + } + if (value === null) { + return ctx.stylize("null", "null"); + } + + // Memorize the context for custom inspection on proxies. + const context = value; + // Always check for proxies to prevent side effects and to prevent triggering + // any proxy handlers. + // TODO(wafuwafu13): Set Proxy + const proxyDetails = core.getProxyDetails(value); + // const proxy = getProxyDetails(value, !!ctx.showProxy); + // if (proxy !== undefined) { + // if (ctx.showProxy) { + // return formatProxy(ctx, proxy, recurseTimes); + // } + // value = proxy; + // } + + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it. + if (ctx.customInspect) { + if ( + ReflectHas(value, customInspect) && + typeof value[customInspect] === "function" + ) { + return String(value[customInspect](inspect, ctx)); + } else if ( + ReflectHas(value, privateCustomInspect) && + typeof value[privateCustomInspect] === "function" + ) { + // TODO(nayeemrmn): `inspect` is passed as an argument because custom + // inspect implementations in `extensions` need it, but may not have access + // to the `Deno` namespace in web workers. Remove when the `Deno` + // namespace is always enabled. + return String(value[privateCustomInspect](inspect, ctx)); + } else if (ReflectHas(value, nodeCustomInspectSymbol)) { + const maybeCustom = value[nodeCustomInspectSymbol]; + if ( + typeof maybeCustom === "function" && + // Filter out the util module, its inspect function is special. + maybeCustom !== ctx.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value) + ) { + // This makes sure the recurseTimes are reported as before while using + // a counter internally. + const depth = ctx.depth === null ? null : ctx.depth - recurseTimes; + // TODO(@crowlKats): proxy handling + const isCrossContext = !ObjectPrototypeIsPrototypeOf( + ObjectPrototype, + context, + ); + const ret = FunctionPrototypeCall( + maybeCustom, + context, + depth, + getUserOptions(ctx, isCrossContext), + ctx.inspect, + ); + // If the custom inspection method returned `this`, don't go into + // infinite recursion. + if (ret !== context) { + if (typeof ret !== "string") { + return formatValue(ctx, ret, recurseTimes); + } + return StringPrototypeReplaceAll( + ret, + "\n", + `\n${StringPrototypeRepeat(" ", ctx.indentationLvl)}`, + ); + } + } + } + } + + // Using an array here is actually better for the average case than using + // a Set. `seen` will only check for the depth and will never grow too large. + if (ArrayPrototypeIncludes(ctx.seen, value)) { + let index = 1; + if (ctx.circular === undefined) { + ctx.circular = new SafeMap(); + MapPrototypeSet(ctx.circular, value, index); + } else { + index = ctx.circular.get(value); + if (index === undefined) { + index = ctx.circular.size + 1; + MapPrototypeSet(ctx.circular, value, index); + } + } + return ctx.stylize(`[Circular *${index}]`, "special"); + } + + return formatRaw(ctx, value, recurseTimes, typedArray, proxyDetails); +} + +function getClassBase(value, constructor, tag) { + const hasName = ObjectHasOwn(value, "name"); + const name = (hasName && value.name) || "(anonymous)"; + let base = `class ${name}`; + if (constructor !== "Function" && constructor !== null) { + base += ` [${constructor}]`; + } + if (tag !== "" && constructor !== tag) { + base += ` [${tag}]`; + } + if (constructor !== null) { + const superName = ObjectGetPrototypeOf(value).name; + if (superName) { + base += ` extends ${superName}`; + } + } else { + base += " extends [null prototype]"; + } + return `[${base}]`; +} + +const stripCommentsRegExp = new SafeRegExp( + "(\\/\\/.*?\\n)|(\\/\\*(.|\\n)*?\\*\\/)", + "g", +); +const classRegExp = new SafeRegExp("^(\\s+[^(]*?)\\s*{"); + +function getFunctionBase(value, constructor, tag) { + const stringified = FunctionPrototypeToString(value); + if ( + StringPrototypeStartsWith(stringified, "class") && + StringPrototypeEndsWith(stringified, "}") + ) { + const slice = StringPrototypeSlice(stringified, 5, -1); + const bracketIndex = StringPrototypeIndexOf(slice, "{"); + if ( + bracketIndex !== -1 && + (!StringPrototypeIncludes( + StringPrototypeSlice(slice, 0, bracketIndex), + "(", + ) || + // Slow path to guarantee that it's indeed a class. + RegExpPrototypeExec( + classRegExp, + RegExpPrototypeSymbolReplace(stripCommentsRegExp, slice), + ) !== null) + ) { + return getClassBase(value, constructor, tag); + } + } + let type = "Function"; + if (isGeneratorFunction(value)) { + type = `Generator${type}`; + } + if (isAsyncFunction(value)) { + type = `Async${type}`; + } + if (isAsyncGeneratorFunction(value)) { + type = `AsyncGenerator${type}`; + } + let base = `[${type}`; + if (constructor === null) { + base += " (null prototype)"; + } + if (value.name === "") { + base += " (anonymous)"; + } else { + base += `: ${value.name}`; + } + base += "]"; + if (constructor !== type && constructor !== null) { + base += ` ${constructor}`; + } + if (tag !== "" && constructor !== tag) { + base += ` [${tag}]`; + } + return base; +} + +function formatRaw(ctx, value, recurseTimes, typedArray, proxyDetails) { + let keys; + let protoProps; + if (ctx.showHidden && (recurseTimes <= ctx.depth || ctx.depth === null)) { + protoProps = []; + } + + const constructor = getConstructorName(value, ctx, recurseTimes, protoProps); + // Reset the variable to check for this later on. + if (protoProps !== undefined && protoProps.length === 0) { + protoProps = undefined; + } + + let tag = value[SymbolToStringTag]; + // Only list the tag in case it's non-enumerable / not an own property. + // Otherwise we'd print this twice. + if ( + typeof tag !== "string" + // TODO(wafuwafu13): Implement + // (tag !== "" && + // (ctx.showHidden + // ? Object.prototype.hasOwnProperty + // : Object.prototype.propertyIsEnumerable)( + // value, + // Symbol.toStringTag, + // )) + ) { + tag = ""; + } + let base = ""; + let formatter = () => []; + let braces; + let noIterator = true; + let i = 0; + const filter = ctx.showHidden ? 0 : 2; + + let extrasType = kObjectType; + + if (proxyDetails != null && ctx.showProxy) { + return `Proxy ` + formatValue(ctx, proxyDetails, recurseTimes); + } else { + // Iterators and the rest are split to reduce checks. + // We have to check all values in case the constructor is set to null. + // Otherwise it would not possible to identify all types properly. + if (ReflectHas(value, SymbolIterator) || constructor === null) { + noIterator = false; + if (ArrayIsArray(value)) { + // Only set the constructor for non ordinary ("Array [...]") arrays. + const prefix = + constructor !== "Array" || tag !== "" + ? getPrefix(constructor, tag, "Array", `(${value.length})`) + : ""; + keys = core.ops.op_get_non_index_property_names(value, filter); + braces = [`${prefix}[`, "]"]; + if ( + value.length === 0 && + keys.length === 0 && + protoProps === undefined + ) { + return `${braces[0]}]`; + } + extrasType = kArrayExtrasType; + formatter = formatArray; + } else if (isSet(value)) { + const size = SetPrototypeGetSize(value); + const prefix = getPrefix(constructor, tag, "Set", `(${size})`); + keys = getKeys(value, ctx.showHidden); + formatter = + constructor !== null + ? FunctionPrototypeBind(formatSet, null, value) + : FunctionPrototypeBind(formatSet, null, SetPrototypeValues(value)); + if (size === 0 && keys.length === 0 && protoProps === undefined) { + return `${prefix}{}`; + } + braces = [`${prefix}{`, "}"]; + } else if (isMap(value)) { + const size = MapPrototypeGetSize(value); + const prefix = getPrefix(constructor, tag, "Map", `(${size})`); + keys = getKeys(value, ctx.showHidden); + formatter = + constructor !== null + ? FunctionPrototypeBind(formatMap, null, value) + : FunctionPrototypeBind( + formatMap, + null, + MapPrototypeEntries(value), + ); + if (size === 0 && keys.length === 0 && protoProps === undefined) { + return `${prefix}{}`; + } + braces = [`${prefix}{`, "}"]; + } else if (isTypedArray(value)) { + keys = core.ops.op_get_non_index_property_names(value, filter); + const bound = value; + const fallback = ""; + if (constructor === null) { + // TODO(wafuwafu13): Implement + // fallback = TypedArrayPrototypeGetSymbolToStringTag(value); + // // Reconstruct the array information. + // bound = new primordials[fallback](value); + } + const size = TypedArrayPrototypeGetLength(value); + const prefix = getPrefix(constructor, tag, fallback, `(${size})`); + braces = [`${prefix}[`, "]"]; + if (value.length === 0 && keys.length === 0 && !ctx.showHidden) { + return `${braces[0]}]`; + } + // Special handle the value. The original value is required below. The + // bound function is required to reconstruct missing information. + formatter = FunctionPrototypeBind(formatTypedArray, null, bound, size); + extrasType = kArrayExtrasType; + } else if (isMapIterator(value)) { + keys = getKeys(value, ctx.showHidden); + braces = getIteratorBraces("Map", tag); + // Add braces to the formatter parameters. + formatter = FunctionPrototypeBind(formatIterator, null, braces); + } else if (isSetIterator(value)) { + keys = getKeys(value, ctx.showHidden); + braces = getIteratorBraces("Set", tag); + // Add braces to the formatter parameters. + formatter = FunctionPrototypeBind(formatIterator, null, braces); + } else { + noIterator = true; + } + } + if (noIterator) { + keys = getKeys(value, ctx.showHidden); + braces = ["{", "}"]; + if (constructor === "Object") { + if (isArgumentsObject(value)) { + braces[0] = "[Arguments] {"; + } else if (tag !== "") { + braces[0] = `${getPrefix(constructor, tag, "Object")}{`; + } + if (keys.length === 0 && protoProps === undefined) { + return `${braces[0]}}`; + } + } else if (typeof value === "function") { + base = getFunctionBase(value, constructor, tag); + if (keys.length === 0 && protoProps === undefined) { + return ctx.stylize(base, "special"); + } + } else if (isRegExp(value)) { + // Make RegExps say that they are RegExps + base = RegExpPrototypeToString( + constructor !== null ? value : new SafeRegExp(value), + ); + const prefix = getPrefix(constructor, tag, "RegExp"); + if (prefix !== "RegExp ") { + base = `${prefix}${base}`; + } + if ( + (keys.length === 0 && protoProps === undefined) || + (recurseTimes > ctx.depth && ctx.depth !== null) + ) { + return ctx.stylize(base, "regexp"); + } + } else if (ObjectPrototypeIsPrototypeOf(DatePrototype, value)) { + const date = proxyDetails ? proxyDetails[0] : value; + if (isNaN(DatePrototypeGetTime(date))) { + return ctx.stylize("Invalid Date", "date"); + } else { + base = DatePrototypeToISOString(date); + if (keys.length === 0 && protoProps === undefined) { + return ctx.stylize(base, "date"); + } + } + } else if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, value)) { + base = inspectError(value, ctx); + if (keys.length === 0 && protoProps === undefined) { + return base; + } + } else if (isAnyArrayBuffer(value)) { + // Fast path for ArrayBuffer and SharedArrayBuffer. + // Can't do the same for DataView because it has a non-primitive + // .buffer property that we need to recurse for. + const arrayType = isArrayBuffer(value) + ? "ArrayBuffer" + : "SharedArrayBuffer"; + + const prefix = getPrefix(constructor, tag, arrayType); + if (typedArray === undefined) { + formatter = formatArrayBuffer; + } else if (keys.length === 0 && protoProps === undefined) { + return ( + prefix + + `{ byteLength: ${formatNumber( + ctx.stylize, + TypedArrayPrototypeGetByteLength(value), + )} }` + ); + } + braces[0] = `${prefix}{`; + ArrayPrototypeUnshift(keys, "byteLength"); + } else if (isDataView(value)) { + braces[0] = `${getPrefix(constructor, tag, "DataView")}{`; + // .buffer goes last, it's not a primitive like the others. + ArrayPrototypeUnshift(keys, "byteLength", "byteOffset", "buffer"); + } else if (isPromise(value)) { + braces[0] = `${getPrefix(constructor, tag, "Promise")}{`; + formatter = formatPromise; + } else if (isWeakSet(value)) { + braces[0] = `${getPrefix(constructor, tag, "WeakSet")}{`; + formatter = ctx.showHidden ? formatWeakSet : formatWeakCollection; + } else if (isWeakMap(value)) { + braces[0] = `${getPrefix(constructor, tag, "WeakMap")}{`; + formatter = ctx.showHidden ? formatWeakMap : formatWeakCollection; + } else if (isModuleNamespaceObject(value)) { + braces[0] = `${getPrefix(constructor, tag, "Module")}{`; + // Special handle keys for namespace objects. + formatter = FunctionPrototypeBind(formatNamespaceObject, null, keys); + } else if (isBoxedPrimitive(value)) { + base = getBoxedBase(value, ctx, keys, constructor, tag); + if (keys.length === 0 && protoProps === undefined) { + return base; + } + } else { + if (keys.length === 0 && protoProps === undefined) { + // TODO(wafuwafu13): Implement + // if (isExternal(value)) { + // const address = getExternalValue(value).toString(16); + // return ctx.stylize(`[External: ${address}]`, 'special'); + // } + return `${getCtxStyle(value, constructor, tag)}{}`; + } + braces[0] = `${getCtxStyle(value, constructor, tag)}{`; + } + } + } + + if (recurseTimes > ctx.depth && ctx.depth !== null) { + let constructorName = StringPrototypeSlice( + getCtxStyle(value, constructor, tag), + 0, + -1, + ); + if (constructor !== null) { + constructorName = `[${constructorName}]`; + } + return ctx.stylize(constructorName, "special"); + } + recurseTimes += 1; + + ArrayPrototypePush(ctx.seen, value); + ctx.currentDepth = recurseTimes; + let output; + try { + output = formatter(ctx, value, recurseTimes); + for (i = 0; i < keys.length; i++) { + ArrayPrototypePush( + output, + formatProperty(ctx, value, recurseTimes, keys[i], extrasType), + ); + } + if (protoProps !== undefined) { + ArrayPrototypePushApply(output, protoProps); + } + } catch (error) { + // TODO(wafuwafu13): Implement stack overflow check + return ctx.stylize( + `[Internal Formatting Error] ${error.stack}`, + "internalError", + ); + } + + if (ctx.circular !== undefined) { + const index = ctx.circular.get(value); + if (index !== undefined) { + const reference = ctx.stylize(``, "special"); + // Add reference always to the very beginning of the output. + if (ctx.compact !== true) { + base = base === "" ? reference : `${reference} ${base}`; + } else { + braces[0] = `${reference} ${braces[0]}`; + } + } + } + ArrayPrototypePop(ctx.seen); + + if (ctx.sorted) { + const comparator = ctx.sorted === true ? undefined : ctx.sorted; + if (extrasType === kObjectType) { + output = ArrayPrototypeSort(output, comparator); + } else if (keys.length > 1) { + const sorted = ArrayPrototypeSort( + ArrayPrototypeSlice(output, output.length - keys.length), + comparator, + ); + ArrayPrototypeSplice( + output, + output.length - keys.length, + keys.length, + ...new SafeArrayIterator(sorted), + ); + } + } + + const res = reduceToSingleString( + ctx, + output, + base, + braces, + extrasType, + recurseTimes, + value, + ); + const budget = ctx.budget[ctx.indentationLvl] || 0; + const newLength = budget + res.length; + ctx.budget[ctx.indentationLvl] = newLength; + // If any indentationLvl exceeds this limit, limit further inspecting to the + // minimum. Otherwise the recursive algorithm might continue inspecting the + // object even though the maximum string size (~2 ** 28 on 32 bit systems and + // ~2 ** 30 on 64 bit systems) exceeded. The actual output is not limited at + // exactly 2 ** 27 but a bit higher. This depends on the object shape. + // This limit also makes sure that huge objects don't block the event loop + // significantly. + if (newLength > 2 ** 27) { + ctx.depth = -1; + } + return res; +} + +const builtInObjectsRegExp = new SafeRegExp("^[A-Z][a-zA-Z0-9]+$"); +const builtInObjects = new SafeSet( + ArrayPrototypeFilter(ObjectGetOwnPropertyNames(globalThis), (e) => + RegExpPrototypeTest(builtInObjectsRegExp, e), + ), +); + +function addPrototypeProperties(ctx, main, obj, recurseTimes, output) { + let depth = 0; + let keys; + let keySet; + do { + if (depth !== 0 || main === obj) { + obj = ObjectGetPrototypeOf(obj); + // Stop as soon as a null prototype is encountered. + if (obj === null) { + return; + } + // Stop as soon as a built-in object type is detected. + const descriptor = ObjectGetOwnPropertyDescriptor(obj, "constructor"); + if ( + descriptor !== undefined && + typeof descriptor.value === "function" && + SetPrototypeHas(builtInObjects, descriptor.value.name) + ) { + return; + } + } + + if (depth === 0) { + keySet = new SafeSet(); + } else { + ArrayPrototypeForEach(keys, (key) => SetPrototypeAdd(keySet, key)); + } + // Get all own property names and symbols. + keys = ReflectOwnKeys(obj); + ArrayPrototypePush(ctx.seen, main); + for (const key of new SafeArrayIterator(keys)) { + // Ignore the `constructor` property and keys that exist on layers above. + if ( + key === "constructor" || + ObjectHasOwn(main, key) || + (depth !== 0 && SetPrototypeHas(keySet, key)) + ) { + continue; + } + const desc = ObjectGetOwnPropertyDescriptor(obj, key); + if (typeof desc.value === "function") { + continue; + } + const value = formatProperty( + ctx, + obj, + recurseTimes, + key, + kObjectType, + desc, + main, + ); + if (ctx.colors) { + // Faint! + ArrayPrototypePush(output, `\u001b[2m${value}\u001b[22m`); + } else { + ArrayPrototypePush(output, value); + } + } + ArrayPrototypePop(ctx.seen); + // Limit the inspection to up to three prototype layers. Using `recurseTimes` + // is not a good choice here, because it's as if the properties are declared + // on the current object from the users perspective. + } while (++depth !== 3); +} + +function isInstanceof(proto, object) { + try { + return ObjectPrototypeIsPrototypeOf(proto, object); + } catch { + return false; + } +} + +function getConstructorName(obj, ctx, recurseTimes, protoProps) { + let firstProto; + const tmp = obj; + while (obj || isUndetectableObject(obj)) { + let descriptor; + try { + descriptor = ObjectGetOwnPropertyDescriptor(obj, "constructor"); + } catch { + /* this could fail */ + } + if ( + descriptor !== undefined && + typeof descriptor.value === "function" && + descriptor.value.name !== "" && + isInstanceof(descriptor.value.prototype, tmp) + ) { + if ( + protoProps !== undefined && + (firstProto !== obj || + !SetPrototypeHas(builtInObjects, descriptor.value.name)) + ) { + addPrototypeProperties( + ctx, + tmp, + firstProto || tmp, + recurseTimes, + protoProps, + ); + } + return String(descriptor.value.name); + } + + obj = ObjectGetPrototypeOf(obj); + if (firstProto === undefined) { + firstProto = obj; + } + } + + if (firstProto === null) { + return null; + } + + const res = core.ops.op_get_constructor_name(tmp); + + if (recurseTimes > ctx.depth && ctx.depth !== null) { + return `${res} `; + } + + const protoConstr = getConstructorName( + firstProto, + ctx, + recurseTimes + 1, + protoProps, + ); + + if (protoConstr === null) { + return `${res} <${inspect(firstProto, { + ...ctx, + customInspect: false, + depth: -1, + })}>`; + } + + return `${res} <${protoConstr}>`; +} + +const formatPrimitiveRegExp = new SafeRegExp("(?<=\n)"); +function formatPrimitive(fn, value, ctx) { + if (typeof value === "string") { + let trailer = ""; + if (value.length > ctx.maxStringLength) { + const remaining = value.length - ctx.maxStringLength; + value = StringPrototypeSlice(value, 0, ctx.maxStringLength); + trailer = `... ${remaining} more character${remaining > 1 ? "s" : ""}`; + } + if ( + ctx.compact !== true && + // TODO(BridgeAR): Add unicode support. Use the readline getStringWidth + // function. + value.length > kMinLineLength && + value.length > ctx.breakLength - ctx.indentationLvl - 4 + ) { + return ( + ArrayPrototypeJoin( + ArrayPrototypeMap( + StringPrototypeSplit(value, formatPrimitiveRegExp), + (line) => fn(quoteString(line, ctx), "string"), + ), + ` +\n${StringPrototypeRepeat(" ", ctx.indentationLvl + 2)}`, + ) + trailer + ); + } + return fn(quoteString(value, ctx), "string") + trailer; + } + if (typeof value === "number") { + return formatNumber(fn, value); + } + if (typeof value === "bigint") { + return formatBigInt(fn, value); + } + if (typeof value === "boolean") { + return fn(`${value}`, "boolean"); + } + if (typeof value === "undefined") { + return fn("undefined", "undefined"); + } + // es6 symbol primitive + return fn(maybeQuoteSymbol(value, ctx), "symbol"); +} + +function getPrefix(constructor, tag, fallback, size = "") { + if (constructor === null) { + if (tag !== "" && fallback !== tag) { + return `[${fallback}${size}: null prototype] [${tag}] `; + } + return `[${fallback}${size}: null prototype] `; + } + + if (tag !== "" && constructor !== tag) { + return `${constructor}${size} [${tag}] `; + } + return `${constructor}${size} `; +} + +function formatArray(ctx, value, recurseTimes) { + const valLen = value.length; + const len = MathMin(MathMax(0, ctx.maxArrayLength), valLen); + + const remaining = valLen - len; + const output = []; + for (let i = 0; i < len; i++) { + // Special handle sparse arrays. + if (!ObjectHasOwn(value, i)) { + return formatSpecialArray(ctx, value, recurseTimes, len, output, i); + } + ArrayPrototypePush( + output, + formatProperty(ctx, value, recurseTimes, i, kArrayType), + ); + } + if (remaining > 0) { + ArrayPrototypePush( + output, + `... ${remaining} more item${remaining > 1 ? "s" : ""}`, + ); + } + return output; +} + +function getCtxStyle(value, constructor, tag) { + let fallback = ""; + if (constructor === null) { + fallback = core.ops.op_get_constructor_name(value); + if (fallback === tag) { + fallback = "Object"; + } + } + return getPrefix(constructor, tag, fallback); +} + +// Look up the keys of the object. +function getKeys(value, showHidden) { + let keys; + const symbols = ObjectGetOwnPropertySymbols(value); + if (showHidden) { + keys = ObjectGetOwnPropertyNames(value); + if (symbols.length !== 0) { + ArrayPrototypePushApply(keys, symbols); + } + } else { + // This might throw if `value` is a Module Namespace Object from an + // unevaluated module, but we don't want to perform the actual type + // check because it's expensive. + // TODO(devsnek): track https://github.com/tc39/ecma262/issues/1209 + // and modify this logic as needed. + try { + keys = ObjectKeys(value); + } catch (err) { + assert( + isNativeError(err) && + err.name === "ReferenceError" && + isModuleNamespaceObject(value), + ); + keys = ObjectGetOwnPropertyNames(value); + } + if (symbols.length !== 0) { + const filter = (key) => ObjectPrototypePropertyIsEnumerable(value, key); + ArrayPrototypePushApply(keys, ArrayPrototypeFilter(symbols, filter)); + } + } + return keys; +} + +function formatSet(value, ctx, _ignored, recurseTimes) { + ctx.indentationLvl += 2; + + const values = [...new SafeSetIterator(value)]; + const valLen = SetPrototypeGetSize(value); + const len = MathMin(MathMax(0, ctx.iterableLimit), valLen); + + const remaining = valLen - len; + const output = []; + for (let i = 0; i < len; i++) { + ArrayPrototypePush(output, formatValue(ctx, values[i], recurseTimes)); + } + if (remaining > 0) { + ArrayPrototypePush( + output, + `... ${remaining} more item${remaining > 1 ? "s" : ""}`, + ); + } + + ctx.indentationLvl -= 2; + return output; +} + +function formatMap(value, ctx, _ignored, recurseTimes) { + ctx.indentationLvl += 2; + + const values = [...new SafeMapIterator(value)]; + const valLen = MapPrototypeGetSize(value); + const len = MathMin(MathMax(0, ctx.iterableLimit), valLen); + + const remaining = valLen - len; + const output = []; + for (let i = 0; i < len; i++) { + ArrayPrototypePush( + output, + `${formatValue(ctx, values[i][0], recurseTimes)} => ${formatValue( + ctx, + values[i][1], + recurseTimes, + )}`, + ); + } + if (remaining > 0) { + ArrayPrototypePush( + output, + `... ${remaining} more item${remaining > 1 ? "s" : ""}`, + ); + } + + ctx.indentationLvl -= 2; + return output; +} + +function formatTypedArray(value, length, ctx, _ignored, recurseTimes) { + const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), length); + const remaining = value.length - maxLength; + const output = new Array(maxLength); + const elementFormatter = + value.length > 0 && typeof value[0] === "number" + ? formatNumber + : formatBigInt; + for (let i = 0; i < maxLength; ++i) { + output[i] = elementFormatter(ctx.stylize, value[i]); + } + if (remaining > 0) { + output[maxLength] = `... ${remaining} more item${remaining > 1 ? "s" : ""}`; + } + if (ctx.showHidden) { + // .buffer goes last, it's not a primitive like the others. + // All besides `BYTES_PER_ELEMENT` are actually getters. + ctx.indentationLvl += 2; + for (const key of new SafeArrayIterator([ + "BYTES_PER_ELEMENT", + "length", + "byteLength", + "byteOffset", + "buffer", + ])) { + const str = formatValue(ctx, value[key], recurseTimes, true); + ArrayPrototypePush(output, `[${key}]: ${str}`); + } + ctx.indentationLvl -= 2; + } + return output; +} + +function getIteratorBraces(type, tag) { + if (tag !== `${type} Iterator`) { + if (tag !== "") { + tag += "] ["; + } + tag += `${type} Iterator`; + } + return [`[${tag}] {`, "}"]; +} + +const iteratorRegExp = new SafeRegExp(" Iterator] {$"); +function formatIterator(braces, ctx, value, recurseTimes) { + // TODO(wafuwafu13): Implement + // const { 0: entries, 1: isKeyValue } = previewEntries(value, true); + const { 0: entries, 1: isKeyValue } = value; + if (isKeyValue) { + // Mark entry iterators as such. + braces[0] = StringPrototypeReplace( + braces[0], + iteratorRegExp, + " Entries] {", + ); + return formatMapIterInner(ctx, recurseTimes, entries, kMapEntries); + } + + return formatSetIterInner(ctx, recurseTimes, entries, kIterator); +} + +function handleCircular(value, ctx) { + let index = 1; + if (ctx.circular === undefined) { + ctx.circular = new SafeMap(); + MapPrototypeSet(ctx.circular, value, index); + } else { + index = MapPrototypeGet(ctx.circular, value); + if (index === undefined) { + index = MapPrototypeGetSize(ctx.circular) + 1; + MapPrototypeSet(ctx.circular, value, index); + } + } + // Circular string is cyan + return ctx.stylize(`[Circular *${index}]`, "special"); +} + +const AGGREGATE_ERROR_HAS_AT_PATTERN = new SafeRegExp(/\s+at/); +const AGGREGATE_ERROR_NOT_EMPTY_LINE_PATTERN = new SafeRegExp(/^(?!\s*$)/gm); + +function inspectError(value, ctx) { + const causes = [value]; + + let err = value; + while (err.cause) { + if (ArrayPrototypeIncludes(causes, err.cause)) { + ArrayPrototypePush(causes, handleCircular(err.cause, ctx)); + break; + } else { + ArrayPrototypePush(causes, err.cause); + err = err.cause; + } + } + + const refMap = new SafeMap(); + for (let i = 0; i < causes.length; ++i) { + const cause = causes[i]; + if (ctx.circular !== undefined) { + const index = MapPrototypeGet(ctx.circular, cause); + if (index !== undefined) { + MapPrototypeSet( + refMap, + cause, + ctx.stylize(` `, "special"), + ); + } + } + } + ArrayPrototypeShift(causes); + + let finalMessage = MapPrototypeGet(refMap, value) ?? ""; + + if (ObjectPrototypeIsPrototypeOf(AggregateErrorPrototype, value)) { + const stackLines = StringPrototypeSplit(value.stack, "\n"); + while (true) { + const line = ArrayPrototypeShift(stackLines); + if (RegExpPrototypeTest(AGGREGATE_ERROR_HAS_AT_PATTERN, line)) { + ArrayPrototypeUnshift(stackLines, line); + break; + } else if (typeof line === "undefined") { + break; + } + + finalMessage += line; + finalMessage += "\n"; + } + const aggregateMessage = ArrayPrototypeJoin( + ArrayPrototypeMap(value.errors, (error) => + StringPrototypeReplace( + inspectArgs([error]), + AGGREGATE_ERROR_NOT_EMPTY_LINE_PATTERN, + StringPrototypeRepeat(" ", 4), + ), + ), + "\n", + ); + finalMessage += aggregateMessage; + finalMessage += "\n"; + finalMessage += ArrayPrototypeJoin(stackLines, "\n"); + } else { + const stack = value.stack; + if (stack?.includes("\n at")) { + finalMessage += stack; + } else { + finalMessage += `[${stack || ErrorPrototypeToString(value)}]`; + } + } + finalMessage += ArrayPrototypeJoin( + ArrayPrototypeMap( + causes, + (cause) => + "\nCaused by " + + (MapPrototypeGet(refMap, cause) ?? "") + + (cause?.stack ?? cause), + ), + "", + ); + + return finalMessage; +} + +const hexSliceLookupTable = (function () { + const alphabet = "0123456789abcdef"; + const table = new Array(256); + for (let i = 0; i < 16; ++i) { + const i16 = i * 16; + for (let j = 0; j < 16; ++j) { + table[i16 + j] = alphabet[i] + alphabet[j]; + } + } + return table; +})(); + +function hexSlice(buf, start, end) { + const len = TypedArrayPrototypeGetLength(buf); + if (!start || start < 0) { + start = 0; + } + if (!end || end < 0 || end > len) { + end = len; + } + let out = ""; + for (let i = start; i < end; ++i) { + out += hexSliceLookupTable[buf[i]]; + } + return out; +} + +const arrayBufferRegExp = new SafeRegExp("(.{2})", "g"); +function formatArrayBuffer(ctx, value) { + let valLen; + try { + valLen = ArrayBufferPrototypeGetByteLength(value); + } catch { + valLen = getSharedArrayBufferByteLength(value); + } + const len = MathMin(MathMax(0, ctx.maxArrayLength), valLen); + let buffer; + try { + buffer = new Uint8Array(value, 0, len); + } catch { + return [ctx.stylize("(detached)", "special")]; + } + let str = StringPrototypeTrim( + StringPrototypeReplace(hexSlice(buffer), arrayBufferRegExp, "$1 "), + ); + + const remaining = valLen - len; + if (remaining > 0) { + str += ` ... ${remaining} more byte${remaining > 1 ? "s" : ""}`; + } + return [`${ctx.stylize("[Uint8Contents]", "special")}: <${str}>`]; +} + +function formatNumber(fn, value) { + // Format -0 as '-0'. Checking `value === -0` won't distinguish 0 from -0. + return fn(ObjectIs(value, -0) ? "-0" : `${value}`, "number"); +} + +const PromiseState = { + Pending: 0, + Fulfilled: 1, + Rejected: 2, +}; + +function formatPromise(ctx, value, recurseTimes) { + let output; + let opResult; + // This op will fail for non-promises, but we get here for some promise-likes. + try { + opResult = core.getPromiseDetails(value); + } catch { + return [ctx.stylize("", "special")]; + } + const { 0: state, 1: result } = opResult; + if (state === PromiseState.Pending) { + output = [ctx.stylize("", "special")]; + } else { + ctx.indentationLvl += 2; + const str = formatValue(ctx, result, recurseTimes); + ctx.indentationLvl -= 2; + output = [ + state === PromiseState.Rejected + ? `${ctx.stylize("", "special")} ${str}` + : str, + ]; + } + return output; +} + +function formatWeakCollection(ctx) { + return [ctx.stylize("", "special")]; +} + +function formatWeakSet(ctx, value, recurseTimes) { + // TODO(wafuwafu13): Implement + // const entries = previewEntries(value); + const entries = value; + return formatSetIterInner(ctx, recurseTimes, entries, kWeak); +} + +function formatWeakMap(ctx, value, recurseTimes) { + // TODO(wafuwafu13): Implement + // const entries = previewEntries(value); + const entries = value; + return formatMapIterInner(ctx, recurseTimes, entries, kWeak); +} + +function formatProperty( + ctx, + value, + recurseTimes, + key, + type, + desc, + original = value, +) { + let name, str; + let extra = " "; + desc = desc || + ObjectGetOwnPropertyDescriptor(value, key) || { + value: value[key], + enumerable: true, + }; + if (desc.value !== undefined) { + const diff = ctx.compact !== true || type !== kObjectType ? 2 : 3; + ctx.indentationLvl += diff; + str = formatValue(ctx, desc.value, recurseTimes); + if (diff === 3 && ctx.breakLength < getStringWidth(str, ctx.colors)) { + extra = `\n${StringPrototypeRepeat(" ", ctx.indentationLvl)}`; + } + ctx.indentationLvl -= diff; + } else if (desc.get !== undefined) { + const label = desc.set !== undefined ? "Getter/Setter" : "Getter"; + const s = ctx.stylize; + const sp = "special"; + if ( + ctx.getters && + (ctx.getters === true || + (ctx.getters === "get" && desc.set === undefined) || + (ctx.getters === "set" && desc.set !== undefined)) + ) { + try { + const tmp = FunctionPrototypeCall(desc.get, original); + ctx.indentationLvl += 2; + if (tmp === null) { + str = `${s(`[${label}:`, sp)} ${s("null", "null")}${s("]", sp)}`; + } else if (typeof tmp === "object") { + str = `${s(`[${label}]`, sp)} ${formatValue(ctx, tmp, recurseTimes)}`; + } else { + const primitive = formatPrimitive(s, tmp, ctx); + str = `${s(`[${label}:`, sp)} ${primitive}${s("]", sp)}`; + } + ctx.indentationLvl -= 2; + } catch (err) { + const message = ``; + str = `${s(`[${label}:`, sp)} ${message}${s("]", sp)}`; + } + } else { + str = ctx.stylize(`[${label}]`, sp); + } + } else if (desc.set !== undefined) { + str = ctx.stylize("[Setter]", "special"); + } else { + str = ctx.stylize("undefined", "undefined"); + } + if (type === kArrayType) { + return str; + } + if (typeof key === "symbol") { + name = `[${ctx.stylize(maybeQuoteSymbol(key, ctx), "symbol")}]`; + } else if (key === "__proto__") { + name = "['__proto__']"; + } else if (desc.enumerable === false) { + const tmp = StringPrototypeReplace( + key, + strEscapeSequencesReplacer, + escapeFn, + ); + + name = `[${tmp}]`; + } else if (keyStrRegExp.test(key)) { + name = ctx.stylize(key, "name"); + } else { + name = ctx.stylize(quoteString(key, ctx), "string"); + } + return `${name}:${extra}${str}`; +} + +const colorRegExp = new SafeRegExp("\u001b\\[\\d\\d?m", "g"); +function removeColors(str) { + return StringPrototypeReplace(str, colorRegExp, ""); +} + +function isBelowBreakLength(ctx, output, start, base) { + // Each entry is separated by at least a comma. Thus, we start with a total + // length of at least `output.length`. In addition, some cases have a + // whitespace in-between each other that is added to the total as well. + // TODO(BridgeAR): Add unicode support. Use the readline getStringWidth + // function. Check the performance overhead and make it an opt-in in case it's + // significant. + let totalLength = output.length + start; + if (totalLength + output.length > ctx.breakLength) { + return false; + } + for (let i = 0; i < output.length; i++) { + if (ctx.colors) { + totalLength += removeColors(output[i]).length; + } else { + totalLength += output[i].length; + } + if (totalLength > ctx.breakLength) { + return false; + } + } + // Do not line up properties on the same line if `base` contains line breaks. + return base === "" || !StringPrototypeIncludes(base, "\n"); +} + +function formatBigInt(fn, value) { + return fn(`${value}n`, "bigint"); +} + +function formatNamespaceObject(keys, ctx, value, recurseTimes) { + const output = new Array(keys.length); + for (let i = 0; i < keys.length; i++) { + try { + output[i] = formatProperty( + ctx, + value, + recurseTimes, + keys[i], + kObjectType, + ); + } catch (_err) { + // TODO(wafuwfu13): Implement + // assert(isNativeError(err) && err.name === 'ReferenceError'); + // Use the existing functionality. This makes sure the indentation and + // line breaks are always correct. Otherwise it is very difficult to keep + // this aligned, even though this is a hacky way of dealing with this. + const tmp = { [keys[i]]: "" }; + output[i] = formatProperty(ctx, tmp, recurseTimes, keys[i], kObjectType); + const pos = StringPrototypeLastIndexOf(output[i], " "); + // We have to find the last whitespace and have to replace that value as + // it will be visualized as a regular string. + output[i] = + StringPrototypeSlice(output[i], 0, pos + 1) + + ctx.stylize("", "special"); + } + } + // Reset the keys to an empty array. This prevents duplicated inspection. + keys.length = 0; + return output; +} + +// The array is sparse and/or has extra keys +function formatSpecialArray(ctx, value, recurseTimes, maxLength, output, i) { + const keys = ObjectKeys(value); + let index = i; + for (; i < keys.length && output.length < maxLength; i++) { + const key = keys[i]; + const tmp = +key; + // Arrays can only have up to 2^32 - 1 entries + if (tmp > 2 ** 32 - 2) { + break; + } + if (`${index}` !== key) { + if (!numberRegExp.test(key)) { + break; + } + const emptyItems = tmp - index; + const ending = emptyItems > 1 ? "s" : ""; + const message = `<${emptyItems} empty item${ending}>`; + ArrayPrototypePush(output, ctx.stylize(message, "undefined")); + index = tmp; + if (output.length === maxLength) { + break; + } + } + ArrayPrototypePush( + output, + formatProperty(ctx, value, recurseTimes, key, kArrayType), + ); + index++; + } + const remaining = value.length - index; + if (output.length !== maxLength) { + if (remaining > 0) { + const ending = remaining > 1 ? "s" : ""; + const message = `<${remaining} empty item${ending}>`; + ArrayPrototypePush(output, ctx.stylize(message, "undefined")); + } + } else if (remaining > 0) { + ArrayPrototypePush( + output, + `... ${remaining} more item${remaining > 1 ? "s" : ""}`, + ); + } + return output; +} + +function getBoxedBase(value, ctx, keys, constructor, tag) { + let type, primitive; + if (isNumberObject(value)) { + type = "Number"; + primitive = NumberPrototypeValueOf(value); + } else if (isStringObject(value)) { + type = "String"; + primitive = StringPrototypeValueOf(value); + // For boxed Strings, we have to remove the 0-n indexed entries, + // since they just noisy up the output and are redundant + // Make boxed primitive Strings look like such + ArrayPrototypeSplice(keys, 0, value.length); + } else if (isBooleanObject(value)) { + type = "Boolean"; + primitive = BooleanPrototypeValueOf(value); + } else if (isBigIntObject(value)) { + type = "BigInt"; + primitive = BigIntPrototypeValueOf(value); + } else { + type = "Symbol"; + primitive = SymbolPrototypeValueOf(value); + } + + let base = `[${type}`; + if (type !== constructor) { + if (constructor === null) { + base += " (null prototype)"; + } else { + base += ` (${constructor})`; + } + } + base += `: ${formatPrimitive(stylizeNoColor, primitive, ctx)}]`; + if (tag !== "" && tag !== constructor) { + base += ` [${tag}]`; + } + if (keys.length !== 0 || ctx.stylize === stylizeNoColor) { + return base; + } + return ctx.stylize(base, StringPrototypeToLowerCase(type)); +} + +function reduceToSingleString( + ctx, + output, + base, + braces, + extrasType, + recurseTimes, + value, +) { + if (ctx.compact !== true) { + if (typeof ctx.compact === "number" && ctx.compact >= 1) { + // Memorize the original output length. In case the output is grouped, + // prevent lining up the entries on a single line. + const entries = output.length; + // Group array elements together if the array contains at least six + // separate entries. + if (extrasType === kArrayExtrasType && entries > 6) { + output = groupArrayElements(ctx, output, value); + } + // `ctx.currentDepth` is set to the most inner depth of the currently + // inspected object part while `recurseTimes` is the actual current depth + // that is inspected. + // + // Example: + // + // const a = { first: [ 1, 2, 3 ], second: { inner: [ 1, 2, 3 ] } } + // + // The deepest depth of `a` is 2 (a.second.inner) and `a.first` has a max + // depth of 1. + // + // Consolidate all entries of the local most inner depth up to + // `ctx.compact`, as long as the properties are smaller than + // `ctx.breakLength`. + if ( + ctx.currentDepth - recurseTimes < ctx.compact && + entries === output.length + ) { + // Line up all entries on a single line in case the entries do not + // exceed `breakLength`. Add 10 as constant to start next to all other + // factors that may reduce `breakLength`. + const start = + output.length + + ctx.indentationLvl + + braces[0].length + + base.length + + 10; + if (isBelowBreakLength(ctx, output, start, base)) { + const joinedOutput = ArrayPrototypeJoin(output, ", "); + if (!StringPrototypeIncludes(joinedOutput, "\n")) { + return ( + `${base ? `${base} ` : ""}${braces[0]} ${joinedOutput}` + + ` ${braces[1]}` + ); + } + } + } + } + // Line up each entry on an individual line. + const indentation = `\n${StringPrototypeRepeat(" ", ctx.indentationLvl)}`; + return ( + `${base ? `${base} ` : ""}${braces[0]}${indentation} ` + + `${ArrayPrototypeJoin(output, `,${indentation} `)}${ + ctx.trailingComma ? "," : "" + }${indentation}${braces[1]}` + ); + } + // Line up all entries on a single line in case the entries do not exceed + // `breakLength`. + if (isBelowBreakLength(ctx, output, 0, base)) { + return ( + `${braces[0]}${base ? ` ${base}` : ""} ${ArrayPrototypeJoin( + output, + ", ", + )} ` + braces[1] + ); + } + const indentation = StringPrototypeRepeat(" ", ctx.indentationLvl); + // If the opening "brace" is too large, like in the case of "Set {", + // we need to force the first item to be on the next line or the + // items will not line up correctly. + const ln = + base === "" && braces[0].length === 1 + ? " " + : `${base ? ` ${base}` : ""}\n${indentation} `; + // Line up each entry on an individual line. + return `${braces[0]}${ln}${ArrayPrototypeJoin( + output, + `,\n${indentation} `, + )} ${braces[1]}`; +} + +function groupArrayElements(ctx, output, value) { + let totalLength = 0; + let maxLength = 0; + let i = 0; + let outputLength = output.length; + if (ctx.maxArrayLength < output.length) { + // This makes sure the "... n more items" part is not taken into account. + outputLength--; + } + const separatorSpace = 2; // Add 1 for the space and 1 for the separator. + const dataLen = new Array(outputLength); + // Calculate the total length of all output entries and the individual max + // entries length of all output entries. We have to remove colors first, + // otherwise the length would not be calculated properly. + for (; i < outputLength; i++) { + const len = getStringWidth(output[i], ctx.colors); + dataLen[i] = len; + totalLength += len + separatorSpace; + if (maxLength < len) { + maxLength = len; + } + } + // Add two to `maxLength` as we add a single whitespace character plus a comma + // in-between two entries. + const actualMax = maxLength + separatorSpace; + // Check if at least three entries fit next to each other and prevent grouping + // of arrays that contains entries of very different length (i.e., if a single + // entry is longer than 1/5 of all other entries combined). Otherwise the + // space in-between small entries would be enormous. + if ( + actualMax * 3 + ctx.indentationLvl < ctx.breakLength && + (totalLength / actualMax > 5 || maxLength <= 6) + ) { + const approxCharHeights = 2.5; + const averageBias = MathSqrt(actualMax - totalLength / output.length); + const biasedMax = MathMax(actualMax - 3 - averageBias, 1); + // Dynamically check how many columns seem possible. + const columns = MathMin( + // Ideally a square should be drawn. We expect a character to be about 2.5 + // times as high as wide. This is the area formula to calculate a square + // which contains n rectangles of size `actualMax * approxCharHeights`. + // Divide that by `actualMax` to receive the correct number of columns. + // The added bias increases the columns for short entries. + MathRound( + MathSqrt(approxCharHeights * biasedMax * outputLength) / biasedMax, + ), + // Do not exceed the breakLength. + MathFloor((ctx.breakLength - ctx.indentationLvl) / actualMax), + // Limit array grouping for small `compact` modes as the user requested + // minimal grouping. + ctx.compact * 4, + // Limit the columns to a maximum of fifteen. + 15, + ); + // Return with the original output if no grouping should happen. + if (columns <= 1) { + return output; + } + const tmp = []; + const maxLineLength = []; + for (let i = 0; i < columns; i++) { + let lineMaxLength = 0; + for (let j = i; j < output.length; j += columns) { + if (dataLen[j] > lineMaxLength) { + lineMaxLength = dataLen[j]; + } + } + lineMaxLength += separatorSpace; + maxLineLength[i] = lineMaxLength; + } + let order = StringPrototypePadStart; + if (value !== undefined) { + for (let i = 0; i < output.length; i++) { + if (typeof value[i] !== "number" && typeof value[i] !== "bigint") { + order = StringPrototypePadEnd; + break; + } + } + } + // Each iteration creates a single line of grouped entries. + for (let i = 0; i < outputLength; i += columns) { + // The last lines may contain less entries than columns. + const max = MathMin(i + columns, outputLength); + let str = ""; + let j = i; + for (; j < max - 1; j++) { + // Calculate extra color padding in case it's active. This has to be + // done line by line as some lines might contain more colors than + // others. + const padding = maxLineLength[j - i] + output[j].length - dataLen[j]; + str += order(`${output[j]}, `, padding, " "); + } + if (order === StringPrototypePadStart) { + const padding = + maxLineLength[j - i] + output[j].length - dataLen[j] - separatorSpace; + str += StringPrototypePadStart(output[j], padding, " "); + } else { + str += output[j]; + } + ArrayPrototypePush(tmp, str); + } + if (ctx.maxArrayLength < output.length) { + ArrayPrototypePush(tmp, output[outputLength]); + } + output = tmp; + } + return output; +} + +function formatMapIterInner(ctx, recurseTimes, entries, state) { + const maxArrayLength = MathMax(ctx.maxArrayLength, 0); + // Entries exist as [key1, val1, key2, val2, ...] + const len = entries.length / 2; + const remaining = len - maxArrayLength; + const maxLength = MathMin(maxArrayLength, len); + const output = new Array(maxLength); + let i = 0; + ctx.indentationLvl += 2; + if (state === kWeak) { + for (; i < maxLength; i++) { + const pos = i * 2; + output[i] = `${formatValue( + ctx, + entries[pos], + recurseTimes, + )} => ${formatValue(ctx, entries[pos + 1], recurseTimes)}`; + } + // Sort all entries to have a halfway reliable output (if more entries than + // retrieved ones exist, we can not reliably return the same output) if the + // output is not sorted anyway. + if (!ctx.sorted) { + ArrayPrototypeSort(output); + } + } else { + for (; i < maxLength; i++) { + const pos = i * 2; + const res = [ + formatValue(ctx, entries[pos], recurseTimes), + formatValue(ctx, entries[pos + 1], recurseTimes), + ]; + output[i] = reduceToSingleString( + ctx, + res, + "", + ["[", "]"], + kArrayExtrasType, + recurseTimes, + ); + } + } + ctx.indentationLvl -= 2; + if (remaining > 0) { + ArrayPrototypePush( + output, + `... ${remaining} more item${remaining > 1 ? "s" : ""}`, + ); + } + return output; +} + +function formatSetIterInner(ctx, recurseTimes, entries, state) { + const maxArrayLength = MathMax(ctx.maxArrayLength, 0); + const maxLength = MathMin(maxArrayLength, entries.length); + const output = new Array(maxLength); + ctx.indentationLvl += 2; + for (let i = 0; i < maxLength; i++) { + output[i] = formatValue(ctx, entries[i], recurseTimes); + } + ctx.indentationLvl -= 2; + if (state === kWeak && !ctx.sorted) { + // Sort all entries to have a halfway reliable output (if more entries than + // retrieved ones exist, we can not reliably return the same output) if the + // output is not sorted anyway. + ArrayPrototypeSort(output); + } + const remaining = entries.length - maxLength; + if (remaining > 0) { + ArrayPrototypePush( + output, + `... ${remaining} more item${remaining > 1 ? "s" : ""}`, + ); + } + return output; +} + +// Regex used for ansi escape code splitting +// Adopted from https://github.com/chalk/ansi-regex/blob/HEAD/index.js +// License: MIT, authors: @sindresorhus, Qix-, arjunmehta and LitoMore +// Matches all ansi escape code sequences in a string +const ansiPattern = + "[\\u001B\\u009B][[\\]()#;?]*" + + "(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*" + + "|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)" + + "|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"; +const ansi = new SafeRegExp(ansiPattern, "g"); + +/** + * Returns the number of columns required to display the given string. + */ +function getStringWidth(str, removeControlChars = true) { + let width = 0; + + if (removeControlChars) { + str = stripVTControlCharacters(str); + } + str = StringPrototypeNormalize(str, "NFC"); + for (const char of new SafeStringIterator(str)) { + const code = StringPrototypeCodePointAt(char, 0); + if (isFullWidthCodePoint(code)) { + width += 2; + } else if (!isZeroWidthCodePoint(code)) { + width++; + } + } + + return width; +} + +const isZeroWidthCodePoint = (code) => { + return ( + code <= 0x1f || // C0 control codes + (code >= 0x7f && code <= 0x9f) || // C1 control codes + (code >= 0x300 && code <= 0x36f) || // Combining Diacritical Marks + (code >= 0x200b && code <= 0x200f) || // Modifying Invisible Characters + // Combining Diacritical Marks for Symbols + (code >= 0x20d0 && code <= 0x20ff) || + (code >= 0xfe00 && code <= 0xfe0f) || // Variation Selectors + (code >= 0xfe20 && code <= 0xfe2f) || // Combining Half Marks + (code >= 0xe0100 && code <= 0xe01ef) + ); // Variation Selectors +}; + +/** + * Remove all VT control characters. Use to estimate displayed string width. + */ +function stripVTControlCharacters(str) { + return StringPrototypeReplace(str, ansi, ""); +} + +function hasOwnProperty(obj, v) { + if (obj == null) { + return false; + } + return ObjectHasOwn(obj, v); +} + +// Copyright Joyent, Inc. and other Node contributors. MIT license. +// Forked from Node's lib/internal/cli_table.js + +const tableChars = { + middleMiddle: "\u2500", + rowMiddle: "\u253c", + topRight: "\u2510", + topLeft: "\u250c", + leftMiddle: "\u251c", + topMiddle: "\u252c", + bottomRight: "\u2518", + bottomLeft: "\u2514", + bottomMiddle: "\u2534", + rightMiddle: "\u2524", + left: "\u2502 ", + right: " \u2502", + middle: " \u2502 ", +}; + +function isFullWidthCodePoint(code) { + // Code points are partially derived from: + // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt + return ( + code >= 0x1100 && + (code <= 0x115f || // Hangul Jamo + code === 0x2329 || // LEFT-POINTING ANGLE BRACKET + code === 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK Radicals Supplement .. Enclosed CJK Letters and Months + (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) || + // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A + (code >= 0x3250 && code <= 0x4dbf) || + // CJK Unified Ideographs .. Yi Radicals + (code >= 0x4e00 && code <= 0xa4c6) || + // Hangul Jamo Extended-A + (code >= 0xa960 && code <= 0xa97c) || + // Hangul Syllables + (code >= 0xac00 && code <= 0xd7a3) || + // CJK Compatibility Ideographs + (code >= 0xf900 && code <= 0xfaff) || + // Vertical Forms + (code >= 0xfe10 && code <= 0xfe19) || + // CJK Compatibility Forms .. Small Form Variants + (code >= 0xfe30 && code <= 0xfe6b) || + // Halfwidth and Fullwidth Forms + (code >= 0xff01 && code <= 0xff60) || + (code >= 0xffe0 && code <= 0xffe6) || + // Kana Supplement + (code >= 0x1b000 && code <= 0x1b001) || + // Enclosed Ideographic Supplement + (code >= 0x1f200 && code <= 0x1f251) || + // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff + // Emoticons 0x1f600 - 0x1f64f + (code >= 0x1f300 && code <= 0x1f64f) || + // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane + (code >= 0x20000 && code <= 0x3fffd)) + ); +} + +function renderRow(row, columnWidths, columnRightAlign) { + let out = tableChars.left; + for (let i = 0; i < row.length; i++) { + const cell = row[i]; + const len = getStringWidth(cell); + const padding = StringPrototypeRepeat(" ", columnWidths[i] - len); + if (columnRightAlign?.[i]) { + out += `${padding}${cell}`; + } else { + out += `${cell}${padding}`; + } + if (i !== row.length - 1) { + out += tableChars.middle; + } + } + out += tableChars.right; + return out; +} + +function cliTable(head, columns) { + const rows = []; + const columnWidths = ArrayPrototypeMap(head, (h) => getStringWidth(h)); + const longestColumn = ArrayPrototypeReduce( + columns, + (n, a) => MathMax(n, a.length), + 0, + ); + const columnRightAlign = ArrayPrototypeFill( + new Array(columnWidths.length), + true, + ); + + for (let i = 0; i < head.length; i++) { + const column = columns[i]; + for (let j = 0; j < longestColumn; j++) { + if (rows[j] === undefined) { + rows[j] = []; + } + const value = (rows[j][i] = hasOwnProperty(column, j) ? column[j] : ""); + const width = columnWidths[i] || 0; + const counted = getStringWidth(value); + columnWidths[i] = MathMax(width, counted); + columnRightAlign[i] &= NumberIsInteger(+value); + } + } + + const divider = ArrayPrototypeMap(columnWidths, (i) => + StringPrototypeRepeat(tableChars.middleMiddle, i + 2), + ); + + let result = + `${tableChars.topLeft}${ArrayPrototypeJoin( + divider, + tableChars.topMiddle, + )}` + + `${tableChars.topRight}\n${renderRow(head, columnWidths)}\n` + + `${tableChars.leftMiddle}${ArrayPrototypeJoin( + divider, + tableChars.rowMiddle, + )}` + + `${tableChars.rightMiddle}\n`; + + for (let i = 0; i < rows.length; ++i) { + const row = rows[i]; + result += `${renderRow(row, columnWidths, columnRightAlign)}\n`; + } + + result += + `${tableChars.bottomLeft}${ArrayPrototypeJoin( + divider, + tableChars.bottomMiddle, + )}` + tableChars.bottomRight; + + return result; +} +/* End of forked part */ + +// We can match Node's quoting behavior exactly by swapping the double quote and +// single quote in this array. That would give preference to single quotes. +// However, we prefer double quotes as the default. + +const denoInspectDefaultOptions = { + indentationLvl: 0, + currentDepth: 0, + stylize: stylizeNoColor, + + showHidden: false, + depth: 4, + colors: false, + showProxy: false, + breakLength: 80, + escapeSequences: true, + compact: 3, + sorted: false, + getters: false, + + // node only + maxArrayLength: 100, + maxStringLength: 100, // deno: strAbbreviateSize: 100 + customInspect: true, + + // deno only + /** You can override the quotes preference in inspectString. + * Used by util.inspect() */ + // TODO(kt3k): Consider using symbol as a key to hide this from the public + // API. + quotes: ['"', "'", "`"], + iterableLimit: 100, // similar to node's maxArrayLength, but doesn't only apply to arrays + trailingComma: false, + + inspect, + + // TODO(@crowlKats): merge into indentationLvl + indentLevel: 0, +}; + +function getDefaultInspectOptions() { + return { + budget: {}, + seen: [], + ...denoInspectDefaultOptions, + }; +} + +const DEFAULT_INDENT = " "; // Default indent string + +const STR_ABBREVIATE_SIZE = 100; + +class CSI { + static kClear = "\x1b[1;1H"; + static kClearScreenDown = "\x1b[0J"; +} + +const QUOTE_SYMBOL_REG = new SafeRegExp(/^[a-zA-Z_][a-zA-Z_.0-9]*$/); + +function maybeQuoteSymbol(symbol, ctx) { + const description = SymbolPrototypeGetDescription(symbol); + + if (description === undefined) { + return SymbolPrototypeToString(symbol); + } + + if (RegExpPrototypeTest(QUOTE_SYMBOL_REG, description)) { + return SymbolPrototypeToString(symbol); + } + + return `Symbol(${quoteString(description, ctx)})`; +} + +/** Surround the string in quotes. + * + * The quote symbol is chosen by taking the first of the `QUOTES` array which + * does not occur in the string. If they all occur, settle with `QUOTES[0]`. + * + * Insert a backslash before any occurrence of the chosen quote symbol and + * before any backslash. + */ +function quoteString(string, ctx) { + const quote = + ArrayPrototypeFind( + ctx.quotes, + (c) => !StringPrototypeIncludes(string, c), + ) ?? ctx.quotes[0]; + const escapePattern = new SafeRegExp(`(?=[${quote}\\\\])`, "g"); + string = StringPrototypeReplace(string, escapePattern, "\\"); + if (ctx.escapeSequences) { + string = replaceEscapeSequences(string); + } + return `${quote}${string}${quote}`; +} + +const ESCAPE_PATTERN = new SafeRegExp(/([\b\f\n\r\t\v])/g); +const ESCAPE_MAP = ObjectFreeze({ + "\b": "\\b", + "\f": "\\f", + "\n": "\\n", + "\r": "\\r", + "\t": "\\t", + "\v": "\\v", +}); + +const ESCAPE_PATTERN2 = new SafeRegExp("[\x00-\x1f\x7f-\x9f]", "g"); + +// Replace escape sequences that can modify output. +function replaceEscapeSequences(string) { + return StringPrototypeReplace( + StringPrototypeReplace(string, ESCAPE_PATTERN, (c) => ESCAPE_MAP[c]), + ESCAPE_PATTERN2, + (c) => + "\\x" + + StringPrototypePadStart( + NumberPrototypeToString(StringPrototypeCharCodeAt(c, 0), 16), + 2, + "0", + ), + ); +} + +// Print strings when they are inside of arrays or objects with quotes +function inspectValueWithQuotes(value, ctx) { + const abbreviateSize = + typeof ctx.strAbbreviateSize === "undefined" + ? STR_ABBREVIATE_SIZE + : ctx.strAbbreviateSize; + switch (typeof value) { + case "string": { + const trunc = + value.length > abbreviateSize + ? StringPrototypeSlice(value, 0, abbreviateSize) + "..." + : value; + return ctx.stylize(quoteString(trunc, ctx), "string"); // Quoted strings are green + } + default: + return formatValue(ctx, value, 0); + } +} + +const colorKeywords = new SafeMap([ + ["black", "#000000"], + ["silver", "#c0c0c0"], + ["gray", "#808080"], + ["white", "#ffffff"], + ["maroon", "#800000"], + ["red", "#ff0000"], + ["purple", "#800080"], + ["fuchsia", "#ff00ff"], + ["green", "#008000"], + ["lime", "#00ff00"], + ["olive", "#808000"], + ["yellow", "#ffff00"], + ["navy", "#000080"], + ["blue", "#0000ff"], + ["teal", "#008080"], + ["aqua", "#00ffff"], + ["orange", "#ffa500"], + ["aliceblue", "#f0f8ff"], + ["antiquewhite", "#faebd7"], + ["aquamarine", "#7fffd4"], + ["azure", "#f0ffff"], + ["beige", "#f5f5dc"], + ["bisque", "#ffe4c4"], + ["blanchedalmond", "#ffebcd"], + ["blueviolet", "#8a2be2"], + ["brown", "#a52a2a"], + ["burlywood", "#deb887"], + ["cadetblue", "#5f9ea0"], + ["chartreuse", "#7fff00"], + ["chocolate", "#d2691e"], + ["coral", "#ff7f50"], + ["cornflowerblue", "#6495ed"], + ["cornsilk", "#fff8dc"], + ["crimson", "#dc143c"], + ["cyan", "#00ffff"], + ["darkblue", "#00008b"], + ["darkcyan", "#008b8b"], + ["darkgoldenrod", "#b8860b"], + ["darkgray", "#a9a9a9"], + ["darkgreen", "#006400"], + ["darkgrey", "#a9a9a9"], + ["darkkhaki", "#bdb76b"], + ["darkmagenta", "#8b008b"], + ["darkolivegreen", "#556b2f"], + ["darkorange", "#ff8c00"], + ["darkorchid", "#9932cc"], + ["darkred", "#8b0000"], + ["darksalmon", "#e9967a"], + ["darkseagreen", "#8fbc8f"], + ["darkslateblue", "#483d8b"], + ["darkslategray", "#2f4f4f"], + ["darkslategrey", "#2f4f4f"], + ["darkturquoise", "#00ced1"], + ["darkviolet", "#9400d3"], + ["deeppink", "#ff1493"], + ["deepskyblue", "#00bfff"], + ["dimgray", "#696969"], + ["dimgrey", "#696969"], + ["dodgerblue", "#1e90ff"], + ["firebrick", "#b22222"], + ["floralwhite", "#fffaf0"], + ["forestgreen", "#228b22"], + ["gainsboro", "#dcdcdc"], + ["ghostwhite", "#f8f8ff"], + ["gold", "#ffd700"], + ["goldenrod", "#daa520"], + ["greenyellow", "#adff2f"], + ["grey", "#808080"], + ["honeydew", "#f0fff0"], + ["hotpink", "#ff69b4"], + ["indianred", "#cd5c5c"], + ["indigo", "#4b0082"], + ["ivory", "#fffff0"], + ["khaki", "#f0e68c"], + ["lavender", "#e6e6fa"], + ["lavenderblush", "#fff0f5"], + ["lawngreen", "#7cfc00"], + ["lemonchiffon", "#fffacd"], + ["lightblue", "#add8e6"], + ["lightcoral", "#f08080"], + ["lightcyan", "#e0ffff"], + ["lightgoldenrodyellow", "#fafad2"], + ["lightgray", "#d3d3d3"], + ["lightgreen", "#90ee90"], + ["lightgrey", "#d3d3d3"], + ["lightpink", "#ffb6c1"], + ["lightsalmon", "#ffa07a"], + ["lightseagreen", "#20b2aa"], + ["lightskyblue", "#87cefa"], + ["lightslategray", "#778899"], + ["lightslategrey", "#778899"], + ["lightsteelblue", "#b0c4de"], + ["lightyellow", "#ffffe0"], + ["limegreen", "#32cd32"], + ["linen", "#faf0e6"], + ["magenta", "#ff00ff"], + ["mediumaquamarine", "#66cdaa"], + ["mediumblue", "#0000cd"], + ["mediumorchid", "#ba55d3"], + ["mediumpurple", "#9370db"], + ["mediumseagreen", "#3cb371"], + ["mediumslateblue", "#7b68ee"], + ["mediumspringgreen", "#00fa9a"], + ["mediumturquoise", "#48d1cc"], + ["mediumvioletred", "#c71585"], + ["midnightblue", "#191970"], + ["mintcream", "#f5fffa"], + ["mistyrose", "#ffe4e1"], + ["moccasin", "#ffe4b5"], + ["navajowhite", "#ffdead"], + ["oldlace", "#fdf5e6"], + ["olivedrab", "#6b8e23"], + ["orangered", "#ff4500"], + ["orchid", "#da70d6"], + ["palegoldenrod", "#eee8aa"], + ["palegreen", "#98fb98"], + ["paleturquoise", "#afeeee"], + ["palevioletred", "#db7093"], + ["papayawhip", "#ffefd5"], + ["peachpuff", "#ffdab9"], + ["peru", "#cd853f"], + ["pink", "#ffc0cb"], + ["plum", "#dda0dd"], + ["powderblue", "#b0e0e6"], + ["rosybrown", "#bc8f8f"], + ["royalblue", "#4169e1"], + ["saddlebrown", "#8b4513"], + ["salmon", "#fa8072"], + ["sandybrown", "#f4a460"], + ["seagreen", "#2e8b57"], + ["seashell", "#fff5ee"], + ["sienna", "#a0522d"], + ["skyblue", "#87ceeb"], + ["slateblue", "#6a5acd"], + ["slategray", "#708090"], + ["slategrey", "#708090"], + ["snow", "#fffafa"], + ["springgreen", "#00ff7f"], + ["steelblue", "#4682b4"], + ["tan", "#d2b48c"], + ["thistle", "#d8bfd8"], + ["tomato", "#ff6347"], + ["turquoise", "#40e0d0"], + ["violet", "#ee82ee"], + ["wheat", "#f5deb3"], + ["whitesmoke", "#f5f5f5"], + ["yellowgreen", "#9acd32"], + ["rebeccapurple", "#663399"], +]); + +const HASH_PATTERN = new SafeRegExp( + /^#([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})?$/, +); +const SMALL_HASH_PATTERN = new SafeRegExp( + /^#([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])?$/, +); +const RGB_PATTERN = new SafeRegExp( + /^rgba?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/, +); +const HSL_PATTERN = new SafeRegExp( + /^hsla?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)%\s*,\s*([+\-]?\d*\.?\d+)%\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/, +); + +function parseCssColor(colorString) { + if (MapPrototypeHas(colorKeywords, colorString)) { + colorString = MapPrototypeGet(colorKeywords, colorString); + } + // deno-fmt-ignore + const hashMatch = StringPrototypeMatch(colorString, HASH_PATTERN); + if (hashMatch != null) { + return [ + Number(`0x${hashMatch[1]}`), + Number(`0x${hashMatch[2]}`), + Number(`0x${hashMatch[3]}`), + ]; + } + // deno-fmt-ignore + const smallHashMatch = StringPrototypeMatch(colorString, SMALL_HASH_PATTERN); + if (smallHashMatch != null) { + return [ + Number(`0x${smallHashMatch[1]}${smallHashMatch[1]}`), + Number(`0x${smallHashMatch[2]}${smallHashMatch[2]}`), + Number(`0x${smallHashMatch[3]}${smallHashMatch[3]}`), + ]; + } + // deno-fmt-ignore + const rgbMatch = StringPrototypeMatch(colorString, RGB_PATTERN); + if (rgbMatch != null) { + return [ + MathRound(MathMax(0, MathMin(255, Number(rgbMatch[1])))), + MathRound(MathMax(0, MathMin(255, Number(rgbMatch[2])))), + MathRound(MathMax(0, MathMin(255, Number(rgbMatch[3])))), + ]; + } + // deno-fmt-ignore + const hslMatch = StringPrototypeMatch(colorString, HSL_PATTERN); + if (hslMatch != null) { + // https://www.rapidtables.com/convert/color/hsl-to-rgb.html + let h = Number(hslMatch[1]) % 360; + if (h < 0) { + h += 360; + } + const s = MathMax(0, MathMin(100, Number(hslMatch[2]))) / 100; + const l = MathMax(0, MathMin(100, Number(hslMatch[3]))) / 100; + const c = (1 - MathAbs(2 * l - 1)) * s; + const x = c * (1 - MathAbs(((h / 60) % 2) - 1)); + const m = l - c / 2; + let r_; + let g_; + let b_; + if (h < 60) { + ({ 0: r_, 1: g_, 2: b_ } = [c, x, 0]); + } else if (h < 120) { + ({ 0: r_, 1: g_, 2: b_ } = [x, c, 0]); + } else if (h < 180) { + ({ 0: r_, 1: g_, 2: b_ } = [0, c, x]); + } else if (h < 240) { + ({ 0: r_, 1: g_, 2: b_ } = [0, x, c]); + } else if (h < 300) { + ({ 0: r_, 1: g_, 2: b_ } = [x, 0, c]); + } else { + ({ 0: r_, 1: g_, 2: b_ } = [c, 0, x]); + } + return [ + MathRound((r_ + m) * 255), + MathRound((g_ + m) * 255), + MathRound((b_ + m) * 255), + ]; + } + return null; +} + +function getDefaultCss() { + return { + backgroundColor: null, + color: null, + fontWeight: null, + fontStyle: null, + textDecorationColor: null, + textDecorationLine: [], + }; +} + +const SPACE_PATTERN = new SafeRegExp(/\s+/g); + +function parseCss(cssString) { + const css = getDefaultCss(); + + const rawEntries = []; + let inValue = false; + let currentKey = null; + let parenthesesDepth = 0; + let currentPart = ""; + for (let i = 0; i < cssString.length; i++) { + const c = cssString[i]; + if (c == "(") { + parenthesesDepth++; + } else if (parenthesesDepth > 0) { + if (c == ")") { + parenthesesDepth--; + } + } else if (inValue) { + if (c == ";") { + const value = StringPrototypeTrim(currentPart); + if (value != "") { + ArrayPrototypePush(rawEntries, [currentKey, value]); + } + currentKey = null; + currentPart = ""; + inValue = false; + continue; + } + } else if (c == ":") { + currentKey = StringPrototypeTrim(currentPart); + currentPart = ""; + inValue = true; + continue; + } + currentPart += c; + } + if (inValue && parenthesesDepth == 0) { + const value = StringPrototypeTrim(currentPart); + if (value != "") { + ArrayPrototypePush(rawEntries, [currentKey, value]); + } + currentKey = null; + currentPart = ""; + } + + for (let i = 0; i < rawEntries.length; ++i) { + const { 0: key, 1: value } = rawEntries[i]; + if (key == "background-color") { + if (value != null) { + css.backgroundColor = value; + } + } else if (key == "color") { + if (value != null) { + css.color = value; + } + } else if (key == "font-weight") { + if (value == "bold") { + css.fontWeight = value; + } + } else if (key == "font-style") { + if ( + ArrayPrototypeIncludes(["italic", "oblique", "oblique 14deg"], value) + ) { + css.fontStyle = "italic"; + } + } else if (key == "text-decoration-line") { + css.textDecorationLine = []; + const lineTypes = StringPrototypeSplit(value, SPACE_PATTERN); + for (let i = 0; i < lineTypes.length; ++i) { + const lineType = lineTypes[i]; + if ( + ArrayPrototypeIncludes( + ["line-through", "overline", "underline"], + lineType, + ) + ) { + ArrayPrototypePush(css.textDecorationLine, lineType); + } + } + } else if (key == "text-decoration-color") { + const color = parseCssColor(value); + if (color != null) { + css.textDecorationColor = color; + } + } else if (key == "text-decoration") { + css.textDecorationColor = null; + css.textDecorationLine = []; + const args = StringPrototypeSplit(value, SPACE_PATTERN); + for (let i = 0; i < args.length; ++i) { + const arg = args[i]; + const maybeColor = parseCssColor(arg); + if (maybeColor != null) { + css.textDecorationColor = maybeColor; + } else if ( + ArrayPrototypeIncludes(["line-through", "overline", "underline"], arg) + ) { + ArrayPrototypePush(css.textDecorationLine, arg); + } + } + } + } + + return css; +} + +function colorEquals(color1, color2) { + return ( + color1?.[0] == color2?.[0] && + color1?.[1] == color2?.[1] && + color1?.[2] == color2?.[2] + ); +} + +function cssToAnsi(css, prevCss = null) { + prevCss = prevCss ?? getDefaultCss(); + let ansi = ""; + if (!colorEquals(css.backgroundColor, prevCss.backgroundColor)) { + if (css.backgroundColor == null) { + ansi += "\x1b[49m"; + } else if (css.backgroundColor == "black") { + ansi += `\x1b[40m`; + } else if (css.backgroundColor == "red") { + ansi += `\x1b[41m`; + } else if (css.backgroundColor == "green") { + ansi += `\x1b[42m`; + } else if (css.backgroundColor == "yellow") { + ansi += `\x1b[43m`; + } else if (css.backgroundColor == "blue") { + ansi += `\x1b[44m`; + } else if (css.backgroundColor == "magenta") { + ansi += `\x1b[45m`; + } else if (css.backgroundColor == "cyan") { + ansi += `\x1b[46m`; + } else if (css.backgroundColor == "white") { + ansi += `\x1b[47m`; + } else { + if (ArrayIsArray(css.backgroundColor)) { + const { 0: r, 1: g, 2: b } = css.backgroundColor; + ansi += `\x1b[48;2;${r};${g};${b}m`; + } else { + const parsed = parseCssColor(css.backgroundColor); + if (parsed !== null) { + const { 0: r, 1: g, 2: b } = parsed; + ansi += `\x1b[48;2;${r};${g};${b}m`; + } else { + ansi += "\x1b[49m"; + } + } + } + } + if (!colorEquals(css.color, prevCss.color)) { + if (css.color == null) { + ansi += "\x1b[39m"; + } else if (css.color == "black") { + ansi += `\x1b[30m`; + } else if (css.color == "red") { + ansi += `\x1b[31m`; + } else if (css.color == "green") { + ansi += `\x1b[32m`; + } else if (css.color == "yellow") { + ansi += `\x1b[33m`; + } else if (css.color == "blue") { + ansi += `\x1b[34m`; + } else if (css.color == "magenta") { + ansi += `\x1b[35m`; + } else if (css.color == "cyan") { + ansi += `\x1b[36m`; + } else if (css.color == "white") { + ansi += `\x1b[37m`; + } else { + if (ArrayIsArray(css.color)) { + const { 0: r, 1: g, 2: b } = css.color; + ansi += `\x1b[38;2;${r};${g};${b}m`; + } else { + const parsed = parseCssColor(css.color); + if (parsed !== null) { + const { 0: r, 1: g, 2: b } = parsed; + ansi += `\x1b[38;2;${r};${g};${b}m`; + } else { + ansi += "\x1b[39m"; + } + } + } + } + if (css.fontWeight != prevCss.fontWeight) { + if (css.fontWeight == "bold") { + ansi += `\x1b[1m`; + } else { + ansi += "\x1b[22m"; + } + } + if (css.fontStyle != prevCss.fontStyle) { + if (css.fontStyle == "italic") { + ansi += `\x1b[3m`; + } else { + ansi += "\x1b[23m"; + } + } + if (!colorEquals(css.textDecorationColor, prevCss.textDecorationColor)) { + if (css.textDecorationColor != null) { + const { 0: r, 1: g, 2: b } = css.textDecorationColor; + ansi += `\x1b[58;2;${r};${g};${b}m`; + } else { + ansi += "\x1b[59m"; + } + } + if ( + ArrayPrototypeIncludes(css.textDecorationLine, "line-through") != + ArrayPrototypeIncludes(prevCss.textDecorationLine, "line-through") + ) { + if (ArrayPrototypeIncludes(css.textDecorationLine, "line-through")) { + ansi += "\x1b[9m"; + } else { + ansi += "\x1b[29m"; + } + } + if ( + ArrayPrototypeIncludes(css.textDecorationLine, "overline") != + ArrayPrototypeIncludes(prevCss.textDecorationLine, "overline") + ) { + if (ArrayPrototypeIncludes(css.textDecorationLine, "overline")) { + ansi += "\x1b[53m"; + } else { + ansi += "\x1b[55m"; + } + } + if ( + ArrayPrototypeIncludes(css.textDecorationLine, "underline") != + ArrayPrototypeIncludes(prevCss.textDecorationLine, "underline") + ) { + if (ArrayPrototypeIncludes(css.textDecorationLine, "underline")) { + ansi += "\x1b[4m"; + } else { + ansi += "\x1b[24m"; + } + } + return ansi; +} + +function inspectArgs(args, inspectOptions = {}) { + const ctx = { + ...getDefaultInspectOptions(), + ...inspectOptions, + }; + if (inspectOptions.iterableLimit !== undefined) { + ctx.maxArrayLength = inspectOptions.iterableLimit; + } + if (inspectOptions.strAbbreviateSize !== undefined) { + ctx.maxStringLength = inspectOptions.strAbbreviateSize; + } + if (ctx.colors) ctx.stylize = createStylizeWithColor(styles, colors); + if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity; + if (ctx.maxStringLength === null) ctx.maxStringLength = Infinity; + + const noColor = getNoColor(); + const first = args[0]; + let a = 0; + let string = ""; + + if (typeof first == "string" && args.length > 1) { + a++; + // Index of the first not-yet-appended character. Use this so we only + // have to append to `string` when a substitution occurs / at the end. + let appendedChars = 0; + let usedStyle = false; + let prevCss = null; + for (let i = 0; i < first.length - 1; i++) { + if (first[i] == "%") { + const char = first[++i]; + if (a < args.length) { + let formattedArg = null; + if (char == "s") { + // Format as a string. + formattedArg = String(args[a++]); + } else if (ArrayPrototypeIncludes(["d", "i"], char)) { + // Format as an integer. + const value = args[a++]; + if (typeof value == "bigint") { + formattedArg = `${value}n`; + } else if (typeof value == "number") { + formattedArg = `${NumberParseInt(String(value))}`; + } else { + formattedArg = "NaN"; + } + } else if (char == "f") { + // Format as a floating point value. + const value = args[a++]; + if (typeof value == "number") { + formattedArg = `${value}`; + } else { + formattedArg = "NaN"; + } + } else if (ArrayPrototypeIncludes(["O", "o"], char)) { + // Format as an object. + formattedArg = formatValue(ctx, args[a++], 0); + } else if (char == "c") { + const value = args[a++]; + if (!noColor) { + const css = parseCss(value); + formattedArg = cssToAnsi(css, prevCss); + if (formattedArg != "") { + usedStyle = true; + prevCss = css; + } + } else { + formattedArg = ""; + } + } + + if (formattedArg != null) { + string += + StringPrototypeSlice(first, appendedChars, i - 1) + formattedArg; + appendedChars = i + 1; + } + } + if (char == "%") { + string += StringPrototypeSlice(first, appendedChars, i - 1) + "%"; + appendedChars = i + 1; + } + } + } + string += StringPrototypeSlice(first, appendedChars); + if (usedStyle) { + string += "\x1b[0m"; + } + } + + for (; a < args.length; a++) { + if (a > 0) { + string += " "; + } + if (typeof args[a] == "string") { + string += args[a]; + } else { + // Use default maximum depth for null or undefined arguments. + string += formatValue(ctx, args[a], 0); + } + } + + if (ctx.indentLevel > 0) { + const groupIndent = StringPrototypeRepeat(DEFAULT_INDENT, ctx.indentLevel); + string = + groupIndent + StringPrototypeReplaceAll(string, "\n", `\n${groupIndent}`); + } + + return string; +} + +function createStylizeWithColor(styles, colors) { + return function stylizeWithColor(str, styleType) { + const style = styles[styleType]; + if (style !== undefined) { + const color = colors[style]; + if (color !== undefined) { + return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`; + } + } + return str; + }; +} + +const countMap = new SafeMap(); +const timerMap = new SafeMap(); +const isConsoleInstance = Symbol("isConsoleInstance"); + +function getConsoleInspectOptions() { + const color = !getNoColor(); + return { + ...getDefaultInspectOptions(), + colors: color, + stylize: color ? createStylizeWithColor(styles, colors) : stylizeNoColor, + }; +} + +class Console { + #printFunc = null; + [isConsoleInstance] = false; + + constructor(printFunc) { + this.#printFunc = printFunc; + this.indentLevel = 0; + this[isConsoleInstance] = true; + + // ref https://console.spec.whatwg.org/#console-namespace + // For historical web-compatibility reasons, the namespace object for + // console must have as its [[Prototype]] an empty object, created as if + // by ObjectCreate(%ObjectPrototype%), instead of %ObjectPrototype%. + const console = ObjectCreate( + {}, + { + [SymbolToStringTag]: { + enumerable: false, + writable: false, + configurable: true, + value: "console", + }, + }, + ); + ObjectAssign(console, this); + return console; + } + + log = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 1, + ); + }; + + debug = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 0, + ); + }; + + info = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 1, + ); + }; + + dir = (obj = undefined, options = {}) => { + this.#printFunc( + inspectArgs([obj], { ...getConsoleInspectOptions(), ...options }) + "\n", + 1, + ); + }; + + dirxml = this.dir; + + warn = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 2, + ); + }; + + error = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 3, + ); + }; + + assert = (condition = false, ...args) => { + if (condition) { + return; + } + + if (args.length === 0) { + this.error("Assertion failed"); + return; + } + + const [first, ...rest] = new SafeArrayIterator(args); + + if (typeof first === "string") { + this.error(`Assertion failed: ${first}`, ...new SafeArrayIterator(rest)); + return; + } + + this.error(`Assertion failed:`, ...new SafeArrayIterator(args)); + }; + + count = (label = "default") => { + label = String(label); + + if (MapPrototypeHas(countMap, label)) { + const current = MapPrototypeGet(countMap, label) || 0; + MapPrototypeSet(countMap, label, current + 1); + } else { + MapPrototypeSet(countMap, label, 1); + } + + this.info(`${label}: ${MapPrototypeGet(countMap, label)}`); + }; + + countReset = (label = "default") => { + label = String(label); + + if (MapPrototypeHas(countMap, label)) { + MapPrototypeSet(countMap, label, 0); + } else { + this.warn(`Count for '${label}' does not exist`); + } + }; + + table = (data = undefined, properties = undefined) => { + if (properties !== undefined && !ArrayIsArray(properties)) { + throw new Error( + "The 'properties' argument must be of type Array. " + + "Received type " + + typeof properties, + ); + } + + if (data === null || typeof data !== "object") { + return this.log(data); + } + + const stringifyValue = (value) => + inspectValueWithQuotes(value, { + ...getDefaultInspectOptions(), + depth: 1, + compact: true, + }); + const toTable = (header, body) => this.log(cliTable(header, body)); + + let resultData; + const isSet = ObjectPrototypeIsPrototypeOf(SetPrototype, data); + const isMap = ObjectPrototypeIsPrototypeOf(MapPrototype, data); + const valuesKey = "Values"; + const indexKey = isSet || isMap ? "(iter idx)" : "(idx)"; + + if (isSet) { + resultData = [...new SafeSetIterator(data)]; + } else if (isMap) { + let idx = 0; + resultData = {}; + + MapPrototypeForEach(data, (v, k) => { + resultData[idx] = { Key: k, Values: v }; + idx++; + }); + } else { + resultData = data; + } + + const keys = ObjectKeys(resultData); + const numRows = keys.length; + + const objectValues = properties + ? ObjectFromEntries( + ArrayPrototypeMap(properties, (name) => [ + name, + ArrayPrototypeFill(new Array(numRows), ""), + ]), + ) + : {}; + const indexKeys = []; + const values = []; + + let hasPrimitives = false; + ArrayPrototypeForEach(keys, (k, idx) => { + const value = resultData[k]; + const primitive = + value === null || + (typeof value !== "function" && typeof value !== "object"); + if (properties === undefined && primitive) { + hasPrimitives = true; + ArrayPrototypePush(values, stringifyValue(value)); + } else { + const valueObj = value || {}; + const keys = properties || ObjectKeys(valueObj); + for (let i = 0; i < keys.length; ++i) { + const k = keys[i]; + if (!primitive && ReflectHas(valueObj, k)) { + if (!ReflectHas(objectValues, k)) { + objectValues[k] = ArrayPrototypeFill(new Array(numRows), ""); + } + objectValues[k][idx] = stringifyValue(valueObj[k]); + } + } + ArrayPrototypePush(values, ""); + } + + ArrayPrototypePush(indexKeys, k); + }); + + const headerKeys = ObjectKeys(objectValues); + const bodyValues = ObjectValues(objectValues); + const headerProps = properties || [ + ...new SafeArrayIterator(headerKeys), + !isMap && hasPrimitives && valuesKey, + ]; + const header = ArrayPrototypeFilter( + [indexKey, ...new SafeArrayIterator(headerProps)], + Boolean, + ); + const body = [indexKeys, ...new SafeArrayIterator(bodyValues), values]; + + toTable(header, body); + }; + + time = (label = "default") => { + label = String(label); + + if (MapPrototypeHas(timerMap, label)) { + this.warn(`Timer '${label}' already exists`); + return; + } + + MapPrototypeSet(timerMap, label, DateNow()); + }; + + timeLog = (label = "default", ...args) => { + label = String(label); + + if (!MapPrototypeHas(timerMap, label)) { + this.warn(`Timer '${label}' does not exist`); + return; + } + + const startTime = MapPrototypeGet(timerMap, label); + const duration = DateNow() - startTime; + + this.info(`${label}: ${duration}ms`, ...new SafeArrayIterator(args)); + }; + + timeEnd = (label = "default") => { + label = String(label); + + if (!MapPrototypeHas(timerMap, label)) { + this.warn(`Timer '${label}' does not exist`); + return; + } + + const startTime = MapPrototypeGet(timerMap, label); + MapPrototypeDelete(timerMap, label); + const duration = DateNow() - startTime; + + this.info(`${label}: ${duration}ms`); + }; + + group = (...label) => { + if (label.length > 0) { + this.log(...new SafeArrayIterator(label)); + } + this.indentLevel += 2; + }; + + groupCollapsed = this.group; + + groupEnd = () => { + if (this.indentLevel > 0) { + this.indentLevel -= 2; + } + }; + + clear = () => { + this.indentLevel = 0; + this.#printFunc(CSI.kClear, 1); + this.#printFunc(CSI.kClearScreenDown, 1); + }; + + trace = (...args) => { + const message = inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: 0, + }); + const err = { + name: "Trace", + message, + }; + ErrorCaptureStackTrace(err, this.trace); + this.error(err.stack); + }; + + // These methods are noops, but when the inspector is connected, they + // call into V8. + profile = (_label) => {}; + profileEnd = (_label) => {}; + timeStamp = (_label) => {}; + + static [SymbolHasInstance](instance) { + return instance[isConsoleInstance]; + } +} + +const customInspect = SymbolFor("Deno.customInspect"); + +function inspect(value, inspectOptions = {}) { + // Default options + const ctx = { + ...getDefaultInspectOptions(), + ...inspectOptions, + }; + if (inspectOptions.iterableLimit !== undefined) { + ctx.maxArrayLength = inspectOptions.iterableLimit; + } + if (inspectOptions.strAbbreviateSize !== undefined) { + ctx.maxStringLength = inspectOptions.strAbbreviateSize; + } + + if (ctx.colors) ctx.stylize = createStylizeWithColor(styles, colors); + if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity; + if (ctx.maxStringLength === null) ctx.maxStringLength = Infinity; + return formatValue(ctx, value, 0); +} + +/** Creates a proxy that represents a subset of the properties + * of the original object optionally without evaluating the properties + * in order to get the values. */ +function createFilteredInspectProxy({ object, keys, evaluate }) { + const obj = class {}; + if (object.constructor?.name) { + ObjectDefineProperty(obj, "name", { value: object.constructor.name }); + } + + return new Proxy(new obj(), { + get(_target, key) { + if (key === SymbolToStringTag) { + return object.constructor?.name; + } else if (ArrayPrototypeIncludes(keys, key)) { + return ReflectGet(object, key); + } else { + return undefined; + } + }, + getOwnPropertyDescriptor(_target, key) { + if (!ArrayPrototypeIncludes(keys, key)) { + return undefined; + } else if (evaluate) { + return getEvaluatedDescriptor(object, key); + } else { + return ( + getDescendantPropertyDescriptor(object, key) ?? + getEvaluatedDescriptor(object, key) + ); + } + }, + has(_target, key) { + return ArrayPrototypeIncludes(keys, key); + }, + ownKeys() { + return keys; + }, + }); + + function getDescendantPropertyDescriptor(object, key) { + let propertyDescriptor = ReflectGetOwnPropertyDescriptor(object, key); + if (!propertyDescriptor) { + const prototype = ReflectGetPrototypeOf(object); + if (prototype) { + propertyDescriptor = getDescendantPropertyDescriptor(prototype, key); + } + } + return propertyDescriptor; + } + + function getEvaluatedDescriptor(object, key) { + return { + configurable: true, + enumerable: true, + value: object[key], + }; + } +} + +globalThis.console = new Console(globalThis.__boa_print); diff --git a/boa_examples/src/bin/deno_console.rs b/boa_examples/src/bin/deno_console.rs new file mode 100644 index 00000000000..8ba040c4b63 --- /dev/null +++ b/boa_examples/src/bin/deno_console.rs @@ -0,0 +1,35 @@ +use boa_engine::property::Attribute; +use boa_engine::NativeFunction; +use boa_engine::{js_string, object::FunctionObjectBuilder, Context, JsResult, JsValue}; +use boa_parser::Source; + +fn print(_: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult { + print!("{}", args[0].to_string(context)?.to_std_string_escaped()); + Ok(JsValue::undefined()) +} + +fn main() -> JsResult<()> { + let context = &mut Context::default(); + + let func = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(print)) + .name("__boa_print") + .length(0) + .build(); + + context + .register_global_property(js_string!("__boa_print"), func, Attribute::default()) + .unwrap(); + + let core_str = include_str!("../../scripts/deno/00_core.js"); + let console_str = include_str!("../../scripts/deno/01_console.js"); + let code_str = include_str!("../../scripts/console.js"); + + for s in [core_str, console_str, code_str] { + // Execute code using anonymous arrow functions to avoid polluting the global scope. + let bytes = format!(r#"(()=>{{{}}})()"#, s).into_bytes(); + let source = Source::from_bytes(&bytes); + context.eval(source)?; + } + + Ok(()) +}