@mettascript/node 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/check-TCNAUG5Q.js +13 -0
- package/dist/chunk-CWWQ3S7I.js +142 -0
- package/dist/chunk-DHXPSARV.js +233 -0
- package/dist/chunk-ELBXIFJR.js +126 -0
- package/dist/chunk-L4U6ZIAR.js +24 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +11 -0
- package/dist/debug-cli.d.ts +1 -0
- package/dist/debug-cli.js +15 -0
- package/dist/debug-main-Y254S5BG.js +8 -0
- package/dist/graph-main-WRL2UUTS.js +51 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +160 -0
- package/dist/metta-cli.d.ts +1 -0
- package/dist/metta-cli.js +67 -0
- package/dist/run-main-MK6LUIIS.js +7 -0
- package/dist/source.d.ts +17 -0
- package/dist/source.js +12 -0
- package/package.json +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MesTTo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @mettascript/node
|
|
2
|
+
|
|
3
|
+
Node.js entry for [MeTTaScript](https://github.com/MesTTo/MeTTaScript): the `metta` command-line interface, file-based `import!`, and a `SharedArrayBuffer` worker-thread parallel matcher. Re-exports everything from [`@mettascript/core`](https://github.com/MesTTo/MeTTaScript/tree/main/packages/core).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @mettascript/node
|
|
9
|
+
# for the CLI on your PATH:
|
|
10
|
+
npm install -g @mettascript/node
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## CLI
|
|
14
|
+
|
|
15
|
+
`metta` is a single command with subcommands:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
metta run program.metta # run a program (metta program.metta is shorthand)
|
|
19
|
+
metta check program.metta # static analysis (--json, --undefined-symbols)
|
|
20
|
+
metta debug --file program.metta why '(main)' # engine debugger (why/eval/run)
|
|
21
|
+
metta graph program.metta -o out.gif # render the reduction as an animated GIF
|
|
22
|
+
metta --version
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Without a global install: `npx -p @mettascript/node metta run program.metta`.
|
|
26
|
+
|
|
27
|
+
Host runtimes are explicit and lazy:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
metta run --py program.metta # Python through pythonia
|
|
31
|
+
metta run --prolog program.metta # Prolog through a local swipl executable
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Without those flags the CLI never loads Python, Prolog, or their optional
|
|
35
|
+
dependencies. `metta graph` similarly loads [`@mettascript/grapher`](https://github.com/MesTTo/MeTTaScript/tree/main/packages/grapher)
|
|
36
|
+
and its renderers only when invoked; install them with `npm install @mettascript/grapher gifenc sharp`.
|
|
37
|
+
|
|
38
|
+
The earlier `metta-ts` (run) and `metta-debug` (debug) commands remain as aliases,
|
|
39
|
+
so existing scripts keep working.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { runFile, ParallelFlatMatcher } from "@mettascript/node";
|
|
45
|
+
|
|
46
|
+
// Run a .metta file (resolves import! against the file system).
|
|
47
|
+
for (const { query, results } of runFile("program.metta")) {
|
|
48
|
+
console.log(query, results);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`ParallelFlatMatcher` scans a large flat knowledge base across `worker_threads` over a shared token buffer. It pays off only for a large KB scanned by a non-selective query whose result set is small; a keyed query is already near-constant-time via the in-memory argument index.
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
[MIT](https://github.com/MesTTo/MeTTaScript/blob/main/LICENSE).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/check.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { analyzeSource, renderAll, DiagnosticSeverity } from "@mettascript/core";
|
|
4
|
+
function checkFile(file, opts) {
|
|
5
|
+
const src = readFileSync(file, "utf8");
|
|
6
|
+
const diags = analyzeSource(src, { undefinedSymbols: opts.undefinedSymbols });
|
|
7
|
+
const hasError = diags.some((d) => d.severity === DiagnosticSeverity.Error);
|
|
8
|
+
const text = opts.json ? JSON.stringify(diags, null, 2) : diags.length > 0 ? renderAll(src, file, diags) : "";
|
|
9
|
+
return { text, exitCode: hasError ? 1 : 0 };
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
checkFile
|
|
13
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// src/source.ts
|
|
2
|
+
import "@mettascript/libraries";
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_FUEL,
|
|
5
|
+
evalSequential,
|
|
6
|
+
evalSequentialAllDirectives,
|
|
7
|
+
parseAll,
|
|
8
|
+
runProgramAsync,
|
|
9
|
+
standardTokenizer
|
|
10
|
+
} from "@mettascript/core";
|
|
11
|
+
|
|
12
|
+
// src/par-hyperpose.ts
|
|
13
|
+
import { Worker } from "worker_threads";
|
|
14
|
+
import { createRequire } from "module";
|
|
15
|
+
var WORKER_SRC = `
|
|
16
|
+
const { workerData } = require("node:worker_threads");
|
|
17
|
+
const { corePath, rulesSrc, branchSrc, sab, base, cap, fuel, hostEffects } = workerData;
|
|
18
|
+
const view = new Int32Array(sab);
|
|
19
|
+
(async () => {
|
|
20
|
+
try {
|
|
21
|
+
const m = await import(corePath);
|
|
22
|
+
if (hostEffects === false && typeof m.setHostEffectsEnabled === "function") {
|
|
23
|
+
m.setHostEffectsEnabled(false);
|
|
24
|
+
}
|
|
25
|
+
const r = m.runProgram(rulesSrc + "\\n!" + branchSrc, fuel);
|
|
26
|
+
const last = r[r.length - 1];
|
|
27
|
+
const out = JSON.stringify((last && last.results ? last.results : []).map((a) => m.format(a)));
|
|
28
|
+
if (out.length > cap) { Atomics.store(view, base, -1); }
|
|
29
|
+
else {
|
|
30
|
+
for (let i = 0; i < out.length; i++) view[base + 2 + i] = out.charCodeAt(i);
|
|
31
|
+
Atomics.store(view, base + 1, out.length);
|
|
32
|
+
Atomics.store(view, base, 1);
|
|
33
|
+
}
|
|
34
|
+
} catch (e) {
|
|
35
|
+
Atomics.store(view, base, -1); // import failure, fuel exhaustion, anything: this branch bailed
|
|
36
|
+
} finally {
|
|
37
|
+
// Always signal completion, even on a caught failure, so the main thread's Atomics.wait cannot block
|
|
38
|
+
// forever waiting for a branch that already errored.
|
|
39
|
+
Atomics.add(view, 0, 1);
|
|
40
|
+
Atomics.notify(view, 0);
|
|
41
|
+
}
|
|
42
|
+
})();
|
|
43
|
+
`;
|
|
44
|
+
var CAP = 16384;
|
|
45
|
+
function evalBranchesParallel(corePath, rulesSrc, branchSrcs, firstOnly, fuel, options = {}) {
|
|
46
|
+
const n = branchSrcs.length;
|
|
47
|
+
const region = 2 + CAP;
|
|
48
|
+
const sab = new SharedArrayBuffer((1 + n * region) * 4);
|
|
49
|
+
const view = new Int32Array(sab);
|
|
50
|
+
const workers = branchSrcs.map(
|
|
51
|
+
(br, i) => new Worker(WORKER_SRC, {
|
|
52
|
+
eval: true,
|
|
53
|
+
workerData: {
|
|
54
|
+
corePath,
|
|
55
|
+
rulesSrc,
|
|
56
|
+
branchSrc: br,
|
|
57
|
+
sab,
|
|
58
|
+
base: 1 + i * region,
|
|
59
|
+
cap: CAP,
|
|
60
|
+
fuel,
|
|
61
|
+
hostEffects: options.hostEffects
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
const out = new Array(n).fill(null);
|
|
66
|
+
const read = (i) => {
|
|
67
|
+
const base = 1 + i * region;
|
|
68
|
+
const status = Atomics.load(view, base);
|
|
69
|
+
if (status === 0) return void 0;
|
|
70
|
+
if (status === -1) return null;
|
|
71
|
+
const len = Atomics.load(view, base + 1);
|
|
72
|
+
let s = "";
|
|
73
|
+
for (let j = 0; j < len; j++) s += String.fromCharCode(view[base + 2 + j]);
|
|
74
|
+
return JSON.parse(s);
|
|
75
|
+
};
|
|
76
|
+
const WAIT_MS = 6e4;
|
|
77
|
+
let done = false;
|
|
78
|
+
while (!done) {
|
|
79
|
+
const prev = Atomics.load(view, 0);
|
|
80
|
+
for (let i = 0; i < n; i++) {
|
|
81
|
+
if (out[i] !== null) continue;
|
|
82
|
+
const r = read(i);
|
|
83
|
+
if (r === void 0) continue;
|
|
84
|
+
out[i] = r;
|
|
85
|
+
if (firstOnly && r !== null && r.length > 0) {
|
|
86
|
+
done = true;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (Atomics.load(view, 0) >= n) done = true;
|
|
91
|
+
if (!done && Atomics.wait(view, 0, prev, WAIT_MS) === "timed-out" && Atomics.load(view, 0) === prev)
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
for (const w of workers) void w.terminate();
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
function makeParEvalImpl(fuel, options = {}) {
|
|
98
|
+
const corePath = createRequire(import.meta.url).resolve("@mettascript/core");
|
|
99
|
+
return (rulesSrc, branchSrcs, firstOnly) => evalBranchesParallel(corePath, rulesSrc, branchSrcs, firstOnly, fuel, options);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/source.ts
|
|
103
|
+
function withDefaultParallelism(fuel, opts, parOptions) {
|
|
104
|
+
const base = opts ?? {};
|
|
105
|
+
return {
|
|
106
|
+
...base,
|
|
107
|
+
tabling: base.tabling ?? true,
|
|
108
|
+
parEvalImpl: base.parEvalImpl ?? makeParEvalImpl(fuel, parOptions)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function runSource(src, fuel = DEFAULT_FUEL, imports = /* @__PURE__ */ new Map(), opts) {
|
|
112
|
+
return evalSequential(
|
|
113
|
+
parseAll(src, standardTokenizer()),
|
|
114
|
+
fuel,
|
|
115
|
+
imports,
|
|
116
|
+
withDefaultParallelism(fuel, opts)
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
function runSourceAllDirectives(src, fuel = DEFAULT_FUEL, imports = /* @__PURE__ */ new Map(), opts) {
|
|
120
|
+
return evalSequentialAllDirectives(
|
|
121
|
+
parseAll(src, standardTokenizer()),
|
|
122
|
+
fuel,
|
|
123
|
+
imports,
|
|
124
|
+
withDefaultParallelism(fuel, opts)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
function runSourceAsync(src, asyncOps = /* @__PURE__ */ new Map(), fuel = DEFAULT_FUEL, imports = /* @__PURE__ */ new Map(), opts, parOptions) {
|
|
128
|
+
return runProgramAsync(
|
|
129
|
+
src,
|
|
130
|
+
asyncOps,
|
|
131
|
+
fuel,
|
|
132
|
+
imports,
|
|
133
|
+
withDefaultParallelism(fuel, opts, parOptions)
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
makeParEvalImpl,
|
|
139
|
+
runSource,
|
|
140
|
+
runSourceAllDirectives,
|
|
141
|
+
runSourceAsync
|
|
142
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readImports
|
|
3
|
+
} from "./chunk-L4U6ZIAR.js";
|
|
4
|
+
|
|
5
|
+
// src/run-main.ts
|
|
6
|
+
import { parseArgs } from "util";
|
|
7
|
+
import { existsSync, readFileSync } from "fs";
|
|
8
|
+
import { resolve, dirname } from "path";
|
|
9
|
+
import {
|
|
10
|
+
atomEq,
|
|
11
|
+
emptyExpr,
|
|
12
|
+
format,
|
|
13
|
+
isErrorAtom,
|
|
14
|
+
setOutputSink,
|
|
15
|
+
setRawSink
|
|
16
|
+
} from "@mettascript/core";
|
|
17
|
+
async function reexecWithLargerStack() {
|
|
18
|
+
const { spawnSync } = await import("child_process");
|
|
19
|
+
const res = spawnSync(
|
|
20
|
+
process.execPath,
|
|
21
|
+
["--stack-size=8000", process.argv[1] ?? "", ...process.argv.slice(2)],
|
|
22
|
+
{ stdio: "inherit", env: { ...process.env, METTA_TS_STACK: "1" } }
|
|
23
|
+
);
|
|
24
|
+
process.exit(res.status ?? 1);
|
|
25
|
+
}
|
|
26
|
+
async function runCliSource(src, fuel, imports, opts, includeNonBang) {
|
|
27
|
+
const { runSource, runSourceAllDirectives } = await import("./source.js");
|
|
28
|
+
return includeNonBang ? runSourceAllDirectives(src, fuel, imports, opts) : runSource(src, fuel, imports, opts);
|
|
29
|
+
}
|
|
30
|
+
async function runToBuffer(file, fuel, opts, includeNonBang) {
|
|
31
|
+
const src = readFileSync(file, "utf8");
|
|
32
|
+
const fileDir = dirname(resolve(file));
|
|
33
|
+
const imports = readImports(src, fileDir, dirname(fileDir));
|
|
34
|
+
const buf = [];
|
|
35
|
+
const prevOut = setOutputSink((line) => buf.push(line + "\n"));
|
|
36
|
+
const prevRaw = setRawSink((text) => buf.push(text));
|
|
37
|
+
try {
|
|
38
|
+
const results = await runCliSource(src, fuel, imports, opts, includeNonBang);
|
|
39
|
+
for (const r of results)
|
|
40
|
+
buf.push(
|
|
41
|
+
"[" + r.results.map((a) => formatCliResult(a, includeNonBang ? r.query : void 0)).join(", ") + "]\n"
|
|
42
|
+
);
|
|
43
|
+
return buf.join("");
|
|
44
|
+
} finally {
|
|
45
|
+
setOutputSink(prevOut);
|
|
46
|
+
setRawSink(prevRaw);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function formatCliResult(atom, query) {
|
|
50
|
+
if (query !== void 0 && isErrorAtom(atom) && atomEq(atom, query) && atom.kind === "expr")
|
|
51
|
+
return "( " + atom.items.map(format).join(" ") + ")";
|
|
52
|
+
return format(atom);
|
|
53
|
+
}
|
|
54
|
+
function importName(a) {
|
|
55
|
+
if (a?.kind === "sym") return a.name;
|
|
56
|
+
if (a?.kind === "gnd" && a.value.g === "str") return a.value.s;
|
|
57
|
+
if (a?.kind === "expr" && a.items.length === 2 && a.items[0]?.kind === "sym" && a.items[0].name === "library")
|
|
58
|
+
return importName(a.items[1]);
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
function errorMessage(e) {
|
|
62
|
+
return e instanceof Error ? e.message : String(e);
|
|
63
|
+
}
|
|
64
|
+
function resolveHostPath(fileDir, name) {
|
|
65
|
+
const fileRelative = resolve(fileDir, name);
|
|
66
|
+
if (existsSync(fileRelative)) return fileRelative;
|
|
67
|
+
return resolve(name);
|
|
68
|
+
}
|
|
69
|
+
async function runInteropToBuffer(file, fuel, opts, modes) {
|
|
70
|
+
const [{ composeHostInterops }, { runSourceAsync }] = await Promise.all([
|
|
71
|
+
import("@mettascript/core/host"),
|
|
72
|
+
import("./source.js")
|
|
73
|
+
]);
|
|
74
|
+
const interops = [];
|
|
75
|
+
const src = readFileSync(file, "utf8");
|
|
76
|
+
const fileDir = dirname(resolve(file));
|
|
77
|
+
const resolveImportPath = (p) => resolveHostPath(fileDir, p);
|
|
78
|
+
if (modes.py) {
|
|
79
|
+
if (process.env.METTA_TS_FORCE_NO_PYTHONIA === "1")
|
|
80
|
+
throw new Error("--py needs the pythonia backend; run: npm install pythonia");
|
|
81
|
+
let python;
|
|
82
|
+
try {
|
|
83
|
+
({ python } = await import("pythonia"));
|
|
84
|
+
} catch {
|
|
85
|
+
throw new Error(
|
|
86
|
+
"--py needs the pythonia backend; run: npm install pythonia (and ensure python3 is on PATH)"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const [{ pyCoreAsyncOps, PY_METTA_SRC }, { pythoniaBridge }] = await Promise.all([
|
|
90
|
+
import("@mettascript/py"),
|
|
91
|
+
import("@mettascript/py/pythonia")
|
|
92
|
+
]);
|
|
93
|
+
const bridge = pythoniaBridge(python);
|
|
94
|
+
interops.push({
|
|
95
|
+
name: "pythonia",
|
|
96
|
+
prelude: PY_METTA_SRC,
|
|
97
|
+
asyncOps: pyCoreAsyncOps(bridge),
|
|
98
|
+
hostImport: async (_space, target) => {
|
|
99
|
+
const name = importName(target);
|
|
100
|
+
if (!name?.endsWith(".py")) return { tag: "noReduce" };
|
|
101
|
+
try {
|
|
102
|
+
await bridge.import(resolveImportPath(name));
|
|
103
|
+
return { tag: "ok", results: [emptyExpr] };
|
|
104
|
+
} catch (e) {
|
|
105
|
+
return { tag: "runtimeError", msg: `import!: ${name}: ${errorMessage(e)}` };
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
dispose: () => bridge.dispose()
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (modes.prolog) {
|
|
112
|
+
if (process.env.METTA_TS_FORCE_NO_SWIPL === "1")
|
|
113
|
+
throw new Error("--prolog needs SWI-Prolog on PATH; install swipl");
|
|
114
|
+
const [{ PROLOG_METTA_SRC, prologCoreAsyncOps }, { swiPrologBridge }] = await Promise.all([
|
|
115
|
+
import("@mettascript/prolog"),
|
|
116
|
+
import("@mettascript/prolog/swi-node")
|
|
117
|
+
]);
|
|
118
|
+
const bridge = swiPrologBridge();
|
|
119
|
+
interops.push({
|
|
120
|
+
name: "swi-prolog",
|
|
121
|
+
prelude: PROLOG_METTA_SRC,
|
|
122
|
+
asyncOps: prologCoreAsyncOps(bridge, {
|
|
123
|
+
resolvePath: resolveImportPath
|
|
124
|
+
}),
|
|
125
|
+
hostImport: async (_space, target) => {
|
|
126
|
+
const name = importName(target);
|
|
127
|
+
if (!name?.endsWith(".pl")) return { tag: "noReduce" };
|
|
128
|
+
try {
|
|
129
|
+
await bridge.consult(resolveImportPath(name));
|
|
130
|
+
return { tag: "ok", results: [emptyExpr] };
|
|
131
|
+
} catch (e) {
|
|
132
|
+
return { tag: "runtimeError", msg: `import!: ${name}: ${errorMessage(e)}` };
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
dispose: () => bridge.dispose()
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const host = composeHostInterops(interops);
|
|
139
|
+
const buf = [];
|
|
140
|
+
const prevOut = setOutputSink((line) => buf.push(line + "\n"));
|
|
141
|
+
const prevRaw = setRawSink((text) => buf.push(text));
|
|
142
|
+
try {
|
|
143
|
+
const results = await runSourceAsync(
|
|
144
|
+
host.prelude === void 0 ? src : `${host.prelude}
|
|
145
|
+
${src}`,
|
|
146
|
+
new Map(host.asyncOps ?? []),
|
|
147
|
+
fuel,
|
|
148
|
+
readImports(src, fileDir, dirname(fileDir)),
|
|
149
|
+
{
|
|
150
|
+
...opts ?? {},
|
|
151
|
+
...host.hostImport !== void 0 ? { hostImport: host.hostImport } : {}
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
for (const r of results) buf.push("[" + r.results.map(format).join(", ") + "]\n");
|
|
155
|
+
return buf.join("");
|
|
156
|
+
} finally {
|
|
157
|
+
setOutputSink(prevOut);
|
|
158
|
+
setRawSink(prevRaw);
|
|
159
|
+
await host.dispose?.();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function runCliMain(argv, prog = "metta run") {
|
|
163
|
+
const { positionals, values } = parseArgs({
|
|
164
|
+
args: argv,
|
|
165
|
+
allowPositionals: true,
|
|
166
|
+
options: {
|
|
167
|
+
"max-steps": { type: "string" },
|
|
168
|
+
"max-stack-depth": { type: "string" },
|
|
169
|
+
"hash-cons": { type: "boolean" },
|
|
170
|
+
"flat-atomspace": { type: "boolean" },
|
|
171
|
+
check: { type: "boolean" },
|
|
172
|
+
json: { type: "boolean" },
|
|
173
|
+
"undefined-symbols": { type: "boolean" },
|
|
174
|
+
py: { type: "boolean" },
|
|
175
|
+
prolog: { type: "boolean" },
|
|
176
|
+
conformance: { type: "boolean" }
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
const file = positionals[0];
|
|
180
|
+
if (file === void 0) {
|
|
181
|
+
process.stderr.write(
|
|
182
|
+
`usage: ${prog} [--check [--json] [--undefined-symbols]] [--py] [--prolog] [--conformance] [--max-steps=N] [--max-stack-depth=N] [--hash-cons] <file.metta>
|
|
183
|
+
`
|
|
184
|
+
);
|
|
185
|
+
process.exit(2);
|
|
186
|
+
}
|
|
187
|
+
if (values.check === true) {
|
|
188
|
+
const { checkFile } = await import("./check-TCNAUG5Q.js");
|
|
189
|
+
const { text, exitCode } = checkFile(file, {
|
|
190
|
+
json: values.json === true,
|
|
191
|
+
undefinedSymbols: values["undefined-symbols"] === true
|
|
192
|
+
});
|
|
193
|
+
if (text.length > 0) {
|
|
194
|
+
(values.json === true ? process.stdout : process.stderr).write(text + "\n");
|
|
195
|
+
}
|
|
196
|
+
process.exit(exitCode);
|
|
197
|
+
}
|
|
198
|
+
const fuel = values["max-steps"] !== void 0 ? Number(values["max-steps"]) : void 0;
|
|
199
|
+
const maxStackDepth = values["max-stack-depth"] !== void 0 ? Number(values["max-stack-depth"]) : void 0;
|
|
200
|
+
const hashCons = values["hash-cons"] === true || process.env.METTA_TS_HASHCONS === "1" || process.env.METTA_TS_HASHCONS === "true";
|
|
201
|
+
const flatAtomspace = values["flat-atomspace"] === true || process.env.METTA_TS_FLAT_ATOMSPACE === "1" || process.env.METTA_TS_FLAT_ATOMSPACE === "true";
|
|
202
|
+
const opts = maxStackDepth !== void 0 || hashCons || flatAtomspace ? {
|
|
203
|
+
...maxStackDepth !== void 0 ? { maxStackDepth } : {},
|
|
204
|
+
...hashCons || flatAtomspace ? {
|
|
205
|
+
experimental: {
|
|
206
|
+
...hashCons ? { hashCons: true } : {},
|
|
207
|
+
...flatAtomspace ? { flatAtomspace: true } : {}
|
|
208
|
+
}
|
|
209
|
+
} : {}
|
|
210
|
+
} : void 0;
|
|
211
|
+
if (values.py === true || values.prolog === true) {
|
|
212
|
+
const output = await runInteropToBuffer(file, fuel, opts, {
|
|
213
|
+
py: values.py === true,
|
|
214
|
+
prolog: values.prolog === true
|
|
215
|
+
});
|
|
216
|
+
process.stdout.write(output);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (process.env.METTA_TS_STACK !== void 0) {
|
|
220
|
+
process.stdout.write(await runToBuffer(file, fuel, opts, values.conformance === true));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
process.stdout.write(await runToBuffer(file, fuel, opts, values.conformance === true));
|
|
225
|
+
} catch (e) {
|
|
226
|
+
if (!(e instanceof RangeError)) throw e;
|
|
227
|
+
await reexecWithLargerStack();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export {
|
|
232
|
+
runCliMain
|
|
233
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runSource
|
|
3
|
+
} from "./chunk-CWWQ3S7I.js";
|
|
4
|
+
import {
|
|
5
|
+
readImports
|
|
6
|
+
} from "./chunk-L4U6ZIAR.js";
|
|
7
|
+
|
|
8
|
+
// src/debug-main.ts
|
|
9
|
+
import { parseArgs } from "util";
|
|
10
|
+
import { existsSync, readFileSync } from "fs";
|
|
11
|
+
import { dirname, resolve } from "path";
|
|
12
|
+
import { format, setOutputSink, setRawSink } from "@mettascript/core";
|
|
13
|
+
import { assembleQuery, explainCall } from "@mettascript/debug";
|
|
14
|
+
function loadProgram(source, file) {
|
|
15
|
+
if (file !== void 0) {
|
|
16
|
+
const p = resolve(file);
|
|
17
|
+
if (!existsSync(p)) throw new Error(`file not found: ${p}`);
|
|
18
|
+
return { src: readFileSync(p, "utf8"), baseDir: dirname(p) };
|
|
19
|
+
}
|
|
20
|
+
if (source !== void 0) return { src: source, baseDir: process.cwd() };
|
|
21
|
+
throw new Error("provide --source '<metta>' or --file <path>");
|
|
22
|
+
}
|
|
23
|
+
function runQuery(loaded, call, fuel) {
|
|
24
|
+
setOutputSink(() => {
|
|
25
|
+
});
|
|
26
|
+
setRawSink(() => {
|
|
27
|
+
});
|
|
28
|
+
const program = assembleQuery(loaded.src, call);
|
|
29
|
+
const imports = readImports(program, loaded.baseDir, dirname(loaded.baseDir));
|
|
30
|
+
return explainCall(runSource, loaded.src, call, { fuel, imports });
|
|
31
|
+
}
|
|
32
|
+
function printHuman(obj) {
|
|
33
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
34
|
+
if (Array.isArray(v)) {
|
|
35
|
+
if (v.length === 0) continue;
|
|
36
|
+
process.stdout.write(`${k}:
|
|
37
|
+
`);
|
|
38
|
+
for (const item of v) process.stdout.write(` ${String(item)}
|
|
39
|
+
`);
|
|
40
|
+
} else if (v !== null && typeof v === "object") {
|
|
41
|
+
const entries = Object.entries(v);
|
|
42
|
+
if (entries.length === 0) continue;
|
|
43
|
+
process.stdout.write(`${k}:
|
|
44
|
+
`);
|
|
45
|
+
for (const [ik, iv] of entries) process.stdout.write(` ${ik}: ${String(iv)}
|
|
46
|
+
`);
|
|
47
|
+
} else {
|
|
48
|
+
process.stdout.write(`${k}: ${String(v)}
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function emit(llm, obj) {
|
|
54
|
+
if (llm) process.stdout.write(`${JSON.stringify(obj, null, 2)}
|
|
55
|
+
`);
|
|
56
|
+
else printHuman(obj);
|
|
57
|
+
}
|
|
58
|
+
function usage(prog) {
|
|
59
|
+
return `${prog} \u2014 headless MeTTa engine debugger
|
|
60
|
+
|
|
61
|
+
usage:
|
|
62
|
+
${prog} (--source '<metta>' | --file <path>) why '(<call>)' [--llm] [--max-steps N]
|
|
63
|
+
${prog} (--source '<metta>' | --file <path>) eval '(<expr>)' [--llm] [--max-steps N]
|
|
64
|
+
${prog} (--source '<metta>' | --file <path>) run [--llm] [--max-steps N]
|
|
65
|
+
|
|
66
|
+
commands:
|
|
67
|
+
why run the call with the trace bus and report which grounded reducer fired, any higher-order
|
|
68
|
+
specialization, any stack-overflow cut point, the reduction count, and the result.
|
|
69
|
+
eval evaluate one expression and print its result.
|
|
70
|
+
run run the whole program and print each !-query's results.`;
|
|
71
|
+
}
|
|
72
|
+
function runDebugMain(argv, prog = "metta debug") {
|
|
73
|
+
const { values, positionals } = parseArgs({
|
|
74
|
+
args: argv,
|
|
75
|
+
options: {
|
|
76
|
+
source: { type: "string" },
|
|
77
|
+
file: { type: "string" },
|
|
78
|
+
"max-steps": { type: "string" },
|
|
79
|
+
llm: { type: "boolean", default: false },
|
|
80
|
+
help: { type: "boolean", default: false }
|
|
81
|
+
},
|
|
82
|
+
allowPositionals: true
|
|
83
|
+
});
|
|
84
|
+
const cmd = positionals[0];
|
|
85
|
+
if (values.help === true || cmd === void 0) {
|
|
86
|
+
process.stdout.write(`${usage(prog)}
|
|
87
|
+
`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const fuel = values["max-steps"] !== void 0 ? Number(values["max-steps"]) : void 0;
|
|
91
|
+
const loaded = loadProgram(values.source, values.file);
|
|
92
|
+
const llm = values.llm === true;
|
|
93
|
+
if (cmd === "why") {
|
|
94
|
+
const call = positionals[1];
|
|
95
|
+
if (call === void 0) throw new Error(`usage: ${prog} why '(<call>)'`);
|
|
96
|
+
const { result, summary: s } = runQuery(loaded, call, fuel);
|
|
97
|
+
emit(llm, {
|
|
98
|
+
call,
|
|
99
|
+
result,
|
|
100
|
+
grounded: s.grounded,
|
|
101
|
+
specialized: s.specialized,
|
|
102
|
+
overflow: s.overflow,
|
|
103
|
+
reductions: s.reductions
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (cmd === "eval") {
|
|
108
|
+
const expr = positionals[1];
|
|
109
|
+
if (expr === void 0) throw new Error(`usage: ${prog} eval '(<expr>)'`);
|
|
110
|
+
emit(llm, { result: runQuery(loaded, expr, fuel).result });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (cmd === "run") {
|
|
114
|
+
const imports = readImports(loaded.src, loaded.baseDir, dirname(loaded.baseDir));
|
|
115
|
+
const results = runSource(loaded.src, fuel, imports).map((g) => g.results.map(format));
|
|
116
|
+
emit(llm, { results });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
throw new Error(`unknown command '${cmd}'
|
|
120
|
+
|
|
121
|
+
${usage(prog)}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export {
|
|
125
|
+
runDebugMain
|
|
126
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/file-imports.ts
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import { resolve, sep } from "path";
|
|
4
|
+
import { collectImports, parseAll, standardTokenizer } from "@mettascript/core";
|
|
5
|
+
function readImports(src, baseDir, importRoot = baseDir) {
|
|
6
|
+
const imports = /* @__PURE__ */ new Map();
|
|
7
|
+
if (!src.includes("import!")) return imports;
|
|
8
|
+
const base = resolve(baseDir);
|
|
9
|
+
const root = resolve(importRoot);
|
|
10
|
+
for (const name of collectImports(src)) {
|
|
11
|
+
const path = resolve(base, name.endsWith(".metta") ? name : name + ".metta");
|
|
12
|
+
if (path !== root && !path.startsWith(root + sep)) continue;
|
|
13
|
+
if (existsSync(path))
|
|
14
|
+
imports.set(
|
|
15
|
+
name,
|
|
16
|
+
parseAll(readFileSync(path, "utf8"), standardTokenizer()).filter((top) => !top.bang).map((top) => top.atom)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return imports;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
readImports
|
|
24
|
+
};
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
runCliMain
|
|
4
|
+
} from "./chunk-DHXPSARV.js";
|
|
5
|
+
import "./chunk-L4U6ZIAR.js";
|
|
6
|
+
|
|
7
|
+
// src/cli.ts
|
|
8
|
+
runCliMain(process.argv.slice(2), "metta-ts").catch((e) => {
|
|
9
|
+
process.stderr.write((e instanceof Error ? e.message : String(e)) + "\n");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
runDebugMain
|
|
4
|
+
} from "./chunk-ELBXIFJR.js";
|
|
5
|
+
import "./chunk-CWWQ3S7I.js";
|
|
6
|
+
import "./chunk-L4U6ZIAR.js";
|
|
7
|
+
|
|
8
|
+
// src/debug-cli.ts
|
|
9
|
+
try {
|
|
10
|
+
runDebugMain(process.argv.slice(2), "metta-debug");
|
|
11
|
+
} catch (e) {
|
|
12
|
+
process.stderr.write(`${e instanceof Error ? e.message : String(e)}
|
|
13
|
+
`);
|
|
14
|
+
process.exit(2);
|
|
15
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/graph-main.ts
|
|
2
|
+
import { parseArgs } from "util";
|
|
3
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { basename, extname, resolve } from "path";
|
|
5
|
+
function parseView(value) {
|
|
6
|
+
if (value === void 0) return void 0;
|
|
7
|
+
if (value === "blocks" || value === "graph" || value === "side-by-side") return value;
|
|
8
|
+
throw new Error(`--view must be blocks, graph, or side-by-side, got ${value}`);
|
|
9
|
+
}
|
|
10
|
+
async function runGraphMain(argv) {
|
|
11
|
+
const { positionals, values } = parseArgs({
|
|
12
|
+
args: argv,
|
|
13
|
+
allowPositionals: true,
|
|
14
|
+
options: {
|
|
15
|
+
out: { type: "string", short: "o" },
|
|
16
|
+
view: { type: "string" },
|
|
17
|
+
width: { type: "string" },
|
|
18
|
+
"max-steps": { type: "string" }
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
const file = positionals[0];
|
|
22
|
+
if (file === void 0) {
|
|
23
|
+
process.stderr.write(
|
|
24
|
+
"usage: metta graph <file.metta> [-o out.gif] [--view blocks|graph|side-by-side] [--width N] [--max-steps N]\n"
|
|
25
|
+
);
|
|
26
|
+
process.exit(2);
|
|
27
|
+
}
|
|
28
|
+
const view = parseView(values.view);
|
|
29
|
+
const src = readFileSync(resolve(file), "utf8");
|
|
30
|
+
let renderReductionGif;
|
|
31
|
+
try {
|
|
32
|
+
({ renderReductionGif } = await import("@mettascript/grapher/node"));
|
|
33
|
+
} catch (e) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"metta graph needs @mettascript/grapher and its renderers; install them with: npm install @mettascript/grapher gifenc sharp",
|
|
36
|
+
{ cause: e }
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
const gif = await renderReductionGif(src, {
|
|
40
|
+
...view !== void 0 ? { view } : {},
|
|
41
|
+
...values.width !== void 0 ? { width: Number(values.width) } : {},
|
|
42
|
+
...values["max-steps"] !== void 0 ? { maxSteps: Number(values["max-steps"]) } : {}
|
|
43
|
+
});
|
|
44
|
+
const out = values.out ?? `${basename(file, extname(file))}.gif`;
|
|
45
|
+
writeFileSync(out, gif);
|
|
46
|
+
process.stdout.write(`wrote ${out} (${gif.byteLength} bytes)
|
|
47
|
+
`);
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
runGraphMain
|
|
51
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Atom, FlatKB, RunOptions, QueryResult } from '@mettascript/core';
|
|
2
|
+
export * from '@mettascript/core';
|
|
3
|
+
export { ParEvalOptions, makeParEvalImpl, runSource, runSourceAllDirectives, runSourceAsync } from './source.js';
|
|
4
|
+
|
|
5
|
+
/** Pre-read every `import!` target referenced in `src`, resolving names against `baseDir`. */
|
|
6
|
+
declare function readImports(src: string, baseDir: string, importRoot?: string): Map<string, Atom[]>;
|
|
7
|
+
|
|
8
|
+
/** A worker-pool, SharedArrayBuffer-backed parallel matcher over a {@link FlatKB}. Build it once from a
|
|
9
|
+
* KB, reuse the warm pool across queries, and `close()` when done. */
|
|
10
|
+
declare class ParallelFlatMatcher {
|
|
11
|
+
private readonly kb;
|
|
12
|
+
private readonly tokens;
|
|
13
|
+
private readonly counter;
|
|
14
|
+
private readonly numFacts;
|
|
15
|
+
private readonly workers;
|
|
16
|
+
private queue;
|
|
17
|
+
constructor(kb: FlatKB, workerCount?: number);
|
|
18
|
+
/** Match `pattern` across the KB in parallel. Returns one binding map (variable name -> atom) per
|
|
19
|
+
* match, identical (up to order) to the single-threaded `FlatKB.match`. Concurrent calls are
|
|
20
|
+
* serialized internally (the workers share one counter), so it is safe to call without awaiting. */
|
|
21
|
+
match(pattern: Atom): Promise<Array<Map<string, Atom>>>;
|
|
22
|
+
private matchOne;
|
|
23
|
+
/** Terminate the worker pool. */
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Run a `.metta` file from disk, resolving `import!` relative to the file's directory. `fuel` is the step
|
|
28
|
+
* ceiling; `opts` carries interpreter settings such as the initial `maxStackDepth`. */
|
|
29
|
+
declare function runFile(path: string, fuel?: number, opts?: RunOptions): QueryResult[];
|
|
30
|
+
/** Run a file and return one result entry for every top-level directive. */
|
|
31
|
+
declare function runFileAllDirectives(path: string, fuel?: number, opts?: RunOptions): QueryResult[];
|
|
32
|
+
|
|
33
|
+
export { ParallelFlatMatcher, readImports, runFile, runFileAllDirectives };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import {
|
|
2
|
+
makeParEvalImpl,
|
|
3
|
+
runSource,
|
|
4
|
+
runSourceAllDirectives,
|
|
5
|
+
runSourceAsync
|
|
6
|
+
} from "./chunk-CWWQ3S7I.js";
|
|
7
|
+
import {
|
|
8
|
+
readImports
|
|
9
|
+
} from "./chunk-L4U6ZIAR.js";
|
|
10
|
+
|
|
11
|
+
// src/index.ts
|
|
12
|
+
import { readFileSync } from "fs";
|
|
13
|
+
import { resolve, dirname } from "path";
|
|
14
|
+
import "@mettascript/core";
|
|
15
|
+
export * from "@mettascript/core";
|
|
16
|
+
|
|
17
|
+
// src/flat-parallel.ts
|
|
18
|
+
import { Worker } from "worker_threads";
|
|
19
|
+
import { availableParallelism } from "os";
|
|
20
|
+
import { encodePattern, decodeAt } from "@mettascript/core";
|
|
21
|
+
var WORKER_SRC = `
|
|
22
|
+
const { parentPort, workerData } = require("node:worker_threads");
|
|
23
|
+
const tokens = new Int32Array(workerData.tokensSAB);
|
|
24
|
+
const offsets = new Int32Array(workerData.offsetsSAB);
|
|
25
|
+
const counter = new Int32Array(workerData.counterSAB);
|
|
26
|
+
const numFacts = workerData.numFacts;
|
|
27
|
+
const T_ARITY = 0, T_SYMBOL = 1, T_NEWVAR = 2, T_VARREF = 3;
|
|
28
|
+
const tg = (t) => (t >>> 28) & 0xf, pl = (t) => t & 0x0fffffff;
|
|
29
|
+
function subLen(tk, pos) {
|
|
30
|
+
const t = tk[pos];
|
|
31
|
+
if (tg(t) === T_ARITY) { let l = 1, p = pos + 1; for (let i = 0; i < pl(t); i++) { const x = subLen(tk, p); l += x; p += x; } return l; }
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
function rangeEq(tk, as, bs) {
|
|
35
|
+
const l = subLen(tk, as); if (l !== subLen(tk, bs)) return false;
|
|
36
|
+
for (let i = 0; i < l; i++) if (tk[as + i] !== tk[bs + i]) return false; return true;
|
|
37
|
+
}
|
|
38
|
+
function matchAt(pat, fact, fs) {
|
|
39
|
+
const binds = []; let vc = 0, pp = 0, fp = fs;
|
|
40
|
+
function go() {
|
|
41
|
+
const pt = pat[pp], ptag = tg(pt);
|
|
42
|
+
if (ptag === T_NEWVAR) { const l = subLen(fact, fp); binds[vc++] = [fp, fp + l]; pp++; fp += l; return true; }
|
|
43
|
+
if (ptag === T_VARREF) { const ref = binds[pl(pt)]; if (!ref) return false; if (!rangeEq(fact, ref[0], fp)) return false; pp++; fp += subLen(fact, fp); return true; }
|
|
44
|
+
if (ptag === T_SYMBOL) { if (fact[fp] !== pt) return false; pp++; fp++; return true; }
|
|
45
|
+
if (fact[fp] !== pt) return false; const n = pl(pt); pp++; fp++;
|
|
46
|
+
for (let i = 0; i < n; i++) if (!go()) return false; return true;
|
|
47
|
+
}
|
|
48
|
+
return go() ? binds : null;
|
|
49
|
+
}
|
|
50
|
+
parentPort.on("message", (pat) => {
|
|
51
|
+
const out = [];
|
|
52
|
+
for (;;) {
|
|
53
|
+
const idx = Atomics.add(counter, 0, 1);
|
|
54
|
+
if (idx >= numFacts) break;
|
|
55
|
+
const binds = matchAt(pat, tokens, offsets[idx]);
|
|
56
|
+
if (binds) out.push(binds); // binds: array indexed by de Bruijn id -> [start, end)
|
|
57
|
+
}
|
|
58
|
+
parentPort.postMessage(out);
|
|
59
|
+
});
|
|
60
|
+
`;
|
|
61
|
+
var ParallelFlatMatcher = class {
|
|
62
|
+
kb;
|
|
63
|
+
tokens;
|
|
64
|
+
counter;
|
|
65
|
+
numFacts;
|
|
66
|
+
workers;
|
|
67
|
+
// All workers share one work-stealing counter, so calls must not overlap; serialize them on a queue.
|
|
68
|
+
queue = Promise.resolve();
|
|
69
|
+
constructor(kb, workerCount = Math.max(1, availableParallelism() - 1)) {
|
|
70
|
+
this.kb = kb;
|
|
71
|
+
const toks = kb.tokenArray;
|
|
72
|
+
const offs = kb.factOffsets;
|
|
73
|
+
this.numFacts = offs.length;
|
|
74
|
+
const tokensSAB = new SharedArrayBuffer(toks.length * 4);
|
|
75
|
+
this.tokens = new Int32Array(tokensSAB);
|
|
76
|
+
this.tokens.set(toks);
|
|
77
|
+
const offsetsSAB = new SharedArrayBuffer(Math.max(1, offs.length) * 4);
|
|
78
|
+
new Int32Array(offsetsSAB).set(offs);
|
|
79
|
+
const counterSAB = new SharedArrayBuffer(4);
|
|
80
|
+
this.counter = new Int32Array(counterSAB);
|
|
81
|
+
this.workers = Array.from(
|
|
82
|
+
{ length: Math.max(1, workerCount) },
|
|
83
|
+
() => new Worker(WORKER_SRC, {
|
|
84
|
+
eval: true,
|
|
85
|
+
workerData: { tokensSAB, offsetsSAB, counterSAB, numFacts: this.numFacts }
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
/** Match `pattern` across the KB in parallel. Returns one binding map (variable name -> atom) per
|
|
90
|
+
* match, identical (up to order) to the single-threaded `FlatKB.match`. Concurrent calls are
|
|
91
|
+
* serialized internally (the workers share one counter), so it is safe to call without awaiting. */
|
|
92
|
+
async match(pattern) {
|
|
93
|
+
const run = this.queue.then(() => this.matchOne(pattern));
|
|
94
|
+
this.queue = run.catch(() => void 0);
|
|
95
|
+
return run;
|
|
96
|
+
}
|
|
97
|
+
async matchOne(pattern) {
|
|
98
|
+
const enc = encodePattern(pattern, this.kb.interner);
|
|
99
|
+
if (enc === null) return [];
|
|
100
|
+
Atomics.store(this.counter, 0, 0);
|
|
101
|
+
const replies = this.workers.map(
|
|
102
|
+
(w) => (
|
|
103
|
+
// Reject on worker error or early exit, so a crashing worker fails the match instead of leaving
|
|
104
|
+
// `Promise.all` and the serialized queue hung forever. Remove all listeners when any fires so a
|
|
105
|
+
// long-lived worker does not accumulate them across calls.
|
|
106
|
+
new Promise((res, rej) => {
|
|
107
|
+
const onMsg = (m) => (cleanup(), res(m));
|
|
108
|
+
const onErr = (e) => (cleanup(), rej(e));
|
|
109
|
+
const onExit = (code) => (cleanup(), rej(new Error(`match worker exited (${code})`)));
|
|
110
|
+
const cleanup = () => {
|
|
111
|
+
w.off("message", onMsg);
|
|
112
|
+
w.off("error", onErr);
|
|
113
|
+
w.off("exit", onExit);
|
|
114
|
+
};
|
|
115
|
+
w.on("message", onMsg);
|
|
116
|
+
w.on("error", onErr);
|
|
117
|
+
w.on("exit", onExit);
|
|
118
|
+
})
|
|
119
|
+
)
|
|
120
|
+
);
|
|
121
|
+
for (const w of this.workers) w.postMessage(enc.tokens);
|
|
122
|
+
const perWorker = await Promise.all(replies);
|
|
123
|
+
const out = [];
|
|
124
|
+
for (const matches of perWorker)
|
|
125
|
+
for (const ranges of matches) {
|
|
126
|
+
const m = /* @__PURE__ */ new Map();
|
|
127
|
+
ranges.forEach(
|
|
128
|
+
(r, idx) => m.set(enc.varNames[idx], decodeAt(this.tokens, r[0], this.kb.interner)[0])
|
|
129
|
+
);
|
|
130
|
+
out.push(m);
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
/** Terminate the worker pool. */
|
|
135
|
+
async close() {
|
|
136
|
+
await Promise.all(this.workers.map((w) => w.terminate()));
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/index.ts
|
|
141
|
+
function runFile(path, fuel, opts) {
|
|
142
|
+
const src = readFileSync(path, "utf8");
|
|
143
|
+
const fileDir = dirname(resolve(path));
|
|
144
|
+
return runSource(src, fuel, readImports(src, fileDir, dirname(fileDir)), opts);
|
|
145
|
+
}
|
|
146
|
+
function runFileAllDirectives(path, fuel, opts) {
|
|
147
|
+
const src = readFileSync(path, "utf8");
|
|
148
|
+
const fileDir = dirname(resolve(path));
|
|
149
|
+
return runSourceAllDirectives(src, fuel, readImports(src, fileDir, dirname(fileDir)), opts);
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
ParallelFlatMatcher,
|
|
153
|
+
makeParEvalImpl,
|
|
154
|
+
readImports,
|
|
155
|
+
runFile,
|
|
156
|
+
runFileAllDirectives,
|
|
157
|
+
runSource,
|
|
158
|
+
runSourceAllDirectives,
|
|
159
|
+
runSourceAsync
|
|
160
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/metta-cli.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
function version() {
|
|
6
|
+
const require2 = createRequire(import.meta.url);
|
|
7
|
+
return require2("../package.json").version;
|
|
8
|
+
}
|
|
9
|
+
var HELP = `metta \u2014 MeTTaScript command-line interface
|
|
10
|
+
|
|
11
|
+
usage:
|
|
12
|
+
metta <file.metta> [options] run a program (shorthand for "metta run")
|
|
13
|
+
metta run <file.metta> [options] run a program, printing each !-query's results
|
|
14
|
+
metta check <file.metta> [options] statically analyze a program (--json, --undefined-symbols)
|
|
15
|
+
metta debug (--file <p> | --source '<m>') <why|eval|run> [--llm] debug the engine
|
|
16
|
+
metta graph <file.metta> [-o out.gif] [--view blocks|graph|side-by-side] render a reduction GIF
|
|
17
|
+
metta --version | --help
|
|
18
|
+
|
|
19
|
+
Run "metta run --help" style is not needed; each subcommand prints its own usage on a missing argument.
|
|
20
|
+
"metta-ts" and "metta-debug" remain as aliases.`;
|
|
21
|
+
async function main() {
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const first = args[0];
|
|
24
|
+
if (first === void 0 || first === "--help" || first === "-h" || first === "help") {
|
|
25
|
+
process.stdout.write(`${HELP}
|
|
26
|
+
`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (first === "--version" || first === "-v") {
|
|
30
|
+
process.stdout.write(`${version()}
|
|
31
|
+
`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const rest = args.slice(1);
|
|
35
|
+
switch (first) {
|
|
36
|
+
case "run": {
|
|
37
|
+
const { runCliMain } = await import("./run-main-MK6LUIIS.js");
|
|
38
|
+
await runCliMain(rest, "metta run");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
case "check": {
|
|
42
|
+
const { runCliMain } = await import("./run-main-MK6LUIIS.js");
|
|
43
|
+
await runCliMain(["--check", ...rest], "metta check");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
case "debug": {
|
|
47
|
+
const { runDebugMain } = await import("./debug-main-Y254S5BG.js");
|
|
48
|
+
runDebugMain(rest, "metta debug");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
case "graph": {
|
|
52
|
+
const { runGraphMain } = await import("./graph-main-WRL2UUTS.js");
|
|
53
|
+
await runGraphMain(rest);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
default: {
|
|
57
|
+
const { runCliMain } = await import("./run-main-MK6LUIIS.js");
|
|
58
|
+
await runCliMain(args, "metta");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
main().catch((e) => {
|
|
64
|
+
process.stderr.write(`${e instanceof Error ? e.message : String(e)}
|
|
65
|
+
`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|
package/dist/source.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Atom, RunOptions, QueryResult, AsyncGroundFn } from '@mettascript/core';
|
|
2
|
+
|
|
3
|
+
interface ParEvalOptions {
|
|
4
|
+
readonly hostEffects?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/** Build the `RunOptions.parEvalImpl` hook the core runner calls for `(once (hyperpose …))`. Resolves the
|
|
7
|
+
* built `@metta-ts/core` entry once (the worker re-imports it) and binds the step ceiling. */
|
|
8
|
+
declare function makeParEvalImpl(fuel: number, options?: ParEvalOptions): (rulesSrc: string, branchSrcs: string[], firstOnly: boolean) => (string[] | null)[];
|
|
9
|
+
|
|
10
|
+
/** Run a MeTTa source string with an in-memory import map and Node's worker-thread hyperpose hook. */
|
|
11
|
+
declare function runSource(src: string, fuel?: number, imports?: Map<string, Atom[]>, opts?: RunOptions): QueryResult[];
|
|
12
|
+
/** Run a source string and return one result entry for every top-level directive. */
|
|
13
|
+
declare function runSourceAllDirectives(src: string, fuel?: number, imports?: Map<string, Atom[]>, opts?: RunOptions): QueryResult[];
|
|
14
|
+
/** Async source runner for embedders that register async grounded operations. */
|
|
15
|
+
declare function runSourceAsync(src: string, asyncOps?: Map<string, AsyncGroundFn>, fuel?: number, imports?: Map<string, Atom[]>, opts?: RunOptions, parOptions?: ParEvalOptions): Promise<QueryResult[]>;
|
|
16
|
+
|
|
17
|
+
export { type ParEvalOptions, makeParEvalImpl, runSource, runSourceAllDirectives, runSourceAsync };
|
package/dist/source.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mettascript/node",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"metta": "./dist/metta-cli.js",
|
|
10
|
+
"metta-ts": "./dist/cli.js",
|
|
11
|
+
"metta-debug": "./dist/debug-cli.js"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./source": {
|
|
19
|
+
"types": "./dist/source.d.ts",
|
|
20
|
+
"default": "./dist/source.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@mettascript/core": "2.0.0",
|
|
29
|
+
"@mettascript/py": "2.0.0",
|
|
30
|
+
"@mettascript/debug": "2.0.0",
|
|
31
|
+
"@mettascript/prolog": "2.0.0",
|
|
32
|
+
"@mettascript/libraries": "2.0.0"
|
|
33
|
+
},
|
|
34
|
+
"author": "MesTTo",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20"
|
|
37
|
+
},
|
|
38
|
+
"sideEffects": false,
|
|
39
|
+
"module": "./dist/index.js",
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"description": "Node.js entry for MeTTaScript: the metta-ts CLI, file import!, optional Python/Prolog interop, and a worker-thread parallel matcher.",
|
|
44
|
+
"keywords": [
|
|
45
|
+
"metta",
|
|
46
|
+
"hyperon",
|
|
47
|
+
"cli",
|
|
48
|
+
"atomspace",
|
|
49
|
+
"typescript"
|
|
50
|
+
],
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/MesTTo/MeTTaScript.git",
|
|
54
|
+
"directory": "packages/node"
|
|
55
|
+
},
|
|
56
|
+
"homepage": "https://github.com/MesTTo/MeTTaScript#readme",
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/MesTTo/MeTTaScript/issues"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"pythonia": "^1.2.6",
|
|
62
|
+
"ws": "^7.5.10",
|
|
63
|
+
"@mettascript/grapher": "2.0.0"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"pythonia": "^1.2.6",
|
|
67
|
+
"@mettascript/grapher": "^2.0.0"
|
|
68
|
+
},
|
|
69
|
+
"peerDependenciesMeta": {
|
|
70
|
+
"@mettascript/grapher": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"pythonia": {
|
|
74
|
+
"optional": true
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"scripts": {
|
|
78
|
+
"build": "tsup src/index.ts src/cli.ts src/debug-cli.ts src/metta-cli.ts src/source.ts --format esm --dts --clean"
|
|
79
|
+
}
|
|
80
|
+
}
|