@opencode/codemode 0.0.0-reserved → 2.0.1
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/README.md +184 -2
- package/dist/codemode.d.ts +145 -0
- package/dist/codemode.js +66 -0
- package/dist/data.d.ts +25 -0
- package/dist/data.js +158 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/interpreter/errors.d.ts +10 -0
- package/dist/interpreter/errors.js +112 -0
- package/dist/interpreter/execute.d.ts +5 -0
- package/dist/interpreter/execute.js +171 -0
- package/dist/interpreter/globals.d.ts +13 -0
- package/dist/interpreter/globals.js +64 -0
- package/dist/interpreter/host.d.ts +41 -0
- package/dist/interpreter/host.js +44 -0
- package/dist/interpreter/intrinsics.d.ts +10 -0
- package/dist/interpreter/intrinsics.js +41 -0
- package/dist/interpreter/methods.d.ts +4 -0
- package/dist/interpreter/methods.js +837 -0
- package/dist/interpreter/model.d.ts +89 -0
- package/dist/interpreter/model.js +90 -0
- package/dist/interpreter/objects.d.ts +37 -0
- package/dist/interpreter/objects.js +154 -0
- package/dist/interpreter/promises.d.ts +31 -0
- package/dist/interpreter/promises.js +270 -0
- package/dist/interpreter/references.d.ts +7 -0
- package/dist/interpreter/references.js +93 -0
- package/dist/interpreter/runner.d.ts +26 -0
- package/dist/interpreter/runner.js +45 -0
- package/dist/interpreter/runtime.d.ts +19 -0
- package/dist/interpreter/runtime.js +1942 -0
- package/dist/interpreter/scope.d.ts +15 -0
- package/dist/interpreter/scope.js +79 -0
- package/dist/interpreter/transpile.node.d.ts +5 -0
- package/dist/interpreter/transpile.node.js +19 -0
- package/dist/interpreter/transpile.workerd.d.ts +5 -0
- package/dist/interpreter/transpile.workerd.js +6 -0
- package/dist/namespace.d.ts +15 -0
- package/dist/namespace.js +7 -0
- package/dist/openapi/index.d.ts +7 -0
- package/dist/openapi/index.js +101 -0
- package/dist/openapi/runtime.d.ts +4 -0
- package/dist/openapi/runtime.js +283 -0
- package/dist/openapi/spec.d.ts +20 -0
- package/dist/openapi/spec.js +583 -0
- package/dist/openapi/types.d.ts +122 -0
- package/dist/openapi/types.js +2 -0
- package/dist/stdlib/array.d.ts +3 -0
- package/dist/stdlib/array.js +68 -0
- package/dist/stdlib/collections.d.ts +9 -0
- package/dist/stdlib/collections.js +173 -0
- package/dist/stdlib/console.d.ts +3 -0
- package/dist/stdlib/console.js +137 -0
- package/dist/stdlib/date.d.ts +8 -0
- package/dist/stdlib/date.js +208 -0
- package/dist/stdlib/json.d.ts +3 -0
- package/dist/stdlib/json.js +101 -0
- package/dist/stdlib/math.d.ts +8 -0
- package/dist/stdlib/math.js +89 -0
- package/dist/stdlib/number.d.ts +4 -0
- package/dist/stdlib/number.js +69 -0
- package/dist/stdlib/object.d.ts +7 -0
- package/dist/stdlib/object.js +106 -0
- package/dist/stdlib/regexp.d.ts +10 -0
- package/dist/stdlib/regexp.js +120 -0
- package/dist/stdlib/string.d.ts +2 -0
- package/dist/stdlib/string.js +51 -0
- package/dist/stdlib/url.d.ts +16 -0
- package/dist/stdlib/url.js +161 -0
- package/dist/stdlib/value.d.ts +8 -0
- package/dist/stdlib/value.js +98 -0
- package/dist/stdlib/web.d.ts +4 -0
- package/dist/stdlib/web.js +21 -0
- package/dist/tool-error.d.ts +11 -0
- package/dist/tool-error.js +9 -0
- package/dist/tool-runtime.d.ts +69 -0
- package/dist/tool-runtime.js +254 -0
- package/dist/tool-schema.d.ts +16 -0
- package/dist/tool-schema.js +263 -0
- package/dist/tool.d.ts +66 -0
- package/dist/tool.js +16 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +1 -0
- package/dist/values.d.ts +37 -0
- package/dist/values.js +56 -0
- package/package.json +37 -6
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { ToolError } from "../tool-error.js";
|
|
3
|
+
import { toData, ToolRuntimeError } from "../data.js";
|
|
4
|
+
import { formatLocation, InterpreterRuntimeError, ProgramThrow, sourceLocation } from "./model.js";
|
|
5
|
+
import { containsRuntimeReference } from "./references.js";
|
|
6
|
+
import { HostFunction } from "./host.js";
|
|
7
|
+
import { createErrorValue, isErrorType } from "./intrinsics.js";
|
|
8
|
+
import { get, hasPrototype, ProgramArray, ProgramError, ProgramObject, set } from "./objects.js";
|
|
9
|
+
import {} from "./runner.js";
|
|
10
|
+
import { coerceToString } from "../stdlib/value.js";
|
|
11
|
+
export const normalizeError = (error) => {
|
|
12
|
+
if (error instanceof InterpreterRuntimeError) {
|
|
13
|
+
return {
|
|
14
|
+
kind: error.kind,
|
|
15
|
+
message: `${error.message}${formatLocation(error.node)}`,
|
|
16
|
+
...(error.node?.loc ? { location: sourceLocation(error.node) } : {}),
|
|
17
|
+
...(error.suggestions ? { suggestions: error.suggestions } : {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (error instanceof ToolRuntimeError) {
|
|
21
|
+
return {
|
|
22
|
+
kind: error.kind,
|
|
23
|
+
message: error.message,
|
|
24
|
+
...(error.suggestions.length > 0 ? { suggestions: error.suggestions } : {}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (error instanceof ToolError) {
|
|
28
|
+
return { kind: "ToolFailure", message: error.message };
|
|
29
|
+
}
|
|
30
|
+
if (error instanceof ProgramThrow) {
|
|
31
|
+
const value = error.value;
|
|
32
|
+
let message;
|
|
33
|
+
if (containsRuntimeReference(value)) {
|
|
34
|
+
// Never expose runtime reference internals through thrown values.
|
|
35
|
+
message = "a non-data value";
|
|
36
|
+
}
|
|
37
|
+
else if (typeof value === "string") {
|
|
38
|
+
message = value;
|
|
39
|
+
}
|
|
40
|
+
else if (value instanceof ProgramObject && typeof get(value, "message") === "string") {
|
|
41
|
+
message = get(value, "message");
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
try {
|
|
45
|
+
message = JSON.stringify(toData(value, "Thrown value")) ?? String(value);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
message = String(value);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { kind: "ExecutionFailure", message: `Uncaught: ${message}` };
|
|
52
|
+
}
|
|
53
|
+
if (error instanceof RangeError && /call stack|recursion/i.test(error.message)) {
|
|
54
|
+
return {
|
|
55
|
+
kind: "ExecutionFailure",
|
|
56
|
+
message: "Execution exceeded the maximum nesting depth.",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (error instanceof Error) {
|
|
60
|
+
return {
|
|
61
|
+
kind: error.name === "SyntaxError" ? "ParseError" : "ExecutionFailure",
|
|
62
|
+
message: error.message,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
kind: "ExecutionFailure",
|
|
67
|
+
message: String(error),
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
export const caughtErrorValue = (runner, thrown) => {
|
|
71
|
+
if (thrown instanceof ProgramThrow)
|
|
72
|
+
return thrown.value;
|
|
73
|
+
const prototypes = runner.intrinsics.errors;
|
|
74
|
+
if (thrown instanceof InterpreterRuntimeError)
|
|
75
|
+
return createErrorValue(prototypes[thrown.type], thrown.message);
|
|
76
|
+
const type = thrown instanceof Error && isErrorType(thrown.name) ? thrown.name : "Error";
|
|
77
|
+
return createErrorValue(prototypes[type], normalizeError(thrown).message);
|
|
78
|
+
};
|
|
79
|
+
export const createAggregateErrorValue = (runner, errors, message) => {
|
|
80
|
+
const value = createErrorValue(runner.intrinsics.errors.AggregateError, message);
|
|
81
|
+
set(value, "errors", new ProgramArray(errors));
|
|
82
|
+
return value;
|
|
83
|
+
};
|
|
84
|
+
const constructAggregateErrorValue = (runner, args, node) => Effect.gen(function* () {
|
|
85
|
+
const cursor = yield* runner.syncIterator(args[0], node);
|
|
86
|
+
if (cursor === undefined) {
|
|
87
|
+
throw new InterpreterRuntimeError("new AggregateError(...) expects a synchronous iterable of errors.", node);
|
|
88
|
+
}
|
|
89
|
+
const errors = [];
|
|
90
|
+
while (true) {
|
|
91
|
+
const step = yield* cursor.next;
|
|
92
|
+
if (step.done) {
|
|
93
|
+
return createAggregateErrorValue(runner, errors, args[1] === undefined ? "" : coerceToString(args[1]));
|
|
94
|
+
}
|
|
95
|
+
errors.push(step.value);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
/** An error constructor such as `Error` or `TypeError`; callable with or without `new`, like JS. */
|
|
99
|
+
export const errorGlobal = (type, runner) => {
|
|
100
|
+
const prototype = runner.intrinsics.errors[type];
|
|
101
|
+
const construct = (args, node) => type === "AggregateError"
|
|
102
|
+
? constructAggregateErrorValue(runner, args, node)
|
|
103
|
+
: Effect.sync(() => createErrorValue(prototype, args[0] === undefined ? undefined : coerceToString(args[0])));
|
|
104
|
+
const fn = new HostFunction({
|
|
105
|
+
name: type,
|
|
106
|
+
call: construct,
|
|
107
|
+
construct,
|
|
108
|
+
instanceOf: (value) => hasPrototype(value, prototype),
|
|
109
|
+
});
|
|
110
|
+
set(prototype, "constructor", fn);
|
|
111
|
+
return fn;
|
|
112
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { ResolvedExecutionLimits, Result } from "../codemode.js";
|
|
3
|
+
import { ToolRuntime } from "../tool-runtime.js";
|
|
4
|
+
import type { Host } from "./globals.js";
|
|
5
|
+
export declare const executeProgram: <R>(code: string, prepared: ToolRuntime.Prepared<R>, limits: ResolvedExecutionLimits, hooks: ToolRuntime.ToolCallHooks<R>, extraGlobals?: (host: Host<R>) => ReadonlyArray<readonly [string, unknown]>) => Effect.Effect<Result, never, R>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { parse } from "acorn";
|
|
2
|
+
import { Cause, Effect, Scope } from "effect";
|
|
3
|
+
// #transpile: conditional import — full typescript on node/bun, an identity
|
|
4
|
+
// pass-through on workerd (the compiler is ~11 MiB and can't init there).
|
|
5
|
+
import { transpile } from "#transpile";
|
|
6
|
+
import { toData } from "../data.js";
|
|
7
|
+
import { ToolRuntime } from "../tool-runtime.js";
|
|
8
|
+
import { normalizeError } from "./errors.js";
|
|
9
|
+
import { InterpreterRuntimeError } from "./model.js";
|
|
10
|
+
import { PromiseRuntime } from "./promises.js";
|
|
11
|
+
import { Runtime } from "./runtime.js";
|
|
12
|
+
export const executeProgram = (code, prepared, limits, hooks, extraGlobals) => {
|
|
13
|
+
if (code.trim().length === 0) {
|
|
14
|
+
return Effect.succeed({
|
|
15
|
+
ok: false,
|
|
16
|
+
error: { kind: "ParseError", message: "Code cannot be empty." },
|
|
17
|
+
toolCalls: [],
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
// Allocate execution state inside suspension so reused Effects never share it.
|
|
21
|
+
return Effect.suspend(() => {
|
|
22
|
+
const tools = ToolRuntime.make(prepared, limits.maxToolCalls, hooks);
|
|
23
|
+
const logs = [];
|
|
24
|
+
const logged = () => (logs.length > 0 ? { logs: [...logs] } : {});
|
|
25
|
+
// Set only after copy-out so timeouts cannot report invalid values as completed.
|
|
26
|
+
let returned;
|
|
27
|
+
const base = Effect.acquireUseRelease(Scope.make("parallel"), (scope) => Effect.gen(function* () {
|
|
28
|
+
const program = parseProgram(code);
|
|
29
|
+
const promises = new PromiseRuntime(scope);
|
|
30
|
+
const value = yield* new Runtime(tools.execute, tools.search, tools.keys, promises, logs, extraGlobals).run(program);
|
|
31
|
+
const result = toData(value, "Execution result", "result");
|
|
32
|
+
returned = { value: result, promises };
|
|
33
|
+
const warnings = yield* promises.interrupt();
|
|
34
|
+
return {
|
|
35
|
+
ok: true,
|
|
36
|
+
value: result,
|
|
37
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
38
|
+
...logged(),
|
|
39
|
+
toolCalls: tools.calls,
|
|
40
|
+
};
|
|
41
|
+
}), (scope, exit) => Scope.close(scope, exit));
|
|
42
|
+
const timeoutMs = limits.timeoutMs;
|
|
43
|
+
const operation = timeoutMs === undefined
|
|
44
|
+
? base
|
|
45
|
+
: base.pipe(Effect.timeoutOrElse({
|
|
46
|
+
duration: timeoutMs,
|
|
47
|
+
orElse: () => Effect.sync(() => {
|
|
48
|
+
if (returned === undefined) {
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
error: { kind: "TimeoutExceeded", message: `Execution timed out after ${timeoutMs}ms.` },
|
|
52
|
+
...logged(),
|
|
53
|
+
toolCalls: tools.calls,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Keep the timeout warning first so truncation preserves it.
|
|
57
|
+
return {
|
|
58
|
+
ok: true,
|
|
59
|
+
value: returned.value,
|
|
60
|
+
warnings: [
|
|
61
|
+
{
|
|
62
|
+
kind: "TimeoutExceeded",
|
|
63
|
+
message: `The program returned, but background work was still running at the ${timeoutMs}ms timeout and was interrupted. Await all started promises.`,
|
|
64
|
+
},
|
|
65
|
+
...returned.promises.diagnostics(),
|
|
66
|
+
],
|
|
67
|
+
...logged(),
|
|
68
|
+
toolCalls: tools.calls,
|
|
69
|
+
};
|
|
70
|
+
}),
|
|
71
|
+
}));
|
|
72
|
+
return operation.pipe(Effect.catchCause((cause) => Cause.hasInterruptsOnly(cause)
|
|
73
|
+
? Effect.interrupt
|
|
74
|
+
: Effect.succeed({
|
|
75
|
+
ok: false,
|
|
76
|
+
error: normalizeError(Cause.squash(cause)),
|
|
77
|
+
...logged(),
|
|
78
|
+
toolCalls: tools.calls,
|
|
79
|
+
})), Effect.map((result) => limits.maxOutputBytes === undefined ? result : boundOutput(result, limits.maxOutputBytes)));
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
const parseProgram = (code) => {
|
|
83
|
+
const transpiled = transpile(`async function __codemode__() {\n${code}\n}`);
|
|
84
|
+
if (transpiled.error !== undefined) {
|
|
85
|
+
throw new InterpreterRuntimeError(`Failed to parse TypeScript: ${transpiled.error}`, undefined, "ParseError");
|
|
86
|
+
}
|
|
87
|
+
const bodyStart = transpiled.outputText.indexOf("{") + 1;
|
|
88
|
+
const bodyEnd = transpiled.outputText.lastIndexOf("}");
|
|
89
|
+
const executableCode = transpiled.outputText.slice(bodyStart, bodyEnd);
|
|
90
|
+
return parse(executableCode, {
|
|
91
|
+
ecmaVersion: "latest",
|
|
92
|
+
sourceType: "script",
|
|
93
|
+
allowReturnOutsideFunction: true,
|
|
94
|
+
allowAwaitOutsideFunction: true,
|
|
95
|
+
locations: true,
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
const utf8ByteLength = (value) => new TextEncoder().encode(value).byteLength;
|
|
99
|
+
// Drop a replacement character produced by truncating inside a UTF-8 sequence.
|
|
100
|
+
const utf8Truncate = (value, maxBytes) => {
|
|
101
|
+
const bytes = new TextEncoder().encode(value);
|
|
102
|
+
if (bytes.byteLength <= maxBytes)
|
|
103
|
+
return value;
|
|
104
|
+
const text = new TextDecoder("utf-8").decode(bytes.slice(0, Math.max(0, maxBytes)));
|
|
105
|
+
return text.endsWith("\uFFFD") ? text.slice(0, -1) : text;
|
|
106
|
+
};
|
|
107
|
+
// Warnings have a separate budget so result data cannot starve diagnostics.
|
|
108
|
+
const boundOutput = (result, maxOutputBytes) => {
|
|
109
|
+
let truncated = false;
|
|
110
|
+
let value = null;
|
|
111
|
+
let valueBytes = 0;
|
|
112
|
+
if (result.ok) {
|
|
113
|
+
const serialized = JSON.stringify(result.value) ?? "null";
|
|
114
|
+
const bytes = utf8ByteLength(serialized);
|
|
115
|
+
if (bytes > maxOutputBytes) {
|
|
116
|
+
truncated = true;
|
|
117
|
+
value = `${utf8Truncate(serialized, maxOutputBytes)} [result truncated: ${bytes} bytes exceeds the ${maxOutputBytes}-byte output limit; return a smaller value]`;
|
|
118
|
+
valueBytes = maxOutputBytes;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
value = result.value;
|
|
122
|
+
valueBytes = bytes;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const warnings = result.ok ? (result.warnings ?? []) : [];
|
|
126
|
+
const keptWarnings = [];
|
|
127
|
+
let warningBytes = 0;
|
|
128
|
+
for (const warning of warnings) {
|
|
129
|
+
const bytes = utf8ByteLength(JSON.stringify(warning)) + 1;
|
|
130
|
+
if (warningBytes + bytes > maxOutputBytes)
|
|
131
|
+
break;
|
|
132
|
+
warningBytes += bytes;
|
|
133
|
+
keptWarnings.push(warning);
|
|
134
|
+
}
|
|
135
|
+
if (keptWarnings.length < warnings.length) {
|
|
136
|
+
truncated = true;
|
|
137
|
+
keptWarnings.push({
|
|
138
|
+
kind: "Truncated",
|
|
139
|
+
message: `${warnings.length - keptWarnings.length} additional warnings omitted by the output limit.`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const logs = result.logs ?? [];
|
|
143
|
+
const kept = [];
|
|
144
|
+
const logBudget = Math.max(0, maxOutputBytes - valueBytes);
|
|
145
|
+
let logBytes = 0;
|
|
146
|
+
for (const line of logs) {
|
|
147
|
+
const lineBytes = utf8ByteLength(line) + 1;
|
|
148
|
+
if (logBytes + lineBytes > logBudget)
|
|
149
|
+
break;
|
|
150
|
+
logBytes += lineBytes;
|
|
151
|
+
kept.push(line);
|
|
152
|
+
}
|
|
153
|
+
if (kept.length < logs.length) {
|
|
154
|
+
truncated = true;
|
|
155
|
+
kept.push(`[logs truncated: showing ${kept.length} of ${logs.length} lines]`);
|
|
156
|
+
}
|
|
157
|
+
if (!truncated)
|
|
158
|
+
return result;
|
|
159
|
+
const warningsPart = keptWarnings.length > 0 ? { warnings: keptWarnings } : {};
|
|
160
|
+
const logsPart = kept.length > 0 ? { logs: kept } : {};
|
|
161
|
+
return result.ok
|
|
162
|
+
? {
|
|
163
|
+
ok: true,
|
|
164
|
+
value,
|
|
165
|
+
...warningsPart,
|
|
166
|
+
...logsPart,
|
|
167
|
+
truncated: true,
|
|
168
|
+
toolCalls: result.toolCalls,
|
|
169
|
+
}
|
|
170
|
+
: { ok: false, error: result.error, ...logsPart, truncated: true, toolCalls: result.toolCalls };
|
|
171
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { type PromiseRuntime } from "./promises.js";
|
|
3
|
+
import type { Runner } from "./runner.js";
|
|
4
|
+
/** What the built-in globals need from the interpreter that owns them. */
|
|
5
|
+
export type Host<R> = {
|
|
6
|
+
readonly runner: Runner<R>;
|
|
7
|
+
readonly promises: PromiseRuntime<R>;
|
|
8
|
+
readonly search: (args: Array<unknown>) => Effect.Effect<unknown, unknown, R>;
|
|
9
|
+
readonly toolKeys: (path: ReadonlyArray<string>) => ReadonlyArray<string>;
|
|
10
|
+
readonly logs: Array<string>;
|
|
11
|
+
};
|
|
12
|
+
/** The immutable global bindings of every program, in declaration order. */
|
|
13
|
+
export declare const globals: <R>(host: Host<R>) => ReadonlyArray<readonly [string, unknown]>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { arrayGlobal } from "../stdlib/array.js";
|
|
3
|
+
import { mapGlobal, setGlobal } from "../stdlib/collections.js";
|
|
4
|
+
import { consoleGlobal } from "../stdlib/console.js";
|
|
5
|
+
import { dateGlobal } from "../stdlib/date.js";
|
|
6
|
+
import { jsonGlobal } from "../stdlib/json.js";
|
|
7
|
+
import { mathGlobal } from "../stdlib/math.js";
|
|
8
|
+
import { numberGlobal } from "../stdlib/number.js";
|
|
9
|
+
import { objectGlobal } from "../stdlib/object.js";
|
|
10
|
+
import { regexpGlobal } from "../stdlib/regexp.js";
|
|
11
|
+
import { stringGlobal } from "../stdlib/string.js";
|
|
12
|
+
import { uriGlobal, urlGlobal, urlSearchParamsGlobal } from "../stdlib/url.js";
|
|
13
|
+
import { coercion } from "../stdlib/value.js";
|
|
14
|
+
import { atobGlobal, btoaGlobal, cryptoGlobal } from "../stdlib/web.js";
|
|
15
|
+
import { ToolReference } from "../tool-runtime.js";
|
|
16
|
+
import { errorGlobal } from "./errors.js";
|
|
17
|
+
import { errorTypes } from "./intrinsics.js";
|
|
18
|
+
import { HostFunction } from "./host.js";
|
|
19
|
+
import { AsyncIteratorSymbol, InterpreterRuntimeError, IteratorSymbol } from "./model.js";
|
|
20
|
+
import { promiseGlobal } from "./promises.js";
|
|
21
|
+
const symbolGlobal = new HostFunction({
|
|
22
|
+
name: "Symbol",
|
|
23
|
+
call: (_, node) => Effect.sync(() => {
|
|
24
|
+
throw new InterpreterRuntimeError("Symbol is not callable; only Symbol.asyncIterator and Symbol.iterator are available.", node);
|
|
25
|
+
}),
|
|
26
|
+
callback: false,
|
|
27
|
+
members: { asyncIterator: AsyncIteratorSymbol, iterator: IteratorSymbol },
|
|
28
|
+
});
|
|
29
|
+
/** The immutable global bindings of every program, in declaration order. */
|
|
30
|
+
export const globals = (host) => [
|
|
31
|
+
["tools", new ToolReference([])],
|
|
32
|
+
["search", new HostFunction({ name: "search", call: (args) => host.search(args), callback: false })],
|
|
33
|
+
["undefined", undefined],
|
|
34
|
+
["NaN", NaN],
|
|
35
|
+
["Infinity", Infinity],
|
|
36
|
+
["Object", objectGlobal(host.runner, host.toolKeys)],
|
|
37
|
+
["Array", arrayGlobal(host.runner)],
|
|
38
|
+
["Math", mathGlobal(host.runner)],
|
|
39
|
+
["JSON", jsonGlobal(host.runner)],
|
|
40
|
+
["console", consoleGlobal(host.logs)],
|
|
41
|
+
["Promise", promiseGlobal(host.runner, host.promises)],
|
|
42
|
+
["Symbol", symbolGlobal],
|
|
43
|
+
["Number", numberGlobal],
|
|
44
|
+
["String", stringGlobal],
|
|
45
|
+
["Boolean", coercion("Boolean", { instanceOf: () => false })],
|
|
46
|
+
["parseInt", coercion("parseInt")],
|
|
47
|
+
["parseFloat", coercion("parseFloat")],
|
|
48
|
+
["isFinite", coercion("isFinite")],
|
|
49
|
+
["isNaN", coercion("isNaN")],
|
|
50
|
+
["Date", dateGlobal(host.runner)],
|
|
51
|
+
["RegExp", regexpGlobal],
|
|
52
|
+
["Map", mapGlobal(host.runner)],
|
|
53
|
+
["Set", setGlobal(host.runner)],
|
|
54
|
+
["URL", urlGlobal],
|
|
55
|
+
["URLSearchParams", urlSearchParamsGlobal(host.runner)],
|
|
56
|
+
["encodeURI", uriGlobal("encodeURI")],
|
|
57
|
+
["encodeURIComponent", uriGlobal("encodeURIComponent")],
|
|
58
|
+
["decodeURI", uriGlobal("decodeURI")],
|
|
59
|
+
["decodeURIComponent", uriGlobal("decodeURIComponent")],
|
|
60
|
+
["atob", atobGlobal],
|
|
61
|
+
["btoa", btoaGlobal],
|
|
62
|
+
["crypto", cryptoGlobal],
|
|
63
|
+
...errorTypes.map((type) => [type, errorGlobal(type, host.runner)]),
|
|
64
|
+
];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { type AstNode } from "./model.js";
|
|
3
|
+
export type HostCall<R> = (args: Array<unknown>, node: AstNode) => Effect.Effect<unknown, unknown, R>;
|
|
4
|
+
type HostMember = (key: PropertyKey, node: AstNode) => unknown;
|
|
5
|
+
type HostFunctionOptions<R> = {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly call: HostCall<R>;
|
|
8
|
+
/** `new name(...)`; without it `new` is unsupported syntax. */
|
|
9
|
+
readonly construct?: HostCall<R>;
|
|
10
|
+
/** Static members read through `name.key`; unknown keys read as `undefined` unless the function decides otherwise. */
|
|
11
|
+
readonly members?: Record<string, unknown> | HostMember;
|
|
12
|
+
/** `value instanceof name`; without it the operator rejects this right-hand side. */
|
|
13
|
+
readonly instanceOf?: (value: unknown) => boolean;
|
|
14
|
+
/** Whether callback sites (array methods, replacers, promise reactions) admit this function. Defaults to true. */
|
|
15
|
+
readonly callback?: boolean;
|
|
16
|
+
};
|
|
17
|
+
/** A host-implemented function value. `typeof` is "function". */
|
|
18
|
+
export declare class HostFunction<R = never> {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly call: HostCall<R>;
|
|
21
|
+
readonly construct: HostCall<R> | undefined;
|
|
22
|
+
readonly member: HostMember;
|
|
23
|
+
readonly instanceOf: ((value: unknown) => boolean) | undefined;
|
|
24
|
+
readonly callback: boolean;
|
|
25
|
+
constructor(options: HostFunctionOptions<R>);
|
|
26
|
+
}
|
|
27
|
+
/** A host-implemented object of static members. `typeof` is "object" and it is not callable. */
|
|
28
|
+
export declare class HostNamespace {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly member: HostMember;
|
|
31
|
+
constructor(name: string, members: Record<string, unknown> | HostMember);
|
|
32
|
+
}
|
|
33
|
+
export type SyncOptions = Omit<HostFunctionOptions<never>, "name" | "call">;
|
|
34
|
+
type SyncImpl = (args: Array<unknown>, node: AstNode) => unknown;
|
|
35
|
+
/** Lifts a synchronous implementation, which may throw `InterpreterRuntimeError`, into a host call. */
|
|
36
|
+
export declare const syncCall: (impl: SyncImpl) => HostCall<never>;
|
|
37
|
+
/** A synchronous host function. */
|
|
38
|
+
export declare const sync: (name: string, impl: SyncImpl, options?: SyncOptions) => HostFunction<never>;
|
|
39
|
+
/** The `call` of a constructor that JS requires to be invoked with `new`. */
|
|
40
|
+
export declare const requiresNew: (name: string) => HostCall<never>;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { InterpreterRuntimeError } from "./model.js";
|
|
3
|
+
/** A host-implemented function value. `typeof` is "function". */
|
|
4
|
+
export class HostFunction {
|
|
5
|
+
name;
|
|
6
|
+
call;
|
|
7
|
+
construct;
|
|
8
|
+
member;
|
|
9
|
+
instanceOf;
|
|
10
|
+
callback;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.name = options.name;
|
|
13
|
+
this.call = options.call;
|
|
14
|
+
this.construct = options.construct;
|
|
15
|
+
this.member = memberLookup(options.members);
|
|
16
|
+
this.instanceOf = options.instanceOf;
|
|
17
|
+
this.callback = options.callback ?? true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** A host-implemented object of static members. `typeof` is "object" and it is not callable. */
|
|
21
|
+
export class HostNamespace {
|
|
22
|
+
name;
|
|
23
|
+
member;
|
|
24
|
+
constructor(name, members) {
|
|
25
|
+
this.name = name;
|
|
26
|
+
this.member = memberLookup(members);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const memberLookup = (members) => {
|
|
30
|
+
if (members === undefined)
|
|
31
|
+
return () => undefined;
|
|
32
|
+
if (typeof members === "function")
|
|
33
|
+
return members;
|
|
34
|
+
const table = new Map(Object.entries(members));
|
|
35
|
+
return (key) => (typeof key === "string" ? table.get(key) : undefined);
|
|
36
|
+
};
|
|
37
|
+
/** Lifts a synchronous implementation, which may throw `InterpreterRuntimeError`, into a host call. */
|
|
38
|
+
export const syncCall = (impl) => (args, node) => Effect.sync(() => impl(args, node));
|
|
39
|
+
/** A synchronous host function. */
|
|
40
|
+
export const sync = (name, impl, options = {}) => new HostFunction({ name, call: syncCall(impl), ...options });
|
|
41
|
+
/** The `call` of a constructor that JS requires to be invoked with `new`. */
|
|
42
|
+
export const requiresNew = (name) => (_, node) => Effect.sync(() => {
|
|
43
|
+
throw new InterpreterRuntimeError(`Constructor ${name} requires 'new'.`, node);
|
|
44
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ProgramError, ProgramObject } from "./objects.js";
|
|
2
|
+
export declare const errorTypes: readonly ["Error", "TypeError", "RangeError", "SyntaxError", "ReferenceError", "EvalError", "URIError", "AggregateError"];
|
|
3
|
+
export type ErrorType = (typeof errorTypes)[number];
|
|
4
|
+
export declare const isErrorType: (name: string) => name is ErrorType;
|
|
5
|
+
/** The built-in prototype objects of one runtime. Constructors attach themselves as `constructor` when created. */
|
|
6
|
+
export type Intrinsics = {
|
|
7
|
+
readonly errors: Readonly<Record<ErrorType, ProgramObject>>;
|
|
8
|
+
};
|
|
9
|
+
export declare const createErrorValue: (prototype: ProgramObject, message: string | undefined) => ProgramError;
|
|
10
|
+
export declare const createIntrinsics: () => Intrinsics;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ProgramError, ProgramObject, set } from "./objects.js";
|
|
2
|
+
export const errorTypes = [
|
|
3
|
+
"Error",
|
|
4
|
+
"TypeError",
|
|
5
|
+
"RangeError",
|
|
6
|
+
"SyntaxError",
|
|
7
|
+
"ReferenceError",
|
|
8
|
+
"EvalError",
|
|
9
|
+
"URIError",
|
|
10
|
+
"AggregateError",
|
|
11
|
+
];
|
|
12
|
+
export const isErrorType = (name) => errorTypes.includes(name);
|
|
13
|
+
export const createErrorValue = (prototype, message) => {
|
|
14
|
+
const value = new ProgramError(prototype);
|
|
15
|
+
if (message !== undefined)
|
|
16
|
+
set(value, "message", message);
|
|
17
|
+
return value;
|
|
18
|
+
};
|
|
19
|
+
export const createIntrinsics = () => {
|
|
20
|
+
const error = new ProgramObject();
|
|
21
|
+
set(error, "name", "Error");
|
|
22
|
+
set(error, "message", "");
|
|
23
|
+
const derived = (type) => {
|
|
24
|
+
const proto = new ProgramObject(error);
|
|
25
|
+
set(proto, "name", type);
|
|
26
|
+
set(proto, "message", "");
|
|
27
|
+
return proto;
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
errors: {
|
|
31
|
+
Error: error,
|
|
32
|
+
TypeError: derived("TypeError"),
|
|
33
|
+
RangeError: derived("RangeError"),
|
|
34
|
+
SyntaxError: derived("SyntaxError"),
|
|
35
|
+
ReferenceError: derived("ReferenceError"),
|
|
36
|
+
EvalError: derived("EvalError"),
|
|
37
|
+
URIError: derived("URIError"),
|
|
38
|
+
AggregateError: derived("AggregateError"),
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { type AstNode, IntrinsicReference } from "./model.js";
|
|
3
|
+
import { type Runner } from "./runner.js";
|
|
4
|
+
export declare const invokeIntrinsic: <R>(runner: Runner<R>, ref: IntrinsicReference, args: Array<unknown>, node: AstNode) => Effect.Effect<unknown, unknown, R>;
|