forked from anomalyco/opencode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjson.ts
More file actions
149 lines (138 loc) · 5.58 KB
/
Copy pathjson.ts
File metadata and controls
149 lines (138 loc) · 5.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { Effect } from "effect"
import type { CallbackRunner } from "../interpreter/methods.js"
import { applyCollectionCallback } from "../interpreter/methods.js"
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
import { typeofValue } from "../interpreter/references.js"
import { copyIn, copyOut, type SafeObject } from "../tool-runtime.js"
import {
CodeModeDate,
CodeModeMap,
CodeModeRegExp,
CodeModeSet,
CodeModeURL,
CodeModeURLSearchParams,
} from "../values.js"
export const jsonStatics = new Set(["parse", "stringify"])
export type JsonMethodName = "parse" | "stringify"
export const invokeJsonMethod = <R>(
runner: CallbackRunner<R>,
name: JsonMethodName,
args: Array<unknown>,
node: AstNode,
): Effect.Effect<unknown, unknown, R> => {
return name === "parse" ? parse(runner, args, node) : stringify(runner, args, node)
}
const parse = <R>(
runner: CallbackRunner<R>,
args: Array<unknown>,
node: AstNode,
): Effect.Effect<unknown, unknown, R> => {
const text = args[0]
if (typeof text !== "string") throw new InterpreterRuntimeError("JSON.parse expects a string.", node)
const parsed = (() => {
try {
return copyIn(JSON.parse(text), "JSON.parse result")
} catch (error) {
throw new InterpreterRuntimeError(
`JSON.parse received invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
node,
).as("SyntaxError")
}
})()
if (typeofValue(args[1]) !== "function") return Effect.succeed(parsed)
const apply = applyCollectionCallback(runner, args[1], "JSON.parse", node)
const root: SafeObject = Object.create(null) as SafeObject
root[""] = parsed
const visit = (holder: SafeObject | Array<unknown>, key: string): Effect.Effect<unknown, unknown, R> =>
Effect.gen(function* () {
const value = holder[key as keyof typeof holder]
if (Array.isArray(value)) {
const length = value.length
for (let index = 0; index < length; index += 1) {
const revived = yield* visit(value, String(index))
if (revived === undefined) Reflect.deleteProperty(value, index)
else value[index] = revived
}
} else if (isPlainObject(value)) {
for (const name of Object.keys(value)) {
const revived = yield* visit(value, name)
if (revived === undefined) Reflect.deleteProperty(value, name)
else value[name] = revived
}
}
return yield* apply([key, value])
})
return visit(root, "")
}
const stringify = <R>(
runner: CallbackRunner<R>,
args: Array<unknown>,
node: AstNode,
): Effect.Effect<unknown, unknown, R> => {
const space = args[2]
const indent = typeof space === "number" || typeof space === "string" ? space : undefined
const replacer = args[1]
const callable = typeofValue(replacer) === "function"
const checked = copyIn(args[0], "JSON.stringify value", callable)
const input = callable ? args[0] : checked
if (Array.isArray(replacer)) {
const properties = replacer
.filter((item): item is string | number => typeof item === "string" || typeof item === "number")
.map(String)
return Effect.succeed(JSON.stringify(copyOut(input, "json"), properties, indent))
}
if (!callable) {
return Effect.succeed(JSON.stringify(copyOut(input, "json"), null, indent))
}
const apply = applyCollectionCallback(runner, replacer, "JSON.stringify", node)
const root: SafeObject = Object.create(null) as SafeObject
root[""] = input
const stack = new Set<object>()
const visit = (holder: SafeObject | Array<unknown>, key: string): Effect.Effect<unknown, unknown, R> =>
Effect.gen(function* () {
const value = yield* apply([key, toJSONValue(holder[key as keyof typeof holder])])
if (value === undefined || typeofValue(value) === "function") return undefined
copyIn(value, "JSON.stringify replacer result", true)
if (typeof value === "number") return Number.isFinite(value) ? value : null
if (value === null || typeof value === "string" || typeof value === "boolean") return value
if (Array.isArray(value)) {
if (stack.has(value))
throw new InterpreterRuntimeError("Converting circular structure to JSON.", node).as("TypeError")
stack.add(value)
const result: Array<unknown> = []
for (let index = 0; index < value.length; index += 1) {
result.push((yield* visit(value, String(index))) ?? null)
}
stack.delete(value)
return result
}
if (!isPlainObject(value)) return {}
if (stack.has(value))
throw new InterpreterRuntimeError("Converting circular structure to JSON.", node).as("TypeError")
stack.add(value)
const result: SafeObject = Object.create(null) as SafeObject
for (const name of Object.keys(value)) {
const item = yield* visit(value, name)
if (item !== undefined) result[name] = item
}
stack.delete(value)
return result
})
return Effect.map(visit(root, ""), (value) => JSON.stringify(value, null, indent))
}
const toJSONValue = (value: unknown): unknown => {
if (value instanceof CodeModeDate) {
return Number.isFinite(value.time) ? new Date(value.time).toISOString() : null
}
if (value instanceof CodeModeURL) return value.url.href
return value
}
const isPlainObject = (value: unknown): value is SafeObject =>
value !== null &&
typeof value === "object" &&
!(value instanceof CodeModeDate) &&
!(value instanceof CodeModeRegExp) &&
!(value instanceof CodeModeMap) &&
!(value instanceof CodeModeSet) &&
!(value instanceof CodeModeURL) &&
!(value instanceof CodeModeURLSearchParams)