@opencode/codemode 0.0.0-dev-19602 → 0.0.0-dev-19610
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/codemode.d.ts +3 -0
- package/dist/codemode.js +12 -1
- package/dist/data.d.ts +1 -0
- package/dist/data.js +9 -2
- package/dist/extension.d.ts +12 -0
- package/dist/extension.js +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/interpreter/extensions.d.ts +8 -0
- package/dist/interpreter/extensions.js +270 -0
- package/dist/interpreter/globals.d.ts +2 -0
- package/dist/interpreter/globals.js +41 -38
- package/dist/interpreter/limits.d.ts +8 -0
- package/dist/interpreter/limits.js +17 -0
- package/dist/interpreter/objects.d.ts +6 -1
- package/dist/interpreter/objects.js +13 -3
- package/dist/interpreter/promises.js +5 -1
- package/dist/interpreter/references.js +4 -1
- package/dist/interpreter/runtime.js +9 -2
- package/dist/stdlib/array.js +16 -7
- package/dist/stdlib/json.js +5 -1
- package/dist/stdlib/object.js +1 -1
- package/dist/stdlib/string.js +28 -7
- package/package.json +1 -1
package/dist/codemode.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
|
+
import type { Extension } from "./extension.js";
|
|
2
3
|
import { type Services, type ToolDescription, ToolRuntime } from "./tool-runtime.js";
|
|
3
4
|
import type { Tools } from "./tools.js";
|
|
4
5
|
/** A tool call admitted during an execution. */
|
|
@@ -29,6 +30,8 @@ export type ResolvedExecutionLimits = {
|
|
|
29
30
|
export type Options<Provided extends Record<string, unknown> = {}> = ToolRuntime.ToolCallHooks<Services<Provided>> & {
|
|
30
31
|
/** Explicit tools exposed to the program as `tools`. */
|
|
31
32
|
tools?: Provided & Tools<Services<Provided>>;
|
|
33
|
+
/** Host classes and functions exposed as globals; see `Extension.make`. */
|
|
34
|
+
extensions?: ReadonlyArray<Extension>;
|
|
32
35
|
/** Resource limits enforced on each execution. */
|
|
33
36
|
limits?: ExecutionLimits;
|
|
34
37
|
};
|
package/dist/codemode.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
import { executeProgram } from "./interpreter/execute.js";
|
|
3
|
+
import { extensionGlobals } from "./interpreter/extensions.js";
|
|
4
|
+
import { globalNames } from "./interpreter/globals.js";
|
|
3
5
|
import { ToolRuntime } from "./tool-runtime.js";
|
|
4
6
|
/** Signature-construction helpers for host-owned catalog instructions. */
|
|
5
7
|
export { searchSignature, toolExpression } from "./tool-runtime.js";
|
|
@@ -59,8 +61,17 @@ export const execute = (options) => make(options).execute(options.code);
|
|
|
59
61
|
export const make = (options = {}) => {
|
|
60
62
|
const prepared = ToolRuntime.prepare((options.tools ?? {}));
|
|
61
63
|
const limits = resolveExecutionLimits(options.limits);
|
|
64
|
+
const extensions = options.extensions ?? [];
|
|
65
|
+
const bound = new Set(globalNames);
|
|
66
|
+
for (const extension of extensions) {
|
|
67
|
+
for (const name of Object.keys(extension.globals)) {
|
|
68
|
+
if (bound.has(name))
|
|
69
|
+
throw new TypeError(`Extension "${extension.name}" global "${name}" is already defined.`);
|
|
70
|
+
bound.add(name);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
62
73
|
return {
|
|
63
74
|
catalog: prepared.catalog,
|
|
64
|
-
execute: (code) => executeProgram(code, prepared, limits, options),
|
|
75
|
+
execute: (code) => executeProgram(code, prepared, limits, options, (host) => extensionGlobals(host, extensions)),
|
|
65
76
|
};
|
|
66
77
|
};
|
package/dist/data.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * as Data from "./data.js";
|
|
2
2
|
import type { DiagnosticKind } from "./codemode.js";
|
|
3
3
|
import type { Prototypes } from "./interpreter/intrinsics.js";
|
|
4
|
+
export declare const MAX_VALUE_DEPTH = 32;
|
|
4
5
|
export declare class ToolRuntimeError extends Error {
|
|
5
6
|
readonly kind: Extract<DiagnosticKind, "UnknownTool" | "InvalidToolInput" | "InvalidToolOutput" | "InvalidDataValue" | "ToolCallLimitExceeded">;
|
|
6
7
|
readonly suggestions: ReadonlyArray<string>;
|
package/dist/data.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * as Data from "./data.js";
|
|
2
|
-
import { Callable, define, entries, get, isWrapper, parseArrayIndex, ProgramArray, ProgramDate, ProgramError, ProgramGenerator, ProgramMap, ProgramObject, ProgramPromise, ProgramRegExp, ProgramSet, ProgramURL, ProgramURLSearchParams, } from "./interpreter/objects.js";
|
|
3
|
-
const MAX_VALUE_DEPTH = 32;
|
|
2
|
+
import { Callable, define, entries, get, isWrapper, parseArrayIndex, ProgramArray, ProgramDate, ProgramError, ProgramGenerator, ProgramHandle, ProgramMap, ProgramObject, ProgramPromise, ProgramRegExp, ProgramSet, ProgramURL, ProgramURLSearchParams, } from "./interpreter/objects.js";
|
|
3
|
+
export const MAX_VALUE_DEPTH = 32;
|
|
4
4
|
export class ToolRuntimeError extends Error {
|
|
5
5
|
kind;
|
|
6
6
|
suggestions;
|
|
@@ -49,6 +49,13 @@ const copy = (value, label, mode, depth, seen, protos, stripProto = true) => {
|
|
|
49
49
|
if ((value instanceof Callable || value instanceof ProgramGenerator) && mode !== "program") {
|
|
50
50
|
throw new ToolRuntimeError("InvalidDataValue", `${label} must contain data only.`);
|
|
51
51
|
}
|
|
52
|
+
if (value instanceof ProgramHandle && mode !== "program") {
|
|
53
|
+
throw new ToolRuntimeError("InvalidDataValue", `${label} contains a ${value.instance.constructor.name}, which only extension functions accept.`);
|
|
54
|
+
}
|
|
55
|
+
// Host-produced input never holds program objects; one arriving here would come back as a host object.
|
|
56
|
+
if (value instanceof ProgramObject && mode === "data") {
|
|
57
|
+
throw new ToolRuntimeError("InvalidDataValue", `${label} must be host data, not a program value.`);
|
|
58
|
+
}
|
|
52
59
|
if (protos !== undefined && mode === "program") {
|
|
53
60
|
if (value instanceof ProgramObject)
|
|
54
61
|
return value;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * as Extension from "./extension.js";
|
|
2
|
+
/**
|
|
3
|
+
* Host classes and functions a program uses directly, like JavaScript. Values crossing in either direction are
|
|
4
|
+
* converted, never shared: plain data is copied, instances of the classes stay on the host behind program-side
|
|
5
|
+
* handles. Extension calls are not tool calls.
|
|
6
|
+
*/
|
|
7
|
+
export type Extension = {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
/** Each value is a class or a function; everything on a class is exposed, including statics and accessors. */
|
|
10
|
+
readonly globals: Readonly<Record<string, Function>>;
|
|
11
|
+
};
|
|
12
|
+
export declare const make: (options: Extension) => Extension;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * as Extension from "./extension.js";
|
|
2
|
+
export const make = (options) => {
|
|
3
|
+
for (const [name, value] of Object.entries(options.globals)) {
|
|
4
|
+
if (typeof value !== "function") {
|
|
5
|
+
throw new TypeError(`Extension "${options.name}" global "${name}" must be a class or a function.`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
return { name: options.name, globals: { ...options.globals } };
|
|
9
|
+
};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Extension } from "../extension.js";
|
|
2
|
+
import type { Host } from "./globals.js";
|
|
3
|
+
/**
|
|
4
|
+
* The global bindings of one run's extensions. Everything crossing the boundary is converted: plain data is
|
|
5
|
+
* copied, built-in wrappers are copied, instances of exposed classes travel as handles, and a host Promise becomes
|
|
6
|
+
* a program promise. Prototypes, constructors, and handle identity are all per run.
|
|
7
|
+
*/
|
|
8
|
+
export declare const extensionGlobals: <R>(host: Host<R>, extensions: ReadonlyArray<Extension>) => ReadonlyArray<readonly [string, unknown]>;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { MAX_VALUE_DEPTH } from "../data.js";
|
|
3
|
+
import { coerceToString } from "../stdlib/value.js";
|
|
4
|
+
import { createErrorValue, isErrorType } from "./intrinsics.js";
|
|
5
|
+
import { ProgramThrow, typeError } from "./model.js";
|
|
6
|
+
import { constructor, fn } from "./native.js";
|
|
7
|
+
import { Callable, define, defineAccessor, entries, get, hidden, ProgramArray, ProgramDate, ProgramError, ProgramGenerator, ProgramHandle, ProgramMap, ProgramObject, ProgramPromise, ProgramRegExp, ProgramSet, ProgramURL, ProgramURLSearchParams, } from "./objects.js";
|
|
8
|
+
import { describeValue } from "./references.js";
|
|
9
|
+
const isClass = (value) => typeof value === "function" && typeof value.prototype === "object" && value.prototype !== null;
|
|
10
|
+
// Own keys the native function already carries.
|
|
11
|
+
const ownFunctionKeys = new Set(["length", "name", "prototype"]);
|
|
12
|
+
const ownPrototypeKeys = new Set(["constructor"]);
|
|
13
|
+
/**
|
|
14
|
+
* The global bindings of one run's extensions. Everything crossing the boundary is converted: plain data is
|
|
15
|
+
* copied, built-in wrappers are copied, instances of exposed classes travel as handles, and a host Promise becomes
|
|
16
|
+
* a program promise. Prototypes, constructors, and handle identity are all per run.
|
|
17
|
+
*/
|
|
18
|
+
export const extensionGlobals = (host, extensions) => {
|
|
19
|
+
const protos = host.runner.prototypes;
|
|
20
|
+
const classes = new Set(extensions.flatMap((extension) => Object.values(extension.globals)).filter(isClass));
|
|
21
|
+
// Host prototype object → this run's program prototype, so an instance wraps as its most-derived exposed class.
|
|
22
|
+
const prototypes = new Map();
|
|
23
|
+
const exposed = new Map();
|
|
24
|
+
const classOf = new Map();
|
|
25
|
+
const handles = new WeakMap();
|
|
26
|
+
const toHost = (value, label, depth = 0, seen = new Set()) => {
|
|
27
|
+
if (depth > MAX_VALUE_DEPTH)
|
|
28
|
+
throw typeError(`${label} exceeds the maximum value depth of ${MAX_VALUE_DEPTH}.`);
|
|
29
|
+
if (value === null || typeof value !== "object") {
|
|
30
|
+
if (isPrimitive(value))
|
|
31
|
+
return value;
|
|
32
|
+
throw typeError(`${label} contains ${describeValue(value)}, which cannot be passed to an extension.`);
|
|
33
|
+
}
|
|
34
|
+
if (value instanceof ProgramHandle)
|
|
35
|
+
return value.instance;
|
|
36
|
+
if (value instanceof ProgramDate)
|
|
37
|
+
return new Date(value.time);
|
|
38
|
+
if (value instanceof ProgramRegExp)
|
|
39
|
+
return new RegExp(value.regex.source, value.regex.flags);
|
|
40
|
+
if (value instanceof ProgramURL)
|
|
41
|
+
return new URL(value.url.href);
|
|
42
|
+
if (value instanceof ProgramURLSearchParams)
|
|
43
|
+
return new URLSearchParams(value.params);
|
|
44
|
+
const next = (item) => toHost(item, label, depth + 1, seen);
|
|
45
|
+
if (value instanceof ProgramMap)
|
|
46
|
+
return new Map([...value.map].map(([key, item]) => [next(key), next(item)]));
|
|
47
|
+
if (value instanceof ProgramSet)
|
|
48
|
+
return new Set([...value.set].map(next));
|
|
49
|
+
if (!(value instanceof ProgramObject) ||
|
|
50
|
+
value instanceof Callable ||
|
|
51
|
+
value instanceof ProgramGenerator ||
|
|
52
|
+
value instanceof ProgramPromise) {
|
|
53
|
+
throw typeError(`${label} contains ${describeValue(value)}, which cannot be passed to an extension.`);
|
|
54
|
+
}
|
|
55
|
+
if (value instanceof ProgramError) {
|
|
56
|
+
const name = coerceToString(get(value, "name"));
|
|
57
|
+
const message = get(value, "message");
|
|
58
|
+
const text = message === undefined ? "" : coerceToString(message);
|
|
59
|
+
return name === "AggregateError" ? new AggregateError([], text) : new (hostErrors[name] ?? Error)(text);
|
|
60
|
+
}
|
|
61
|
+
if (seen.has(value))
|
|
62
|
+
throw typeError(`${label} contains a circular value.`);
|
|
63
|
+
seen.add(value);
|
|
64
|
+
const copied = value instanceof ProgramArray
|
|
65
|
+
? value.items.map(next)
|
|
66
|
+
: Object.fromEntries(entries(value)
|
|
67
|
+
.filter(([key]) => key !== "__proto__")
|
|
68
|
+
.map(([key, item]) => [key, next(item)]));
|
|
69
|
+
seen.delete(value);
|
|
70
|
+
return copied;
|
|
71
|
+
};
|
|
72
|
+
const fromHost = (value, label, depth = 0, seen = new Set()) => {
|
|
73
|
+
if (depth > MAX_VALUE_DEPTH)
|
|
74
|
+
throw typeError(`${label} exceeds the maximum value depth of ${MAX_VALUE_DEPTH}.`);
|
|
75
|
+
if (isPrimitive(value))
|
|
76
|
+
return value;
|
|
77
|
+
if (value !== null && typeof value === "object") {
|
|
78
|
+
const existing = handles.get(value);
|
|
79
|
+
if (existing !== undefined)
|
|
80
|
+
return existing;
|
|
81
|
+
const proto = handlePrototype(value);
|
|
82
|
+
if (proto !== undefined) {
|
|
83
|
+
const handle = new ProgramHandle(proto, value);
|
|
84
|
+
handles.set(value, handle);
|
|
85
|
+
return handle;
|
|
86
|
+
}
|
|
87
|
+
if (value instanceof Date)
|
|
88
|
+
return new ProgramDate(protos.Date, value.getTime());
|
|
89
|
+
if (value instanceof RegExp)
|
|
90
|
+
return new ProgramRegExp(protos.RegExp, value.source, value.flags);
|
|
91
|
+
if (value instanceof Error) {
|
|
92
|
+
return createErrorValue(protos[isErrorType(value.name) ? value.name : "Error"], value.message);
|
|
93
|
+
}
|
|
94
|
+
if (value instanceof URL)
|
|
95
|
+
return new ProgramURL(protos.URL, protos.URLSearchParams, new URL(value.href));
|
|
96
|
+
if (value instanceof URLSearchParams) {
|
|
97
|
+
return new ProgramURLSearchParams(protos.URLSearchParams, new URLSearchParams(value));
|
|
98
|
+
}
|
|
99
|
+
const next = (item) => fromHost(item, label, depth + 1, seen);
|
|
100
|
+
if (value instanceof Map) {
|
|
101
|
+
const wrapped = new ProgramMap(protos.Map);
|
|
102
|
+
for (const [key, item] of value)
|
|
103
|
+
wrapped.map.set(next(key), next(item));
|
|
104
|
+
return wrapped;
|
|
105
|
+
}
|
|
106
|
+
if (value instanceof Set) {
|
|
107
|
+
const wrapped = new ProgramSet(protos.Set);
|
|
108
|
+
for (const item of value)
|
|
109
|
+
wrapped.set.add(next(item));
|
|
110
|
+
return wrapped;
|
|
111
|
+
}
|
|
112
|
+
if (seen.has(value))
|
|
113
|
+
throw typeError(`${label} produced a circular value.`);
|
|
114
|
+
seen.add(value);
|
|
115
|
+
if (Array.isArray(value)) {
|
|
116
|
+
const copied = new ProgramArray(protos.Array, value.map(next));
|
|
117
|
+
seen.delete(value);
|
|
118
|
+
return copied;
|
|
119
|
+
}
|
|
120
|
+
const prototype = Object.getPrototypeOf(value);
|
|
121
|
+
if (prototype === Object.prototype || prototype === null) {
|
|
122
|
+
const copied = new ProgramObject(protos.Object);
|
|
123
|
+
for (const [key, item] of Object.entries(value))
|
|
124
|
+
define(copied, key, next(item));
|
|
125
|
+
seen.delete(value);
|
|
126
|
+
return copied;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
throw typeError(`${label} produced ${describeHost(value)}, which the program cannot hold.`);
|
|
130
|
+
};
|
|
131
|
+
const handlePrototype = (instance) => {
|
|
132
|
+
for (let level = Object.getPrototypeOf(instance); level !== null; level = Object.getPrototypeOf(level)) {
|
|
133
|
+
const proto = prototypes.get(level);
|
|
134
|
+
if (proto !== undefined)
|
|
135
|
+
return proto;
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
};
|
|
139
|
+
// Runs host code with already-converted inputs. Whatever it returns, resolves, throws, or rejects with crosses
|
|
140
|
+
// the same way, so the program catches what the author threw.
|
|
141
|
+
const invoke = (run, label) => {
|
|
142
|
+
const thrown = (reason) => new ProgramThrow(fromHost(reason, label));
|
|
143
|
+
let result;
|
|
144
|
+
try {
|
|
145
|
+
result = run();
|
|
146
|
+
}
|
|
147
|
+
catch (reason) {
|
|
148
|
+
return Effect.fail(thrown(reason));
|
|
149
|
+
}
|
|
150
|
+
if (!(result instanceof Promise))
|
|
151
|
+
return Effect.succeed(fromHost(result, label));
|
|
152
|
+
return host.promises.create(Effect.map(Effect.tryPromise({ try: () => result, catch: thrown }), (settled) => fromHost(settled, label)));
|
|
153
|
+
};
|
|
154
|
+
const args = (values, label) => values.map((value, index) => toHost(value, `Argument ${index + 1} to ${label}`));
|
|
155
|
+
// Own members of each level from `from` up to (excluding) `root`, child first, as JS resolves them.
|
|
156
|
+
const members = (target, from, root, skip, label, receiver) => {
|
|
157
|
+
for (let level = from; level !== null && level !== root; level = Object.getPrototypeOf(level)) {
|
|
158
|
+
for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(level))) {
|
|
159
|
+
if (skip.has(key) || target.props.has(key))
|
|
160
|
+
continue;
|
|
161
|
+
const name = `${label}.${key}`;
|
|
162
|
+
if (typeof descriptor.value === "function") {
|
|
163
|
+
const method = descriptor.value;
|
|
164
|
+
const impl = (thisValue, values) => {
|
|
165
|
+
const self = receiver(thisValue, name);
|
|
166
|
+
const converted = args(values, name);
|
|
167
|
+
return invoke(() => method.apply(self, converted), name);
|
|
168
|
+
};
|
|
169
|
+
define(target, key, fn(protos, key, method.length, impl), hidden);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// Data properties stay host-side: a program write to one would change the host class itself.
|
|
173
|
+
if ("value" in descriptor)
|
|
174
|
+
continue;
|
|
175
|
+
const get = descriptor.get;
|
|
176
|
+
const set = descriptor.set;
|
|
177
|
+
defineAccessor(target, key, get === undefined
|
|
178
|
+
? undefined
|
|
179
|
+
: (thisValue) => {
|
|
180
|
+
const value = get.call(receiver(thisValue, name));
|
|
181
|
+
if (value instanceof Promise)
|
|
182
|
+
throw typeError(`${name} returned a Promise; a getter must be synchronous.`);
|
|
183
|
+
return fromHost(value, name);
|
|
184
|
+
}, set === undefined
|
|
185
|
+
? undefined
|
|
186
|
+
: (thisValue, value) => {
|
|
187
|
+
set.call(receiver(thisValue, name), toHost(value, `${name} value`));
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const expose = (cls) => {
|
|
193
|
+
const existing = exposed.get(cls);
|
|
194
|
+
if (existing !== undefined)
|
|
195
|
+
return existing;
|
|
196
|
+
const ancestor = exposedAncestor(cls);
|
|
197
|
+
const base = ancestor === undefined ? undefined : expose(ancestor);
|
|
198
|
+
const proto = new ProgramObject(base === undefined ? protos.Object : base.proto);
|
|
199
|
+
prototypes.set(cls.prototype, proto);
|
|
200
|
+
const name = cls.name;
|
|
201
|
+
const ctor = constructor(protos, proto, {
|
|
202
|
+
name,
|
|
203
|
+
length: cls.length,
|
|
204
|
+
call: (_, values) => {
|
|
205
|
+
const converted = args(values, name);
|
|
206
|
+
return invoke(() => cls.apply(undefined, converted), name);
|
|
207
|
+
},
|
|
208
|
+
construct: (values) => {
|
|
209
|
+
const label = `new ${name}`;
|
|
210
|
+
const construct = cls;
|
|
211
|
+
const converted = args(values, label);
|
|
212
|
+
return invoke(() => new construct(...converted), label);
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
if (base !== undefined)
|
|
216
|
+
ctor.proto = base.ctor;
|
|
217
|
+
const entry = { ctor, proto };
|
|
218
|
+
exposed.set(cls, entry);
|
|
219
|
+
classOf.set(ctor, cls);
|
|
220
|
+
// A static called through an exposed subclass sees that subclass as `this`, like JS.
|
|
221
|
+
members(ctor, cls, ancestor ?? Function.prototype, ownFunctionKeys, name, (thisValue) => {
|
|
222
|
+
const called = classOf.get(thisValue);
|
|
223
|
+
return called !== undefined && (called === cls || called.prototype instanceof cls) ? called : cls;
|
|
224
|
+
});
|
|
225
|
+
members(proto, cls.prototype, ancestor?.prototype ?? Object.prototype, ownPrototypeKeys, `${name}.prototype`, (thisValue, member) => {
|
|
226
|
+
if (thisValue instanceof ProgramHandle && thisValue.instance instanceof cls)
|
|
227
|
+
return thisValue.instance;
|
|
228
|
+
throw typeError(`Illegal invocation: ${member} called on ${describeValue(thisValue)}.`);
|
|
229
|
+
});
|
|
230
|
+
return entry;
|
|
231
|
+
};
|
|
232
|
+
const exposedAncestor = (cls) => {
|
|
233
|
+
for (let level = Object.getPrototypeOf(cls); isClass(level); level = Object.getPrototypeOf(level)) {
|
|
234
|
+
if (classes.has(level))
|
|
235
|
+
return level;
|
|
236
|
+
}
|
|
237
|
+
return undefined;
|
|
238
|
+
};
|
|
239
|
+
return extensions.flatMap((extension) => Object.entries(extension.globals).map(([name, value]) => {
|
|
240
|
+
if (isClass(value))
|
|
241
|
+
return [name, expose(value).ctor];
|
|
242
|
+
const impl = (_, values) => {
|
|
243
|
+
const converted = args(values, name);
|
|
244
|
+
return invoke(() => value.apply(undefined, converted), name);
|
|
245
|
+
};
|
|
246
|
+
return [name, fn(protos, name, value.length, impl)];
|
|
247
|
+
}));
|
|
248
|
+
};
|
|
249
|
+
const hostErrors = {
|
|
250
|
+
TypeError,
|
|
251
|
+
RangeError,
|
|
252
|
+
SyntaxError,
|
|
253
|
+
ReferenceError,
|
|
254
|
+
EvalError,
|
|
255
|
+
URIError,
|
|
256
|
+
};
|
|
257
|
+
// The primitives the interpreter operates on; symbols and BigInts are not among them.
|
|
258
|
+
const isPrimitive = (value) => value === null ||
|
|
259
|
+
value === undefined ||
|
|
260
|
+
typeof value === "string" ||
|
|
261
|
+
typeof value === "number" ||
|
|
262
|
+
typeof value === "boolean";
|
|
263
|
+
const describeHost = (value) => {
|
|
264
|
+
if (typeof value === "function")
|
|
265
|
+
return "a function";
|
|
266
|
+
if (typeof value !== "object" || value === null)
|
|
267
|
+
return `a ${typeof value}`;
|
|
268
|
+
const name = value.constructor?.name;
|
|
269
|
+
return name === undefined || name === "" ? "an object" : `a ${name}`;
|
|
270
|
+
};
|
|
@@ -9,5 +9,7 @@ export type Host<R> = {
|
|
|
9
9
|
readonly toolKeys: (path: ReadonlyArray<string>) => ReadonlyArray<string>;
|
|
10
10
|
readonly logs: Array<string>;
|
|
11
11
|
};
|
|
12
|
+
/** Names bound in every program before extensions apply. */
|
|
13
|
+
export declare const globalNames: ReadonlySet<string>;
|
|
12
14
|
/** The immutable global bindings of every program, in declaration order. */
|
|
13
15
|
export declare const globals: <R>(host: Host<R>) => ReadonlyArray<readonly [string, unknown]>;
|
|
@@ -42,44 +42,47 @@ const symbolGlobal = (runner) => {
|
|
|
42
42
|
constants(symbol, { asyncIterator: AsyncIteratorSymbol, iterator: IteratorSymbol });
|
|
43
43
|
return symbol;
|
|
44
44
|
};
|
|
45
|
+
// A table rather than a list so the names are known before any runtime exists.
|
|
46
|
+
const table = {
|
|
47
|
+
tools: () => new ToolReference([]),
|
|
48
|
+
search: (host) => native(host.runner.prototypes, { name: "search", call: (_, args) => host.search(args), callback: false }),
|
|
49
|
+
undefined: () => undefined,
|
|
50
|
+
NaN: () => NaN,
|
|
51
|
+
Infinity: () => Infinity,
|
|
52
|
+
Object: (host) => objectGlobal(host.runner, host.toolKeys),
|
|
53
|
+
Function: (host) => functionGlobal(host.runner),
|
|
54
|
+
Array: (host) => arrayGlobal(host.runner),
|
|
55
|
+
Math: (host) => mathGlobal(host.runner),
|
|
56
|
+
JSON: (host) => jsonGlobal(host.runner),
|
|
57
|
+
console: (host) => consoleGlobal(host.runner, host.logs),
|
|
58
|
+
Promise: (host) => promiseGlobal(host.runner, host.promises),
|
|
59
|
+
Symbol: (host) => symbolGlobal(host.runner),
|
|
60
|
+
Number: (host) => numberGlobal(host.runner),
|
|
61
|
+
String: (host) => stringGlobal(host.runner),
|
|
62
|
+
Boolean: (host) => booleanGlobal(host.runner),
|
|
63
|
+
parseInt: (host) => coercion(host.runner, "parseInt", 2),
|
|
64
|
+
parseFloat: (host) => coercion(host.runner, "parseFloat"),
|
|
65
|
+
isFinite: (host) => coercion(host.runner, "isFinite"),
|
|
66
|
+
isNaN: (host) => coercion(host.runner, "isNaN"),
|
|
67
|
+
Date: (host) => dateGlobal(host.runner),
|
|
68
|
+
RegExp: (host) => regexpGlobal(host.runner),
|
|
69
|
+
Map: (host) => mapGlobal(host.runner),
|
|
70
|
+
Set: (host) => setGlobal(host.runner),
|
|
71
|
+
URL: (host) => urlGlobal(host.runner),
|
|
72
|
+
URLSearchParams: (host) => urlSearchParamsGlobal(host.runner),
|
|
73
|
+
encodeURI: (host) => uriGlobal(host.runner, "encodeURI"),
|
|
74
|
+
encodeURIComponent: (host) => uriGlobal(host.runner, "encodeURIComponent"),
|
|
75
|
+
decodeURI: (host) => uriGlobal(host.runner, "decodeURI"),
|
|
76
|
+
decodeURIComponent: (host) => uriGlobal(host.runner, "decodeURIComponent"),
|
|
77
|
+
atob: (host) => base64Global(host.runner, "atob"),
|
|
78
|
+
btoa: (host) => base64Global(host.runner, "btoa"),
|
|
79
|
+
crypto: (host) => cryptoGlobal(host.runner),
|
|
80
|
+
...Object.fromEntries(errorTypes.map((type) => [type, (host) => errorGlobal(type, host.runner)])),
|
|
81
|
+
};
|
|
82
|
+
/** Names bound in every program before extensions apply. */
|
|
83
|
+
export const globalNames = new Set(Object.keys(table));
|
|
45
84
|
/** The immutable global bindings of every program, in declaration order. */
|
|
46
85
|
export const globals = (host) => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return [
|
|
50
|
-
["tools", new ToolReference([])],
|
|
51
|
-
["search", native(runner.prototypes, { name: "search", call: (_, args) => host.search(args), callback: false })],
|
|
52
|
-
["undefined", undefined],
|
|
53
|
-
["NaN", NaN],
|
|
54
|
-
["Infinity", Infinity],
|
|
55
|
-
["Object", objectGlobal(runner, host.toolKeys)],
|
|
56
|
-
["Function", functionGlobal(runner)],
|
|
57
|
-
["Array", arrayGlobal(runner)],
|
|
58
|
-
["Math", mathGlobal(runner)],
|
|
59
|
-
["JSON", jsonGlobal(runner)],
|
|
60
|
-
["console", consoleGlobal(runner, host.logs)],
|
|
61
|
-
["Promise", promiseGlobal(runner, host.promises)],
|
|
62
|
-
["Symbol", symbolGlobal(runner)],
|
|
63
|
-
["Number", numberGlobal(runner)],
|
|
64
|
-
["String", stringGlobal(runner)],
|
|
65
|
-
["Boolean", booleanGlobal(runner)],
|
|
66
|
-
["parseInt", coercion(runner, "parseInt", 2)],
|
|
67
|
-
["parseFloat", coercion(runner, "parseFloat")],
|
|
68
|
-
["isFinite", coercion(runner, "isFinite")],
|
|
69
|
-
["isNaN", coercion(runner, "isNaN")],
|
|
70
|
-
["Date", dateGlobal(runner)],
|
|
71
|
-
["RegExp", regexpGlobal(runner)],
|
|
72
|
-
["Map", mapGlobal(runner)],
|
|
73
|
-
["Set", setGlobal(runner)],
|
|
74
|
-
["URL", urlGlobal(runner)],
|
|
75
|
-
["URLSearchParams", urlSearchParamsGlobal(runner)],
|
|
76
|
-
["encodeURI", uriGlobal(runner, "encodeURI")],
|
|
77
|
-
["encodeURIComponent", uriGlobal(runner, "encodeURIComponent")],
|
|
78
|
-
["decodeURI", uriGlobal(runner, "decodeURI")],
|
|
79
|
-
["decodeURIComponent", uriGlobal(runner, "decodeURIComponent")],
|
|
80
|
-
["atob", base64Global(runner, "atob")],
|
|
81
|
-
["btoa", base64Global(runner, "btoa")],
|
|
82
|
-
["crypto", cryptoGlobal(runner)],
|
|
83
|
-
...errorTypes.map((type) => [type, errorGlobal(type, runner)]),
|
|
84
|
-
];
|
|
86
|
+
generatorGlobals(host.runner, host.promises);
|
|
87
|
+
return Object.entries(table).map(([name, factory]) => [name, factory(host)]);
|
|
85
88
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Longest string a built-in or operator may produce. */
|
|
2
|
+
export declare const MAX_STRING_LENGTH: number;
|
|
3
|
+
/** Longest array a built-in may create or grow to. */
|
|
4
|
+
export declare const MAX_ARRAY_LENGTH = 10000000;
|
|
5
|
+
/** Most promises that may be pending at once. */
|
|
6
|
+
export declare const MAX_PENDING_PROMISES = 10000;
|
|
7
|
+
export declare const checkStringLength: (length: number) => void;
|
|
8
|
+
export declare const checkArrayLength: (length: number) => void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { rangeError } from "./model.js";
|
|
2
|
+
// The timeout only fires between interpreter steps, so a single built-in must not be able to materialize an
|
|
3
|
+
// unbounded value. These bound what one call may build; programs cannot reach such sizes any other way.
|
|
4
|
+
/** Longest string a built-in or operator may produce. */
|
|
5
|
+
export const MAX_STRING_LENGTH = 1 << 24;
|
|
6
|
+
/** Longest array a built-in may create or grow to. */
|
|
7
|
+
export const MAX_ARRAY_LENGTH = 10_000_000;
|
|
8
|
+
/** Most promises that may be pending at once. */
|
|
9
|
+
export const MAX_PENDING_PROMISES = 10_000;
|
|
10
|
+
export const checkStringLength = (length) => {
|
|
11
|
+
if (length > MAX_STRING_LENGTH)
|
|
12
|
+
throw rangeError("Invalid string length");
|
|
13
|
+
};
|
|
14
|
+
export const checkArrayLength = (length) => {
|
|
15
|
+
if (length > MAX_ARRAY_LENGTH)
|
|
16
|
+
throw rangeError("Invalid array length");
|
|
17
|
+
};
|
|
@@ -104,6 +104,11 @@ export declare class ProgramURL extends ProgramObject {
|
|
|
104
104
|
readonly searchParams: ProgramURLSearchParams;
|
|
105
105
|
constructor(proto: ProgramObject, searchParamsProto: ProgramObject, url: URL);
|
|
106
106
|
}
|
|
107
|
+
/** An instance of an extension class: the host object lives in a field no property path reaches. */
|
|
108
|
+
export declare class ProgramHandle extends ProgramObject {
|
|
109
|
+
readonly instance: object;
|
|
110
|
+
constructor(proto: ProgramObject, instance: object);
|
|
111
|
+
}
|
|
107
112
|
/** Built-in objects that wrap a host value; data-like, but never plain data. */
|
|
108
113
|
export declare const isWrapper: (value: unknown) => value is ProgramDate | ProgramRegExp | ProgramMap | ProgramSet | ProgramURL | ProgramURLSearchParams;
|
|
109
114
|
export declare const parseArrayIndex: (key: string | number) => number | undefined;
|
|
@@ -119,7 +124,7 @@ export declare const hasPrototype: (value: unknown, proto: ProgramObject) => boo
|
|
|
119
124
|
export declare const set: (target: ProgramObject, key: PropertyKey, value: unknown) => boolean;
|
|
120
125
|
/** [[DefineOwnProperty]] for a data property, ignoring the chain. */
|
|
121
126
|
export declare const define: (target: ProgramObject, key: PropertyKey, value: unknown, attrs?: Attributes) => void;
|
|
122
|
-
export declare const defineAccessor: (target: ProgramObject, key: PropertyKey, get: Getter, set?: Setter) => void;
|
|
127
|
+
export declare const defineAccessor: (target: ProgramObject, key: PropertyKey, get: Getter | undefined, set?: Setter) => void;
|
|
123
128
|
export declare const remove: (target: ProgramObject, key: PropertyKey) => boolean;
|
|
124
129
|
export declare const ownKeys: (target: ProgramObject) => Array<string | symbol>;
|
|
125
130
|
/** Own enumerable keys, including the iterator symbols; what spread and `Object.assign` copy. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { checkArrayLength } from "./limits.js";
|
|
1
2
|
import { AsyncIteratorSymbol, IteratorSymbol, } from "./model.js";
|
|
2
3
|
/** Ordinary assignment: writable, enumerable, configurable. */
|
|
3
4
|
export const data = { writable: true, enumerable: true, configurable: true };
|
|
@@ -114,6 +115,14 @@ export class ProgramURL extends ProgramObject {
|
|
|
114
115
|
this.searchParams = new ProgramURLSearchParams(searchParamsProto, url.searchParams);
|
|
115
116
|
}
|
|
116
117
|
}
|
|
118
|
+
/** An instance of an extension class: the host object lives in a field no property path reaches. */
|
|
119
|
+
export class ProgramHandle extends ProgramObject {
|
|
120
|
+
instance;
|
|
121
|
+
constructor(proto, instance) {
|
|
122
|
+
super(proto);
|
|
123
|
+
this.instance = instance;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
117
126
|
/** Built-in objects that wrap a host value; data-like, but never plain data. */
|
|
118
127
|
export const isWrapper = (value) => value instanceof ProgramDate ||
|
|
119
128
|
value instanceof ProgramRegExp ||
|
|
@@ -121,13 +130,13 @@ export const isWrapper = (value) => value instanceof ProgramDate ||
|
|
|
121
130
|
value instanceof ProgramSet ||
|
|
122
131
|
value instanceof ProgramURL ||
|
|
123
132
|
value instanceof ProgramURLSearchParams;
|
|
124
|
-
const
|
|
133
|
+
const MAX_ARRAY_INDEX = 4_294_967_295;
|
|
125
134
|
export const parseArrayIndex = (key) => {
|
|
126
135
|
const property = String(key);
|
|
127
136
|
if (!/^(0|[1-9]\d*)$/.test(property))
|
|
128
137
|
return undefined;
|
|
129
138
|
const index = Number(property);
|
|
130
|
-
return index <
|
|
139
|
+
return index < MAX_ARRAY_INDEX ? index : undefined;
|
|
131
140
|
};
|
|
132
141
|
const canonical = (key) => (typeof key === "symbol" ? key : String(key));
|
|
133
142
|
const index = (target, key) => target instanceof ProgramArray && typeof key === "string" ? parseArrayIndex(key) : undefined;
|
|
@@ -182,8 +191,9 @@ const writeArray = (target, name, value) => {
|
|
|
182
191
|
if (name !== "length")
|
|
183
192
|
return undefined;
|
|
184
193
|
const length = typeof value === "number" ? value : Number(value);
|
|
185
|
-
if (!Number.isInteger(length) || length < 0
|
|
194
|
+
if (!Number.isInteger(length) || length < 0)
|
|
186
195
|
return false;
|
|
196
|
+
checkArrayLength(length);
|
|
187
197
|
target.items.length = length;
|
|
188
198
|
return true;
|
|
189
199
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Cause, Deferred, Effect, Exit, Fiber, Scope } from "effect";
|
|
2
|
-
import {
|
|
2
|
+
import { MAX_PENDING_PROMISES } from "./limits.js";
|
|
3
|
+
import { CallSite, ProgramThrow, rangeError, typeError } from "./model.js";
|
|
3
4
|
import { Callable, define, get, hidden, ProgramArray, ProgramFunction, ProgramObject, ProgramPromise, record, } from "./objects.js";
|
|
4
5
|
import { constructor, fn, methods, native, receiver, requiresNew } from "./native.js";
|
|
5
6
|
import { createAggregateErrorValue, locate, materialize, normalizeError } from "./errors.js";
|
|
@@ -33,6 +34,9 @@ export class PromiseRuntime {
|
|
|
33
34
|
}
|
|
34
35
|
create(effect) {
|
|
35
36
|
return Effect.flatMap(CallSite, (site) => {
|
|
37
|
+
if (this.active.size >= MAX_PENDING_PROMISES) {
|
|
38
|
+
throw rangeError(`Too many pending promises (limit ${MAX_PENDING_PROMISES}); await promises before creating more.`);
|
|
39
|
+
}
|
|
36
40
|
// Allocate before forking so reruns get distinct IDs and diagnostics retain creation order.
|
|
37
41
|
const id = this.nextID++;
|
|
38
42
|
const body = Effect.catchDefect(effect, (defect) => Effect.die(locate(defect, site.node)));
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ToolReference } from "../tool-runtime.js";
|
|
2
2
|
import { invalidData } from "./model.js";
|
|
3
|
-
import { Callable, getOwn, isWrapper, ownKeys, ProgramArray, ProgramDate, ProgramGenerator, ProgramMap, ProgramObject, ProgramPromise, ProgramRegExp, ProgramSet, ProgramURL, ProgramURLSearchParams, } from "./objects.js";
|
|
3
|
+
import { Callable, getOwn, isWrapper, ownKeys, ProgramArray, ProgramDate, ProgramGenerator, ProgramHandle, ProgramMap, ProgramObject, ProgramPromise, ProgramRegExp, ProgramSet, ProgramURL, ProgramURLSearchParams, } from "./objects.js";
|
|
4
4
|
/** Values that cannot cross the data boundary. */
|
|
5
5
|
export const isRuntimeReference = (value) => value instanceof Callable ||
|
|
6
6
|
value instanceof ProgramGenerator ||
|
|
7
|
+
value instanceof ProgramHandle ||
|
|
7
8
|
value instanceof ToolReference ||
|
|
8
9
|
value instanceof ProgramPromise ||
|
|
9
10
|
isWrapper(value);
|
|
@@ -65,6 +66,8 @@ export const describeValue = (value) => {
|
|
|
65
66
|
return "a URLSearchParams";
|
|
66
67
|
if (value instanceof ProgramGenerator)
|
|
67
68
|
return "a generator";
|
|
69
|
+
if (value instanceof ProgramHandle)
|
|
70
|
+
return `a ${value.instance.constructor.name}`;
|
|
68
71
|
if (isRuntimeReference(value))
|
|
69
72
|
return "a function";
|
|
70
73
|
if (typeof value === "object")
|
|
@@ -2,6 +2,7 @@ import { Cause, Deferred, Effect, Exit } from "effect";
|
|
|
2
2
|
import { toProgram } from "../data.js";
|
|
3
3
|
import { ToolReference } from "../tool-runtime.js";
|
|
4
4
|
import { AsyncIteratorSymbol, CallSite, GeneratorReturn, IteratorSymbol, OptionalShortCircuit, invalidData, ProgramThrow, rangeError, typeError, unsupportedSyntax, } from "./model.js";
|
|
5
|
+
import { checkStringLength } from "./limits.js";
|
|
5
6
|
import { locate, materialize } from "./errors.js";
|
|
6
7
|
import { globals } from "./globals.js";
|
|
7
8
|
import { assign, Callable, define, get, has, hasPrototype, keys, NativeFunction, parseArrayIndex, ProgramArray, ProgramDate, ProgramFunction, ProgramGenerator, ProgramMap, ProgramObject, ProgramPromise, ProgramSet, ProgramURLSearchParams, record, remove, set, } from "./objects.js";
|
|
@@ -1054,8 +1055,12 @@ class Frame {
|
|
|
1054
1055
|
const l = coerceOperand(lhs);
|
|
1055
1056
|
const r = coerceOperand(rhs);
|
|
1056
1057
|
switch (operator) {
|
|
1057
|
-
case "+":
|
|
1058
|
-
|
|
1058
|
+
case "+": {
|
|
1059
|
+
const sum = l + r;
|
|
1060
|
+
if (typeof sum === "string")
|
|
1061
|
+
checkStringLength(sum.length);
|
|
1062
|
+
return sum;
|
|
1063
|
+
}
|
|
1059
1064
|
case "-":
|
|
1060
1065
|
return l - r;
|
|
1061
1066
|
case "*":
|
|
@@ -1641,9 +1646,11 @@ class Frame {
|
|
|
1641
1646
|
throw typeError("Invalid template literal quasi.", quasi);
|
|
1642
1647
|
}
|
|
1643
1648
|
output += quasi.value.cooked;
|
|
1649
|
+
checkStringLength(output.length);
|
|
1644
1650
|
if (index < expressions.length) {
|
|
1645
1651
|
const raw = yield* self.evaluateExpression(expressions[index]);
|
|
1646
1652
|
output += coerceToString(toProgram(self.runtime.prototypes, raw, "Template interpolation"));
|
|
1653
|
+
checkStringLength(output.length);
|
|
1647
1654
|
}
|
|
1648
1655
|
}
|
|
1649
1656
|
return output;
|
package/dist/stdlib/array.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { constructor, methods, prototypeFrom, receiver } from "../interpreter/native.js";
|
|
3
|
+
import { checkArrayLength, checkStringLength, MAX_ARRAY_LENGTH } from "../interpreter/limits.js";
|
|
3
4
|
import { invalidData, rangeError, typeError } from "../interpreter/model.js";
|
|
4
5
|
import { get, ProgramArray, ProgramGenerator, ProgramObject } from "../interpreter/objects.js";
|
|
5
6
|
import { describeValue, rejectCircularInsertion } from "../interpreter/references.js";
|
|
6
7
|
import { applyCollectionCallback, preserveConsumerError } from "../interpreter/runner.js";
|
|
7
8
|
import { compareText } from "../tool-runtime.js";
|
|
8
9
|
import { coerceToNumber, coerceToString } from "./value.js";
|
|
9
|
-
const MAX_LENGTH = 4_294_967_295;
|
|
10
10
|
const arrayLikeSource = (source) => {
|
|
11
11
|
if (source instanceof ProgramObject && typeof get(source, "length") === "number") {
|
|
12
12
|
const length = get(source, "length");
|
|
13
13
|
const normalized = Number.isNaN(length) || length <= 0 ? 0 : Math.trunc(length);
|
|
14
|
-
|
|
15
|
-
throw new RangeError("Invalid array length");
|
|
14
|
+
checkArrayLength(normalized);
|
|
16
15
|
return { length: normalized, source };
|
|
17
16
|
}
|
|
18
17
|
throw invalidData(`Array.from expects an array, string, Map, Set, or array-like value, received ${describeValue(source)}.`);
|
|
@@ -87,7 +86,7 @@ export const arrayGlobal = (runner) => {
|
|
|
87
86
|
const first = args[0];
|
|
88
87
|
if (typeof first !== "number")
|
|
89
88
|
return new ProgramArray(into, [first]);
|
|
90
|
-
if (!Number.isInteger(first) || first < 0 || first >
|
|
89
|
+
if (!Number.isInteger(first) || first < 0 || first > MAX_ARRAY_LENGTH)
|
|
91
90
|
throw rangeError("Invalid array length.");
|
|
92
91
|
// Sparse like JS: Array(3) has holes, and combinator loops already skip them.
|
|
93
92
|
return new ProgramArray(into, new Array(first));
|
|
@@ -130,7 +129,11 @@ export const arrayGlobal = (runner) => {
|
|
|
130
129
|
if (args.length > 1 || (args.length === 1 && typeof args[0] !== "string")) {
|
|
131
130
|
throw typeError("Array.join expects zero arguments or one string separator.");
|
|
132
131
|
}
|
|
133
|
-
|
|
132
|
+
const joined = target
|
|
133
|
+
.map((item) => coerceToString(item ?? ""))
|
|
134
|
+
.join(args.length === 0 ? "," : args[0]);
|
|
135
|
+
checkStringLength(joined.length);
|
|
136
|
+
return joined;
|
|
134
137
|
},
|
|
135
138
|
],
|
|
136
139
|
[
|
|
@@ -175,14 +178,20 @@ export const arrayGlobal = (runner) => {
|
|
|
175
178
|
[
|
|
176
179
|
"concat",
|
|
177
180
|
1,
|
|
178
|
-
(thisValue, args) =>
|
|
181
|
+
(thisValue, args) => {
|
|
182
|
+
const joined = self(thisValue, "concat").items.concat(...args.map((item) => (item instanceof ProgramArray ? item.items : item)));
|
|
183
|
+
checkArrayLength(joined.length);
|
|
184
|
+
return wrap(joined);
|
|
185
|
+
},
|
|
179
186
|
],
|
|
180
187
|
[
|
|
181
188
|
"flat",
|
|
182
189
|
0,
|
|
183
190
|
(thisValue, args) => {
|
|
184
191
|
const flatten = (items, depth) => items.flatMap((item) => (item instanceof ProgramArray && depth > 0 ? flatten(item.items, depth - 1) : [item]));
|
|
185
|
-
|
|
192
|
+
const flattened = flatten(self(thisValue, "flat").items, optNumber("flat", args[0], "depth") ?? 1);
|
|
193
|
+
checkArrayLength(flattened.length);
|
|
194
|
+
return wrap(flattened);
|
|
186
195
|
},
|
|
187
196
|
],
|
|
188
197
|
[
|
package/dist/stdlib/json.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { methods } from "../interpreter/native.js";
|
|
3
3
|
import { applyCollectionCallback } from "../interpreter/runner.js";
|
|
4
|
+
import { checkStringLength } from "../interpreter/limits.js";
|
|
4
5
|
import { syntaxError, typeError } from "../interpreter/model.js";
|
|
5
6
|
import { typeofValue } from "../interpreter/references.js";
|
|
6
7
|
import { fromData, toData, toProgram } from "../data.js";
|
|
@@ -54,7 +55,10 @@ const stringify = (runner, args) => {
|
|
|
54
55
|
.map(String)
|
|
55
56
|
: null;
|
|
56
57
|
// A string cannot pollute, so __proto__ stays: JSON.stringify includes own __proto__ keys, like JS.
|
|
57
|
-
|
|
58
|
+
const text = JSON.stringify(toData(args[0], "JSON.stringify value", "json", false), properties, indent);
|
|
59
|
+
if (text !== undefined)
|
|
60
|
+
checkStringLength(text.length);
|
|
61
|
+
return Effect.succeed(text);
|
|
58
62
|
}
|
|
59
63
|
// Validate up front; the replacer walk below reads the original value.
|
|
60
64
|
toProgram(runner.prototypes, args[0], "JSON.stringify value");
|
package/dist/stdlib/object.js
CHANGED
|
@@ -99,7 +99,7 @@ export const objectGlobal = (runner, toolKeys) => {
|
|
|
99
99
|
const first = args[0];
|
|
100
100
|
if (first === null || first === undefined)
|
|
101
101
|
return new ProgramObject(protos.Object);
|
|
102
|
-
if (
|
|
102
|
+
if (first instanceof ProgramObject)
|
|
103
103
|
return first;
|
|
104
104
|
throw typeError(`Object(${typeof first}) wrapper objects are not supported; use the primitive value directly.`);
|
|
105
105
|
};
|
package/dist/stdlib/string.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { toProgram } from "../data.js";
|
|
3
3
|
import { constructor, methods } from "../interpreter/native.js";
|
|
4
|
+
import { checkArrayLength, checkStringLength } from "../interpreter/limits.js";
|
|
4
5
|
import { invalidData, rangeError, typeError } from "../interpreter/model.js";
|
|
5
6
|
import { ProgramArray, ProgramPromise, ProgramRegExp, record } from "../interpreter/objects.js";
|
|
6
7
|
import { containsOpaqueReference, typeofValue } from "../interpreter/references.js";
|
|
@@ -156,9 +157,11 @@ export const stringGlobal = (runner) => {
|
|
|
156
157
|
if (args[0] === undefined) {
|
|
157
158
|
return wrap(requestedLimit !== undefined && requestedLimit >>> 0 === 0 ? [] : [value]);
|
|
158
159
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
const parts = args[0] instanceof ProgramRegExp
|
|
161
|
+
? value.split(args[0].regex, requestedLimit)
|
|
162
|
+
: value.split(str("split", args, 0), requestedLimit === undefined ? undefined : requestedLimit >>> 0);
|
|
163
|
+
checkArrayLength(parts.length);
|
|
164
|
+
return wrap(parts);
|
|
162
165
|
}),
|
|
163
166
|
simple("slice", 2, (value, args) => value.slice(optNum("slice", args, 0), optNum("slice", args, 1))),
|
|
164
167
|
simple("includes", 1, (value, args) => {
|
|
@@ -192,7 +195,12 @@ export const stringGlobal = (runner) => {
|
|
|
192
195
|
if (!pattern.global) {
|
|
193
196
|
throw typeError(`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.`);
|
|
194
197
|
}
|
|
195
|
-
|
|
198
|
+
const matches = [];
|
|
199
|
+
for (const match of value.matchAll(pattern)) {
|
|
200
|
+
checkArrayLength(matches.length + 1);
|
|
201
|
+
matches.push(matchToValue(protos, match));
|
|
202
|
+
}
|
|
203
|
+
return new ProgramArray(protos.Array, matches);
|
|
196
204
|
}),
|
|
197
205
|
simple("search", 1, (value, args) => value.search(toHostRegex(args[0], "search"))),
|
|
198
206
|
simple("repeat", 1, (value, args) => {
|
|
@@ -200,10 +208,19 @@ export const stringGlobal = (runner) => {
|
|
|
200
208
|
if (!Number.isFinite(count) || count < 0) {
|
|
201
209
|
throw rangeError("String.repeat expects a finite non-negative count.");
|
|
202
210
|
}
|
|
211
|
+
checkStringLength(value.length * count);
|
|
203
212
|
return value.repeat(count);
|
|
204
213
|
}),
|
|
205
|
-
simple("padStart", 1, (value, args) =>
|
|
206
|
-
|
|
214
|
+
simple("padStart", 1, (value, args) => {
|
|
215
|
+
const length = num("padStart", args, 0);
|
|
216
|
+
checkStringLength(length);
|
|
217
|
+
return value.padStart(length, optStr("padStart", args, 1));
|
|
218
|
+
}),
|
|
219
|
+
simple("padEnd", 1, (value, args) => {
|
|
220
|
+
const length = num("padEnd", args, 0);
|
|
221
|
+
checkStringLength(length);
|
|
222
|
+
return value.padEnd(length, optStr("padEnd", args, 1));
|
|
223
|
+
}),
|
|
207
224
|
simple("charAt", 1, (value, args) => value.charAt(optNum("charAt", args, 0) ?? 0)),
|
|
208
225
|
simple("at", 1, (value, args) => value.at(optNum("at", args, 0) ?? 0)),
|
|
209
226
|
simple("substring", 2, (value, args) => value.substring(optNum("substring", args, 0) ?? 0, optNum("substring", args, 1))),
|
|
@@ -212,7 +229,11 @@ export const stringGlobal = (runner) => {
|
|
|
212
229
|
simple("toWellFormed", 0, (value) => value.toWellFormed()),
|
|
213
230
|
simple("charCodeAt", 1, (value, args) => value.charCodeAt(optNum("charCodeAt", args, 0) ?? 0)),
|
|
214
231
|
simple("codePointAt", 1, (value, args) => value.codePointAt(optNum("codePointAt", args, 0) ?? 0)),
|
|
215
|
-
simple("concat", 1, (value, args) =>
|
|
232
|
+
simple("concat", 1, (value, args) => {
|
|
233
|
+
const joined = value.concat(...args.map((_, index) => str("concat", args, index)));
|
|
234
|
+
checkStringLength(joined.length);
|
|
235
|
+
return joined;
|
|
236
|
+
}),
|
|
216
237
|
]);
|
|
217
238
|
return string;
|
|
218
239
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@opencode/codemode",
|
|
4
|
-
"version": "0.0.0-dev-
|
|
4
|
+
"version": "0.0.0-dev-19610",
|
|
5
5
|
"description": "Effect-native confined code execution over schema-described tools",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|