@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 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,8 @@
1
+ import {
2
+ runDebugMain
3
+ } from "./chunk-ELBXIFJR.js";
4
+ import "./chunk-CWWQ3S7I.js";
5
+ import "./chunk-L4U6ZIAR.js";
6
+ export {
7
+ runDebugMain
8
+ };
@@ -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
+ };
@@ -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
+ });
@@ -0,0 +1,7 @@
1
+ import {
2
+ runCliMain
3
+ } from "./chunk-DHXPSARV.js";
4
+ import "./chunk-L4U6ZIAR.js";
5
+ export {
6
+ runCliMain
7
+ };
@@ -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
@@ -0,0 +1,12 @@
1
+ import {
2
+ makeParEvalImpl,
3
+ runSource,
4
+ runSourceAllDirectives,
5
+ runSourceAsync
6
+ } from "./chunk-CWWQ3S7I.js";
7
+ export {
8
+ makeParEvalImpl,
9
+ runSource,
10
+ runSourceAllDirectives,
11
+ runSourceAsync
12
+ };
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
+ }