Skip to content

Commit b042562

Browse files
committed
MeTTa TS 1.1.0
1 parent f366e1f commit b042562

42 files changed

Lines changed: 2092 additions & 58 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ packages/*/docs/
1111
website/.vitepress/dist/
1212
website/.vitepress/cache/
1313
packages/node/bench/lib
14+
15+
# Local hyperon venv for the @metta-ts/py differential oracle (HYPERON_LIVE=1)
16+
.venv-hyperon/

README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Other packages, add as needed:
2121
npm install @metta-ts/hyperon # a Python-hyperon-style class API
2222
npm install @metta-ts/node # CLI + file import! + a parallel matcher
2323
npm install @metta-ts/browser # web entry + in-memory virtual file system
24+
npm install @metta-ts/py # call Python (py-call / py-atom) via pythonia
2425
```
2526

2627
For the command-line runner, install `@metta-ts/node` globally (or use `npx`):
@@ -201,14 +202,15 @@ node packages/node/dist/cli.js examples/factorial.metta
201202

202203
## Packages
203204

204-
| Package | What it is |
205-
| --------------------------------------------- | --------------------------------------------------------------------------------------------- |
206-
| [`@metta-ts/core`](packages/core) | The interpreter, parser, type system, and standard library. Zero platform dependencies. |
207-
| [`@metta-ts/hyperon`](packages/hyperon) | A TypeScript class API over the core, modeled on Python's `hyperon`. |
208-
| [`@metta-ts/edsl`](packages/edsl) | An ergonomic, typed eDSL: term builders, special-form combinators, and a tagged template. |
209-
| [`@metta-ts/node`](packages/node) | The `metta-ts` CLI, file `import!`, and a `SharedArrayBuffer` worker-thread parallel matcher. |
210-
| [`@metta-ts/browser`](packages/browser) | Browser entry point with an in-memory virtual file system for `import!`. |
211-
| [`@metta-ts/das-client`](packages/das-client) | Optional client to SingularityNET's Distributed AtomSpace via a Connect gateway. |
205+
| Package | What it is |
206+
| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
207+
| [`@metta-ts/core`](packages/core) | The interpreter, parser, type system, and standard library. Zero platform dependencies. |
208+
| [`@metta-ts/hyperon`](packages/hyperon) | A TypeScript class API over the core, modeled on Python's `hyperon`. |
209+
| [`@metta-ts/edsl`](packages/edsl) | An ergonomic, typed eDSL: term builders, special-form combinators, and a tagged template. |
210+
| [`@metta-ts/node`](packages/node) | The `metta-ts` CLI, file `import!`, and a `SharedArrayBuffer` worker-thread parallel matcher. |
211+
| [`@metta-ts/browser`](packages/browser) | Browser entry point with an in-memory virtual file system for `import!`. |
212+
| [`@metta-ts/py`](packages/py) | Optional Python interop: PeTTa's `py-call` and Hyperon's `py-atom` over a caller-supplied pythonia bridge. |
213+
| [`@metta-ts/das-client`](packages/das-client) | Optional client to SingularityNET's Distributed AtomSpace via a Connect gateway. |
212214

213215
## Performance
214216

RELEASE_NOTES.md

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,40 @@
1-
# MeTTa TS 1.0.9
1+
# MeTTa TS 1.1.0
22

33
A pure-TypeScript implementation of [MeTTa](https://metta-lang.dev) (Meta Type Talk), the OpenCog Hyperon language. It runs anywhere TypeScript runs: the browser, Node, Deno, Bun, and edge or serverless functions. No native addons, no WASM, no Rust.
44

55
## Tested on Linux
66

77
This release is tested on Linux (Node 20, the CI matrix): lint, format, typecheck, the full test suite, and the build all run there. Because the engine is pure TypeScript with no native addon and no WASM, it is meant to be cross-platform and should run unchanged on any JavaScript runtime. Other operating systems are not yet part of the tested matrix.
88

9-
## What's new: a recovering CST for editors and language servers
9+
## What's new: Python interop
1010

11-
1.0.8 added a span-tracking parse for the static analyzer. This release turns it into a concrete syntax tree an editor can build on. `parseCst` never throws, so a language server can keep offering features while a document is mid-edit: an unclosed `(` closes at end of input, an unexpected `)` and an unterminated string each become a diagnostic instead of an exception, and deep nesting is bounded without a recursive overflow. The tree also carries what an editor needs and the analyzer did not: the comments, a syntactic kind per node, the paren spans, and the span of a top-level `!` query.
11+
This release adds `@metta-ts/py`, an optional package that lets a MeTTa program call into Python. It carries two surfaces over one bridge: PeTTa's `py-call` and Hyperon's `py-atom` family.
1212

13-
Leaf atoms still come from the interpreter's own reader primitives, so on valid input the CST is byte-identical to `parseAll`. A 1000-run differential checks the atoms and bang flags against the plain reader, and a 2000-run fuzz checks that the parser never throws on arbitrary input. The diagnostics are shaped as Language Server Protocol `Diagnostic`s with a range, a severity, and a stable code, so an editor consumes them directly. The static analyzer and `metta-ts --check` from 1.0.8 are unchanged and still read the interpreter's own signature table:
13+
`py-call` dispatches on the head of its argument, the way PeTTa does. A bare name is a builtin, a dotted name is a module function, and a leading-dot name is a method on a live object. `py-eval` runs a Python expression string and `py-str` folds a MeTTa list into a Python string:
1414

15+
```metta
16+
!(py-call (math.gcd 12 18)) ; 6
17+
!(py-eval "2 ** 10") ; 1024
1518
```
16-
error[arity-mismatch]: prog.metta:1:2
17-
|
18-
1 | !(car-atom 1 2)
19-
| ^^^^^^^^^^^^^^ car-atom expects 1 argument, got 2
19+
20+
The `py-atom` family is Hyperon's surface over the same bridge. `py-atom` resolves a dotted path into an atom you can apply or read as a value, and `py-dot`, `py-list`, `py-tuple`, `py-dict`, and `py-chain` round it out:
21+
22+
```metta
23+
!((py-atom operator.add) 40 2) ; 42
24+
!(py-atom math.pi) ; 3.141592653589793
2025
```
2126

22-
None of this touches the evaluator. `parseCst` is off the `runFile` hot path; the only change to shared code is that `readStringAt` now reports whether a string was terminated instead of throwing, and the plain parser re-throws exactly as before. The 270-assertion Hyperon oracle and every byte-identical experimental suite still pass, and a parse/eval microbench is within noise of 1.0.8.
27+
Python runs in a separate CPython process and MeTTa talks to it over IPC, so the interpreter stays pure TypeScript and as fast as before. The package ships no Python dependency of its own: you pass in a bridge, the way `MeTTaGrapher` takes a GIF encoder. The reference bridge wraps [pythonia](https://www.npmjs.com/package/pythonia). Because a call crosses a process boundary the ops are asynchronous, so you run with `runAsync`, or from the command line with `metta-ts --py program.metta`.
28+
29+
Value conversions follow PeTTa and its `janus` bridge: numbers both ways, a Python string to a Symbol, `True`/`False`/`None` to `(@ true)`/`(@ false)`/`(@ none)`, a list to an expression, and anything else to a live handle. A raised Python error becomes an `(Error <expr> <message>)` atom carrying the real Python message, and evaluation continues, where PeTTa aborts. Enabling this grants the program the host's Python, so it is opt-in and meant for trusted source only.
30+
31+
Two differential oracles pin the behaviour. A byte-parity suite runs the same corpus through a live PeTTa checkout and through this package, comparing the result lines exactly. A second suite runs the `py-atom` surface through pip `hyperon`, comparing results on the numeric surface where the two marshallings coincide. Both are gated behind environment flags so the default suite needs no Python.
32+
33+
The one change to the engine is that a grounded atom's executor may now return a `Promise`, which is what lets an applied `py-atom` run asynchronously. Nothing returned a Promise from that path before, so the synchronous behaviour is unchanged: the 270-assertion Hyperon oracle is byte-identical and the corpus microbench is within noise of 1.0.9.
2334

2435
## Corpus benchmark
2536

26-
The engine is unchanged in this release, so the PeTTa-corpus benchmark (107 shared programs, 97 both engines pass, median 2.01x, geomean 2.06x) is identical to 1.0.7. See [`packages/node/bench/RESULTS-corpus.md`](packages/node/bench/RESULTS-corpus.md) for the full per-program table.
37+
The engine is unchanged for pure-MeTTa programs, so the PeTTa-corpus benchmark (107 shared programs, 97 both engines pass, median 2.01x, geomean 2.06x) is identical to 1.0.9. See [`packages/node/bench/RESULTS-corpus.md`](packages/node/bench/RESULTS-corpus.md) for the full per-program table.
2738

2839
## Major performance gains (since 1.0.0)
2940

@@ -36,16 +47,17 @@ The speed comes from general engine work:
3647
- automatic tabling of pure functions, including ones defined at runtime, and moded (variant) tabling for non-ground pure calls;
3748
- a native-code compiler for the pure deterministic int/bool/tuple subset, with tail-recursion compiled to loops and higher-order specialisation;
3849
- worker-thread parallelism: `(once (hyperpose ...))` races branches across CPU cores on Node, and a `SharedArrayBuffer` flat matcher scans large knowledge bases in parallel;
39-
- the compiled clause-skeleton and JavaScript-codegen search for match-free nondeterministic groups, added in 1.0.7.
50+
- the compiled clause-skeleton and JavaScript-codegen search for match-free nondeterministic groups.
4051

4152
Every optimisation is verified byte-identical against the 270-assertion Hyperon oracle.
4253

4354
## What is in this release
4455

45-
- `@metta-ts/core` is the interpreter, parser, type system, pattern matching, and standard library, as a single ESM bundle. It now also carries the static analyzer and its diagnostic model, described above. It passes all 270 assertions of Hyperon's oracle corpus (the full dependent-type tier, spaces and mutable state, nondeterminism, grounded operations, and documentation), cross-checked against [LeaTTa](https://github.com/MesTTo/LeaTTa), the machine-checked (Lean 4) MeTTa semantics pinned to the same commit.
56+
- `@metta-ts/core` is the interpreter, parser, type system, pattern matching, standard library, and static analyzer, as a single ESM bundle. It passes all 270 assertions of Hyperon's oracle corpus, cross-checked against [LeaTTa](https://github.com/MesTTo/LeaTTa), the machine-checked (Lean 4) MeTTa semantics pinned to the same commit.
4657
- `@metta-ts/hyperon` is a TypeScript class API modeled on Python's `hyperon`, with a JavaScript interop layer (`js-atom`, `js-dot`, `js-list`, `js-dict`) that calls into the host runtime directly.
4758
- `@metta-ts/edsl` is a typed eDSL with term builders, special-form combinators, and a tagged-template surface.
48-
- `@metta-ts/node` has the `metta-ts` CLI, now with `--check` for static analysis, plus file `import!` and the worker-thread parallel matcher.
59+
- `@metta-ts/py` is the new Python interop package, described above: `py-call` and the `py-atom` family over a caller-supplied pythonia bridge, opt-in and asynchronous.
60+
- `@metta-ts/node` has the `metta-ts` CLI, with `--check` for static analysis and `--py` for Python interop, plus file `import!` and the worker-thread parallel matcher.
4961
- `@metta-ts/browser` is a browser entry with an in-memory virtual file system for `import!`.
5062
- `@metta-ts/grapher` renders a MeTTa reduction as a node graph or a nested-block view, as static SVGs or an animated GIF, with a data-driven stylesheet for node size and colour.
5163
- `@metta-ts/das-client` and `@metta-ts/das-gateway` are an optional client to SingularityNET's Distributed AtomSpace, run end to end against a live cluster, with atom handles matching the AtomDB byte for byte.
@@ -55,19 +67,19 @@ Every optimisation is verified byte-identical against the 270-assertion Hyperon
5567
```bash
5668
npm install @metta-ts/core # the interpreter (works in any JS runtime)
5769
npm install -g @metta-ts/node # the metta-ts CLI
70+
npm install @metta-ts/py pythonia # optional: call Python from MeTTa
5871
```
5972

60-
Check a file without running it:
73+
Run a Python-using program from the command line:
6174

6275
```bash
63-
metta-ts --check program.metta # arity errors, rustc-style
64-
metta-ts --check --undefined-symbols program.metta # also "did you mean" on unknown heads
65-
metta-ts --check --json program.metta # diagnostics as an LSP Diagnostic[]
76+
metta-ts --py program.metta # needs pythonia installed and python3 on PATH
6677
```
6778

6879
## Provenance
6980

7081
- Semantics: [hyperon-experimental](https://github.com/trueagi-io/hyperon-experimental), pinned to commit `3f76dc4`.
82+
- Python interop surface: PeTTa's `py-call` and Hyperon's [`py-atom`](https://trueagi-io.github.io/hyperon-experimental/reference/atoms/) family, over [pythonia](https://www.npmjs.com/package/pythonia).
7183
- Verified spec and differential oracle: [LeaTTa](https://github.com/MesTTo/LeaTTa) (Lean 4).
7284
- Formal models: [Alloy](https://alloytools.org) specs in [`spec/`](spec/) for the matcher's deep loop rejection and the compiled search's occurs check.
7385
- License: [MIT](LICENSE).

examples/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"stdlib": "tsx stdlib.ts",
1616
"types": "tsx types.ts",
1717
"js-interop": "tsx js-interop.ts",
18+
"python-interop": "tsx python-interop.ts",
1819
"json-module": "tsx json-module.ts",
1920
"custom-matching": "tsx custom-matching.ts",
2021
"concurrency": "tsx concurrency.ts",
@@ -28,7 +29,10 @@
2829
"@metta-ts/edsl": "workspace:*",
2930
"@metta-ts/grapher": "workspace:*",
3031
"@metta-ts/hyperon": "workspace:*",
31-
"@metta-ts/node": "workspace:*"
32+
"@metta-ts/node": "workspace:*",
33+
"@metta-ts/py": "workspace:*",
34+
"pythonia": "^1.2.6",
35+
"ws": "^7.5.10"
3236
},
3337
"devDependencies": {
3438
"tsx": "^4.19.2",

examples/python-interop.metta

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
; SPDX-FileCopyrightText: 2026 MesTTo
2+
; SPDX-License-Identifier: MIT
3+
4+
; Python interop from MeTTa source. Run it with the CLI (needs pythonia installed and python3 on PATH):
5+
; metta-ts --py examples/python-interop.metta
6+
7+
; py-call dispatches on the head: a builtin, a module function, or a .method on a live object.
8+
!(py-call (abs -5)) ; [5]
9+
!(py-call (math.gcd 12 18)) ; [6]
10+
11+
; py-eval runs a Python expression; a returned list comes back as an expression.
12+
!(py-eval "2 ** 10") ; [1024]
13+
!(py-eval "[1, 2.5, 'x', True, None]") ; [(1 2.5 x (@ true) (@ none))]
14+
15+
; The py-atom family is Hyperon's surface over the same bridge.
16+
!((py-atom operator.add) 40 2) ; [42]
17+
!(py-atom math.pi) ; [3.141592653589793]
18+
19+
; A Python error becomes an (Error ...) atom and the run keeps going, where PeTTa would abort.
20+
!(py-eval "1 / 0") ; [(Error ... ZeroDivisionError: division by zero)]

examples/python-interop.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-FileCopyrightText: 2026 MesTTo
2+
//
3+
// SPDX-License-Identifier: MIT
4+
5+
// Python interop: a MeTTa program calling into CPython through @metta-ts/py. Python runs in a separate
6+
// process and MeTTa talks to it over IPC, so the interpreter stays pure TypeScript. The ops are async
7+
// (a call crosses a process boundary), so you run with `runAsync` and pass in a bridge yourself.
8+
//
9+
// Run it (after `pnpm build`, and with python3 on your PATH): npx tsx examples/python-interop.ts
10+
import { MeTTa } from "@metta-ts/hyperon";
11+
import { registerPyInterop, pythoniaBridge } from "@metta-ts/py";
12+
import { python } from "pythonia";
13+
14+
const m = new MeTTa();
15+
const bridge = pythoniaBridge(python);
16+
registerPyInterop(m, bridge);
17+
18+
const run1 = async (q: string): Promise<string[]> =>
19+
(await m.runAsync(q))[0]!.map((a) => a.toString());
20+
21+
// py-call dispatches on the head: a builtin, a module function, or a `.method` on a live object.
22+
console.log("abs(-5):", await run1(`!(py-call (abs -5))`)); // [ '5' ]
23+
console.log("math.gcd(12, 18):", await run1(`!(py-call (math.gcd 12 18))`)); // [ '6' ]
24+
25+
// py-eval evaluates a Python expression string; a returned list converts to a MeTTa expression.
26+
console.log("2 ** 10:", await run1(`!(py-eval "2 ** 10")`)); // [ '1024' ]
27+
console.log("a list:", await run1(`!(py-eval "[1, 2.5, 'x', True, None]")`)); // [ '(1 2.5 x (@ true) (@ none))' ]
28+
29+
// True/False/None come back as (@ true)/(@ false)/(@ none), kept distinct from MeTTa symbols.
30+
console.log("bool(1):", await run1(`!(py-call (bool 1))`)); // [ '(@ true)' ]
31+
32+
// A non-primitive result stays a live handle you keep passing around.
33+
console.log("Fraction 1/3:", await run1(`!(py-call (str (py-call (fractions.Fraction 1 3))))`)); // [ '1/3' ]
34+
35+
// The py-atom family is Hyperon's surface over the same bridge: resolve a path into an atom you apply.
36+
console.log("operator.add:", await run1(`!((py-atom operator.add) 40 2)`)); // [ '42' ]
37+
console.log("math.pi:", await run1(`!(py-atom math.pi)`)); // [ '3.141592653589793' ]
38+
39+
// Errors do not crash the run. A raised Python exception (here ZeroDivisionError) and an unresolvable
40+
// path both come back as an (Error ...) atom the program can inspect, and evaluation continues. This is
41+
// a deliberate divergence from PeTTa, which aborts on a Python error.
42+
console.log("1 / 0:", await run1(`!(py-eval "1 / 0")`)); // [ '(Error ... ZeroDivisionError: division by zero)' ]
43+
console.log("bad path:", await run1(`!(py-atom nosuch.module)`)); // [ '(Error ... AttributeError: ... no attribute ...)' ]
44+
45+
await bridge.dispose(); // stops the Python subprocess so the process can exit

packages/browser/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@metta-ts/browser",
3-
"version": "1.0.9",
3+
"version": "1.1.0",
44
"license": "MIT",
55
"type": "module",
66
"main": "./dist/index.js",

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@metta-ts/core",
3-
"version": "1.0.9",
3+
"version": "1.1.0",
44
"license": "MIT",
55
"type": "module",
66
"main": "./dist/index.js",

0 commit comments

Comments
 (0)