value.ts 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. export const errorConstructors = new Set([
  2. "Error",
  3. "TypeError",
  4. "RangeError",
  5. "SyntaxError",
  6. "ReferenceError",
  7. "EvalError",
  8. "URIError",
  9. ])
  10. export const valueConstructors = new Set(["Date", "RegExp", "Map", "Set", "URL", "URLSearchParams"])
  11. export const compoundOperators = new Set(["+=", "-=", "*=", "/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", ">>>="])
  12. const ErrorBrand: unique symbol = Symbol("codemode.error")
  13. export const createErrorValue = (name: string, message: string): SafeObject => {
  14. const value = Object.assign(Object.create(null) as SafeObject, { name, message })
  15. Object.defineProperty(value, ErrorBrand, { value: name })
  16. return value
  17. }
  18. export const errorBrandName = (value: unknown): string | undefined =>
  19. value !== null && typeof value === "object"
  20. ? ((value as Record<PropertyKey, unknown>)[ErrorBrand] as string | undefined)
  21. : undefined
  22. export const boundedData = (value: unknown, label: string): unknown => copyIn(value, label, true)
  23. export const coerceToString = (value: unknown): string => {
  24. if (value === null) return "null"
  25. if (value === undefined) return "undefined"
  26. if (value instanceof SandboxDate)
  27. return Number.isFinite(value.time) ? new Date(value.time).toISOString() : "Invalid Date"
  28. if (value instanceof SandboxRegExp) return `/${value.regex.source}/${value.regex.flags}`
  29. if (value instanceof SandboxMap) return "[object Map]"
  30. if (value instanceof SandboxSet) return "[object Set]"
  31. if (value instanceof SandboxURL) return value.url.href
  32. if (value instanceof SandboxURLSearchParams) return value.params.toString()
  33. if (typeof value === "object") {
  34. return Array.isArray(value)
  35. ? value.map((item) => (item === null || item === undefined ? "" : coerceToString(item))).join(",")
  36. : "[object Object]"
  37. }
  38. return String(value)
  39. }
  40. export const coerceToNumber = (value: unknown): number => {
  41. if (value instanceof SandboxDate) return value.time
  42. if (isSandboxValue(value)) return Number.NaN
  43. return value !== null && typeof value === "object" && !Array.isArray(value) ? Number.NaN : Number(value)
  44. }
  45. export const invokeCoercion = (ref: CoercionFunction, args: Array<unknown>, node: AstNode): unknown => {
  46. const raw = args[0]
  47. if (isSandboxValue(raw)) {
  48. if (ref.name === "Boolean") return true
  49. if (ref.name === "Number") return coerceToNumber(raw)
  50. if (ref.name === "String") return coerceToString(raw)
  51. if (ref.name === "parseInt") return parseInt(coerceToString(raw))
  52. return parseFloat(coerceToString(raw))
  53. }
  54. const value = boundedData(args[0], `${ref.name} input`)
  55. if (ref.name === "Number") return coerceToNumber(value)
  56. if (ref.name === "Boolean") return Boolean(value)
  57. if (ref.name === "parseInt") {
  58. const radix = args[1]
  59. if (radix !== undefined && typeof radix !== "number") {
  60. throw new InterpreterRuntimeError("parseInt expects a numeric radix.", node)
  61. }
  62. return parseInt(coerceToString(value), radix)
  63. }
  64. if (ref.name === "parseFloat") return parseFloat(coerceToString(value))
  65. return coerceToString(value)
  66. }
  67. import { type AstNode, CoercionFunction, InterpreterRuntimeError } from "../interpreter/model.js"
  68. import { copyIn, type SafeObject } from "../tool-runtime.js"
  69. import {
  70. isSandboxValue,
  71. SandboxDate,
  72. SandboxMap,
  73. SandboxRegExp,
  74. SandboxSet,
  75. SandboxURL,
  76. SandboxURLSearchParams,
  77. } from "../values.js"