@opencode/codemode 0.0.0-dev-19530 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data.d.ts +5 -6
- package/dist/data.js +44 -47
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/interpreter/errors.d.ts +3 -5
- package/dist/interpreter/errors.js +22 -51
- package/dist/interpreter/execute.js +3 -5
- package/dist/interpreter/globals.js +47 -69
- package/dist/interpreter/host.d.ts +41 -0
- package/dist/interpreter/host.js +44 -0
- package/dist/interpreter/methods.d.ts +4 -0
- package/dist/interpreter/methods.js +837 -0
- package/dist/interpreter/model.d.ts +36 -13
- package/dist/interpreter/model.js +45 -15
- package/dist/interpreter/objects.d.ts +13 -106
- package/dist/interpreter/objects.js +65 -192
- package/dist/interpreter/promises.d.ts +13 -12
- package/dist/interpreter/promises.js +54 -59
- package/dist/interpreter/references.d.ts +0 -1
- package/dist/interpreter/references.js +33 -20
- package/dist/interpreter/runner.d.ts +11 -15
- package/dist/interpreter/runner.js +21 -21
- package/dist/interpreter/runtime.d.ts +3 -3
- package/dist/interpreter/runtime.js +327 -165
- package/dist/interpreter/scope.js +6 -6
- package/dist/stdlib/array.d.ts +2 -4
- package/dist/stdlib/array.js +32 -424
- package/dist/stdlib/collections.d.ts +7 -3
- package/dist/stdlib/collections.js +119 -291
- package/dist/stdlib/console.d.ts +2 -3
- package/dist/stdlib/console.js +30 -35
- package/dist/stdlib/date.d.ts +7 -1
- package/dist/stdlib/date.js +188 -93
- package/dist/stdlib/json.d.ts +2 -2
- package/dist/stdlib/json.js +27 -27
- package/dist/stdlib/math.d.ts +2 -2
- package/dist/stdlib/math.js +76 -96
- package/dist/stdlib/number.d.ts +4 -3
- package/dist/stdlib/number.js +60 -95
- package/dist/stdlib/object.d.ts +4 -4
- package/dist/stdlib/object.js +54 -128
- package/dist/stdlib/regexp.d.ts +8 -6
- package/dist/stdlib/regexp.js +68 -76
- package/dist/stdlib/string.d.ts +2 -2
- package/dist/stdlib/string.js +50 -213
- package/dist/stdlib/url.d.ts +13 -5
- package/dist/stdlib/url.js +102 -202
- package/dist/stdlib/value.d.ts +9 -5
- package/dist/stdlib/value.js +38 -16
- package/dist/stdlib/web.d.ts +4 -4
- package/dist/stdlib/web.js +10 -12
- package/dist/tool-runtime.d.ts +1 -2
- package/dist/tool-runtime.js +2 -2
- package/dist/values.d.ts +37 -0
- package/dist/values.js +56 -0
- package/package.json +1 -1
- package/dist/interpreter/generators.d.ts +0 -4
- package/dist/interpreter/generators.js +0 -25
- package/dist/interpreter/intrinsics.d.ts +0 -13
- package/dist/interpreter/intrinsics.js +0 -82
- package/dist/interpreter/native.d.ts +0 -19
- package/dist/interpreter/native.js +0 -40
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { toProgram } from "../data.js";
|
|
3
|
+
import { dateSetterArgumentCount, invokeDateMethod } from "../stdlib/date.js";
|
|
4
|
+
import { invokeNumberMethod } from "../stdlib/number.js";
|
|
5
|
+
import { invokeRegExpMethod, matchToValue, toHostRegex } from "../stdlib/regexp.js";
|
|
6
|
+
import { invokeURLMethod, uriArgument } from "../stdlib/url.js";
|
|
7
|
+
import { coerceToNumber, coerceToString } from "../stdlib/value.js";
|
|
8
|
+
import { compareText } from "../tool-runtime.js";
|
|
9
|
+
import { Values } from "../values.js";
|
|
10
|
+
import { IntrinsicReference, InterpreterRuntimeError } from "./model.js";
|
|
11
|
+
import { get, ProgramArray, ProgramObject, record } from "./objects.js";
|
|
12
|
+
import { containsOpaqueReference, rejectCircularInsertion, typeofValue } from "./references.js";
|
|
13
|
+
import { applyCollectionCallback, isSupportedCallback, toPrimitive } from "./runner.js";
|
|
14
|
+
export const invokeIntrinsic = (runner, ref, args, node) => Effect.map(invoke(runner, ref, args, node), (result) => (Array.isArray(result) ? new ProgramArray(result) : result));
|
|
15
|
+
const invoke = (runner, ref, args, node) => {
|
|
16
|
+
if (typeof ref.receiver === "string") {
|
|
17
|
+
if (ref.name === "replace" || ref.name === "replaceAll") {
|
|
18
|
+
if (isSupportedCallback(args[1]))
|
|
19
|
+
return invokeStringReplacer(runner, ref.receiver, ref.name, args, node);
|
|
20
|
+
if (typeofValue(args[1]) === "function") {
|
|
21
|
+
throw new InterpreterRuntimeError(`String.${ref.name} cannot use this callable as a replacer; wrap it in an arrow function, e.g. (match) => tools.ns.tool(match).`, node);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return Effect.succeed(invokeStringMethod(ref.receiver, ref.name, args, node));
|
|
25
|
+
}
|
|
26
|
+
if (typeof ref.receiver === "number") {
|
|
27
|
+
return Effect.succeed(invokeNumberMethod(ref.receiver, ref.name, args, node));
|
|
28
|
+
}
|
|
29
|
+
if (ref.receiver instanceof ProgramArray) {
|
|
30
|
+
return invokeArrayMethod(runner, ref.receiver, ref.name, args, node);
|
|
31
|
+
}
|
|
32
|
+
if (ref.receiver instanceof Values.Date) {
|
|
33
|
+
const target = ref.receiver;
|
|
34
|
+
const argumentCount = dateSetterArgumentCount(ref.name);
|
|
35
|
+
if (argumentCount === undefined)
|
|
36
|
+
return Effect.succeed(invokeDateMethod(target, ref.name, [], node));
|
|
37
|
+
// Native setters read the current time before argument coercion, whose callbacks may mutate the Date.
|
|
38
|
+
const initialTime = target.time;
|
|
39
|
+
return Effect.map(Effect.forEach(args.slice(0, argumentCount), (arg) => coerceNumericArgument(runner, arg, node), {
|
|
40
|
+
concurrency: 1,
|
|
41
|
+
}), (values) => invokeDateMethod(target, ref.name, values, node, initialTime));
|
|
42
|
+
}
|
|
43
|
+
if (ref.receiver instanceof Values.RegExp) {
|
|
44
|
+
return Effect.succeed(invokeRegExpMethod(ref.receiver, ref.name, args, node));
|
|
45
|
+
}
|
|
46
|
+
if (ref.receiver instanceof Values.Map) {
|
|
47
|
+
return invokeMapMethod(runner, ref.receiver, ref.name, args, node);
|
|
48
|
+
}
|
|
49
|
+
if (ref.receiver instanceof Values.Set) {
|
|
50
|
+
return invokeSetMethod(runner, ref.receiver, ref.name, args, node);
|
|
51
|
+
}
|
|
52
|
+
if (ref.receiver instanceof Values.URL) {
|
|
53
|
+
return Effect.succeed(invokeURLMethod(ref.receiver, ref.name, node));
|
|
54
|
+
}
|
|
55
|
+
if (ref.receiver instanceof Values.URLSearchParams) {
|
|
56
|
+
return invokeURLSearchParamsMethod(runner, ref.receiver, ref.name, args, node);
|
|
57
|
+
}
|
|
58
|
+
throw new InterpreterRuntimeError(`Method '${ref.name}' is not available.`, node);
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* ToPrimitive: tries an object's own `valueOf`/`toString` in hint order and returns the first
|
|
62
|
+
* primitive result. Runtime values behave like their JS counterparts (Date yields its time under a
|
|
63
|
+
* number hint; the rest yield their string form). An inherited `toString` yields the default
|
|
64
|
+
* string form, so plain objects become "[object Object]" and arrays join.
|
|
65
|
+
*/
|
|
66
|
+
const coerceNumericArgument = (runner, value, node) => Effect.map(toPrimitive(runner, value, "number", node), coerceToNumber);
|
|
67
|
+
// console is intercepted by the interpreter before reaching here.
|
|
68
|
+
const requireDataArgument = (name, index, arg, node) => {
|
|
69
|
+
if (containsOpaqueReference(arg)) {
|
|
70
|
+
throw new InterpreterRuntimeError(`String.${name} expects argument ${index + 1} to be a data value.`, node, "InvalidDataValue");
|
|
71
|
+
}
|
|
72
|
+
return arg;
|
|
73
|
+
};
|
|
74
|
+
const invokeStringMethod = (value, name, args, node) => {
|
|
75
|
+
// Coerce arguments like native JS; opaque runtime references still reject.
|
|
76
|
+
const str = (index) => coerceToString(requireDataArgument(name, index, args[index], node));
|
|
77
|
+
const num = (index) => coerceToNumber(requireDataArgument(name, index, args[index], node));
|
|
78
|
+
const optNum = (index) => (args[index] === undefined ? undefined : num(index));
|
|
79
|
+
const optStr = (index) => (args[index] === undefined ? undefined : str(index));
|
|
80
|
+
const rejectRegex = () => {
|
|
81
|
+
if (args[0] instanceof Values.RegExp) {
|
|
82
|
+
throw new InterpreterRuntimeError(`String.${name} cannot take a regular expression; use regex.test(string) or String.search instead.`, node).as("TypeError");
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
let result;
|
|
86
|
+
switch (name) {
|
|
87
|
+
case "toLowerCase":
|
|
88
|
+
result = value.toLowerCase();
|
|
89
|
+
break;
|
|
90
|
+
case "toUpperCase":
|
|
91
|
+
result = value.toUpperCase();
|
|
92
|
+
break;
|
|
93
|
+
case "trim":
|
|
94
|
+
result = value.trim();
|
|
95
|
+
break;
|
|
96
|
+
case "trimStart":
|
|
97
|
+
case "trimLeft":
|
|
98
|
+
result = value.trimStart();
|
|
99
|
+
break;
|
|
100
|
+
case "trimEnd":
|
|
101
|
+
case "trimRight":
|
|
102
|
+
result = value.trimEnd();
|
|
103
|
+
break;
|
|
104
|
+
// Locale/options are deliberately unsupported; comparison uses the host default locale.
|
|
105
|
+
case "localeCompare":
|
|
106
|
+
result = value.localeCompare(str(0));
|
|
107
|
+
break;
|
|
108
|
+
case "normalize": {
|
|
109
|
+
const form = optStr(0);
|
|
110
|
+
try {
|
|
111
|
+
result = value.normalize(form);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
throw new InterpreterRuntimeError(`String.normalize expects the form "NFC", "NFD", "NFKC", or "NFKD" (got ${JSON.stringify(form)}).`, node).as("RangeError");
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "split": {
|
|
119
|
+
// Native: an undefined separator returns the whole string, not a split on "undefined",
|
|
120
|
+
// unless the limit truncates to zero.
|
|
121
|
+
if (args[0] === undefined) {
|
|
122
|
+
const requestedLimit = optNum(1);
|
|
123
|
+
result = requestedLimit !== undefined && requestedLimit >>> 0 === 0 ? [] : [value];
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
if (args[0] instanceof Values.RegExp) {
|
|
127
|
+
result = value.split(args[0].regex, optNum(1));
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
const requestedLimit = optNum(1);
|
|
131
|
+
result = value.split(str(0), requestedLimit === undefined ? undefined : requestedLimit >>> 0);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case "slice":
|
|
135
|
+
result = value.slice(optNum(0), optNum(1));
|
|
136
|
+
break;
|
|
137
|
+
case "includes":
|
|
138
|
+
rejectRegex();
|
|
139
|
+
result = value.includes(str(0), optNum(1));
|
|
140
|
+
break;
|
|
141
|
+
case "startsWith":
|
|
142
|
+
rejectRegex();
|
|
143
|
+
result = value.startsWith(str(0), optNum(1));
|
|
144
|
+
break;
|
|
145
|
+
case "endsWith":
|
|
146
|
+
rejectRegex();
|
|
147
|
+
result = value.endsWith(str(0), optNum(1));
|
|
148
|
+
break;
|
|
149
|
+
case "indexOf":
|
|
150
|
+
result = value.indexOf(str(0), optNum(1));
|
|
151
|
+
break;
|
|
152
|
+
case "lastIndexOf":
|
|
153
|
+
result = value.lastIndexOf(str(0), optNum(1));
|
|
154
|
+
break;
|
|
155
|
+
case "replace":
|
|
156
|
+
case "replaceAll": {
|
|
157
|
+
if (args[0] instanceof Values.RegExp) {
|
|
158
|
+
const pattern = args[0].regex;
|
|
159
|
+
const replacement = str(1);
|
|
160
|
+
if (name === "replaceAll" && !pattern.global) {
|
|
161
|
+
throw new InterpreterRuntimeError(`String.replaceAll requires a regular expression with the global (g) flag: write /${pattern.source}/${pattern.flags}g, or use String.replace to replace only the first match.`, node);
|
|
162
|
+
}
|
|
163
|
+
result = name === "replace" ? value.replace(pattern, replacement) : value.replaceAll(pattern, replacement);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
if (name === "replace") {
|
|
167
|
+
result = value.replace(str(0), str(1));
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
result = value.replaceAll(str(0), str(1));
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case "match": {
|
|
174
|
+
const pattern = toHostRegex(args[0], name, node);
|
|
175
|
+
const matched = value.match(pattern);
|
|
176
|
+
if (matched === null)
|
|
177
|
+
return null;
|
|
178
|
+
// Preserve the own `index` and `groups` properties on non-global matches.
|
|
179
|
+
if (pattern.global)
|
|
180
|
+
return toProgram(matched, "String.match result");
|
|
181
|
+
return matchToValue(matched);
|
|
182
|
+
}
|
|
183
|
+
case "matchAll": {
|
|
184
|
+
const pattern = toHostRegex(args[0], name, node, "g");
|
|
185
|
+
if (!pattern.global) {
|
|
186
|
+
throw new InterpreterRuntimeError(`String.matchAll requires a regular expression with the global (g) flag: write /${pattern.source}/${pattern.flags}g, or use String.match for a single match.`, node);
|
|
187
|
+
}
|
|
188
|
+
return new ProgramArray(Array.from(value.matchAll(pattern), matchToValue));
|
|
189
|
+
}
|
|
190
|
+
case "search": {
|
|
191
|
+
result = value.search(toHostRegex(args[0], name, node));
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
case "repeat": {
|
|
195
|
+
const count = num(0);
|
|
196
|
+
if (!Number.isFinite(count) || count < 0)
|
|
197
|
+
throw new InterpreterRuntimeError("String.repeat expects a finite non-negative count.", node).as("RangeError");
|
|
198
|
+
result = value.repeat(count);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
case "padStart":
|
|
202
|
+
result = value.padStart(num(0), optStr(1));
|
|
203
|
+
break;
|
|
204
|
+
case "padEnd":
|
|
205
|
+
result = value.padEnd(num(0), optStr(1));
|
|
206
|
+
break;
|
|
207
|
+
case "charAt":
|
|
208
|
+
result = value.charAt(optNum(0) ?? 0);
|
|
209
|
+
break;
|
|
210
|
+
case "at":
|
|
211
|
+
result = value.at(optNum(0) ?? 0);
|
|
212
|
+
break;
|
|
213
|
+
case "substring":
|
|
214
|
+
result = value.substring(optNum(0) ?? 0, optNum(1));
|
|
215
|
+
break;
|
|
216
|
+
case "substr":
|
|
217
|
+
result = value.substr(optNum(0) ?? 0, optNum(1));
|
|
218
|
+
break;
|
|
219
|
+
case "isWellFormed":
|
|
220
|
+
result = value.isWellFormed();
|
|
221
|
+
break;
|
|
222
|
+
case "toWellFormed":
|
|
223
|
+
result = value.toWellFormed();
|
|
224
|
+
break;
|
|
225
|
+
case "charCodeAt":
|
|
226
|
+
result = value.charCodeAt(optNum(0) ?? 0);
|
|
227
|
+
break;
|
|
228
|
+
case "codePointAt":
|
|
229
|
+
result = value.codePointAt(optNum(0) ?? 0);
|
|
230
|
+
break;
|
|
231
|
+
case "toString":
|
|
232
|
+
result = value;
|
|
233
|
+
break;
|
|
234
|
+
case "concat": {
|
|
235
|
+
result = value.concat(...args.map((_, index) => str(index)));
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
default:
|
|
239
|
+
throw new InterpreterRuntimeError(`String method '${name}' is not available.`, node);
|
|
240
|
+
}
|
|
241
|
+
return toProgram(result, `String.${name} result`);
|
|
242
|
+
};
|
|
243
|
+
const invokeStringReplacer = (runner, value, name, args, node) => {
|
|
244
|
+
const apply = applyCollectionCallback(runner, args[1], `String.${name}`, node);
|
|
245
|
+
const matches = [];
|
|
246
|
+
const collect = (...callbackArgs) => {
|
|
247
|
+
const match = callbackArgs[0];
|
|
248
|
+
const groups = callbackArgs[callbackArgs.length - 1];
|
|
249
|
+
const hasGroups = groups !== null && typeof groups === "object";
|
|
250
|
+
const offset = callbackArgs[callbackArgs.length - (hasGroups ? 3 : 2)];
|
|
251
|
+
if (typeof match !== "string" || typeof offset !== "number") {
|
|
252
|
+
throw new InterpreterRuntimeError(`String.${name} produced an invalid replacement match.`, node);
|
|
253
|
+
}
|
|
254
|
+
if (hasGroups)
|
|
255
|
+
callbackArgs[callbackArgs.length - 1] = record(groups);
|
|
256
|
+
matches.push({ match, offset, args: callbackArgs });
|
|
257
|
+
return match;
|
|
258
|
+
};
|
|
259
|
+
const pattern = args[0];
|
|
260
|
+
if (pattern instanceof Values.RegExp) {
|
|
261
|
+
if (name === "replaceAll" && !pattern.regex.global) {
|
|
262
|
+
throw new InterpreterRuntimeError(`String.replaceAll requires a regular expression with the global (g) flag: write /${pattern.regex.source}/${pattern.regex.flags}g, or use String.replace to replace only the first match.`, node);
|
|
263
|
+
}
|
|
264
|
+
if (name === "replace")
|
|
265
|
+
value.replace(pattern.regex, collect);
|
|
266
|
+
else
|
|
267
|
+
value.replaceAll(pattern.regex, collect);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
const search = coerceToString(requireDataArgument(name, 0, pattern, node));
|
|
271
|
+
if (name === "replace")
|
|
272
|
+
value.replace(search, collect);
|
|
273
|
+
else
|
|
274
|
+
value.replaceAll(search, collect);
|
|
275
|
+
}
|
|
276
|
+
return Effect.gen(function* () {
|
|
277
|
+
const output = [];
|
|
278
|
+
let end = 0;
|
|
279
|
+
for (const match of matches) {
|
|
280
|
+
const replacement = yield* apply(match.args);
|
|
281
|
+
output.push(value.slice(end, match.offset), replacement instanceof Values.Promise
|
|
282
|
+
? "[object Promise]"
|
|
283
|
+
: coerceToString(toProgram(replacement, `String.${name} replacer result`)));
|
|
284
|
+
end = match.offset + match.match.length;
|
|
285
|
+
}
|
|
286
|
+
output.push(value.slice(end));
|
|
287
|
+
return toProgram(output.join(""), `String.${name} result`);
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
const invokeMapMethod = (runner, target, name, args, node) => {
|
|
291
|
+
switch (name) {
|
|
292
|
+
case "get":
|
|
293
|
+
return Effect.succeed(target.map.get(args[0]));
|
|
294
|
+
case "has":
|
|
295
|
+
return Effect.succeed(target.map.has(args[0]));
|
|
296
|
+
case "set":
|
|
297
|
+
return Effect.sync(() => {
|
|
298
|
+
target.map.set(args[0], args[1]);
|
|
299
|
+
return target;
|
|
300
|
+
});
|
|
301
|
+
case "delete":
|
|
302
|
+
return Effect.sync(() => target.map.delete(args[0]));
|
|
303
|
+
case "clear":
|
|
304
|
+
return Effect.sync(() => {
|
|
305
|
+
target.map.clear();
|
|
306
|
+
return undefined;
|
|
307
|
+
});
|
|
308
|
+
case "keys":
|
|
309
|
+
return Effect.sync(() => Array.from(target.map.keys()));
|
|
310
|
+
case "values":
|
|
311
|
+
return Effect.sync(() => Array.from(target.map.values()));
|
|
312
|
+
case "entries":
|
|
313
|
+
return Effect.sync(() => Array.from(target.map.entries(), ([key, item]) => new ProgramArray([key, item])));
|
|
314
|
+
case "forEach": {
|
|
315
|
+
const apply = applyCollectionCallback(runner, args[0], "Map.forEach", node);
|
|
316
|
+
return Effect.gen(function* () {
|
|
317
|
+
for (const [key, item] of Array.from(target.map.entries()))
|
|
318
|
+
yield* apply([item, key, target]);
|
|
319
|
+
return undefined;
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
default:
|
|
323
|
+
throw new InterpreterRuntimeError(`Map method '${name}' is not available.`, node);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const invokeSetMethod = (runner, target, name, args, node) => {
|
|
327
|
+
switch (name) {
|
|
328
|
+
case "has":
|
|
329
|
+
return Effect.succeed(target.set.has(args[0]));
|
|
330
|
+
case "add":
|
|
331
|
+
return Effect.sync(() => {
|
|
332
|
+
target.set.add(args[0]);
|
|
333
|
+
return target;
|
|
334
|
+
});
|
|
335
|
+
case "delete":
|
|
336
|
+
return Effect.sync(() => target.set.delete(args[0]));
|
|
337
|
+
case "clear":
|
|
338
|
+
return Effect.sync(() => {
|
|
339
|
+
target.set.clear();
|
|
340
|
+
return undefined;
|
|
341
|
+
});
|
|
342
|
+
case "keys":
|
|
343
|
+
case "values":
|
|
344
|
+
return Effect.sync(() => Array.from(target.set.values()));
|
|
345
|
+
case "entries":
|
|
346
|
+
return Effect.sync(() => Array.from(target.set.values(), (item) => new ProgramArray([item, item])));
|
|
347
|
+
case "forEach": {
|
|
348
|
+
const apply = applyCollectionCallback(runner, args[0], "Set.forEach", node);
|
|
349
|
+
return Effect.gen(function* () {
|
|
350
|
+
for (const item of Array.from(target.set.values()))
|
|
351
|
+
yield* apply([item, item, target]);
|
|
352
|
+
return undefined;
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
case "union":
|
|
356
|
+
case "intersection":
|
|
357
|
+
case "difference":
|
|
358
|
+
case "symmetricDifference":
|
|
359
|
+
case "isSubsetOf":
|
|
360
|
+
case "isSupersetOf":
|
|
361
|
+
case "isDisjointFrom":
|
|
362
|
+
return invokeSetOperation(runner, target, name, args[0], node);
|
|
363
|
+
default:
|
|
364
|
+
throw new InterpreterRuntimeError(`Set method '${name}' is not available.`, node);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
const invokeSetOperation = (runner, target, name, source, node) => Effect.gen(function* () {
|
|
368
|
+
const other = yield* loadSetRecord(runner, source, name, node);
|
|
369
|
+
if (name === "union") {
|
|
370
|
+
const result = copySet(target);
|
|
371
|
+
for (const item of yield* other.keys())
|
|
372
|
+
result.set.add(item);
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
if (name === "intersection") {
|
|
376
|
+
const result = new Values.Set();
|
|
377
|
+
if (target.set.size <= other.size) {
|
|
378
|
+
for (const item of target.set.values()) {
|
|
379
|
+
if (yield* other.has(item))
|
|
380
|
+
result.set.add(item);
|
|
381
|
+
}
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
for (const item of yield* other.keys()) {
|
|
385
|
+
if (target.set.has(item))
|
|
386
|
+
result.set.add(item);
|
|
387
|
+
}
|
|
388
|
+
return result;
|
|
389
|
+
}
|
|
390
|
+
if (name === "difference") {
|
|
391
|
+
const result = copySet(target);
|
|
392
|
+
if (target.set.size <= other.size) {
|
|
393
|
+
for (const item of result.set.values()) {
|
|
394
|
+
if (yield* other.has(item))
|
|
395
|
+
result.set.delete(item);
|
|
396
|
+
}
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
for (const item of yield* other.keys())
|
|
400
|
+
result.set.delete(item);
|
|
401
|
+
return result;
|
|
402
|
+
}
|
|
403
|
+
if (name === "symmetricDifference") {
|
|
404
|
+
const result = copySet(target);
|
|
405
|
+
for (const item of yield* other.keys()) {
|
|
406
|
+
if (target.set.has(item))
|
|
407
|
+
result.set.delete(item);
|
|
408
|
+
else
|
|
409
|
+
result.set.add(item);
|
|
410
|
+
}
|
|
411
|
+
return result;
|
|
412
|
+
}
|
|
413
|
+
if (name === "isSubsetOf") {
|
|
414
|
+
if (target.set.size > other.size)
|
|
415
|
+
return false;
|
|
416
|
+
for (const item of target.set.values()) {
|
|
417
|
+
if (!(yield* other.has(item)))
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
if (name === "isSupersetOf") {
|
|
423
|
+
if (target.set.size < other.size)
|
|
424
|
+
return false;
|
|
425
|
+
for (const item of yield* other.keys()) {
|
|
426
|
+
if (!target.set.has(item))
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
if (target.set.size <= other.size) {
|
|
432
|
+
for (const item of target.set.values()) {
|
|
433
|
+
if (yield* other.has(item))
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
for (const item of yield* other.keys()) {
|
|
439
|
+
if (target.set.has(item))
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
return true;
|
|
443
|
+
});
|
|
444
|
+
const copySet = (source) => {
|
|
445
|
+
const result = new Values.Set();
|
|
446
|
+
for (const item of source.set.values())
|
|
447
|
+
result.set.add(item);
|
|
448
|
+
return result;
|
|
449
|
+
};
|
|
450
|
+
const loadSetRecord = (runner, source, name, node) => {
|
|
451
|
+
if (source instanceof Values.Set) {
|
|
452
|
+
return Effect.succeed({
|
|
453
|
+
size: source.set.size,
|
|
454
|
+
has: (item) => Effect.succeed(source.set.has(item)),
|
|
455
|
+
keys: () => Effect.succeed(source.set.values()),
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
if (source instanceof Values.Map) {
|
|
459
|
+
return Effect.succeed({
|
|
460
|
+
size: source.map.size,
|
|
461
|
+
has: (item) => Effect.succeed(source.map.has(item)),
|
|
462
|
+
keys: () => Effect.succeed(source.map.keys()),
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
if (!(source instanceof ProgramObject)) {
|
|
466
|
+
throw new InterpreterRuntimeError(`Set.${name} expects a Set-like object.`, node).as("TypeError");
|
|
467
|
+
}
|
|
468
|
+
return Effect.gen(function* () {
|
|
469
|
+
const size = yield* coerceNumericArgument(runner, get(source, "size"), node);
|
|
470
|
+
if (Number.isNaN(size)) {
|
|
471
|
+
throw new InterpreterRuntimeError(`Set.${name} received a Set-like object with an invalid size.`, node).as("TypeError");
|
|
472
|
+
}
|
|
473
|
+
const has = get(source, "has");
|
|
474
|
+
const keys = get(source, "keys");
|
|
475
|
+
if (!isSupportedCallback(has) || !isSupportedCallback(keys)) {
|
|
476
|
+
throw new InterpreterRuntimeError(`Set.${name} expects callable 'has' and 'keys' methods.`, node).as("TypeError");
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
size: Math.max(Math.trunc(size), 0),
|
|
480
|
+
has: (item) => Effect.map(runner.invokeCallable(has, [item], node), Boolean),
|
|
481
|
+
keys: () => Effect.flatMap(runner.invokeCallable(keys, [], node), (result) => {
|
|
482
|
+
if (result instanceof ProgramArray)
|
|
483
|
+
return Effect.succeed(result.items);
|
|
484
|
+
throw new InterpreterRuntimeError(`Set.${name} expected 'keys' to return an iterator.`, node).as("TypeError");
|
|
485
|
+
}),
|
|
486
|
+
};
|
|
487
|
+
});
|
|
488
|
+
};
|
|
489
|
+
const invokeURLSearchParamsMethod = (runner, target, name, args, node) => {
|
|
490
|
+
const arg = (index) => uriArgument(args[index], `URLSearchParams.${name} argument ${index + 1}`);
|
|
491
|
+
const requireArgs = (count) => {
|
|
492
|
+
if (args.length < count) {
|
|
493
|
+
throw new InterpreterRuntimeError(`URLSearchParams.${name} requires ${count} argument${count === 1 ? "" : "s"}.`, node).as("TypeError");
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
switch (name) {
|
|
497
|
+
case "append": {
|
|
498
|
+
requireArgs(2);
|
|
499
|
+
return Effect.sync(() => {
|
|
500
|
+
target.params.append(arg(0), arg(1));
|
|
501
|
+
return undefined;
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
case "delete": {
|
|
505
|
+
requireArgs(1);
|
|
506
|
+
return Effect.sync(() => {
|
|
507
|
+
if (args[1] !== undefined)
|
|
508
|
+
target.params.delete(arg(0), arg(1));
|
|
509
|
+
else
|
|
510
|
+
target.params.delete(arg(0));
|
|
511
|
+
return undefined;
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
case "get":
|
|
515
|
+
requireArgs(1);
|
|
516
|
+
return Effect.sync(() => target.params.get(arg(0)));
|
|
517
|
+
case "getAll":
|
|
518
|
+
requireArgs(1);
|
|
519
|
+
return Effect.sync(() => target.params.getAll(arg(0)));
|
|
520
|
+
case "has":
|
|
521
|
+
requireArgs(1);
|
|
522
|
+
return Effect.sync(() => (args[1] !== undefined ? target.params.has(arg(0), arg(1)) : target.params.has(arg(0))));
|
|
523
|
+
case "set": {
|
|
524
|
+
requireArgs(2);
|
|
525
|
+
return Effect.sync(() => {
|
|
526
|
+
target.params.set(arg(0), arg(1));
|
|
527
|
+
return undefined;
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
case "sort":
|
|
531
|
+
return Effect.sync(() => {
|
|
532
|
+
target.params.sort();
|
|
533
|
+
return undefined;
|
|
534
|
+
});
|
|
535
|
+
case "keys":
|
|
536
|
+
return Effect.sync(() => Array.from(target.params.keys()));
|
|
537
|
+
case "values":
|
|
538
|
+
return Effect.sync(() => Array.from(target.params.values()));
|
|
539
|
+
case "entries":
|
|
540
|
+
return Effect.sync(() => Array.from(target.params.entries(), ([key, value]) => new ProgramArray([key, value])));
|
|
541
|
+
case "toString":
|
|
542
|
+
return Effect.sync(() => target.params.toString());
|
|
543
|
+
case "forEach": {
|
|
544
|
+
requireArgs(1);
|
|
545
|
+
const apply = applyCollectionCallback(runner, args[0], "URLSearchParams.forEach", node);
|
|
546
|
+
return Effect.gen(function* () {
|
|
547
|
+
for (const [key, value] of Array.from(target.params.entries()))
|
|
548
|
+
yield* apply([value, key, target]);
|
|
549
|
+
return undefined;
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
default:
|
|
553
|
+
throw new InterpreterRuntimeError(`URLSearchParams method '${name}' is not available.`, node);
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
const invokeArrayMethod = (runner, receiver, name, args, node) => {
|
|
557
|
+
const target = receiver.items;
|
|
558
|
+
const optNumber = (value, label) => {
|
|
559
|
+
if (value === undefined)
|
|
560
|
+
return undefined;
|
|
561
|
+
if (typeof value !== "number")
|
|
562
|
+
throw new InterpreterRuntimeError(`Array.${name} expects ${label} to be a number.`, node);
|
|
563
|
+
return value;
|
|
564
|
+
};
|
|
565
|
+
switch (name) {
|
|
566
|
+
case "join": {
|
|
567
|
+
if (args.length > 1 || (args.length === 1 && typeof args[0] !== "string")) {
|
|
568
|
+
throw new InterpreterRuntimeError("Array.join expects zero arguments or one string separator.", node);
|
|
569
|
+
}
|
|
570
|
+
return Effect.succeed(target.map((item) => coerceToString(item ?? "")).join(args.length === 0 ? "," : args[0]));
|
|
571
|
+
}
|
|
572
|
+
case "includes":
|
|
573
|
+
if (args.length === 0 || args.length > 2)
|
|
574
|
+
throw new InterpreterRuntimeError("Array.includes expects a value and optional start index.", node);
|
|
575
|
+
return Effect.succeed(target.includes(args[0], optNumber(args[1], "start index")));
|
|
576
|
+
case "indexOf":
|
|
577
|
+
return Effect.succeed(target.indexOf(args[0], optNumber(args[1], "start index")));
|
|
578
|
+
case "lastIndexOf":
|
|
579
|
+
return Effect.succeed(args[1] === undefined
|
|
580
|
+
? target.lastIndexOf(args[0])
|
|
581
|
+
: target.lastIndexOf(args[0], optNumber(args[1], "start index")));
|
|
582
|
+
case "at":
|
|
583
|
+
return Effect.succeed(target.at(optNumber(args[0], "index") ?? 0));
|
|
584
|
+
case "slice":
|
|
585
|
+
return Effect.succeed(target.slice(optNumber(args[0], "start"), optNumber(args[1], "end")));
|
|
586
|
+
case "concat":
|
|
587
|
+
return Effect.succeed(target.concat(...args.map((item) => (item instanceof ProgramArray ? item.items : item))));
|
|
588
|
+
case "flat": {
|
|
589
|
+
const flatten = (items, depth) => items.flatMap((item) => (item instanceof ProgramArray && depth > 0 ? flatten(item.items, depth - 1) : [item]));
|
|
590
|
+
return Effect.succeed(flatten(target, optNumber(args[0], "depth") ?? 1));
|
|
591
|
+
}
|
|
592
|
+
case "reverse":
|
|
593
|
+
target.reverse();
|
|
594
|
+
return Effect.succeed(receiver);
|
|
595
|
+
case "sort": {
|
|
596
|
+
const length = target.length;
|
|
597
|
+
const holeCount = Array.from({ length }, (_, index) => Object.hasOwn(target, index)).filter((own) => !own).length;
|
|
598
|
+
const itemCount = length - holeCount;
|
|
599
|
+
return Effect.map(sortArray(runner, target, args[0], "Array.sort", node), (sorted) => {
|
|
600
|
+
sorted.slice(0, itemCount).forEach((item, index) => {
|
|
601
|
+
target[index] = item;
|
|
602
|
+
});
|
|
603
|
+
Array.from({ length: holeCount }, (_, index) => itemCount + index).forEach((index) => {
|
|
604
|
+
Reflect.deleteProperty(target, index);
|
|
605
|
+
});
|
|
606
|
+
return receiver;
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
case "toSorted":
|
|
610
|
+
return sortArray(runner, target, args[0], "Array.toSorted", node);
|
|
611
|
+
case "toReversed":
|
|
612
|
+
return Effect.succeed([...target].reverse());
|
|
613
|
+
case "with": {
|
|
614
|
+
const index = optNumber(args[0], "index") ?? 0;
|
|
615
|
+
const resolved = index < 0 ? target.length + index : index;
|
|
616
|
+
if (resolved < 0 || resolved >= target.length) {
|
|
617
|
+
throw new InterpreterRuntimeError("Array.with index is out of range.", node);
|
|
618
|
+
}
|
|
619
|
+
const copied = [...target];
|
|
620
|
+
copied[resolved] = args[1];
|
|
621
|
+
return Effect.succeed(copied);
|
|
622
|
+
}
|
|
623
|
+
case "push": {
|
|
624
|
+
// Validate all insertions before mutating to avoid partial cyclic updates.
|
|
625
|
+
for (const item of args)
|
|
626
|
+
rejectCircularInsertion(receiver, item, "Array.push result", node);
|
|
627
|
+
target.push(...args);
|
|
628
|
+
return Effect.succeed(target.length);
|
|
629
|
+
}
|
|
630
|
+
case "unshift": {
|
|
631
|
+
for (const item of args)
|
|
632
|
+
rejectCircularInsertion(receiver, item, "Array.unshift result", node);
|
|
633
|
+
target.unshift(...args);
|
|
634
|
+
return Effect.succeed(target.length);
|
|
635
|
+
}
|
|
636
|
+
case "pop":
|
|
637
|
+
return Effect.succeed(target.pop());
|
|
638
|
+
case "shift":
|
|
639
|
+
return Effect.succeed(target.shift());
|
|
640
|
+
case "splice": {
|
|
641
|
+
if (args.length === 0)
|
|
642
|
+
return Effect.succeed(target.splice(0, 0));
|
|
643
|
+
const start = optNumber(args[0], "start") ?? 0;
|
|
644
|
+
if (args.length === 1)
|
|
645
|
+
return Effect.succeed(target.splice(start));
|
|
646
|
+
const deleteCount = optNumber(args[1], "delete count") ?? 0;
|
|
647
|
+
const inserted = args.slice(2);
|
|
648
|
+
for (const item of inserted)
|
|
649
|
+
rejectCircularInsertion(receiver, item, "Array.splice result", node);
|
|
650
|
+
return Effect.succeed(target.splice(start, deleteCount, ...inserted));
|
|
651
|
+
}
|
|
652
|
+
case "toSpliced": {
|
|
653
|
+
if (args.length === 0)
|
|
654
|
+
return Effect.succeed([...target]);
|
|
655
|
+
const start = optNumber(args[0], "start") ?? 0;
|
|
656
|
+
if (args.length === 1) {
|
|
657
|
+
const copied = [...target];
|
|
658
|
+
copied.splice(start);
|
|
659
|
+
return Effect.succeed(copied);
|
|
660
|
+
}
|
|
661
|
+
const deleteCount = optNumber(args[1], "delete count") ?? 0;
|
|
662
|
+
const copied = [...target];
|
|
663
|
+
copied.splice(start, deleteCount, ...args.slice(2));
|
|
664
|
+
return Effect.succeed(copied);
|
|
665
|
+
}
|
|
666
|
+
case "fill": {
|
|
667
|
+
rejectCircularInsertion(receiver, args[0], "Array.fill result", node);
|
|
668
|
+
target.fill(args[0], optNumber(args[1], "start"), optNumber(args[2], "end"));
|
|
669
|
+
return Effect.succeed(receiver);
|
|
670
|
+
}
|
|
671
|
+
case "copyWithin":
|
|
672
|
+
target.copyWithin(optNumber(args[0], "target index") ?? 0, optNumber(args[1], "start") ?? 0, optNumber(args[2], "end"));
|
|
673
|
+
return Effect.succeed(receiver);
|
|
674
|
+
case "keys":
|
|
675
|
+
return Effect.succeed(Array.from(target.keys()));
|
|
676
|
+
case "values":
|
|
677
|
+
return Effect.succeed([...target]);
|
|
678
|
+
case "entries":
|
|
679
|
+
return Effect.succeed(Array.from(target.entries(), ([index, item]) => new ProgramArray([index, item])));
|
|
680
|
+
}
|
|
681
|
+
const apply = applyCollectionCallback(runner, args[0], `Array.${name}`, node);
|
|
682
|
+
return Effect.gen(function* () {
|
|
683
|
+
// Fix iteration length while reading existing elements live.
|
|
684
|
+
const length = target.length;
|
|
685
|
+
switch (name) {
|
|
686
|
+
case "map": {
|
|
687
|
+
const values = [];
|
|
688
|
+
values.length = length;
|
|
689
|
+
for (let index = 0; index < length; index += 1) {
|
|
690
|
+
if (!(index in target))
|
|
691
|
+
continue;
|
|
692
|
+
values[index] = yield* apply([target[index], index, receiver]);
|
|
693
|
+
}
|
|
694
|
+
return values;
|
|
695
|
+
}
|
|
696
|
+
case "flatMap": {
|
|
697
|
+
const values = [];
|
|
698
|
+
for (let index = 0; index < length; index += 1) {
|
|
699
|
+
if (!(index in target))
|
|
700
|
+
continue;
|
|
701
|
+
const mapped = yield* apply([target[index], index, receiver]);
|
|
702
|
+
if (mapped instanceof ProgramArray)
|
|
703
|
+
values.push(...mapped.items);
|
|
704
|
+
else
|
|
705
|
+
values.push(mapped);
|
|
706
|
+
}
|
|
707
|
+
return values;
|
|
708
|
+
}
|
|
709
|
+
case "filter": {
|
|
710
|
+
const values = [];
|
|
711
|
+
for (let index = 0; index < length; index += 1) {
|
|
712
|
+
if (!(index in target))
|
|
713
|
+
continue;
|
|
714
|
+
const item = target[index];
|
|
715
|
+
if (yield* apply([item, index, receiver]))
|
|
716
|
+
values.push(item);
|
|
717
|
+
}
|
|
718
|
+
return values;
|
|
719
|
+
}
|
|
720
|
+
case "find":
|
|
721
|
+
for (let index = 0; index < length; index += 1) {
|
|
722
|
+
const item = target[index];
|
|
723
|
+
if (yield* apply([item, index, receiver]))
|
|
724
|
+
return item;
|
|
725
|
+
}
|
|
726
|
+
return undefined;
|
|
727
|
+
case "findIndex":
|
|
728
|
+
for (let index = 0; index < length; index += 1) {
|
|
729
|
+
if (yield* apply([target[index], index, receiver]))
|
|
730
|
+
return index;
|
|
731
|
+
}
|
|
732
|
+
return -1;
|
|
733
|
+
case "some":
|
|
734
|
+
for (let index = 0; index < length; index += 1) {
|
|
735
|
+
if (!(index in target))
|
|
736
|
+
continue;
|
|
737
|
+
if (yield* apply([target[index], index, receiver]))
|
|
738
|
+
return true;
|
|
739
|
+
}
|
|
740
|
+
return false;
|
|
741
|
+
case "every":
|
|
742
|
+
for (let index = 0; index < length; index += 1) {
|
|
743
|
+
if (!(index in target))
|
|
744
|
+
continue;
|
|
745
|
+
if (!(yield* apply([target[index], index, receiver])))
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
return true;
|
|
749
|
+
case "forEach":
|
|
750
|
+
for (let index = 0; index < length; index += 1) {
|
|
751
|
+
if (index in target)
|
|
752
|
+
yield* apply([target[index], index, receiver]);
|
|
753
|
+
}
|
|
754
|
+
return undefined;
|
|
755
|
+
case "reduce": {
|
|
756
|
+
let start = 0;
|
|
757
|
+
let accumulator = args[1];
|
|
758
|
+
if (args.length < 2) {
|
|
759
|
+
while (start < length && !(start in target))
|
|
760
|
+
start += 1;
|
|
761
|
+
if (start === length)
|
|
762
|
+
throw new InterpreterRuntimeError("Array.reduce of an empty array with no initial value.", node).as("TypeError");
|
|
763
|
+
accumulator = target[start];
|
|
764
|
+
start += 1;
|
|
765
|
+
}
|
|
766
|
+
for (let index = start; index < length; index += 1) {
|
|
767
|
+
if (!(index in target))
|
|
768
|
+
continue;
|
|
769
|
+
accumulator = yield* apply([accumulator, target[index], index, receiver]);
|
|
770
|
+
}
|
|
771
|
+
return accumulator;
|
|
772
|
+
}
|
|
773
|
+
case "reduceRight": {
|
|
774
|
+
let start = length - 1;
|
|
775
|
+
let accumulator = args[1];
|
|
776
|
+
if (args.length < 2) {
|
|
777
|
+
while (start >= 0 && !(start in target))
|
|
778
|
+
start -= 1;
|
|
779
|
+
if (start < 0)
|
|
780
|
+
throw new InterpreterRuntimeError("Array.reduceRight of an empty array with no initial value.", node).as("TypeError");
|
|
781
|
+
accumulator = target[start];
|
|
782
|
+
start -= 1;
|
|
783
|
+
}
|
|
784
|
+
for (let index = start; index >= 0; index -= 1) {
|
|
785
|
+
if (!(index in target))
|
|
786
|
+
continue;
|
|
787
|
+
accumulator = yield* apply([accumulator, target[index], index, receiver]);
|
|
788
|
+
}
|
|
789
|
+
return accumulator;
|
|
790
|
+
}
|
|
791
|
+
case "findLast":
|
|
792
|
+
for (let index = length - 1; index >= 0; index -= 1) {
|
|
793
|
+
const item = target[index];
|
|
794
|
+
if (yield* apply([item, index, receiver]))
|
|
795
|
+
return item;
|
|
796
|
+
}
|
|
797
|
+
return undefined;
|
|
798
|
+
case "findLastIndex":
|
|
799
|
+
for (let index = length - 1; index >= 0; index -= 1) {
|
|
800
|
+
if (yield* apply([target[index], index, receiver]))
|
|
801
|
+
return index;
|
|
802
|
+
}
|
|
803
|
+
return -1;
|
|
804
|
+
}
|
|
805
|
+
throw new InterpreterRuntimeError(`Array method '${name}' is not available.`, node);
|
|
806
|
+
});
|
|
807
|
+
};
|
|
808
|
+
const sortArray = (runner, target, comparator, name, node) => {
|
|
809
|
+
if (comparator === undefined) {
|
|
810
|
+
return Effect.sync(() => [...target].sort((a, b) => compareText(coerceToString(a), coerceToString(b))));
|
|
811
|
+
}
|
|
812
|
+
const apply = applyCollectionCallback(runner, comparator, name, node);
|
|
813
|
+
const mergeSort = (items) => {
|
|
814
|
+
if (items.length <= 1)
|
|
815
|
+
return Effect.succeed(items);
|
|
816
|
+
const midpoint = Math.floor(items.length / 2);
|
|
817
|
+
return Effect.gen(function* () {
|
|
818
|
+
const left = yield* mergeSort(items.slice(0, midpoint));
|
|
819
|
+
const right = yield* mergeSort(items.slice(midpoint));
|
|
820
|
+
const merged = [];
|
|
821
|
+
let leftIndex = 0;
|
|
822
|
+
let rightIndex = 0;
|
|
823
|
+
while (leftIndex < left.length && rightIndex < right.length) {
|
|
824
|
+
// Treat a NaN comparator result as equal to preserve stable ordering.
|
|
825
|
+
const order = coerceToNumber(yield* apply([left[leftIndex], right[rightIndex]]));
|
|
826
|
+
if (Number.isNaN(order) || order <= 0)
|
|
827
|
+
merged.push(left[leftIndex++]);
|
|
828
|
+
else
|
|
829
|
+
merged.push(right[rightIndex++]);
|
|
830
|
+
}
|
|
831
|
+
return [...merged, ...left.slice(leftIndex), ...right.slice(rightIndex)];
|
|
832
|
+
});
|
|
833
|
+
};
|
|
834
|
+
const defined = target.filter((item) => item !== undefined);
|
|
835
|
+
const undefinedCount = target.length - defined.length;
|
|
836
|
+
return Effect.map(mergeSort(defined), (items) => [...items, ...Array(undefinedCount).fill(undefined)]);
|
|
837
|
+
};
|