mcard-js 2.1.48 → 2.1.49
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/AbstractSqlEngine-DKka6XjT.d.cts +451 -0
- package/dist/AbstractSqlEngine-DKka6XjT.d.ts +451 -0
- package/dist/CardCollection-ZQ3G3Q3A.js +10 -0
- package/dist/IndexedDBEngine-BWXAB46W.js +12 -0
- package/dist/LLMRuntime-PH3MOQ2Y.js +17 -0
- package/dist/LambdaRuntime-YH74FHIW.js +19 -0
- package/dist/Loader-WZXYG4GE.js +12 -0
- package/dist/NetworkRuntime-S4DZCGVN.js +1598 -0
- package/dist/OllamaProvider-SPGO5Z5E.js +9 -0
- package/dist/chunk-3FFEA2XK.js +149 -0
- package/dist/chunk-7AXRV7NS.js +112 -0
- package/dist/chunk-HIVVDGE5.js +497 -0
- package/dist/chunk-KVZYFZJ5.js +427 -0
- package/dist/chunk-NGTY4P6A.js +275 -0
- package/dist/chunk-OUW2SUGM.js +368 -0
- package/dist/chunk-QKH3N62B.js +2360 -0
- package/dist/chunk-QPVEUPMU.js +299 -0
- package/dist/chunk-VYDZR4ZD.js +364 -0
- package/dist/chunk-XJZOEM5F.js +903 -0
- package/dist/chunk-Z7EFXSTO.js +217 -0
- package/dist/index.browser.cjs +37 -1
- package/dist/index.browser.d.cts +20 -2
- package/dist/index.browser.d.ts +20 -2
- package/dist/index.browser.js +10 -6
- package/dist/index.cjs +618 -146
- package/dist/index.d.cts +723 -4
- package/dist/index.d.ts +723 -4
- package/dist/index.js +527 -89
- package/dist/storage/SqliteNodeEngine.cjs +7 -1
- package/dist/storage/SqliteNodeEngine.d.cts +1 -1
- package/dist/storage/SqliteNodeEngine.d.ts +1 -1
- package/dist/storage/SqliteNodeEngine.js +3 -3
- package/dist/storage/SqliteWasmEngine.cjs +7 -1
- package/dist/storage/SqliteWasmEngine.d.cts +1 -1
- package/dist/storage/SqliteWasmEngine.d.ts +1 -1
- package/dist/storage/SqliteWasmEngine.js +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,2360 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IO
|
|
3
|
+
} from "./chunk-MPMRBT5R.js";
|
|
4
|
+
import {
|
|
5
|
+
Either
|
|
6
|
+
} from "./chunk-2KADE3SE.js";
|
|
7
|
+
import {
|
|
8
|
+
SqliteNodeEngine
|
|
9
|
+
} from "./chunk-Z7EFXSTO.js";
|
|
10
|
+
import {
|
|
11
|
+
CardCollection,
|
|
12
|
+
Maybe
|
|
13
|
+
} from "./chunk-QPVEUPMU.js";
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_VM_EXECUTION_TIMEOUT_MS
|
|
16
|
+
} from "./chunk-3FFEA2XK.js";
|
|
17
|
+
import {
|
|
18
|
+
MCard
|
|
19
|
+
} from "./chunk-GGQCF7ZK.js";
|
|
20
|
+
|
|
21
|
+
// src/ptr/node/runtimes/base.ts
|
|
22
|
+
import * as util from "util";
|
|
23
|
+
import * as child_process from "child_process";
|
|
24
|
+
var execFile2 = util.promisify(child_process.execFile);
|
|
25
|
+
async function checkCommand(cmd, args = ["--version"]) {
|
|
26
|
+
try {
|
|
27
|
+
await execFile2(cmd, args);
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function parseOutput(stdout) {
|
|
34
|
+
const trimmed = stdout.trim();
|
|
35
|
+
if (!trimmed) return void 0;
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(trimmed);
|
|
38
|
+
} catch {
|
|
39
|
+
return trimmed;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/ptr/node/runtimes/javascript.ts
|
|
44
|
+
import * as fs2 from "fs";
|
|
45
|
+
import * as path2 from "path";
|
|
46
|
+
import * as vm from "vm";
|
|
47
|
+
import * as child_process2 from "child_process";
|
|
48
|
+
import * as util2 from "util";
|
|
49
|
+
|
|
50
|
+
// src/ptr/node/FileSystemUtils.ts
|
|
51
|
+
import * as fs from "fs";
|
|
52
|
+
import * as path from "path";
|
|
53
|
+
function findProjectRoot(startDir = process.cwd()) {
|
|
54
|
+
let searchDir = startDir;
|
|
55
|
+
for (let i = 0; i < 5; i++) {
|
|
56
|
+
if (fs.existsSync(path.join(searchDir, "pyproject.toml"))) {
|
|
57
|
+
return searchDir;
|
|
58
|
+
}
|
|
59
|
+
searchDir = path.dirname(searchDir);
|
|
60
|
+
}
|
|
61
|
+
return startDir;
|
|
62
|
+
}
|
|
63
|
+
function listFiles(dirPath, recursive) {
|
|
64
|
+
const files = [];
|
|
65
|
+
if (!fs.existsSync(dirPath)) {
|
|
66
|
+
return files;
|
|
67
|
+
}
|
|
68
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
71
|
+
if (entry.isFile()) {
|
|
72
|
+
if (!entry.name.startsWith(".") && !isProblematicFile(fullPath)) {
|
|
73
|
+
files.push(fullPath);
|
|
74
|
+
}
|
|
75
|
+
} else if (entry.isDirectory() && recursive) {
|
|
76
|
+
files.push(...listFiles(fullPath, recursive));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return files;
|
|
80
|
+
}
|
|
81
|
+
function isProblematicFile(filePath) {
|
|
82
|
+
try {
|
|
83
|
+
const stats = fs.statSync(filePath);
|
|
84
|
+
const maxSizeBytes = typeof process !== "undefined" && process.env.MCARD_MAX_SIZE_BYTES ? parseInt(process.env.MCARD_MAX_SIZE_BYTES, 10) : 150 * 1024 * 1024;
|
|
85
|
+
if (stats.size > maxSizeBytes) return true;
|
|
86
|
+
if (stats.size > 1024) {
|
|
87
|
+
const fd = fs.openSync(filePath, "r");
|
|
88
|
+
const buffer = Buffer.alloc(1024);
|
|
89
|
+
fs.readSync(fd, buffer, 0, 1024, 0);
|
|
90
|
+
fs.closeSync(fd);
|
|
91
|
+
let nullCount = 0;
|
|
92
|
+
let controlCount = 0;
|
|
93
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
94
|
+
if (buffer[i] === 0) nullCount++;
|
|
95
|
+
else if (buffer[i] < 32 && buffer[i] !== 9 && buffer[i] !== 10 && buffer[i] !== 13) controlCount++;
|
|
96
|
+
}
|
|
97
|
+
if (nullCount > 300) return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
} catch {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function toRecord(value) {
|
|
105
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
106
|
+
}
|
|
107
|
+
function extractLoaderParams(ctx, defaults = {}) {
|
|
108
|
+
const params = toRecord(ctx.params);
|
|
109
|
+
const balanced = toRecord(ctx.balanced);
|
|
110
|
+
const inputArgs = {
|
|
111
|
+
...toRecord(balanced.input_arguments),
|
|
112
|
+
...toRecord(ctx.input_arguments)
|
|
113
|
+
};
|
|
114
|
+
const outputArgs = {
|
|
115
|
+
...toRecord(balanced.output_arguments),
|
|
116
|
+
...toRecord(ctx.output_arguments)
|
|
117
|
+
};
|
|
118
|
+
const allParams = { ...inputArgs, ...outputArgs, ...params };
|
|
119
|
+
const sourceDir = allParams.source_dir ?? defaults.sourceDir ?? "test_data";
|
|
120
|
+
const recursive = params.recursive !== void 0 ? params.recursive !== false : allParams.recursive !== false;
|
|
121
|
+
const dbPath = allParams.db_path ?? defaults.dbPath;
|
|
122
|
+
return {
|
|
123
|
+
params,
|
|
124
|
+
inputArgs,
|
|
125
|
+
outputArgs,
|
|
126
|
+
allParams,
|
|
127
|
+
sourceDir,
|
|
128
|
+
recursive,
|
|
129
|
+
dbPath
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function computeTimingMetrics(startTime, processedCount) {
|
|
133
|
+
const durationSeconds = (Date.now() - startTime) / 1e3;
|
|
134
|
+
const time_s = Math.round(durationSeconds * 1e4) / 1e4;
|
|
135
|
+
const files_per_sec = durationSeconds > 0 ? Math.round(processedCount / durationSeconds * 100) / 100 : 0;
|
|
136
|
+
return { durationSeconds, time_s, files_per_sec };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/ptr/node/runtimes/javascript.ts
|
|
140
|
+
var writeFile2 = util2.promisify(fs2.writeFile);
|
|
141
|
+
var unlink2 = util2.promisify(fs2.unlink);
|
|
142
|
+
var JavaScriptRuntime = class {
|
|
143
|
+
async execute(code, context, config) {
|
|
144
|
+
if (this.requiresSubprocess(code)) {
|
|
145
|
+
return this.executeSubprocess(code, context);
|
|
146
|
+
}
|
|
147
|
+
return this.executeInVM(code, context);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Check if code requires subprocess execution (has imports/exports).
|
|
151
|
+
*/
|
|
152
|
+
requiresSubprocess(code) {
|
|
153
|
+
return code.includes("require(") || code.includes("import(") || code.includes("import ") || code.includes("export ");
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Execute code in VM sandbox (fast, for simple scripts).
|
|
157
|
+
*/
|
|
158
|
+
async executeInVM(code, context) {
|
|
159
|
+
const sandbox = {
|
|
160
|
+
context,
|
|
161
|
+
target: context,
|
|
162
|
+
result: void 0,
|
|
163
|
+
console: {
|
|
164
|
+
log: (...args) => console.log("[JS]", ...args),
|
|
165
|
+
error: (...args) => console.error("[JS]", ...args),
|
|
166
|
+
warn: (...args) => console.warn("[JS]", ...args)
|
|
167
|
+
},
|
|
168
|
+
Math,
|
|
169
|
+
JSON,
|
|
170
|
+
Date,
|
|
171
|
+
setTimeout,
|
|
172
|
+
clearTimeout,
|
|
173
|
+
setInterval,
|
|
174
|
+
clearInterval,
|
|
175
|
+
process,
|
|
176
|
+
spawn: child_process2.spawn,
|
|
177
|
+
exec: child_process2.exec,
|
|
178
|
+
// Network capabilities
|
|
179
|
+
fetch: global.fetch ? global.fetch.bind(global) : void 0,
|
|
180
|
+
Headers: global.Headers,
|
|
181
|
+
Request: global.Request,
|
|
182
|
+
Response: global.Response,
|
|
183
|
+
URL: global.URL,
|
|
184
|
+
URLSearchParams: global.URLSearchParams
|
|
185
|
+
};
|
|
186
|
+
const codeRunnable = code + "\nresult;";
|
|
187
|
+
const script = new vm.Script(codeRunnable);
|
|
188
|
+
const vmContext = vm.createContext(sandbox);
|
|
189
|
+
const executionResult = script.runInContext(vmContext, { timeout: DEFAULT_VM_EXECUTION_TIMEOUT_MS });
|
|
190
|
+
return sandbox.result !== void 0 ? sandbox.result : executionResult;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Execute code in subprocess (for modules/imports).
|
|
194
|
+
*/
|
|
195
|
+
async executeSubprocess(code, context) {
|
|
196
|
+
const contextStr = JSON.stringify(context);
|
|
197
|
+
const projectRoot = findProjectRoot();
|
|
198
|
+
const wrapper = `
|
|
199
|
+
import process from 'process';
|
|
200
|
+
import { createRequire } from 'module';
|
|
201
|
+
const require = createRequire(import.meta.url);
|
|
202
|
+
const context = JSON.parse(process.argv[2]);
|
|
203
|
+
global.context = context;
|
|
204
|
+
let result;
|
|
205
|
+
|
|
206
|
+
(async () => {
|
|
207
|
+
try {
|
|
208
|
+
${code}
|
|
209
|
+
console.log(JSON.stringify(result));
|
|
210
|
+
} catch (e) {
|
|
211
|
+
console.error(JSON.stringify({ error: e.message, stack: e.stack }));
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
})();
|
|
215
|
+
`;
|
|
216
|
+
const tmpFile = path2.resolve(projectRoot, `tmp_js_${Date.now()}.js`);
|
|
217
|
+
await writeFile2(tmpFile, wrapper);
|
|
218
|
+
try {
|
|
219
|
+
const { stdout, stderr } = await execFile2("node", [tmpFile, contextStr], {
|
|
220
|
+
cwd: projectRoot,
|
|
221
|
+
env: { ...process.env }
|
|
222
|
+
});
|
|
223
|
+
if (stderr && stderr.trim()) {
|
|
224
|
+
try {
|
|
225
|
+
const errObj = JSON.parse(stderr.trim());
|
|
226
|
+
if (errObj.error) throw new Error(errObj.error);
|
|
227
|
+
} catch (e) {
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return parseOutput(stdout);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
const execError = error;
|
|
233
|
+
const stderr = execError.stderr ? `
|
|
234
|
+
Stderr: ${execError.stderr.toString()}` : "";
|
|
235
|
+
try {
|
|
236
|
+
const errObj = JSON.parse(execError.stderr?.toString().trim() || "");
|
|
237
|
+
if (errObj.error) throw new Error(errObj.error);
|
|
238
|
+
} catch {
|
|
239
|
+
}
|
|
240
|
+
throw new Error(`JavaScript execution failed: ${execError.message || String(error)}${stderr}`);
|
|
241
|
+
} finally {
|
|
242
|
+
await unlink2(tmpFile);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// src/ptr/node/runtimes/python.ts
|
|
248
|
+
import * as fs3 from "fs";
|
|
249
|
+
import * as path3 from "path";
|
|
250
|
+
import * as util3 from "util";
|
|
251
|
+
var writeFile4 = util3.promisify(fs3.writeFile);
|
|
252
|
+
var unlink4 = util3.promisify(fs3.unlink);
|
|
253
|
+
var PythonRuntime = class {
|
|
254
|
+
async execute(code, context, config) {
|
|
255
|
+
const entryPoint = config.entry_point || "add";
|
|
256
|
+
const contextStr = JSON.stringify(context);
|
|
257
|
+
const projectRoot = findProjectRoot();
|
|
258
|
+
let pythonCmd = "python3";
|
|
259
|
+
if (process.env.MCARD_PYTHON_CMD) {
|
|
260
|
+
pythonCmd = process.env.MCARD_PYTHON_CMD;
|
|
261
|
+
} else {
|
|
262
|
+
const venvPython = path3.join(projectRoot, ".venv", "bin", "python");
|
|
263
|
+
if (fs3.existsSync(venvPython)) {
|
|
264
|
+
pythonCmd = venvPython;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const wrapper = this.createWrapper(code, entryPoint);
|
|
268
|
+
const tmpFile = path3.resolve(process.cwd(), `tmp_${Date.now()}.py`);
|
|
269
|
+
await writeFile4(tmpFile, wrapper);
|
|
270
|
+
try {
|
|
271
|
+
const { stdout, stderr } = await execFile2(pythonCmd, [tmpFile, contextStr], {
|
|
272
|
+
cwd: projectRoot,
|
|
273
|
+
env: {
|
|
274
|
+
...process.env,
|
|
275
|
+
PYTHONPATH: `${projectRoot}${path3.delimiter}${process.env.PYTHONPATH || ""}`
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
if (stderr) console.error("[Python Stderr]", stderr);
|
|
279
|
+
return parseOutput(stdout);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const execError = error;
|
|
282
|
+
const stderr = execError.stderr ? `
|
|
283
|
+
Stderr: ${execError.stderr.toString()}` : "";
|
|
284
|
+
const stdout = execError.stdout ? `
|
|
285
|
+
Stdout: ${execError.stdout.toString()}` : "";
|
|
286
|
+
throw new Error(`Python execution failed: ${execError.message || String(error)}${stderr}${stdout}`);
|
|
287
|
+
} finally {
|
|
288
|
+
await unlink4(tmpFile);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Create Python wrapper script with entry point invocation.
|
|
293
|
+
*/
|
|
294
|
+
createWrapper(code, entryPoint) {
|
|
295
|
+
let userLogic = code;
|
|
296
|
+
let actualEntryPoint = entryPoint;
|
|
297
|
+
if (code.startsWith("module://")) {
|
|
298
|
+
const withoutPrefix = code.substring(9);
|
|
299
|
+
const colonIndex = withoutPrefix.indexOf(":");
|
|
300
|
+
let modulePath = withoutPrefix;
|
|
301
|
+
let moduleFunc = "";
|
|
302
|
+
if (colonIndex !== -1) {
|
|
303
|
+
modulePath = withoutPrefix.substring(0, colonIndex);
|
|
304
|
+
moduleFunc = withoutPrefix.substring(colonIndex + 1);
|
|
305
|
+
}
|
|
306
|
+
if (modulePath) {
|
|
307
|
+
if (moduleFunc) {
|
|
308
|
+
actualEntryPoint = moduleFunc;
|
|
309
|
+
}
|
|
310
|
+
userLogic = `
|
|
311
|
+
import importlib
|
|
312
|
+
try:
|
|
313
|
+
_mod = importlib.import_module('${modulePath}')
|
|
314
|
+
for _k in dir(_mod):
|
|
315
|
+
if not _k.startswith('_'):
|
|
316
|
+
globals()[_k] = getattr(_mod, _k)
|
|
317
|
+
except Exception as e:
|
|
318
|
+
import sys, json
|
|
319
|
+
print(json.dumps({"error": f"Failed to import module ${modulePath}: {e}"}))
|
|
320
|
+
sys.exit(1)
|
|
321
|
+
`;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return `
|
|
325
|
+
import sys
|
|
326
|
+
import json
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
context = json.loads(sys.argv[1])
|
|
330
|
+
target = context
|
|
331
|
+
|
|
332
|
+
if isinstance(target, str):
|
|
333
|
+
target = target.encode('utf-8')
|
|
334
|
+
except:
|
|
335
|
+
context = {}
|
|
336
|
+
target = {}
|
|
337
|
+
pass
|
|
338
|
+
|
|
339
|
+
# User logic
|
|
340
|
+
${userLogic}
|
|
341
|
+
|
|
342
|
+
# Entry point invocation
|
|
343
|
+
def _is_arg_error(e):
|
|
344
|
+
msg = str(e).lower()
|
|
345
|
+
return 'positional argument' in msg or 'required argument' in msg or 'takes' in msg and 'argument' in msg
|
|
346
|
+
|
|
347
|
+
try:
|
|
348
|
+
fn = None
|
|
349
|
+
if '${actualEntryPoint}' in dir():
|
|
350
|
+
fn = locals()['${actualEntryPoint}']
|
|
351
|
+
elif '${actualEntryPoint}' in globals():
|
|
352
|
+
fn = globals()['${actualEntryPoint}']
|
|
353
|
+
|
|
354
|
+
if fn is not None:
|
|
355
|
+
try:
|
|
356
|
+
res = fn(context)
|
|
357
|
+
except TypeError as e:
|
|
358
|
+
if not _is_arg_error(e):
|
|
359
|
+
raise
|
|
360
|
+
try:
|
|
361
|
+
res = fn(target)
|
|
362
|
+
except TypeError as e2:
|
|
363
|
+
if not _is_arg_error(e2):
|
|
364
|
+
raise
|
|
365
|
+
res = fn()
|
|
366
|
+
print(json.dumps(res))
|
|
367
|
+
else:
|
|
368
|
+
if 'result' in dir() or 'result' in globals():
|
|
369
|
+
print(json.dumps(result if 'result' in dir() else globals().get('result')))
|
|
370
|
+
else:
|
|
371
|
+
print(json.dumps(None))
|
|
372
|
+
except Exception as e:
|
|
373
|
+
print(json.dumps({"error": str(e)}))
|
|
374
|
+
sys.exit(1)
|
|
375
|
+
`;
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// src/ptr/node/runtimes/binary.ts
|
|
380
|
+
import * as path4 from "path";
|
|
381
|
+
var BinaryRuntime = class {
|
|
382
|
+
async execute(binaryPath, context, config, chapterDir) {
|
|
383
|
+
const fullPath = path4.resolve(chapterDir, binaryPath);
|
|
384
|
+
const contextStr = typeof context === "string" ? context : JSON.stringify(context);
|
|
385
|
+
const { stdout } = await execFile2(fullPath, [contextStr]);
|
|
386
|
+
return parseOutput(stdout);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// src/ptr/node/runtimes/wasm.ts
|
|
391
|
+
import * as path5 from "path";
|
|
392
|
+
import * as child_process3 from "child_process";
|
|
393
|
+
var WasmRuntime = class {
|
|
394
|
+
async execute(wasmPath, context, config, chapterDir) {
|
|
395
|
+
const fullPath = path5.resolve(chapterDir, wasmPath);
|
|
396
|
+
const contextStr = typeof context === "string" ? context : JSON.stringify(context ?? {});
|
|
397
|
+
return new Promise((resolve6, reject) => {
|
|
398
|
+
const proc = child_process3.spawn("wasmtime", [fullPath, contextStr]);
|
|
399
|
+
proc.stdin.end();
|
|
400
|
+
let stdout = "";
|
|
401
|
+
let stderr = "";
|
|
402
|
+
proc.stdout.on("data", (data) => {
|
|
403
|
+
stdout += data.toString("utf-8");
|
|
404
|
+
});
|
|
405
|
+
proc.stderr.on("data", (data) => {
|
|
406
|
+
stderr += data.toString("utf-8");
|
|
407
|
+
});
|
|
408
|
+
proc.on("error", (err) => {
|
|
409
|
+
reject(err);
|
|
410
|
+
});
|
|
411
|
+
proc.on("close", (code) => {
|
|
412
|
+
if (code !== 0) {
|
|
413
|
+
if (stderr.includes("command not found") || stderr.includes("wasmtime: not found")) {
|
|
414
|
+
return reject(new Error("WASM runtime requires wasmtime CLI. Install it, e.g. with: brew install wasmtime"));
|
|
415
|
+
}
|
|
416
|
+
return reject(new Error(`wasmtime exited with code ${code}: ${stderr || stdout}`));
|
|
417
|
+
}
|
|
418
|
+
const out = stdout.trim();
|
|
419
|
+
if (!out) {
|
|
420
|
+
return resolve6(void 0);
|
|
421
|
+
}
|
|
422
|
+
const numeric = out.trim();
|
|
423
|
+
if (numeric && !Number.isNaN(Number(numeric))) {
|
|
424
|
+
return resolve6(Number(numeric));
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
return resolve6(JSON.parse(out));
|
|
428
|
+
} catch {
|
|
429
|
+
return resolve6(out);
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// src/ptr/node/runtimes/lean.ts
|
|
437
|
+
import * as fs4 from "fs";
|
|
438
|
+
import * as path6 from "path";
|
|
439
|
+
import * as util4 from "util";
|
|
440
|
+
var writeFile6 = util4.promisify(fs4.writeFile);
|
|
441
|
+
var leanCacheDir = null;
|
|
442
|
+
var leanSourceCache = /* @__PURE__ */ new Map();
|
|
443
|
+
function ensureLeanCacheDir() {
|
|
444
|
+
if (!leanCacheDir) {
|
|
445
|
+
leanCacheDir = path6.resolve(process.cwd(), ".lean_cache");
|
|
446
|
+
if (!fs4.existsSync(leanCacheDir)) {
|
|
447
|
+
fs4.mkdirSync(leanCacheDir, { recursive: true });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return leanCacheDir;
|
|
451
|
+
}
|
|
452
|
+
async function getCachedLeanSource(code) {
|
|
453
|
+
const crypto = await import("crypto");
|
|
454
|
+
const hash = crypto.createHash("sha256").update(code).digest("hex");
|
|
455
|
+
if (leanSourceCache.has(hash)) {
|
|
456
|
+
return leanSourceCache.get(hash);
|
|
457
|
+
}
|
|
458
|
+
const cacheDir = ensureLeanCacheDir();
|
|
459
|
+
const filePath = path6.join(cacheDir, `${hash}.lean`);
|
|
460
|
+
if (!fs4.existsSync(filePath)) {
|
|
461
|
+
await writeFile6(filePath, code);
|
|
462
|
+
}
|
|
463
|
+
leanSourceCache.set(hash, filePath);
|
|
464
|
+
return filePath;
|
|
465
|
+
}
|
|
466
|
+
var LeanRuntime = class {
|
|
467
|
+
async execute(code, context, config) {
|
|
468
|
+
const contextStr = typeof context === "string" ? context : JSON.stringify(context);
|
|
469
|
+
const sourcePath = await getCachedLeanSource(code);
|
|
470
|
+
const os2 = await import("os");
|
|
471
|
+
const elanPath = path6.join(os2.homedir(), ".elan", "bin", "elan");
|
|
472
|
+
let stdout;
|
|
473
|
+
if (fs4.existsSync(elanPath)) {
|
|
474
|
+
const result = await execFile2(elanPath, [
|
|
475
|
+
"run",
|
|
476
|
+
"leanprover/lean4:v4.25.2",
|
|
477
|
+
"lean",
|
|
478
|
+
"--run",
|
|
479
|
+
sourcePath,
|
|
480
|
+
contextStr
|
|
481
|
+
]);
|
|
482
|
+
stdout = result.stdout;
|
|
483
|
+
} else {
|
|
484
|
+
const result = await execFile2("lean", ["--run", sourcePath, contextStr]);
|
|
485
|
+
stdout = result.stdout;
|
|
486
|
+
}
|
|
487
|
+
return parseOutput(stdout);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/ptr/node/runtimes/loader.ts
|
|
492
|
+
import * as fs5 from "fs";
|
|
493
|
+
import * as path7 from "path";
|
|
494
|
+
import * as os from "os";
|
|
495
|
+
var LoaderRuntime = class {
|
|
496
|
+
/**
|
|
497
|
+
* Loader runtime: load files using the standardized loadFileToCollection utility.
|
|
498
|
+
* Returns unified metrics and results matching Python's loader builtin.
|
|
499
|
+
*/
|
|
500
|
+
async execute(_code, context, config) {
|
|
501
|
+
const { loadFileToCollection } = await import("./Loader-WZXYG4GE.js");
|
|
502
|
+
const ctx = context;
|
|
503
|
+
const loaderParams = extractLoaderParams(ctx, { sourceDir: "test_data", dbPath: ":memory:" });
|
|
504
|
+
const { sourceDir, recursive, dbPath } = loaderParams;
|
|
505
|
+
const projectRoot = findProjectRoot();
|
|
506
|
+
const sourcePath = path7.isAbsolute(sourceDir) ? sourceDir : path7.join(projectRoot, sourceDir);
|
|
507
|
+
if (!fs5.existsSync(sourcePath)) {
|
|
508
|
+
return { success: false, error: `Source directory not found: ${sourceDir}` };
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
let resolvedDbPath = dbPath || ":memory:";
|
|
512
|
+
if (dbPath && dbPath !== ":memory:" && !path7.isAbsolute(dbPath)) {
|
|
513
|
+
resolvedDbPath = path7.join(projectRoot, dbPath);
|
|
514
|
+
const dbDir = path7.dirname(resolvedDbPath);
|
|
515
|
+
if (!fs5.existsSync(dbDir)) {
|
|
516
|
+
fs5.mkdirSync(dbDir, { recursive: true });
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (resolvedDbPath !== ":memory:" && fs5.existsSync(resolvedDbPath)) {
|
|
520
|
+
try {
|
|
521
|
+
fs5.unlinkSync(resolvedDbPath);
|
|
522
|
+
const walPath = resolvedDbPath + "-wal";
|
|
523
|
+
const shmPath = resolvedDbPath + "-shm";
|
|
524
|
+
if (fs5.existsSync(walPath)) fs5.unlinkSync(walPath);
|
|
525
|
+
if (fs5.existsSync(shmPath)) fs5.unlinkSync(shmPath);
|
|
526
|
+
} catch (e) {
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const engine = await SqliteNodeEngine.create(resolvedDbPath);
|
|
530
|
+
const collection = new CardCollection(engine);
|
|
531
|
+
const loaderResult = await loadFileToCollection(sourcePath, collection, {
|
|
532
|
+
recursive,
|
|
533
|
+
includeProblematic: false
|
|
534
|
+
});
|
|
535
|
+
const response = {
|
|
536
|
+
success: true,
|
|
537
|
+
metrics: {
|
|
538
|
+
total_files: loaderResult.metrics.filesCount,
|
|
539
|
+
total_directories: loaderResult.metrics.directoriesCount,
|
|
540
|
+
directory_levels: loaderResult.metrics.directoryLevels,
|
|
541
|
+
total_size_bytes: loaderResult.results.reduce((acc, r) => acc + (r.size || 0), 0),
|
|
542
|
+
duration_ms: 0
|
|
543
|
+
},
|
|
544
|
+
files: loaderResult.results.map((r) => ({
|
|
545
|
+
hash: r.hash,
|
|
546
|
+
filename: r.filename,
|
|
547
|
+
content_type: r.contentType,
|
|
548
|
+
size_bytes: r.size,
|
|
549
|
+
path: r.filePath
|
|
550
|
+
}))
|
|
551
|
+
};
|
|
552
|
+
await engine.close();
|
|
553
|
+
return response;
|
|
554
|
+
} catch (err) {
|
|
555
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
var CollectionLoaderRuntime = class {
|
|
560
|
+
/**
|
|
561
|
+
* Collection loader runtime: ingest files into a CardCollection and return a normalized ingest report.
|
|
562
|
+
*/
|
|
563
|
+
async execute(_code, context, config) {
|
|
564
|
+
const ctx = context;
|
|
565
|
+
const defaultDbPath = path7.join(os.tmpdir(), "loader_clm.db");
|
|
566
|
+
const loaderParams = extractLoaderParams(ctx, {
|
|
567
|
+
sourceDir: "chapters/chapter_04_load_dir/test_data",
|
|
568
|
+
dbPath: defaultDbPath
|
|
569
|
+
});
|
|
570
|
+
const { sourceDir, recursive, dbPath = defaultDbPath } = loaderParams;
|
|
571
|
+
const projectRoot = findProjectRoot();
|
|
572
|
+
const sourcePath = path7.isAbsolute(sourceDir) ? sourceDir : path7.join(projectRoot, sourceDir);
|
|
573
|
+
if (!fs5.existsSync(sourcePath)) {
|
|
574
|
+
return { success: false, error: `Source directory not found: ${sourceDir}` };
|
|
575
|
+
}
|
|
576
|
+
const resolvedDbPath = path7.isAbsolute(dbPath) ? dbPath : path7.join(projectRoot, dbPath);
|
|
577
|
+
try {
|
|
578
|
+
const engine = await SqliteNodeEngine.create(resolvedDbPath);
|
|
579
|
+
const collection = new CardCollection(engine);
|
|
580
|
+
const files = listFiles(sourcePath, recursive);
|
|
581
|
+
let ingested = 0;
|
|
582
|
+
let skipped = 0;
|
|
583
|
+
const errors = [];
|
|
584
|
+
const startTime = Date.now();
|
|
585
|
+
for (const filePath of files) {
|
|
586
|
+
try {
|
|
587
|
+
const content = fs5.readFileSync(filePath);
|
|
588
|
+
const card = await MCard.create(new Uint8Array(content));
|
|
589
|
+
await collection.add(card);
|
|
590
|
+
ingested += 1;
|
|
591
|
+
} catch (err) {
|
|
592
|
+
skipped += 1;
|
|
593
|
+
errors.push(`${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
const metrics = computeTimingMetrics(startTime, ingested);
|
|
597
|
+
return {
|
|
598
|
+
success: true,
|
|
599
|
+
report: {
|
|
600
|
+
total_files: files.length,
|
|
601
|
+
ingested,
|
|
602
|
+
skipped,
|
|
603
|
+
errors
|
|
604
|
+
},
|
|
605
|
+
ingest_metrics: {
|
|
606
|
+
db_path: resolvedDbPath,
|
|
607
|
+
time_s: metrics.time_s,
|
|
608
|
+
files_per_sec: metrics.files_per_sec
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
} catch (err) {
|
|
612
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
// src/ptr/node/runtimes/factory.ts
|
|
618
|
+
var _LLMRuntime = null;
|
|
619
|
+
var _LambdaRuntime = null;
|
|
620
|
+
var _NetworkRuntime = null;
|
|
621
|
+
var RuntimeFactory = class {
|
|
622
|
+
/**
|
|
623
|
+
* Get availability status of all runtimes.
|
|
624
|
+
*/
|
|
625
|
+
static async getAvailableRuntimes() {
|
|
626
|
+
const runtimes = ["javascript", "python", "rust", "c", "wasm", "lean", "llm"];
|
|
627
|
+
const status = {};
|
|
628
|
+
for (const r of runtimes) {
|
|
629
|
+
try {
|
|
630
|
+
if (r === "javascript") {
|
|
631
|
+
status[r] = true;
|
|
632
|
+
} else if (r === "python") {
|
|
633
|
+
status[r] = await checkCommand("python3");
|
|
634
|
+
} else if (r === "rust") {
|
|
635
|
+
status[r] = await checkCommand("cargo");
|
|
636
|
+
} else if (r === "c") {
|
|
637
|
+
const gccOk = await checkCommand("gcc");
|
|
638
|
+
status[r] = gccOk || await checkCommand("clang");
|
|
639
|
+
} else if (r === "lean") {
|
|
640
|
+
status[r] = await checkCommand("lean");
|
|
641
|
+
} else if (r === "wasm") {
|
|
642
|
+
status[r] = typeof WebAssembly !== "undefined";
|
|
643
|
+
} else if (r === "llm") {
|
|
644
|
+
const { OllamaProvider } = await import("./OllamaProvider-SPGO5Z5E.js");
|
|
645
|
+
const provider = new OllamaProvider(null, 5);
|
|
646
|
+
status[r] = await provider.validate_connection();
|
|
647
|
+
} else {
|
|
648
|
+
status[r] = false;
|
|
649
|
+
}
|
|
650
|
+
} catch (e) {
|
|
651
|
+
status[r] = false;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return status;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Get a runtime instance by name (synchronous for most runtimes).
|
|
658
|
+
*
|
|
659
|
+
* Note: For llm/lambda/network runtimes, this returns a wrapper that
|
|
660
|
+
* lazily initializes. Use getRuntimeAsync() for guaranteed initialization.
|
|
661
|
+
*/
|
|
662
|
+
static getRuntime(runtimeName, options) {
|
|
663
|
+
switch (runtimeName) {
|
|
664
|
+
case "javascript":
|
|
665
|
+
case "node":
|
|
666
|
+
return new JavaScriptRuntime();
|
|
667
|
+
case "python":
|
|
668
|
+
return new PythonRuntime();
|
|
669
|
+
case "rust":
|
|
670
|
+
case "c":
|
|
671
|
+
return new BinaryRuntime();
|
|
672
|
+
case "wasm":
|
|
673
|
+
return new WasmRuntime();
|
|
674
|
+
case "lean":
|
|
675
|
+
return new LeanRuntime();
|
|
676
|
+
case "loader":
|
|
677
|
+
return new LoaderRuntime();
|
|
678
|
+
case "collection_loader":
|
|
679
|
+
return new CollectionLoaderRuntime();
|
|
680
|
+
case "llm":
|
|
681
|
+
return {
|
|
682
|
+
async execute(code, context, config) {
|
|
683
|
+
if (!_LLMRuntime) {
|
|
684
|
+
const mod = await import("./LLMRuntime-PH3MOQ2Y.js");
|
|
685
|
+
_LLMRuntime = mod.LLMRuntime;
|
|
686
|
+
}
|
|
687
|
+
const runtime = new _LLMRuntime();
|
|
688
|
+
return runtime.execute(code, context, config, "");
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
case "lambda":
|
|
692
|
+
if (!options?.collection) {
|
|
693
|
+
throw new Error("Lambda runtime requires a CardCollection in options");
|
|
694
|
+
}
|
|
695
|
+
const collection = options.collection;
|
|
696
|
+
return {
|
|
697
|
+
async execute(code, context, config) {
|
|
698
|
+
if (!_LambdaRuntime) {
|
|
699
|
+
const mod = await import("./LambdaRuntime-YH74FHIW.js");
|
|
700
|
+
_LambdaRuntime = mod.LambdaRuntime;
|
|
701
|
+
}
|
|
702
|
+
const runtime = new _LambdaRuntime(collection);
|
|
703
|
+
return runtime.execute(code, context, config, "");
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
case "network":
|
|
707
|
+
const netCollection = options?.collection;
|
|
708
|
+
return {
|
|
709
|
+
async execute(code, context, config, chapterDir) {
|
|
710
|
+
if (!_NetworkRuntime) {
|
|
711
|
+
const mod = await import("./NetworkRuntime-S4DZCGVN.js");
|
|
712
|
+
_NetworkRuntime = mod.NetworkRuntime;
|
|
713
|
+
}
|
|
714
|
+
const runtime = new _NetworkRuntime(netCollection);
|
|
715
|
+
return runtime.execute(code, context, config, chapterDir || "");
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
default:
|
|
719
|
+
throw new Error(`Unknown runtime: ${runtimeName}`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Get a runtime instance asynchronously (for runtimes that need dynamic imports).
|
|
724
|
+
*/
|
|
725
|
+
static async getRuntimeAsync(runtimeName, options) {
|
|
726
|
+
switch (runtimeName) {
|
|
727
|
+
case "llm": {
|
|
728
|
+
const { LLMRuntime } = await import("./LLMRuntime-PH3MOQ2Y.js");
|
|
729
|
+
return new LLMRuntime();
|
|
730
|
+
}
|
|
731
|
+
case "lambda": {
|
|
732
|
+
if (!options?.collection) {
|
|
733
|
+
throw new Error("Lambda runtime requires a CardCollection in options");
|
|
734
|
+
}
|
|
735
|
+
const { LambdaRuntime: LambdaRuntime2 } = await import("./LambdaRuntime-YH74FHIW.js");
|
|
736
|
+
return new LambdaRuntime2(options.collection);
|
|
737
|
+
}
|
|
738
|
+
case "network": {
|
|
739
|
+
const { NetworkRuntime } = await import("./NetworkRuntime-S4DZCGVN.js");
|
|
740
|
+
return new NetworkRuntime(options?.collection);
|
|
741
|
+
}
|
|
742
|
+
default:
|
|
743
|
+
return this.getRuntime(runtimeName, options);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
// src/ptr/lambda/LambdaTerm.ts
|
|
749
|
+
function mkVar(name) {
|
|
750
|
+
return { tag: "Var", name };
|
|
751
|
+
}
|
|
752
|
+
function mkAbs(param, bodyHash) {
|
|
753
|
+
return { tag: "Abs", param, body: bodyHash };
|
|
754
|
+
}
|
|
755
|
+
function mkApp(funcHash, argHash) {
|
|
756
|
+
return { tag: "App", func: funcHash, arg: argHash };
|
|
757
|
+
}
|
|
758
|
+
function serializeTerm(term) {
|
|
759
|
+
return JSON.stringify(term);
|
|
760
|
+
}
|
|
761
|
+
function deserializeTerm(content) {
|
|
762
|
+
const parsed = JSON.parse(content);
|
|
763
|
+
if (!parsed.tag) {
|
|
764
|
+
throw new Error("Invalid Lambda term: missing tag");
|
|
765
|
+
}
|
|
766
|
+
switch (parsed.tag) {
|
|
767
|
+
case "Var":
|
|
768
|
+
if (typeof parsed.name !== "string") {
|
|
769
|
+
throw new Error("Invalid Var term: name must be string");
|
|
770
|
+
}
|
|
771
|
+
return mkVar(parsed.name);
|
|
772
|
+
case "Abs":
|
|
773
|
+
if (typeof parsed.param !== "string" || typeof parsed.body !== "string") {
|
|
774
|
+
throw new Error("Invalid Abs term: param and body must be strings");
|
|
775
|
+
}
|
|
776
|
+
return mkAbs(parsed.param, parsed.body);
|
|
777
|
+
case "App":
|
|
778
|
+
if (typeof parsed.func !== "string" || typeof parsed.arg !== "string") {
|
|
779
|
+
throw new Error("Invalid App term: func and arg must be strings");
|
|
780
|
+
}
|
|
781
|
+
return mkApp(parsed.func, parsed.arg);
|
|
782
|
+
default:
|
|
783
|
+
throw new Error(`Unknown Lambda term tag: ${parsed.tag}`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
async function termToMCard(term) {
|
|
787
|
+
return MCard.create(serializeTerm(term));
|
|
788
|
+
}
|
|
789
|
+
function mcardToTerm(mcard) {
|
|
790
|
+
return deserializeTerm(mcard.getContentAsText());
|
|
791
|
+
}
|
|
792
|
+
var termCache = /* @__PURE__ */ new Map();
|
|
793
|
+
async function storeTerm(collection, term) {
|
|
794
|
+
const mcard = await termToMCard(term);
|
|
795
|
+
termCache.set(mcard.hash, term);
|
|
796
|
+
await collection.add(mcard);
|
|
797
|
+
return mcard.hash;
|
|
798
|
+
}
|
|
799
|
+
async function loadTerm(collection, hash) {
|
|
800
|
+
const cached = termCache.get(hash);
|
|
801
|
+
if (cached) return cached;
|
|
802
|
+
const mcard = await collection.get(hash);
|
|
803
|
+
if (!mcard) return null;
|
|
804
|
+
const term = mcardToTerm(mcard);
|
|
805
|
+
termCache.set(hash, term);
|
|
806
|
+
return term;
|
|
807
|
+
}
|
|
808
|
+
async function termExists(collection, hash) {
|
|
809
|
+
const mcard = await collection.get(hash);
|
|
810
|
+
return mcard !== null;
|
|
811
|
+
}
|
|
812
|
+
function prettyPrintShallow(term) {
|
|
813
|
+
switch (term.tag) {
|
|
814
|
+
case "Var":
|
|
815
|
+
return term.name;
|
|
816
|
+
case "Abs":
|
|
817
|
+
return `\u03BB${term.param}.\u3008${term.body.substring(0, 8)}\u2026\u3009`;
|
|
818
|
+
case "App":
|
|
819
|
+
return `(\u3008${term.func.substring(0, 8)}\u2026\u3009 \u3008${term.arg.substring(0, 8)}\u2026\u3009)`;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
async function prettyPrintDeep(collection, hash) {
|
|
823
|
+
const term = await loadTerm(collection, hash);
|
|
824
|
+
if (!term) return `\u3008missing:${hash.substring(0, 8)}\u2026\u3009`;
|
|
825
|
+
switch (term.tag) {
|
|
826
|
+
case "Var":
|
|
827
|
+
return term.name;
|
|
828
|
+
case "Abs":
|
|
829
|
+
const body = await prettyPrintDeep(collection, term.body);
|
|
830
|
+
return `(\u03BB${term.param}.${body})`;
|
|
831
|
+
case "App":
|
|
832
|
+
const func = await prettyPrintDeep(collection, term.func);
|
|
833
|
+
const arg = await prettyPrintDeep(collection, term.arg);
|
|
834
|
+
return `(${func} ${arg})`;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function isVar(term) {
|
|
838
|
+
return term.tag === "Var";
|
|
839
|
+
}
|
|
840
|
+
function isAbs(term) {
|
|
841
|
+
return term.tag === "Abs";
|
|
842
|
+
}
|
|
843
|
+
function isApp(term) {
|
|
844
|
+
return term.tag === "App";
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/ptr/lambda/FreeVariables.ts
|
|
848
|
+
var freeVarCache = /* @__PURE__ */ new Map();
|
|
849
|
+
function freeVariables(collection, termHash) {
|
|
850
|
+
return IO.of(async () => {
|
|
851
|
+
const result = await computeFreeVars(collection, termHash, /* @__PURE__ */ new Set());
|
|
852
|
+
return result;
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
async function computeFreeVars(collection, termHash, visited) {
|
|
856
|
+
const cached = freeVarCache.get(termHash);
|
|
857
|
+
if (cached) {
|
|
858
|
+
return Maybe.just(new Set(cached));
|
|
859
|
+
}
|
|
860
|
+
if (visited.has(termHash)) {
|
|
861
|
+
return Maybe.just(/* @__PURE__ */ new Set());
|
|
862
|
+
}
|
|
863
|
+
visited.add(termHash);
|
|
864
|
+
const term = await loadTerm(collection, termHash);
|
|
865
|
+
if (!term) {
|
|
866
|
+
return Maybe.nothing();
|
|
867
|
+
}
|
|
868
|
+
const result = await freeVarsOfTerm(collection, term, visited);
|
|
869
|
+
freeVarCache.set(termHash, new Set(result));
|
|
870
|
+
return Maybe.just(result);
|
|
871
|
+
}
|
|
872
|
+
async function freeVarsOfTerm(collection, term, visited) {
|
|
873
|
+
switch (term.tag) {
|
|
874
|
+
case "Var":
|
|
875
|
+
return /* @__PURE__ */ new Set([term.name]);
|
|
876
|
+
case "Abs": {
|
|
877
|
+
const bodyFV = await computeFreeVars(collection, term.body, visited);
|
|
878
|
+
if (bodyFV.isNothing) {
|
|
879
|
+
throw new Error(`Body term not found: ${term.body}`);
|
|
880
|
+
}
|
|
881
|
+
const result = new Set(bodyFV.value);
|
|
882
|
+
result.delete(term.param);
|
|
883
|
+
return result;
|
|
884
|
+
}
|
|
885
|
+
case "App": {
|
|
886
|
+
const funcFV = await computeFreeVars(collection, term.func, visited);
|
|
887
|
+
const argFV = await computeFreeVars(collection, term.arg, visited);
|
|
888
|
+
if (funcFV.isNothing) {
|
|
889
|
+
throw new Error(`Function term not found: ${term.func}`);
|
|
890
|
+
}
|
|
891
|
+
if (argFV.isNothing) {
|
|
892
|
+
throw new Error(`Argument term not found: ${term.arg}`);
|
|
893
|
+
}
|
|
894
|
+
return union(funcFV.value, argFV.value);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
function boundVariables(collection, termHash) {
|
|
899
|
+
return IO.of(async () => {
|
|
900
|
+
return computeBoundVars(collection, termHash, /* @__PURE__ */ new Set());
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
async function computeBoundVars(collection, termHash, visited) {
|
|
904
|
+
if (visited.has(termHash)) {
|
|
905
|
+
return Maybe.just(/* @__PURE__ */ new Set());
|
|
906
|
+
}
|
|
907
|
+
visited.add(termHash);
|
|
908
|
+
const term = await loadTerm(collection, termHash);
|
|
909
|
+
if (!term) {
|
|
910
|
+
return Maybe.nothing();
|
|
911
|
+
}
|
|
912
|
+
switch (term.tag) {
|
|
913
|
+
case "Var":
|
|
914
|
+
return Maybe.just(/* @__PURE__ */ new Set());
|
|
915
|
+
case "Abs": {
|
|
916
|
+
const bodyBV = await computeBoundVars(collection, term.body, visited);
|
|
917
|
+
if (bodyBV.isNothing) return bodyBV;
|
|
918
|
+
const result = new Set(bodyBV.value);
|
|
919
|
+
result.add(term.param);
|
|
920
|
+
return Maybe.just(result);
|
|
921
|
+
}
|
|
922
|
+
case "App": {
|
|
923
|
+
const funcBV = await computeBoundVars(collection, term.func, visited);
|
|
924
|
+
const argBV = await computeBoundVars(collection, term.arg, visited);
|
|
925
|
+
if (funcBV.isNothing || argBV.isNothing) {
|
|
926
|
+
return Maybe.nothing();
|
|
927
|
+
}
|
|
928
|
+
return Maybe.just(union(funcBV.value, argBV.value));
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
function isFreeIn(collection, variable, termHash) {
|
|
933
|
+
return freeVariables(collection, termHash).map((maybeFV) => {
|
|
934
|
+
if (maybeFV.isNothing) return false;
|
|
935
|
+
return maybeFV.value.has(variable);
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
function isClosed(collection, termHash) {
|
|
939
|
+
return freeVariables(collection, termHash).map((maybeFV) => {
|
|
940
|
+
if (maybeFV.isNothing) return false;
|
|
941
|
+
return maybeFV.value.size === 0;
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
function generateFresh(base, avoid) {
|
|
945
|
+
if (!avoid.has(base)) {
|
|
946
|
+
return base;
|
|
947
|
+
}
|
|
948
|
+
let candidate = base + "'";
|
|
949
|
+
while (avoid.has(candidate)) {
|
|
950
|
+
candidate += "'";
|
|
951
|
+
}
|
|
952
|
+
return candidate;
|
|
953
|
+
}
|
|
954
|
+
async function generateFreshFor(collection, base, termHash) {
|
|
955
|
+
const fvResult = await freeVariables(collection, termHash).run();
|
|
956
|
+
const bvResult = await boundVariables(collection, termHash).run();
|
|
957
|
+
const avoid = /* @__PURE__ */ new Set();
|
|
958
|
+
if (fvResult.isJust) {
|
|
959
|
+
for (const v of fvResult.value) avoid.add(v);
|
|
960
|
+
}
|
|
961
|
+
if (bvResult.isJust) {
|
|
962
|
+
for (const v of bvResult.value) avoid.add(v);
|
|
963
|
+
}
|
|
964
|
+
return generateFresh(base, avoid);
|
|
965
|
+
}
|
|
966
|
+
function union(a, b) {
|
|
967
|
+
const result = new Set(a);
|
|
968
|
+
for (const item of b) {
|
|
969
|
+
result.add(item);
|
|
970
|
+
}
|
|
971
|
+
return result;
|
|
972
|
+
}
|
|
973
|
+
function difference(a, b) {
|
|
974
|
+
const result = /* @__PURE__ */ new Set();
|
|
975
|
+
for (const item of a) {
|
|
976
|
+
if (!b.has(item)) {
|
|
977
|
+
result.add(item);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return result;
|
|
981
|
+
}
|
|
982
|
+
function intersection(a, b) {
|
|
983
|
+
const result = /* @__PURE__ */ new Set();
|
|
984
|
+
for (const item of a) {
|
|
985
|
+
if (b.has(item)) {
|
|
986
|
+
result.add(item);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return result;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// src/ptr/lambda/AlphaConversion.ts
|
|
993
|
+
function alphaRename(collection, absHash, newParam) {
|
|
994
|
+
return IO.of(async () => {
|
|
995
|
+
const term = await loadTerm(collection, absHash);
|
|
996
|
+
if (!term) {
|
|
997
|
+
return Either.left(`Term not found: ${absHash}`);
|
|
998
|
+
}
|
|
999
|
+
if (!isAbs(term)) {
|
|
1000
|
+
return Either.left(
|
|
1001
|
+
`Alpha conversion only applies to abstractions, got ${term.tag}`
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
if (term.param === newParam) {
|
|
1005
|
+
return Either.right(absHash);
|
|
1006
|
+
}
|
|
1007
|
+
const bodyFV = await freeVariables(collection, term.body).run();
|
|
1008
|
+
if (bodyFV.isJust && bodyFV.value.has(newParam)) {
|
|
1009
|
+
return Either.left(
|
|
1010
|
+
`Cannot \u03B1-rename to '${newParam}': would capture free variable in body`
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
const newBodyHash = await substituteVar(
|
|
1014
|
+
collection,
|
|
1015
|
+
term.body,
|
|
1016
|
+
term.param,
|
|
1017
|
+
newParam
|
|
1018
|
+
);
|
|
1019
|
+
const newAbs = mkAbs(newParam, newBodyHash);
|
|
1020
|
+
const resultHash = await storeTerm(collection, newAbs);
|
|
1021
|
+
return Either.right(resultHash);
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
async function substituteVar(collection, termHash, oldVar, newVar) {
|
|
1025
|
+
const term = await loadTerm(collection, termHash);
|
|
1026
|
+
if (!term) {
|
|
1027
|
+
throw new Error(`Term not found: ${termHash}`);
|
|
1028
|
+
}
|
|
1029
|
+
switch (term.tag) {
|
|
1030
|
+
case "Var":
|
|
1031
|
+
if (term.name === oldVar) {
|
|
1032
|
+
const newTerm = mkVar(newVar);
|
|
1033
|
+
return storeTerm(collection, newTerm);
|
|
1034
|
+
}
|
|
1035
|
+
return termHash;
|
|
1036
|
+
case "Abs":
|
|
1037
|
+
if (term.param === oldVar) {
|
|
1038
|
+
return termHash;
|
|
1039
|
+
}
|
|
1040
|
+
const newBody = await substituteVar(collection, term.body, oldVar, newVar);
|
|
1041
|
+
if (newBody === term.body) {
|
|
1042
|
+
return termHash;
|
|
1043
|
+
}
|
|
1044
|
+
const newAbs = mkAbs(term.param, newBody);
|
|
1045
|
+
return storeTerm(collection, newAbs);
|
|
1046
|
+
case "App":
|
|
1047
|
+
const newFunc = await substituteVar(collection, term.func, oldVar, newVar);
|
|
1048
|
+
const newArg = await substituteVar(collection, term.arg, oldVar, newVar);
|
|
1049
|
+
if (newFunc === term.func && newArg === term.arg) {
|
|
1050
|
+
return termHash;
|
|
1051
|
+
}
|
|
1052
|
+
const newApp = mkApp(newFunc, newArg);
|
|
1053
|
+
return storeTerm(collection, newApp);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
function alphaEquivalent(collection, hash1, hash2) {
|
|
1057
|
+
return IO.of(async () => {
|
|
1058
|
+
if (hash1 === hash2) {
|
|
1059
|
+
return Either.right(true);
|
|
1060
|
+
}
|
|
1061
|
+
const term1 = await loadTerm(collection, hash1);
|
|
1062
|
+
const term2 = await loadTerm(collection, hash2);
|
|
1063
|
+
if (!term1) return Either.left(`Term not found: ${hash1}`);
|
|
1064
|
+
if (!term2) return Either.left(`Term not found: ${hash2}`);
|
|
1065
|
+
const equiv = await checkAlphaEquiv(collection, term1, term2, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
|
|
1066
|
+
return Either.right(equiv);
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
async function checkAlphaEquiv(collection, term1, term2, rename1, rename2) {
|
|
1070
|
+
if (term1.tag !== term2.tag) {
|
|
1071
|
+
return false;
|
|
1072
|
+
}
|
|
1073
|
+
switch (term1.tag) {
|
|
1074
|
+
case "Var": {
|
|
1075
|
+
const t2 = term2;
|
|
1076
|
+
const idx1 = rename1.get(term1.name);
|
|
1077
|
+
const idx2 = rename2.get(t2.name);
|
|
1078
|
+
if (idx1 !== void 0 && idx2 !== void 0) {
|
|
1079
|
+
return idx1 === idx2;
|
|
1080
|
+
}
|
|
1081
|
+
if (idx1 === void 0 && idx2 === void 0) {
|
|
1082
|
+
return term1.name === t2.name;
|
|
1083
|
+
}
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
1086
|
+
case "Abs": {
|
|
1087
|
+
const t2 = term2;
|
|
1088
|
+
const depth = rename1.size;
|
|
1089
|
+
const newRename1 = new Map(rename1);
|
|
1090
|
+
const newRename2 = new Map(rename2);
|
|
1091
|
+
newRename1.set(term1.param, depth);
|
|
1092
|
+
newRename2.set(t2.param, depth);
|
|
1093
|
+
const body1 = await loadTerm(collection, term1.body);
|
|
1094
|
+
const body2 = await loadTerm(collection, t2.body);
|
|
1095
|
+
if (!body1 || !body2) return false;
|
|
1096
|
+
return checkAlphaEquiv(collection, body1, body2, newRename1, newRename2);
|
|
1097
|
+
}
|
|
1098
|
+
case "App": {
|
|
1099
|
+
const t2 = term2;
|
|
1100
|
+
const func1 = await loadTerm(collection, term1.func);
|
|
1101
|
+
const func2 = await loadTerm(collection, t2.func);
|
|
1102
|
+
const arg1 = await loadTerm(collection, term1.arg);
|
|
1103
|
+
const arg2 = await loadTerm(collection, t2.arg);
|
|
1104
|
+
if (!func1 || !func2 || !arg1 || !arg2) return false;
|
|
1105
|
+
const funcEquiv = await checkAlphaEquiv(collection, func1, func2, rename1, rename2);
|
|
1106
|
+
if (!funcEquiv) return false;
|
|
1107
|
+
return checkAlphaEquiv(collection, arg1, arg2, rename1, rename2);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
function alphaNormalize(collection, termHash) {
|
|
1112
|
+
return IO.of(async () => {
|
|
1113
|
+
const result = await normalizeWithDepth(collection, termHash, /* @__PURE__ */ new Map(), 0);
|
|
1114
|
+
if (result === null) {
|
|
1115
|
+
return Either.left(`Term not found: ${termHash}`);
|
|
1116
|
+
}
|
|
1117
|
+
return Either.right(result);
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
async function normalizeWithDepth(collection, termHash, bindings, depth) {
|
|
1121
|
+
const term = await loadTerm(collection, termHash);
|
|
1122
|
+
if (!term) return null;
|
|
1123
|
+
switch (term.tag) {
|
|
1124
|
+
case "Var": {
|
|
1125
|
+
const canonicalName = bindings.get(term.name);
|
|
1126
|
+
if (canonicalName) {
|
|
1127
|
+
const newVar = mkVar(canonicalName);
|
|
1128
|
+
return storeTerm(collection, newVar);
|
|
1129
|
+
}
|
|
1130
|
+
return termHash;
|
|
1131
|
+
}
|
|
1132
|
+
case "Abs": {
|
|
1133
|
+
const canonicalName = `a${depth}`;
|
|
1134
|
+
const newBindings = new Map(bindings);
|
|
1135
|
+
newBindings.set(term.param, canonicalName);
|
|
1136
|
+
const newBody = await normalizeWithDepth(collection, term.body, newBindings, depth + 1);
|
|
1137
|
+
if (newBody === null) return null;
|
|
1138
|
+
const newAbs = mkAbs(canonicalName, newBody);
|
|
1139
|
+
return storeTerm(collection, newAbs);
|
|
1140
|
+
}
|
|
1141
|
+
case "App": {
|
|
1142
|
+
const newFunc = await normalizeWithDepth(collection, term.func, bindings, depth);
|
|
1143
|
+
const newArg = await normalizeWithDepth(collection, term.arg, bindings, depth);
|
|
1144
|
+
if (newFunc === null || newArg === null) return null;
|
|
1145
|
+
if (newFunc === term.func && newArg === term.arg) {
|
|
1146
|
+
return termHash;
|
|
1147
|
+
}
|
|
1148
|
+
const newApp = mkApp(newFunc, newArg);
|
|
1149
|
+
return storeTerm(collection, newApp);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// src/ptr/lambda/BetaReduction.ts
|
|
1155
|
+
function isRedex(collection, termHash) {
|
|
1156
|
+
return IO.of(async () => {
|
|
1157
|
+
const term = await loadTerm(collection, termHash);
|
|
1158
|
+
if (!term || !isApp(term)) return false;
|
|
1159
|
+
const func = await loadTerm(collection, term.func);
|
|
1160
|
+
return func !== null && isAbs(func);
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
function findLeftmostRedex(collection, termHash) {
|
|
1164
|
+
return IO.of(async () => {
|
|
1165
|
+
return findRedexNormalOrder(collection, termHash);
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
async function findRedexNormalOrder(collection, termHash) {
|
|
1169
|
+
const term = await loadTerm(collection, termHash);
|
|
1170
|
+
if (!term) return Maybe.nothing();
|
|
1171
|
+
switch (term.tag) {
|
|
1172
|
+
case "Var":
|
|
1173
|
+
return Maybe.nothing();
|
|
1174
|
+
case "Abs":
|
|
1175
|
+
return findRedexNormalOrder(collection, term.body);
|
|
1176
|
+
case "App": {
|
|
1177
|
+
const func = await loadTerm(collection, term.func);
|
|
1178
|
+
if (func && isAbs(func)) {
|
|
1179
|
+
return Maybe.just(termHash);
|
|
1180
|
+
}
|
|
1181
|
+
const funcRedex = await findRedexNormalOrder(collection, term.func);
|
|
1182
|
+
if (funcRedex.isJust) return funcRedex;
|
|
1183
|
+
return findRedexNormalOrder(collection, term.arg);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
function findInnermostRedex(collection, termHash) {
|
|
1188
|
+
return IO.of(async () => {
|
|
1189
|
+
return findRedexApplicativeOrder(collection, termHash);
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
async function findRedexApplicativeOrder(collection, termHash) {
|
|
1193
|
+
const term = await loadTerm(collection, termHash);
|
|
1194
|
+
if (!term) return Maybe.nothing();
|
|
1195
|
+
switch (term.tag) {
|
|
1196
|
+
case "Var":
|
|
1197
|
+
return Maybe.nothing();
|
|
1198
|
+
case "Abs":
|
|
1199
|
+
return findRedexApplicativeOrder(collection, term.body);
|
|
1200
|
+
case "App": {
|
|
1201
|
+
const funcRedex = await findRedexApplicativeOrder(collection, term.func);
|
|
1202
|
+
if (funcRedex.isJust) return funcRedex;
|
|
1203
|
+
const argRedex = await findRedexApplicativeOrder(collection, term.arg);
|
|
1204
|
+
if (argRedex.isJust) return argRedex;
|
|
1205
|
+
const func = await loadTerm(collection, term.func);
|
|
1206
|
+
if (func && isAbs(func)) {
|
|
1207
|
+
return Maybe.just(termHash);
|
|
1208
|
+
}
|
|
1209
|
+
return Maybe.nothing();
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
function betaReduce(collection, redexHash) {
|
|
1214
|
+
return IO.of(async () => {
|
|
1215
|
+
const app = await loadTerm(collection, redexHash);
|
|
1216
|
+
if (!app) {
|
|
1217
|
+
return Either.left(`Term not found: ${redexHash}`);
|
|
1218
|
+
}
|
|
1219
|
+
if (!isApp(app)) {
|
|
1220
|
+
return Either.left(
|
|
1221
|
+
`Beta reduction requires application term, got ${app.tag}`
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
const func = await loadTerm(collection, app.func);
|
|
1225
|
+
if (!func) {
|
|
1226
|
+
return Either.left(`Function not found: ${app.func}`);
|
|
1227
|
+
}
|
|
1228
|
+
if (!isAbs(func)) {
|
|
1229
|
+
return Either.left(
|
|
1230
|
+
`Not a beta-redex: function is ${func.tag}, not abstraction`
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
const resultHash = await substituteWithCapture(
|
|
1234
|
+
collection,
|
|
1235
|
+
func.body,
|
|
1236
|
+
// M
|
|
1237
|
+
func.param,
|
|
1238
|
+
// x
|
|
1239
|
+
app.arg
|
|
1240
|
+
// N (as hash)
|
|
1241
|
+
);
|
|
1242
|
+
return Either.right(resultHash);
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
async function substituteWithCapture(collection, termHash, variable, replacementHash) {
|
|
1246
|
+
const term = await loadTerm(collection, termHash);
|
|
1247
|
+
if (!term) {
|
|
1248
|
+
throw new Error(`Term not found: ${termHash}`);
|
|
1249
|
+
}
|
|
1250
|
+
switch (term.tag) {
|
|
1251
|
+
case "Var":
|
|
1252
|
+
if (term.name === variable) {
|
|
1253
|
+
return replacementHash;
|
|
1254
|
+
}
|
|
1255
|
+
return termHash;
|
|
1256
|
+
case "Abs": {
|
|
1257
|
+
if (term.param === variable) {
|
|
1258
|
+
return termHash;
|
|
1259
|
+
}
|
|
1260
|
+
const replacementFV = await freeVariables(collection, replacementHash).run();
|
|
1261
|
+
if (replacementFV.isJust && replacementFV.value.has(term.param)) {
|
|
1262
|
+
const allVars = new Set(replacementFV.value);
|
|
1263
|
+
const bodyFV = await freeVariables(collection, term.body).run();
|
|
1264
|
+
if (bodyFV.isJust) {
|
|
1265
|
+
for (const v of bodyFV.value) allVars.add(v);
|
|
1266
|
+
}
|
|
1267
|
+
allVars.add(variable);
|
|
1268
|
+
const freshName = generateFresh(term.param, allVars);
|
|
1269
|
+
const renameResult = await alphaRename(collection, termHash, freshName).run();
|
|
1270
|
+
if (renameResult.isLeft) {
|
|
1271
|
+
throw new Error(`Alpha rename failed: ${renameResult.left}`);
|
|
1272
|
+
}
|
|
1273
|
+
return substituteWithCapture(
|
|
1274
|
+
collection,
|
|
1275
|
+
renameResult.right,
|
|
1276
|
+
variable,
|
|
1277
|
+
replacementHash
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
const newBody = await substituteWithCapture(
|
|
1281
|
+
collection,
|
|
1282
|
+
term.body,
|
|
1283
|
+
variable,
|
|
1284
|
+
replacementHash
|
|
1285
|
+
);
|
|
1286
|
+
if (newBody === term.body) {
|
|
1287
|
+
return termHash;
|
|
1288
|
+
}
|
|
1289
|
+
const newAbs = mkAbs(term.param, newBody);
|
|
1290
|
+
return storeTerm(collection, newAbs);
|
|
1291
|
+
}
|
|
1292
|
+
case "App": {
|
|
1293
|
+
const newFunc = await substituteWithCapture(
|
|
1294
|
+
collection,
|
|
1295
|
+
term.func,
|
|
1296
|
+
variable,
|
|
1297
|
+
replacementHash
|
|
1298
|
+
);
|
|
1299
|
+
const newArg = await substituteWithCapture(
|
|
1300
|
+
collection,
|
|
1301
|
+
term.arg,
|
|
1302
|
+
variable,
|
|
1303
|
+
replacementHash
|
|
1304
|
+
);
|
|
1305
|
+
if (newFunc === term.func && newArg === term.arg) {
|
|
1306
|
+
return termHash;
|
|
1307
|
+
}
|
|
1308
|
+
const newApp = mkApp(newFunc, newArg);
|
|
1309
|
+
return storeTerm(collection, newApp);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
function reduceStep(collection, termHash, strategy = "normal") {
|
|
1314
|
+
return IO.of(async () => {
|
|
1315
|
+
let redexMaybe;
|
|
1316
|
+
switch (strategy) {
|
|
1317
|
+
case "normal":
|
|
1318
|
+
case "lazy":
|
|
1319
|
+
redexMaybe = await findRedexNormalOrder(collection, termHash);
|
|
1320
|
+
break;
|
|
1321
|
+
case "applicative":
|
|
1322
|
+
redexMaybe = await findRedexApplicativeOrder(collection, termHash);
|
|
1323
|
+
break;
|
|
1324
|
+
}
|
|
1325
|
+
if (redexMaybe.isNothing) {
|
|
1326
|
+
return Maybe.nothing();
|
|
1327
|
+
}
|
|
1328
|
+
const redexHash = redexMaybe.value;
|
|
1329
|
+
if (redexHash === termHash) {
|
|
1330
|
+
const result = await betaReduce(collection, redexHash).run();
|
|
1331
|
+
if (result.isLeft) {
|
|
1332
|
+
throw new Error(result.left);
|
|
1333
|
+
}
|
|
1334
|
+
return Maybe.just(result.right);
|
|
1335
|
+
}
|
|
1336
|
+
const reduced = await betaReduce(collection, redexHash).run();
|
|
1337
|
+
if (reduced.isLeft) {
|
|
1338
|
+
throw new Error(reduced.left);
|
|
1339
|
+
}
|
|
1340
|
+
const rebuilt = await rebuildWithReduced(
|
|
1341
|
+
collection,
|
|
1342
|
+
termHash,
|
|
1343
|
+
redexHash,
|
|
1344
|
+
reduced.right
|
|
1345
|
+
);
|
|
1346
|
+
return Maybe.just(rebuilt);
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
async function rebuildWithReduced(collection, termHash, redexHash, reducedHash) {
|
|
1350
|
+
if (termHash === redexHash) {
|
|
1351
|
+
return reducedHash;
|
|
1352
|
+
}
|
|
1353
|
+
const term = await loadTerm(collection, termHash);
|
|
1354
|
+
if (!term) throw new Error(`Term not found: ${termHash}`);
|
|
1355
|
+
switch (term.tag) {
|
|
1356
|
+
case "Var":
|
|
1357
|
+
return termHash;
|
|
1358
|
+
case "Abs": {
|
|
1359
|
+
const newBody = await rebuildWithReduced(
|
|
1360
|
+
collection,
|
|
1361
|
+
term.body,
|
|
1362
|
+
redexHash,
|
|
1363
|
+
reducedHash
|
|
1364
|
+
);
|
|
1365
|
+
if (newBody === term.body) return termHash;
|
|
1366
|
+
const newAbs = mkAbs(term.param, newBody);
|
|
1367
|
+
return storeTerm(collection, newAbs);
|
|
1368
|
+
}
|
|
1369
|
+
case "App": {
|
|
1370
|
+
const newFunc = await rebuildWithReduced(
|
|
1371
|
+
collection,
|
|
1372
|
+
term.func,
|
|
1373
|
+
redexHash,
|
|
1374
|
+
reducedHash
|
|
1375
|
+
);
|
|
1376
|
+
const newArg = await rebuildWithReduced(
|
|
1377
|
+
collection,
|
|
1378
|
+
term.arg,
|
|
1379
|
+
redexHash,
|
|
1380
|
+
reducedHash
|
|
1381
|
+
);
|
|
1382
|
+
if (newFunc === term.func && newArg === term.arg) return termHash;
|
|
1383
|
+
const newApp = mkApp(newFunc, newArg);
|
|
1384
|
+
return storeTerm(collection, newApp);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
function normalize(collection, termHash, strategy = "normal", maxSteps = 1e3, maxTimeMs, onStep) {
|
|
1389
|
+
return IO.of(async () => {
|
|
1390
|
+
const startTime = Date.now();
|
|
1391
|
+
let current = termHash;
|
|
1392
|
+
let steps = 0;
|
|
1393
|
+
const path8 = [termHash];
|
|
1394
|
+
while (steps < maxSteps) {
|
|
1395
|
+
if (maxTimeMs !== void 0 && Date.now() - startTime > maxTimeMs) {
|
|
1396
|
+
return Either.left(
|
|
1397
|
+
`Normalization exceeded time budget of ${maxTimeMs}ms after ${steps} steps`
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
const stepResult = await reduceStep(collection, current, strategy).run();
|
|
1401
|
+
if (stepResult.isNothing) {
|
|
1402
|
+
return Either.right({
|
|
1403
|
+
normalForm: current,
|
|
1404
|
+
steps,
|
|
1405
|
+
reductionPath: path8
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
current = stepResult.value;
|
|
1409
|
+
path8.push(current);
|
|
1410
|
+
steps++;
|
|
1411
|
+
if (onStep) {
|
|
1412
|
+
await onStep(steps, current);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
return Either.left(
|
|
1416
|
+
`Normalization did not terminate within ${maxSteps} steps (possible infinite loop)`
|
|
1417
|
+
);
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
function isNormalForm(collection, termHash) {
|
|
1421
|
+
return findLeftmostRedex(collection, termHash).map((m) => m.isNothing);
|
|
1422
|
+
}
|
|
1423
|
+
function hasNormalForm(collection, termHash, maxSteps = 1e3) {
|
|
1424
|
+
return normalize(collection, termHash, "normal", maxSteps).map((result) => result.isRight);
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// src/ptr/lambda/EtaConversion.ts
|
|
1428
|
+
function isEtaRedex(collection, termHash) {
|
|
1429
|
+
return IO.of(async () => {
|
|
1430
|
+
const term = await loadTerm(collection, termHash);
|
|
1431
|
+
if (!term || !isAbs(term)) return false;
|
|
1432
|
+
const body = await loadTerm(collection, term.body);
|
|
1433
|
+
if (!body || !isApp(body)) return false;
|
|
1434
|
+
const arg = await loadTerm(collection, body.arg);
|
|
1435
|
+
if (!arg || !isVar(arg) || arg.name !== term.param) return false;
|
|
1436
|
+
const funcFV = await freeVariables(collection, body.func).run();
|
|
1437
|
+
if (funcFV.isNothing) return false;
|
|
1438
|
+
return !funcFV.value.has(term.param);
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
function etaReduce(collection, termHash) {
|
|
1442
|
+
return IO.of(async () => {
|
|
1443
|
+
const term = await loadTerm(collection, termHash);
|
|
1444
|
+
if (!term) return Maybe.nothing();
|
|
1445
|
+
if (!isAbs(term)) return Maybe.nothing();
|
|
1446
|
+
const body = await loadTerm(collection, term.body);
|
|
1447
|
+
if (!body) return Maybe.nothing();
|
|
1448
|
+
if (!isApp(body)) return Maybe.nothing();
|
|
1449
|
+
const arg = await loadTerm(collection, body.arg);
|
|
1450
|
+
if (!arg) return Maybe.nothing();
|
|
1451
|
+
if (!isVar(arg) || arg.name !== term.param) {
|
|
1452
|
+
return Maybe.nothing();
|
|
1453
|
+
}
|
|
1454
|
+
const funcFV = await freeVariables(collection, body.func).run();
|
|
1455
|
+
if (funcFV.isNothing) return Maybe.nothing();
|
|
1456
|
+
if (funcFV.value.has(term.param)) {
|
|
1457
|
+
return Maybe.nothing();
|
|
1458
|
+
}
|
|
1459
|
+
return Maybe.just(body.func);
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
function etaReduceE(collection, termHash) {
|
|
1463
|
+
return etaReduce(collection, termHash).map((maybe) => {
|
|
1464
|
+
if (maybe.isNothing) {
|
|
1465
|
+
return Either.left("Not an \u03B7-redex");
|
|
1466
|
+
}
|
|
1467
|
+
return Either.right(maybe.value);
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
function etaExpand(collection, termHash, baseName = "x") {
|
|
1471
|
+
return IO.of(async () => {
|
|
1472
|
+
const freshVar = await generateFreshFor(collection, baseName, termHash);
|
|
1473
|
+
const varTerm = mkVar(freshVar);
|
|
1474
|
+
const varHash = await storeTerm(collection, varTerm);
|
|
1475
|
+
const appTerm = mkApp(termHash, varHash);
|
|
1476
|
+
const appHash = await storeTerm(collection, appTerm);
|
|
1477
|
+
const absTerm = mkAbs(freshVar, appHash);
|
|
1478
|
+
const absHash = await storeTerm(collection, absTerm);
|
|
1479
|
+
return absHash;
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
function etaEquivalent(collection, hash1, hash2) {
|
|
1483
|
+
return IO.of(async () => {
|
|
1484
|
+
if (hash1 === hash2) return true;
|
|
1485
|
+
const reduced1 = await etaReduceDeep(collection, hash1);
|
|
1486
|
+
const reduced2 = await etaReduceDeep(collection, hash2);
|
|
1487
|
+
return reduced1 === reduced2;
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
async function etaReduceDeep(collection, termHash) {
|
|
1491
|
+
const term = await loadTerm(collection, termHash);
|
|
1492
|
+
if (!term) return termHash;
|
|
1493
|
+
switch (term.tag) {
|
|
1494
|
+
case "Var":
|
|
1495
|
+
return termHash;
|
|
1496
|
+
case "Abs": {
|
|
1497
|
+
const etaResult = await etaReduce(collection, termHash).run();
|
|
1498
|
+
if (etaResult.isJust) {
|
|
1499
|
+
return etaReduceDeep(collection, etaResult.value);
|
|
1500
|
+
}
|
|
1501
|
+
const newBody = await etaReduceDeep(collection, term.body);
|
|
1502
|
+
if (newBody === term.body) return termHash;
|
|
1503
|
+
const newAbs = mkAbs(term.param, newBody);
|
|
1504
|
+
return storeTerm(collection, newAbs);
|
|
1505
|
+
}
|
|
1506
|
+
case "App": {
|
|
1507
|
+
const newFunc = await etaReduceDeep(collection, term.func);
|
|
1508
|
+
const newArg = await etaReduceDeep(collection, term.arg);
|
|
1509
|
+
if (newFunc === term.func && newArg === term.arg) return termHash;
|
|
1510
|
+
const newApp = mkApp(newFunc, newArg);
|
|
1511
|
+
return storeTerm(collection, newApp);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
function etaNormalize(collection, termHash) {
|
|
1516
|
+
return IO.of(() => etaReduceDeep(collection, termHash));
|
|
1517
|
+
}
|
|
1518
|
+
function findEtaRedexes(collection, termHash) {
|
|
1519
|
+
return IO.of(async () => {
|
|
1520
|
+
const redexes = [];
|
|
1521
|
+
await collectEtaRedexes(collection, termHash, redexes);
|
|
1522
|
+
return redexes;
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
async function collectEtaRedexes(collection, termHash, redexes) {
|
|
1526
|
+
const term = await loadTerm(collection, termHash);
|
|
1527
|
+
if (!term) return;
|
|
1528
|
+
switch (term.tag) {
|
|
1529
|
+
case "Var":
|
|
1530
|
+
break;
|
|
1531
|
+
case "Abs": {
|
|
1532
|
+
const isRedex3 = await isEtaRedex(collection, termHash).run();
|
|
1533
|
+
if (isRedex3) {
|
|
1534
|
+
redexes.push(termHash);
|
|
1535
|
+
}
|
|
1536
|
+
await collectEtaRedexes(collection, term.body, redexes);
|
|
1537
|
+
break;
|
|
1538
|
+
}
|
|
1539
|
+
case "App": {
|
|
1540
|
+
await collectEtaRedexes(collection, term.func, redexes);
|
|
1541
|
+
await collectEtaRedexes(collection, term.arg, redexes);
|
|
1542
|
+
break;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// src/ptr/lambda/IOEffects.ts
|
|
1548
|
+
var IOEffectsHandler = class {
|
|
1549
|
+
config;
|
|
1550
|
+
events = [];
|
|
1551
|
+
constructor(config = {}) {
|
|
1552
|
+
this.config = {
|
|
1553
|
+
enabled: config.enabled ?? false,
|
|
1554
|
+
console: config.console ?? true,
|
|
1555
|
+
network: config.network,
|
|
1556
|
+
onStep: config.onStep ?? false,
|
|
1557
|
+
onComplete: config.onComplete ?? true,
|
|
1558
|
+
onError: config.onError ?? true,
|
|
1559
|
+
format: config.format ?? "minimal"
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Check if IO effects are enabled
|
|
1564
|
+
*/
|
|
1565
|
+
isEnabled() {
|
|
1566
|
+
return this.config.enabled;
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Emit a step event
|
|
1570
|
+
*/
|
|
1571
|
+
async emitStep(stepNumber, termHash, prettyPrint) {
|
|
1572
|
+
if (!this.config.enabled || !this.config.onStep) return;
|
|
1573
|
+
const event = {
|
|
1574
|
+
type: "step",
|
|
1575
|
+
stepNumber,
|
|
1576
|
+
termHash,
|
|
1577
|
+
prettyPrint,
|
|
1578
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1579
|
+
};
|
|
1580
|
+
this.events.push(event);
|
|
1581
|
+
await this.dispatch(event);
|
|
1582
|
+
}
|
|
1583
|
+
/**
|
|
1584
|
+
* Emit a completion event
|
|
1585
|
+
*/
|
|
1586
|
+
async emitComplete(normalForm, prettyPrint, totalSteps, reductionPath) {
|
|
1587
|
+
if (!this.config.enabled || !this.config.onComplete) return;
|
|
1588
|
+
const event = {
|
|
1589
|
+
type: "complete",
|
|
1590
|
+
normalForm,
|
|
1591
|
+
prettyPrint,
|
|
1592
|
+
totalSteps,
|
|
1593
|
+
reductionPath,
|
|
1594
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1595
|
+
};
|
|
1596
|
+
this.events.push(event);
|
|
1597
|
+
await this.dispatch(event);
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Emit an error event
|
|
1601
|
+
*/
|
|
1602
|
+
async emitError(message, partialSteps, lastTermHash) {
|
|
1603
|
+
if (!this.config.enabled || !this.config.onError) return;
|
|
1604
|
+
const event = {
|
|
1605
|
+
type: "error",
|
|
1606
|
+
message,
|
|
1607
|
+
partialSteps,
|
|
1608
|
+
lastTermHash,
|
|
1609
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1610
|
+
};
|
|
1611
|
+
this.events.push(event);
|
|
1612
|
+
await this.dispatch(event);
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Get all collected events
|
|
1616
|
+
*/
|
|
1617
|
+
getEvents() {
|
|
1618
|
+
return [...this.events];
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Clear collected events
|
|
1622
|
+
*/
|
|
1623
|
+
clearEvents() {
|
|
1624
|
+
this.events = [];
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Dispatch event to configured outputs
|
|
1628
|
+
*/
|
|
1629
|
+
async dispatch(event) {
|
|
1630
|
+
const formatted = this.format(event);
|
|
1631
|
+
if (this.config.console) {
|
|
1632
|
+
this.logToConsole(event, formatted);
|
|
1633
|
+
}
|
|
1634
|
+
if (this.config.network?.enabled && this.config.network.endpoint) {
|
|
1635
|
+
await this.sendToNetwork(event, formatted);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Format event for output
|
|
1640
|
+
*/
|
|
1641
|
+
format(event) {
|
|
1642
|
+
switch (this.config.format) {
|
|
1643
|
+
case "json":
|
|
1644
|
+
return JSON.stringify(event);
|
|
1645
|
+
case "verbose":
|
|
1646
|
+
return this.formatVerbose(event);
|
|
1647
|
+
case "minimal":
|
|
1648
|
+
default:
|
|
1649
|
+
return this.formatMinimal(event);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
formatMinimal(event) {
|
|
1653
|
+
switch (event.type) {
|
|
1654
|
+
case "step":
|
|
1655
|
+
return `[IO:step ${event.stepNumber}] ${event.prettyPrint}`;
|
|
1656
|
+
case "complete":
|
|
1657
|
+
return `[IO:complete] ${event.totalSteps} steps \u2192 ${event.prettyPrint}`;
|
|
1658
|
+
case "error":
|
|
1659
|
+
return `[IO:error] ${event.message}`;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
formatVerbose(event) {
|
|
1663
|
+
const ts = event.timestamp.toISOString();
|
|
1664
|
+
switch (event.type) {
|
|
1665
|
+
case "step":
|
|
1666
|
+
return [
|
|
1667
|
+
`\u250C\u2500 IO Effect: Reduction Step ${event.stepNumber}`,
|
|
1668
|
+
`\u2502 Time: ${ts}`,
|
|
1669
|
+
`\u2502 Hash: ${event.termHash.substring(0, 16)}...`,
|
|
1670
|
+
`\u2502 Term: ${event.prettyPrint}`,
|
|
1671
|
+
`\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`
|
|
1672
|
+
].join("\n");
|
|
1673
|
+
case "complete":
|
|
1674
|
+
return [
|
|
1675
|
+
`\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
|
|
1676
|
+
`\u2551 IO Effect: Normalization Complete`,
|
|
1677
|
+
`\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
|
|
1678
|
+
`\u2551 Time: ${ts}`,
|
|
1679
|
+
`\u2551 Total Steps: ${event.totalSteps}`,
|
|
1680
|
+
`\u2551 Normal Form: ${event.prettyPrint}`,
|
|
1681
|
+
`\u2551 Hash: ${event.normalForm.substring(0, 16)}...`,
|
|
1682
|
+
`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`
|
|
1683
|
+
].join("\n");
|
|
1684
|
+
case "error":
|
|
1685
|
+
return [
|
|
1686
|
+
`\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
|
|
1687
|
+
`\u2551 IO Effect: ERROR`,
|
|
1688
|
+
`\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
|
|
1689
|
+
`\u2551 Time: ${ts}`,
|
|
1690
|
+
`\u2551 Message: ${event.message}`,
|
|
1691
|
+
`\u2551 Partial Steps: ${event.partialSteps}`,
|
|
1692
|
+
`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`
|
|
1693
|
+
].join("\n");
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
logToConsole(event, formatted) {
|
|
1697
|
+
const prefix = "\x1B[36m";
|
|
1698
|
+
const reset = "\x1B[0m";
|
|
1699
|
+
switch (event.type) {
|
|
1700
|
+
case "step":
|
|
1701
|
+
console.log(`${prefix}${formatted}${reset}`);
|
|
1702
|
+
break;
|
|
1703
|
+
case "complete":
|
|
1704
|
+
console.log(`${prefix}${formatted}${reset}`);
|
|
1705
|
+
break;
|
|
1706
|
+
case "error":
|
|
1707
|
+
console.error(`\x1B[31m${formatted}${reset}`);
|
|
1708
|
+
break;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
async sendToNetwork(event, formatted) {
|
|
1712
|
+
if (!this.config.network?.endpoint) return;
|
|
1713
|
+
try {
|
|
1714
|
+
const response = await fetch(this.config.network.endpoint, {
|
|
1715
|
+
method: this.config.network.method || "POST",
|
|
1716
|
+
headers: {
|
|
1717
|
+
"Content-Type": "application/json",
|
|
1718
|
+
...this.config.network.headers
|
|
1719
|
+
},
|
|
1720
|
+
body: JSON.stringify({
|
|
1721
|
+
event: event.type,
|
|
1722
|
+
data: event,
|
|
1723
|
+
formatted
|
|
1724
|
+
})
|
|
1725
|
+
});
|
|
1726
|
+
if (!response.ok) {
|
|
1727
|
+
console.warn(`IO network effect failed: ${response.status}`);
|
|
1728
|
+
}
|
|
1729
|
+
} catch (err) {
|
|
1730
|
+
console.warn(`IO network effect error: ${err}`);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
function createIOHandler(config) {
|
|
1735
|
+
if (!config || !config.io_effects) {
|
|
1736
|
+
return new IOEffectsHandler({ enabled: false });
|
|
1737
|
+
}
|
|
1738
|
+
const ioConfig = config.io_effects;
|
|
1739
|
+
return new IOEffectsHandler({
|
|
1740
|
+
enabled: ioConfig.enabled ?? false,
|
|
1741
|
+
console: ioConfig.console ?? true,
|
|
1742
|
+
network: ioConfig.network,
|
|
1743
|
+
onStep: ioConfig.on_step ?? ioConfig.onStep ?? false,
|
|
1744
|
+
onComplete: ioConfig.on_complete ?? ioConfig.onComplete ?? true,
|
|
1745
|
+
onError: ioConfig.on_error ?? ioConfig.onError ?? true,
|
|
1746
|
+
format: ioConfig.format ?? "minimal"
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
var noopIOHandler = new IOEffectsHandler({ enabled: false });
|
|
1750
|
+
|
|
1751
|
+
// src/ptr/lambda/LambdaRuntime.ts
|
|
1752
|
+
var LambdaRuntime = class {
|
|
1753
|
+
collection;
|
|
1754
|
+
constructor(collection) {
|
|
1755
|
+
this.collection = collection;
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Execute a Lambda operation
|
|
1759
|
+
*
|
|
1760
|
+
* @param codeOrPath - For Lambda runtime, this is the term hash to operate on
|
|
1761
|
+
* @param context - Additional context (varies by operation)
|
|
1762
|
+
* @param config - Lambda configuration with operation type
|
|
1763
|
+
* @param chapterDir - Chapter directory (used for relative paths if needed)
|
|
1764
|
+
*/
|
|
1765
|
+
async execute(codeOrPath, context, config, chapterDir) {
|
|
1766
|
+
let termHash = codeOrPath;
|
|
1767
|
+
const ctx = context && typeof context === "object" ? context : {};
|
|
1768
|
+
const operation = ctx.operation || config.process || config.action || config.operation || "normalize";
|
|
1769
|
+
const lambdaConfig = { ...config, operation };
|
|
1770
|
+
const strategy = lambdaConfig.strategy || ctx.strategy || "normal";
|
|
1771
|
+
const maxSteps = lambdaConfig.maxSteps || ctx.maxSteps || ctx.max_steps || 1e3;
|
|
1772
|
+
const maxTimeMs = lambdaConfig.maxTimeMs || ctx.maxTimeMs || ctx.max_time_ms || ctx.timeoutMs || ctx.timeout_ms;
|
|
1773
|
+
if (termHash && (termHash.includes("\\") || termHash.includes("\u03BB") || termHash.includes(" ") || termHash.includes("("))) {
|
|
1774
|
+
try {
|
|
1775
|
+
termHash = await parseLambdaExpression(this.collection, termHash);
|
|
1776
|
+
} catch (e) {
|
|
1777
|
+
return {
|
|
1778
|
+
success: false,
|
|
1779
|
+
error: `Failed to parse input expression: ${e instanceof Error ? e.message : String(e)}`
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
if (operation === "check-readiness") {
|
|
1784
|
+
return this.doCheckReadiness(ctx);
|
|
1785
|
+
}
|
|
1786
|
+
if (operation.startsWith("num-")) {
|
|
1787
|
+
return this.doNumericOp(operation, ctx);
|
|
1788
|
+
}
|
|
1789
|
+
if (operation === "http-request") {
|
|
1790
|
+
return this.doHttpRequest(ctx);
|
|
1791
|
+
}
|
|
1792
|
+
if (!termHash || termHash === "lambda-op" || termHash === "builtin") {
|
|
1793
|
+
const expression = ctx.expression || ctx.term;
|
|
1794
|
+
if (typeof expression === "string") {
|
|
1795
|
+
try {
|
|
1796
|
+
termHash = await parseLambdaExpression(this.collection, expression);
|
|
1797
|
+
} catch (e) {
|
|
1798
|
+
return { success: false, error: `Invalid expression in context: ${e}` };
|
|
1799
|
+
}
|
|
1800
|
+
} else if (expression) {
|
|
1801
|
+
return { success: false, error: "Expression in context must be a string" };
|
|
1802
|
+
} else if (operation !== "parse" && operation !== "build") {
|
|
1803
|
+
return { success: false, error: `Lambda operation ${operation} requires a term or expression` };
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
try {
|
|
1807
|
+
switch (operation) {
|
|
1808
|
+
case "alpha":
|
|
1809
|
+
return this.doAlphaRename(termHash, lambdaConfig, ctx);
|
|
1810
|
+
case "beta":
|
|
1811
|
+
return this.doBetaReduce(termHash);
|
|
1812
|
+
case "eta-reduce":
|
|
1813
|
+
return this.doEtaReduce(termHash);
|
|
1814
|
+
case "eta-expand":
|
|
1815
|
+
return this.doEtaExpand(termHash, lambdaConfig, ctx);
|
|
1816
|
+
case "normalize":
|
|
1817
|
+
return this.doNormalize(termHash, { ...lambdaConfig, strategy, maxSteps, maxTimeMs });
|
|
1818
|
+
case "step":
|
|
1819
|
+
return this.doStep(termHash, { ...lambdaConfig, strategy });
|
|
1820
|
+
case "alpha-equiv":
|
|
1821
|
+
return this.doAlphaEquiv(termHash, lambdaConfig, ctx);
|
|
1822
|
+
case "eta-equiv":
|
|
1823
|
+
return this.doEtaEquiv(termHash, lambdaConfig, ctx);
|
|
1824
|
+
case "alpha-norm":
|
|
1825
|
+
return this.doAlphaNormalize(termHash);
|
|
1826
|
+
case "eta-norm":
|
|
1827
|
+
return this.doEtaNormalize(termHash);
|
|
1828
|
+
case "free-vars":
|
|
1829
|
+
return this.doFreeVars(termHash);
|
|
1830
|
+
case "is-closed":
|
|
1831
|
+
return this.doIsClosed(termHash);
|
|
1832
|
+
case "is-normal":
|
|
1833
|
+
return this.doIsNormal(termHash);
|
|
1834
|
+
case "parse":
|
|
1835
|
+
return this.doParse(ctx);
|
|
1836
|
+
case "pretty":
|
|
1837
|
+
return this.doPretty(termHash);
|
|
1838
|
+
case "build":
|
|
1839
|
+
return this.doBuild(ctx);
|
|
1840
|
+
case "church-to-int":
|
|
1841
|
+
return this.doChurchToInt(termHash);
|
|
1842
|
+
default:
|
|
1843
|
+
return {
|
|
1844
|
+
success: false,
|
|
1845
|
+
error: `Unknown Lambda operation: ${operation}`
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
} catch (err) {
|
|
1849
|
+
return {
|
|
1850
|
+
success: false,
|
|
1851
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1856
|
+
// Operation Implementations
|
|
1857
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1858
|
+
async doAlphaRename(termHash, config, ctx) {
|
|
1859
|
+
const newName = config.newName || ctx.newName;
|
|
1860
|
+
if (!newName) {
|
|
1861
|
+
return { success: false, error: "Alpha rename requires newName parameter" };
|
|
1862
|
+
}
|
|
1863
|
+
const result = await alphaRename(this.collection, termHash, newName).run();
|
|
1864
|
+
if (result.isLeft) {
|
|
1865
|
+
return { success: false, error: result.left };
|
|
1866
|
+
}
|
|
1867
|
+
const pretty = await prettyPrintDeep(this.collection, result.right);
|
|
1868
|
+
return {
|
|
1869
|
+
success: true,
|
|
1870
|
+
result: result.right,
|
|
1871
|
+
termHash: result.right,
|
|
1872
|
+
prettyPrint: pretty
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
async doBetaReduce(termHash) {
|
|
1876
|
+
const result = await betaReduce(this.collection, termHash).run();
|
|
1877
|
+
if (result.isLeft) {
|
|
1878
|
+
return { success: false, error: result.left };
|
|
1879
|
+
}
|
|
1880
|
+
const pretty = await prettyPrintDeep(this.collection, result.right);
|
|
1881
|
+
return {
|
|
1882
|
+
success: true,
|
|
1883
|
+
result: result.right,
|
|
1884
|
+
termHash: result.right,
|
|
1885
|
+
prettyPrint: pretty
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
async doEtaReduce(termHash) {
|
|
1889
|
+
const result = await etaReduce(this.collection, termHash).run();
|
|
1890
|
+
if (result.isNothing) {
|
|
1891
|
+
return { success: false, error: "Not an \u03B7-redex" };
|
|
1892
|
+
}
|
|
1893
|
+
const pretty = await prettyPrintDeep(this.collection, result.value);
|
|
1894
|
+
return {
|
|
1895
|
+
success: true,
|
|
1896
|
+
result: result.value,
|
|
1897
|
+
termHash: result.value,
|
|
1898
|
+
prettyPrint: pretty
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
async doEtaExpand(termHash, config, ctx) {
|
|
1902
|
+
const freshVar = config.freshVar || ctx.freshVar || "x";
|
|
1903
|
+
const result = await etaExpand(this.collection, termHash, freshVar).run();
|
|
1904
|
+
const pretty = await prettyPrintDeep(this.collection, result);
|
|
1905
|
+
return {
|
|
1906
|
+
success: true,
|
|
1907
|
+
result,
|
|
1908
|
+
termHash: result,
|
|
1909
|
+
prettyPrint: pretty
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
async doNormalize(termHash, config) {
|
|
1913
|
+
const strategy = config.strategy || "normal";
|
|
1914
|
+
const maxSteps = config.maxSteps || 1e3;
|
|
1915
|
+
const maxTimeMs = config.maxTimeMs;
|
|
1916
|
+
const ioHandler = createIOHandler(config);
|
|
1917
|
+
const onStep = ioHandler.isEnabled() ? async (step, hash) => {
|
|
1918
|
+
const pretty2 = await prettyPrintDeep(this.collection, hash);
|
|
1919
|
+
await ioHandler.emitStep(step, hash, pretty2);
|
|
1920
|
+
} : void 0;
|
|
1921
|
+
const result = await normalize(
|
|
1922
|
+
this.collection,
|
|
1923
|
+
termHash,
|
|
1924
|
+
strategy,
|
|
1925
|
+
maxSteps,
|
|
1926
|
+
maxTimeMs,
|
|
1927
|
+
onStep
|
|
1928
|
+
).run();
|
|
1929
|
+
if (result.isLeft) {
|
|
1930
|
+
if (ioHandler.isEnabled()) {
|
|
1931
|
+
await ioHandler.emitError(result.left, 0);
|
|
1932
|
+
}
|
|
1933
|
+
return { success: false, error: result.left };
|
|
1934
|
+
}
|
|
1935
|
+
const normResult = result.right;
|
|
1936
|
+
const pretty = await prettyPrintDeep(this.collection, normResult.normalForm);
|
|
1937
|
+
if (ioHandler.isEnabled()) {
|
|
1938
|
+
await ioHandler.emitComplete(
|
|
1939
|
+
normResult.normalForm,
|
|
1940
|
+
pretty,
|
|
1941
|
+
normResult.steps,
|
|
1942
|
+
normResult.reductionPath
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
return {
|
|
1946
|
+
success: true,
|
|
1947
|
+
result: {
|
|
1948
|
+
normalForm: normResult.normalForm,
|
|
1949
|
+
steps: normResult.steps,
|
|
1950
|
+
reductionPath: normResult.reductionPath,
|
|
1951
|
+
ioEvents: ioHandler.getEvents()
|
|
1952
|
+
// Include IO events in result
|
|
1953
|
+
},
|
|
1954
|
+
termHash: normResult.normalForm,
|
|
1955
|
+
prettyPrint: pretty
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
async doStep(termHash, config) {
|
|
1959
|
+
const strategy = config.strategy || "normal";
|
|
1960
|
+
const result = await reduceStep(this.collection, termHash, strategy).run();
|
|
1961
|
+
if (result.isNothing) {
|
|
1962
|
+
return {
|
|
1963
|
+
success: true,
|
|
1964
|
+
result: { alreadyNormal: true },
|
|
1965
|
+
termHash,
|
|
1966
|
+
prettyPrint: await prettyPrintDeep(this.collection, termHash)
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
const pretty = await prettyPrintDeep(this.collection, result.value);
|
|
1970
|
+
return {
|
|
1971
|
+
success: true,
|
|
1972
|
+
result: result.value,
|
|
1973
|
+
termHash: result.value,
|
|
1974
|
+
prettyPrint: pretty
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
async doAlphaEquiv(termHash, config, ctx) {
|
|
1978
|
+
const compareWith = config.compareWith || ctx.compareWith;
|
|
1979
|
+
if (!compareWith) {
|
|
1980
|
+
return { success: false, error: "Alpha equivalence check requires compareWith parameter" };
|
|
1981
|
+
}
|
|
1982
|
+
const result = await alphaEquivalent(this.collection, termHash, compareWith).run();
|
|
1983
|
+
if (result.isLeft) {
|
|
1984
|
+
return { success: false, error: result.left };
|
|
1985
|
+
}
|
|
1986
|
+
return {
|
|
1987
|
+
success: true,
|
|
1988
|
+
result: { equivalent: result.right }
|
|
1989
|
+
};
|
|
1990
|
+
}
|
|
1991
|
+
async doEtaEquiv(termHash, config, ctx) {
|
|
1992
|
+
const compareWith = config.compareWith || ctx.compareWith;
|
|
1993
|
+
if (!compareWith) {
|
|
1994
|
+
return { success: false, error: "Eta equivalence check requires compareWith parameter" };
|
|
1995
|
+
}
|
|
1996
|
+
const result = await etaEquivalent(this.collection, termHash, compareWith).run();
|
|
1997
|
+
return {
|
|
1998
|
+
success: true,
|
|
1999
|
+
result: { equivalent: result }
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
async doAlphaNormalize(termHash) {
|
|
2003
|
+
const result = await alphaNormalize(this.collection, termHash).run();
|
|
2004
|
+
if (result.isLeft) {
|
|
2005
|
+
return { success: false, error: result.left };
|
|
2006
|
+
}
|
|
2007
|
+
const pretty = await prettyPrintDeep(this.collection, result.right);
|
|
2008
|
+
return {
|
|
2009
|
+
success: true,
|
|
2010
|
+
result: result.right,
|
|
2011
|
+
termHash: result.right,
|
|
2012
|
+
prettyPrint: pretty
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
async doEtaNormalize(termHash) {
|
|
2016
|
+
const result = await etaNormalize(this.collection, termHash).run();
|
|
2017
|
+
const pretty = await prettyPrintDeep(this.collection, result);
|
|
2018
|
+
return {
|
|
2019
|
+
success: true,
|
|
2020
|
+
result,
|
|
2021
|
+
termHash: result,
|
|
2022
|
+
prettyPrint: pretty
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
async doFreeVars(termHash) {
|
|
2026
|
+
const result = await freeVariables(this.collection, termHash).run();
|
|
2027
|
+
if (result.isNothing) {
|
|
2028
|
+
return { success: false, error: `Term not found: ${termHash}` };
|
|
2029
|
+
}
|
|
2030
|
+
return {
|
|
2031
|
+
success: true,
|
|
2032
|
+
result: { freeVariables: Array.from(result.value) }
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
async doIsClosed(termHash) {
|
|
2036
|
+
const result = await isClosed(this.collection, termHash).run();
|
|
2037
|
+
return {
|
|
2038
|
+
success: true,
|
|
2039
|
+
result: { closed: result }
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
async doIsNormal(termHash) {
|
|
2043
|
+
const result = await isNormalForm(this.collection, termHash).run();
|
|
2044
|
+
return {
|
|
2045
|
+
success: true,
|
|
2046
|
+
result: { normalForm: result }
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
async doParse(ctx) {
|
|
2050
|
+
const expression = ctx.expression;
|
|
2051
|
+
if (!expression) {
|
|
2052
|
+
return { success: false, error: "Parse requires expression parameter" };
|
|
2053
|
+
}
|
|
2054
|
+
try {
|
|
2055
|
+
const hash = await parseLambdaExpression(this.collection, expression);
|
|
2056
|
+
const pretty = await prettyPrintDeep(this.collection, hash);
|
|
2057
|
+
return {
|
|
2058
|
+
success: true,
|
|
2059
|
+
result: hash,
|
|
2060
|
+
termHash: hash,
|
|
2061
|
+
prettyPrint: pretty
|
|
2062
|
+
};
|
|
2063
|
+
} catch (err) {
|
|
2064
|
+
return {
|
|
2065
|
+
success: false,
|
|
2066
|
+
error: `Parse error: ${err instanceof Error ? err.message : String(err)}`
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
async doPretty(termHash) {
|
|
2071
|
+
const pretty = await prettyPrintDeep(this.collection, termHash);
|
|
2072
|
+
return {
|
|
2073
|
+
success: true,
|
|
2074
|
+
result: pretty,
|
|
2075
|
+
prettyPrint: pretty
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
async doBuild(ctx) {
|
|
2079
|
+
const spec = ctx.term;
|
|
2080
|
+
if (!spec) {
|
|
2081
|
+
return { success: false, error: "Build requires term specification" };
|
|
2082
|
+
}
|
|
2083
|
+
const hash = await storeTerm(this.collection, spec);
|
|
2084
|
+
const pretty = await prettyPrintDeep(this.collection, hash);
|
|
2085
|
+
return {
|
|
2086
|
+
success: true,
|
|
2087
|
+
result: hash,
|
|
2088
|
+
termHash: hash,
|
|
2089
|
+
prettyPrint: pretty
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Decode a Church numeral to a regular integer.
|
|
2094
|
+
* Church numeral n = λf.λx.f^n(x) where f is applied n times.
|
|
2095
|
+
*
|
|
2096
|
+
* Algorithm:
|
|
2097
|
+
* 1. Normalize the term first
|
|
2098
|
+
* 2. Expect form: Abs(f, Abs(x, body))
|
|
2099
|
+
* 3. Count how many times 'f' appears in application position in body
|
|
2100
|
+
*/
|
|
2101
|
+
async doChurchToInt(termHash) {
|
|
2102
|
+
try {
|
|
2103
|
+
const result = await normalize(this.collection, termHash, "normal", 1e3).run();
|
|
2104
|
+
if (result.isLeft) {
|
|
2105
|
+
return { success: false, error: result.left };
|
|
2106
|
+
}
|
|
2107
|
+
const normResult = result.right;
|
|
2108
|
+
const pretty = await prettyPrintDeep(this.collection, normResult.normalForm);
|
|
2109
|
+
const count = await this.countChurchApplications(normResult.normalForm);
|
|
2110
|
+
return {
|
|
2111
|
+
success: true,
|
|
2112
|
+
result: count,
|
|
2113
|
+
prettyPrint: `${count} (Church: ${pretty})`
|
|
2114
|
+
};
|
|
2115
|
+
} catch (err) {
|
|
2116
|
+
return {
|
|
2117
|
+
success: false,
|
|
2118
|
+
error: `Church decode error: ${err instanceof Error ? err.message : String(err)}`
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
/**
|
|
2123
|
+
* Count applications in a Church numeral body.
|
|
2124
|
+
* Church numeral n has the form: λf.λx.f(f(f(...f(x)...)))
|
|
2125
|
+
* where f appears n times.
|
|
2126
|
+
*/
|
|
2127
|
+
async countChurchApplications(termHash) {
|
|
2128
|
+
const term = await loadTerm(this.collection, termHash);
|
|
2129
|
+
if (!term) return -1;
|
|
2130
|
+
if (term.tag !== "Abs") return -1;
|
|
2131
|
+
const fVar = term.param;
|
|
2132
|
+
const innerTerm = await loadTerm(this.collection, term.body);
|
|
2133
|
+
if (!innerTerm || innerTerm.tag !== "Abs") return -1;
|
|
2134
|
+
const xVar = innerTerm.param;
|
|
2135
|
+
let count = 0;
|
|
2136
|
+
let currentHash = innerTerm.body;
|
|
2137
|
+
while (true) {
|
|
2138
|
+
const body = await loadTerm(this.collection, currentHash);
|
|
2139
|
+
if (!body) break;
|
|
2140
|
+
if (body.tag === "App") {
|
|
2141
|
+
const func = await loadTerm(this.collection, body.func);
|
|
2142
|
+
if (func && func.tag === "Var" && func.name === fVar) {
|
|
2143
|
+
count++;
|
|
2144
|
+
currentHash = body.arg;
|
|
2145
|
+
} else {
|
|
2146
|
+
break;
|
|
2147
|
+
}
|
|
2148
|
+
} else if (body.tag === "Var" && body.name === xVar) {
|
|
2149
|
+
return count;
|
|
2150
|
+
} else {
|
|
2151
|
+
break;
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
return count;
|
|
2155
|
+
}
|
|
2156
|
+
async doCheckReadiness(ctx) {
|
|
2157
|
+
const runtimeName = ctx.runtime_name || "lambda";
|
|
2158
|
+
let available = false;
|
|
2159
|
+
try {
|
|
2160
|
+
const status = await RuntimeFactory.getAvailableRuntimes();
|
|
2161
|
+
available = !!status[runtimeName] || runtimeName === "lambda";
|
|
2162
|
+
} catch (e) {
|
|
2163
|
+
available = runtimeName === "lambda" || runtimeName === "javascript";
|
|
2164
|
+
}
|
|
2165
|
+
return {
|
|
2166
|
+
success: true,
|
|
2167
|
+
result: `Runtime ${runtimeName} status: ${available ? "True" : "False"}`
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
async doNumericOp(op, ctx) {
|
|
2171
|
+
const a = Number(ctx.a ?? 0);
|
|
2172
|
+
const b = Number(ctx.b ?? 0);
|
|
2173
|
+
let res = 0;
|
|
2174
|
+
switch (op) {
|
|
2175
|
+
case "num-add":
|
|
2176
|
+
res = a + b;
|
|
2177
|
+
break;
|
|
2178
|
+
case "num-sub":
|
|
2179
|
+
res = a - b;
|
|
2180
|
+
break;
|
|
2181
|
+
case "num-mul":
|
|
2182
|
+
res = a * b;
|
|
2183
|
+
break;
|
|
2184
|
+
case "num-div":
|
|
2185
|
+
res = b !== 0 ? a / b : 0;
|
|
2186
|
+
break;
|
|
2187
|
+
default:
|
|
2188
|
+
return { success: false, error: `Unknown numeric op: ${op}` };
|
|
2189
|
+
}
|
|
2190
|
+
return {
|
|
2191
|
+
success: true,
|
|
2192
|
+
result: String(res)
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
async doHttpRequest(ctx) {
|
|
2196
|
+
const url = ctx.url;
|
|
2197
|
+
if (!url) return { success: false, error: "http-request requires 'url'" };
|
|
2198
|
+
try {
|
|
2199
|
+
const response = await fetch(url);
|
|
2200
|
+
const text = await response.text();
|
|
2201
|
+
return {
|
|
2202
|
+
success: true,
|
|
2203
|
+
result: text.substring(0, 1e3)
|
|
2204
|
+
// Truncate for safety
|
|
2205
|
+
};
|
|
2206
|
+
} catch (e) {
|
|
2207
|
+
return { success: false, error: `HTTP Request failed: ${e}` };
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
};
|
|
2211
|
+
async function parseLambdaExpression(collection, expression) {
|
|
2212
|
+
const tokens = tokenize(expression);
|
|
2213
|
+
let pos = 0;
|
|
2214
|
+
function peek() {
|
|
2215
|
+
return pos < tokens.length ? tokens[pos] : null;
|
|
2216
|
+
}
|
|
2217
|
+
function consume() {
|
|
2218
|
+
if (pos >= tokens.length) throw new Error("Unexpected end of expression");
|
|
2219
|
+
return tokens[pos++];
|
|
2220
|
+
}
|
|
2221
|
+
function expect(token) {
|
|
2222
|
+
const actual = consume();
|
|
2223
|
+
if (actual !== token) {
|
|
2224
|
+
throw new Error(`Expected '${token}', got '${actual}'`);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
async function parseExpr() {
|
|
2228
|
+
const terms = [];
|
|
2229
|
+
while (peek() && peek() !== ")") {
|
|
2230
|
+
terms.push(await parseTerm());
|
|
2231
|
+
}
|
|
2232
|
+
if (terms.length === 0) {
|
|
2233
|
+
throw new Error("Empty expression");
|
|
2234
|
+
}
|
|
2235
|
+
let result = terms[0];
|
|
2236
|
+
for (let i = 1; i < terms.length; i++) {
|
|
2237
|
+
const app = mkApp(result, terms[i]);
|
|
2238
|
+
result = await storeTerm(collection, app);
|
|
2239
|
+
}
|
|
2240
|
+
return result;
|
|
2241
|
+
}
|
|
2242
|
+
async function parseTerm() {
|
|
2243
|
+
const token = peek();
|
|
2244
|
+
if (token === "\\" || token === "\u03BB") {
|
|
2245
|
+
return parseAbstraction();
|
|
2246
|
+
}
|
|
2247
|
+
if (token === "(") {
|
|
2248
|
+
consume();
|
|
2249
|
+
const expr = await parseExpr();
|
|
2250
|
+
expect(")");
|
|
2251
|
+
return expr;
|
|
2252
|
+
}
|
|
2253
|
+
const name = consume();
|
|
2254
|
+
if (!name.startsWith('"') && !name.match(/^[a-zA-Z_][a-zA-Z0-9_']*$/)) {
|
|
2255
|
+
throw new Error(`Invalid variable name: ${name}`);
|
|
2256
|
+
}
|
|
2257
|
+
const varTerm = mkVar(name);
|
|
2258
|
+
return storeTerm(collection, varTerm);
|
|
2259
|
+
}
|
|
2260
|
+
async function parseAbstraction() {
|
|
2261
|
+
consume();
|
|
2262
|
+
const param = consume();
|
|
2263
|
+
if (!param.match(/^[a-zA-Z_][a-zA-Z0-9_']*$/)) {
|
|
2264
|
+
throw new Error(`Invalid parameter name: ${param}`);
|
|
2265
|
+
}
|
|
2266
|
+
expect(".");
|
|
2267
|
+
const body = await parseExpr();
|
|
2268
|
+
const abs = mkAbs(param, body);
|
|
2269
|
+
return storeTerm(collection, abs);
|
|
2270
|
+
}
|
|
2271
|
+
return parseExpr();
|
|
2272
|
+
}
|
|
2273
|
+
function tokenize(expression) {
|
|
2274
|
+
const tokens = [];
|
|
2275
|
+
let i = 0;
|
|
2276
|
+
while (i < expression.length) {
|
|
2277
|
+
const ch = expression[i];
|
|
2278
|
+
if (/\s/.test(ch)) {
|
|
2279
|
+
i++;
|
|
2280
|
+
continue;
|
|
2281
|
+
}
|
|
2282
|
+
if ("()\\\u03BB.".includes(ch)) {
|
|
2283
|
+
tokens.push(ch);
|
|
2284
|
+
i++;
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
if (ch === '"') {
|
|
2288
|
+
let str = '"';
|
|
2289
|
+
i++;
|
|
2290
|
+
while (i < expression.length && expression[i] !== '"') {
|
|
2291
|
+
str += expression[i++];
|
|
2292
|
+
}
|
|
2293
|
+
if (i < expression.length) {
|
|
2294
|
+
str += '"';
|
|
2295
|
+
i++;
|
|
2296
|
+
} else {
|
|
2297
|
+
throw new Error("Unterminated string literal");
|
|
2298
|
+
}
|
|
2299
|
+
tokens.push(str);
|
|
2300
|
+
continue;
|
|
2301
|
+
}
|
|
2302
|
+
if (/[a-zA-Z_]/.test(ch)) {
|
|
2303
|
+
let name = "";
|
|
2304
|
+
while (i < expression.length && /[a-zA-Z0-9_']/.test(expression[i])) {
|
|
2305
|
+
name += expression[i++];
|
|
2306
|
+
}
|
|
2307
|
+
tokens.push(name);
|
|
2308
|
+
continue;
|
|
2309
|
+
}
|
|
2310
|
+
throw new Error(`Unexpected character: ${ch}`);
|
|
2311
|
+
}
|
|
2312
|
+
return tokens;
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
export {
|
|
2316
|
+
mkVar,
|
|
2317
|
+
mkAbs,
|
|
2318
|
+
mkApp,
|
|
2319
|
+
serializeTerm,
|
|
2320
|
+
deserializeTerm,
|
|
2321
|
+
termToMCard,
|
|
2322
|
+
mcardToTerm,
|
|
2323
|
+
storeTerm,
|
|
2324
|
+
loadTerm,
|
|
2325
|
+
termExists,
|
|
2326
|
+
prettyPrintShallow,
|
|
2327
|
+
prettyPrintDeep,
|
|
2328
|
+
isVar,
|
|
2329
|
+
isAbs,
|
|
2330
|
+
isApp,
|
|
2331
|
+
freeVariables,
|
|
2332
|
+
boundVariables,
|
|
2333
|
+
isFreeIn,
|
|
2334
|
+
isClosed,
|
|
2335
|
+
generateFresh,
|
|
2336
|
+
generateFreshFor,
|
|
2337
|
+
difference,
|
|
2338
|
+
intersection,
|
|
2339
|
+
alphaRename,
|
|
2340
|
+
alphaEquivalent,
|
|
2341
|
+
alphaNormalize,
|
|
2342
|
+
isRedex,
|
|
2343
|
+
findLeftmostRedex,
|
|
2344
|
+
findInnermostRedex,
|
|
2345
|
+
betaReduce,
|
|
2346
|
+
reduceStep,
|
|
2347
|
+
normalize,
|
|
2348
|
+
isNormalForm,
|
|
2349
|
+
hasNormalForm,
|
|
2350
|
+
isEtaRedex,
|
|
2351
|
+
etaReduce,
|
|
2352
|
+
etaReduceE,
|
|
2353
|
+
etaExpand,
|
|
2354
|
+
etaEquivalent,
|
|
2355
|
+
etaNormalize,
|
|
2356
|
+
findEtaRedexes,
|
|
2357
|
+
RuntimeFactory,
|
|
2358
|
+
LambdaRuntime,
|
|
2359
|
+
parseLambdaExpression
|
|
2360
|
+
};
|