Metadata-Version: 2.4
Name: gama-l
Version: 2.0.0
Summary: GAMA-L 2.0 core language: parser, type/effect checker, interpreter and LSP.
License:                                  Apache License
                                   Version 2.0, January 2004
                                http://www.apache.org/licenses/
        
           TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
        
           1. Definitions.
        
              "License" shall mean the terms and conditions for use, reproduction,
              and distribution as defined by Sections 1 through 9 of this document.
        
              "Licensor" shall mean the copyright owner or entity authorized by
              the copyright owner that is granting the License.
        
              "Legal Entity" shall mean the union of the acting entity and all
              other entities that control, are controlled by, or are under common
              control with that entity. For the purposes of this definition,
              "control" means (i) the power, direct or indirect, to cause the
              direction or management of such entity, whether by contract or
              otherwise, or (ii) ownership of fifty percent (50%) or more of the
              outstanding shares, or (iii) beneficial ownership of such entity.
        
              "You" (or "Your") shall mean an individual or Legal Entity
              exercising permissions granted by this License.
        
              "Source" form shall mean the preferred form for making modifications,
              including but not limited to software source code, documentation
              source, and configuration files.
        
              "Object" form shall mean any form resulting from mechanical
              transformation or translation of a Source form, including but
              not limited to compiled object code, generated documentation,
              and conversions to other media types.
        
              "Work" shall mean the work of authorship, whether in Source or
              Object form, made available under the License, as indicated by a
              copyright notice that is included in or attached to the work
              (an example is provided in the Appendix below).
        
              "Derivative Works" shall mean any work, whether in Source or Object
              form, that is based on (or derived from) the Work and for which the
              editorial revisions, annotations, elaborations, or other modifications
              represent, as a whole, an original work of authorship. For the purposes
              of this License, Derivative Works shall not include works that remain
              separable from, or merely link (or bind by name) to the interfaces of,
              the Work and Derivative Works thereof.
        
              "Contribution" shall mean any work of authorship, including
              the original version of the Work and any modifications or additions
              to that Work or Derivative Works thereof, that is intentionally
              submitted to Licensor for inclusion in the Work by the copyright owner
              or by an individual or Legal Entity authorized to submit on behalf of
              the copyright owner. For the purposes of this definition, "submitted"
              means any form of electronic, verbal, or written communication sent
              to the Licensor or its representatives, including but not limited to
              communication on electronic mailing lists, source code control systems,
              and issue tracking systems that are managed by, or on behalf of, the
              Licensor for the purpose of discussing and improving the Work, but
              excluding communication that is conspicuously marked or otherwise
              designated in writing by the copyright owner as "Not a Contribution."
        
              "Contributor" shall mean Licensor and any individual or Legal Entity
              on behalf of whom a Contribution has been received by Licensor and
              subsequently incorporated within the Work.
        
           2. Grant of Copyright License. Subject to the terms and conditions of
              this License, each Contributor hereby grants to You a perpetual,
              worldwide, non-exclusive, no-charge, royalty-free, irrevocable
              copyright license to reproduce, prepare Derivative Works of,
              publicly display, publicly perform, sublicense, and distribute the
              Work and such Derivative Works in Source or Object form.
        
           3. Grant of Patent License. Subject to the terms and conditions of
              this License, each Contributor hereby grants to You a perpetual,
              worldwide, non-exclusive, no-charge, royalty-free, irrevocable
              (except as stated in this section) patent license to make, have made,
              use, offer to sell, sell, import, and otherwise transfer the Work,
              where such license applies only to those patent claims licensable
              by such Contributor that are necessarily infringed by their
              Contribution(s) alone or by combination of their Contribution(s)
              with the Work to which such Contribution(s) was submitted. If You
              institute patent litigation against any entity (including a
              cross-claim or counterclaim in a lawsuit) alleging that the Work
              or a Contribution incorporated within the Work constitutes direct
              or contributory patent infringement, then any patent licenses
              granted to You under this License for that Work shall terminate
              as of the date such litigation is filed.
        
           4. Redistribution. You may reproduce and distribute copies of the
              Work or Derivative Works thereof in any medium, with or without
              modifications, and in Source or Object form, provided that You
              meet the following conditions:
        
              (a) You must give any other recipients of the Work or
                  Derivative Works a copy of this License; and
        
              (b) You must cause any modified files to carry prominent notices
                  stating that You changed the files; and
        
              (c) You must retain, in the Source form of any Derivative Works
                  that You distribute, all copyright, patent, trademark, and
                  attribution notices from the Source form of the Work,
                  excluding those notices that do not pertain to any part of
                  the Derivative Works; and
        
              (d) If the Work includes a "NOTICE" text file as part of its
                  distribution, then any Derivative Works that You distribute must
                  include a readable copy of the attribution notices contained
                  within such NOTICE file, excluding those notices that do not
                  pertain to any part of the Derivative Works, in at least one
                  of the following places: within a NOTICE text file distributed
                  as part of the Derivative Works; within the Source form or
                  documentation, if provided along with the Derivative Works; or,
                  within a display generated by the Derivative Works, if and
                  wherever such third-party notices normally appear. The contents
                  of the NOTICE file are for informational purposes only and
                  do not modify the License. You may add Your own attribution
                  notices within Derivative Works that You distribute, alongside
                  or as an addendum to the NOTICE text from the Work, provided
                  that such additional attribution notices cannot be construed
                  as modifying the License.
        
              You may add Your own copyright statement to Your modifications and
              may provide additional or different license terms and conditions
              for use, reproduction, or distribution of Your modifications, or
              for any such Derivative Works as a whole, provided Your use,
              reproduction, and distribution of the Work otherwise complies with
              the conditions stated in this License.
        
           5. Submission of Contributions. Unless You explicitly state otherwise,
              any Contribution intentionally submitted for inclusion in the Work
              by You to the Licensor shall be under the terms and conditions of
              this License, without any additional terms or conditions.
              Notwithstanding the above, nothing herein shall supersede or modify
              the terms of any separate license agreement you may have executed
              with Licensor regarding such Contributions.
        
           6. Trademarks. This License does not grant permission to use the trade
              names, trademarks, service marks, or product names of the Licensor,
              except as required for reasonable and customary use in describing the
              origin of the Work and reproducing the content of the NOTICE file.
        
           7. Disclaimer of Warranty. Unless required by applicable law or
              agreed to in writing, Licensor provides the Work (and each
              Contributor provides its Contributions) on an "AS IS" BASIS,
              WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
              implied, including, without limitation, any warranties or conditions
              of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
              PARTICULAR PURPOSE. You are solely responsible for determining the
              appropriateness of using or redistributing the Work and assume any
              risks associated with Your exercise of permissions under this License.
        
           8. Limitation of Liability. In no event and under no legal theory,
              whether in tort (including negligence), contract, or otherwise,
              unless required by applicable law (such as deliberate and grossly
              negligent acts) or agreed to in writing, shall any Contributor be
              liable to You for damages, including any direct, indirect, special,
              incidental, or consequential damages of any character arising as a
              result of this License or out of the use or inability to use the
              Work (including but not limited to damages for loss of goodwill,
              work stoppage, computer failure or malfunction, or any and all
              other commercial damages or losses), even if such Contributor
              has been advised of the possibility of such damages.
        
           9. Accepting Warranty or Additional Liability. While redistributing
              the Work or Derivative Works thereof, You may choose to offer,
              and charge a fee for, acceptance of support, warranty, indemnity,
              or other liability obligations and/or rights consistent with this
              License. However, in accepting such obligations, You may act only
              on Your own behalf and on Your sole responsibility, not on behalf
              of any other Contributor, and only if You agree to indemnify,
              defend, and hold each Contributor harmless for any liability
              incurred by, or claims asserted against, such Contributor by reason
              of your accepting any such warranty or additional liability.
        
           END OF TERMS AND CONDITIONS
        
           APPENDIX: How to apply the Apache License to your work.
        
              To apply the Apache License to your work, attach the following
              boilerplate notice, with the fields enclosed by brackets "[]"
              replaced with your own identifying information. (Don't include
              the brackets!)  The text should be enclosed in the appropriate
              comment syntax for the file format. We also recommend that a
              file or class name and description of purpose be included on the
              same "printed page" as the copyright notice for easier
              identification within third-party archives.
        
           Copyright [yyyy] [name of copyright owner]
        
           Licensed under the Apache License, Version 2.0 (the "License");
           you may not use this file except in compliance with the License.
           You may obtain a copy of the License at
        
               http://www.apache.org/licenses/LICENSE-2.0
        
           Unless required by applicable law or agreed to in writing, software
           distributed under the License is distributed on an "AS IS" BASIS,
           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
           See the License for the specific language governing permissions and
           limitations under the License.
        
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# GAMA-L 2.0 core (executable)

This is a real parser + static checker + tree-walking interpreter, not the old
transpiler. It is a **core subset** of `SPEC_2.0.md`; the rest is still roadmap.

## Run

```bash
python3 gamal2.py run examples/invoice.gm
python3 gamal2.py check examples/invoice.gm
python3 gamal2.py test
```

## Package manager (local + git)

```bash
python3 gamal2.py pkg init .
python3 gamal2.py pkg install .
python3 gamal2.py pkg update .
python3 gamal2.py pkg list .
python3 gamal2.py pkg search registry.json
python3 gamal2.py pkg add registry.json math 0.1.0
```

```toml
[dependencies]
math = { path = "math" }
lib = { git = "https://example.com/lib.git", rev = "v1.2.0" }
```

Path dependencies must stay inside the project root; symlinks are rejected.
Git dependencies clone into `.gamal/cache/<name>` and are pinned in
`.gamal/lock.json`; reinstall fails on a lock mismatch until `pkg update`.
Modules resolve from `.gamal/modules/<name>` via `import "name/file.gm"`.
A local/static registry is a JSON file (path, `file://`, or `http(s)://`)
consumed by `pkg search|resolve|add`. For a loopback/private HTTP registry set
`GAMA_L_REGISTRY_ALLOW_PRIVATE=1` (the client blocks private hosts by default).
Serve one with:

```bash
python3 gama_registry_server.py --registry registry.json --port 8089
```

Any git URL (including private remotes) works with the host's git credentials.

## Editor support (LSP, stdio)

```bash
python3 gamal2.py lsp
```

JSON-RPC language server: `initialize`, `didOpen/didChange/didSave`
diagnostics, completion, hover, signature help, definition, references,
document symbols, semantic tokens, code actions, rename, document formatting,
`shutdown`, `exit`. Malformed frames return a JSON-RPC error and no longer
kill the server; multiline strings keep correct line/column positions.
Positions are converted between LSP UTF-16 and Python indexes.

Completion covers the current keywords, types (`Function`, `Result`, `Table`,
`Json`, `Null`, …), all builtins with signature details, document `let`/`fn`
names, and import aliases. For `import "lib.gm" as <cursor>` the only
inferable alias is a sanitized module filename stem; it is omitted when it
collides with a top-level name or an existing alias. After `alias.`, completion
and hover read the imported file's direct top-level `fn` declarations
(namespaced completion does not list module `let` constants yet).
Signature help works for known builtins, user `fn`/lambda-typed `let` values,
and namespaced module functions.

Generic named functions are reflected in hover and signature help with their
declared type parameters (`fn id<T>(x: T) -> T`, `fn pair<A, B>(...)`) and a
`Type parameters: ... (position N)` summary; parameters that use a type
parameter carry a matching `type parameter` note. Completion after `id<`
offers type parameters that are in scope from an enclosing generic function,
then the callee's declared type parameters for context, then the built-in type
names; `fn id<` offers the built-in type names only (type-parameter names are
chosen by the author and cannot be inferred). The core parser supports
`id<Int>(1)` for locally declared generic functions (count/name checks,
formatter round-trip); imported/namespaced generic calls and nested type
arguments are not recognized. LSP signature help also recovers explicit
arguments when the base name resolves to a generic `fn` declaration, and
semantic tokens mark declared type-parameter names as types.

Match-arm patterns get best-effort LSP support even when the core parser
rejects the pattern: arm-head completion offers records, enums and variants,
`Point { ... }` offers the record's fields (inherited ones included), hover on
a pattern record name shows its fields/arity, and hover on a bound variable
infers the field type (record patterns) or the `List<T>` element type. These
record-struct, record-constructor, list and `if`-guard forms are LSP-only and
remain parse errors in the core; a guard after `if` falls back to normal
expression completion, and malformed or unclosed matches stay quiet instead
of crashing.

The core exposes a module's top-level `let` constants as `alias.name`, typed
and evaluated at import time, and module functions see the same renamed
binding. Any other non-declaration top-level statement under an alias
(`print`, `prove`, `verify`, loops, assignments, expression statements) is
rejected (`use` is allowed as a no-op).
Builtin parameter names are LSP-only labels, since the core has no
builtin parameter names. Diagnostics still use the in-memory `check(text)`
path, so a document with an `import` reports the core's "import requires
check_file/run_file" message even though the file would check/run fine on
disk.

A syntax-only VS Code package is in `vscode/` (TextMate grammar + snippets +
a dependency-free LSP client). It highlights `import ... as` namespaces,
`Function`, namespaced calls, lambdas, record `with { ... }` updates,
payload/multi-payload enums, aliased variants (`geo.Color.Red`), `export
fn/let/record/enum`, selective `export { ... }` lists (including `as`
renames), and the postfix `?` operator. Snippets cover those features
(`recordwith`, `payloadenum`, `multipayloadenum`, `payload`, `pair`,
`aliasvariant`, `matchpayload`, `exportfn`, `exportlet`, `exportrecord`,
`tryexpr`) plus `impas`, `callns`, `iife`, and `fnlettype`. Grammar limits:
record-update scopes end at the first `}`, only three-part alias chains are
scoped, and the `as Name` clause of renamed exports is not given a dedicated
scope yet. The extension requires a trusted workspace
before spawning Python, supports `gama-lsp` from PATH or a configured script,
and restarts with bounded backoff.

## Distribution

`pyproject.toml` defines the `gama-l` package and console scripts `gama-l`,
`gama-lsp`, `gama-doctor`, `gama-registry`, `gama-release`; modules are
`gamal2`, `gama_lsp`, `gama_cli`, `gama_fmt`, `gama_doctor`, `gama_mail`,
`gama_registry`, `gama_registry_server`, `gama_ai`, `release`.
The CLI wrapper is `gama_cli.py`. `release.py build` writes deterministic
wheel/tar, `SHA256SUMS`, and `RELEASE.json`; `release.py verify` checks them.

### Reproducible release verification

`scripts/verify_release.sh` runs the whole release smoke test from a clean
temporary directory (no artifacts are written into the repo):

1. `release.py build` twice and compares the SHA256 of the wheel and sdist to
   prove the build is byte-reproducible;
2. `release.py verify` plus `sha256sum -c SHA256SUMS`, and checks the
   `RELEASE.json` manifest;
3. installs the wheel into a fresh virtualenv and runs `gama-l --help`,
   `gama-l run examples/hello.gm`, and `gama-l doctor --json` (8/8);
4. validates `vscode/package.json` and every referenced extension asset, and
   packages the extension when `vsce` is available offline (otherwise it
   reports the missing tool and never publishes).

```bash
bash scripts/verify_release.sh
```

The wheel ships code only; `gama-l doctor` needs a tree with `examples/`, so
run it from the source checkout or pass `--root` to a directory that exposes
`examples/`.

### Release dry run (recommended gate)

`scripts/release_dry_run.sh` is the single "everything except publish" gate. It
builds twice, verifies hashes, installs the wheel into a fresh virtualenv, runs
the shipped CLI and doctor, checks the wheel with `twine` when available,
packages the VSIX when `vsce` is available, and prints a manifest with versions,
sizes, hashes, and tool availability. All work happens in a temporary directory.

```bash
cd gama-lang/v2
bash scripts/release_dry_run.sh

# Optional: keep the verified artifacts + manifest (outside the repo, e.g. CI)
KEEP_DIST=/tmp/gama-l-dist bash scripts/release_dry_run.sh
```

Exact checks:

1. `release.py build --out dist-a` and `release.py build --out dist-b`; the
   wheel and sdist SHA-256 must match across both builds.
2. `release.py verify --out dist-a` and `sha256sum -c SHA256SUMS`.
3. `python3 -m venv`, install the wheel with `--no-index --no-deps`, then
   `gama-l --help`, `gama-l run examples/hello.gm`, and
   `gama-l doctor --json --root <temp-root>` (8/8).
4. `twine check` on the wheel when twine is installed.
5. `vsce package` (or cached `npx --no-install @vscode/vsce`) when available.
6. A manifest containing the core version, extension version, artifact sizes
   and hashes, doctor result, and tool matrix.

Limitations:

- `release.py` writes a deterministic *source* archive, not a PEP 517 sdist (it
  has no `PKG-INFO`). `twine check` therefore validates the wheel only; the
  sdist is covered by `release.py verify` and `sha256sum`. The sdist is not
  directly PyPI-uploadable.
- `twine` and `vsce` are optional. Missing tools are reported and skipped; real
  build/checksum/install/CLI/doctor failures exit non-zero.
- Byte-reproducibility is checked within a single run on the same
  Python/toolchain (two builds); builds on different Python versions are not
  compared.
- Artifacts are never written into the repository (the `dist/` directories are
  git-ignored) and nothing is published. `KEEP_DIST` is only for CI/scratch
  space, not for committing.
- `gama-l doctor` runs against the installed wheel by pointing `--root` at a
  temporary directory that symlinks the source `examples/`.

### Release workflow (tag → artifacts; publish is opt-in)

`.github/workflows/release.yml` runs on `v*` tag pushes and on manual dispatch.
It runs the full test suite (Python 3.10 and 3.12), then the release job runs
`scripts/release_dry_run.sh` with `KEEP_DIST` set, validates the workflow YAML,
and uploads `dist` (wheel, sdist, `SHA256SUMS`, `RELEASE.json`, manifest, and
VSIX when packaged) as the `gama-l-release` artifact.

- Concurrency: `release-${{ github.ref }}` with `cancel-in-progress: false`.
- Timeouts: 20 minutes for tests, 30 minutes for release.
- A tag push **never publishes**: it builds and uploads artifacts only.
- Publishing is opt-in via manual dispatch with `publish: true` **and**
  repository secrets. The PyPI step requires `PYPI_API_TOKEN`; the VS Code
  Marketplace step requires `VSCE_PAT`. Without those secrets the publish steps
  are skipped and only artifacts are uploaded.

## Doctor

```bash
python3 gamal2.py doctor
python3 gamal2.py doctor --json
```

Checks Python, module imports, every example parse/run, formatter idempotence,
LSP API (including rename/formatting/code actions), SQL write guard, and a
local package install.

## Formatting

```bash
python3 gamal2.py fmt file.gm          # print
python3 gamal2.py fmt --check file.gm  # rc 1 if unformatted
python3 gamal2.py fmt --write file.gm  # write in place
```

## Opt-in bytecode IR (CLI)

The standalone bytecode IR prototype in `gama_ir.py` is exposed as an **opt-in**
CLI path. The default `run` command keeps using the reference tree-walking
interpreter; nothing about that backend changes.

```bash
python3 gamal2.py ir run FILE.gm        # compile on gama_ir and run on the IR VM
python3 gamal2.py ir compile FILE.gm    # print the instruction listing
python3 gamal2.py ir disasm FILE.gm     # alias for compile
python3 gamal2.py ir compile --json FILE.gm
```

`ir run` parses and type-checks the file with the normal front end, compiles the
checked AST with `gama_ir`, and prints the VM output. `import "lib.gm"`
statements are resolved by the same core loader `run` uses (flattened, with the
project-root bound, cycle detection and depth limit), so imported functions,
constants and records work in the IR backend too. Aliased imports
(`import "lib.gm" as m`) flatten into dotted declarations (`m.fn`, `m.pi`,
`m.Point`, `m.Color`, `m.Option.Some`) and are supported: `m.fn(...)`, `m.pi`,
`m.Color.Red`, payload constructors `m.Option.Some(5)` and positional record
construction `m.Point(1, 2)` (including `let p: m.Point`) resolve against the
same tables as local names. Enum payload declarations, constructors and match
bindings compile the same way locally and through aliases.
`gama_ir.compile_program` on a bare source string has no filesystem context for
imports — use `gama_ir.compile_file` for files. A construct outside the IR
subset exits nonzero with `IRUnsupported: IR does not support ... (line, col)`;
an IR runtime failure reports `IRRuntimeError`; parse/check/file errors keep
their usual message. `compile`/`disasm` print one `index  OPCODE [arg]` line per
instruction (or `{"file": ..., "instructions": [...]}` with `--json`). This is
also reachable as `gama-l ir ...` through the packaged console script.

Exact supported subset:

- Statements: `let`/`:=`, `print`, `if`/`else` (incl. `else if` chains),
  `while`, `for` over list/text, `break`, `continue`, `match` on `Ok`/`Err`
  and enum variants (nullary, one-payload and multi-payload patterns),
  named `fn` definitions, `return` (inside functions), expression statements,
  `prove name = expr` / `verify expr` (hash-chained receipts with optional HMAC
  and the optional persistent JSONL ledger — see below), and `use` as a no-op.
- Expressions: `Int`/`Decimal`/`Text`/`Bool`/`null`, list `[a, b]` and map
  `{k: v}` literals, variables, enum variants and payload constructors
  (`Color.Red`, `Some(5)`, `Option.Some(5)`, `Pair(1, 2)`), `!`/`-`,
  `+ - * / %`, `== != < <= > >=`, short-circuit `&&`/`||`, indexing `base[i]`
  (list/text, reference `int` coercion, negative indices), calls to named
  user-defined functions (recursion and default parameters included), lambda
  expressions `fn(x: Int) -> Int { ... }` compiled to IR function values that
  capture the defining lexical frame, calls through function values (`f(2)`
  for a `Function`-typed binding or parameter), named functions read as values
  (`let g = add`, including namespaced functions), expression callees such as
  IIFEs `(fn ...)(41)` and returned lambdas `makeAdder(5)(37)`, and Result
  propagation
  `expr?` inside a function returning `Result`/`Any` (an `Err` returns from
  that function; `?` elsewhere is `IRUnsupported`), and pipe expressions
  `base | filter <expr> | select a, b | sum a | limit <expr> | count | map a`.
  Pipe stages run left to right on the same value exactly like the reference
  interpreter: `filter`/`limit` sub-expressions are compiled to their own
  code and evaluated with the active row bound (`filter` rows shadow outer
  bindings; `limit` sees the enclosing scope), and `select`/`sum`/`count`/`map`
  use the interpreter's runtime operations, evaluation order and error
  messages. The normal checker rules apply, so column stages (`select`, `sum`,
  `map`, and column-referencing `filter`) need a `Table`/`Any` base; `count`,
  `limit` and constant `filter` work on lists, and the runtime also accepts
  list/text bases from unchecked ASTs. A hand-built AST with an unknown stage
  keyword or missing stage argument is rejected with `IRUnsupported`.
- Builtins: `len`, `text`, `number`, `round`, `Ok`, `Err` only.

Host callables: `run_program(ast, env={...})`, `run_file` and
`compile_file(path, env=...)` accept plain Python callables in the initial
environment. A call evaluates its arguments on the VM, invokes the Python
function with those values and returns the result unchanged as an IR value.
Host callables are ordinary first-class values: they can be aliased, passed as
`Function` arguments, returned from IR functions and called through variables
or expression callees (`(host)(1)`, `makeHost()(2)`). Positional arity is read
from `inspect.signature` when available and checked before arguments run;
signatures that cannot be introspected defer to the call. Host exceptions
become `IRRuntimeError` at the call site's line/col (`host callable NAME raised
TypeError: ...`; an `IRRuntimeError` from the host is re-raised unchanged).
Host calls count against `GAMA_L_MAX_CALL_DEPTH`; Python-side work inside the
callable is not counted by the IR step/expression caps, and no interpreter is
instantiated. Calls are positional only (the language has no keyword
arguments), and host objects are not traversed for methods: bind
`obj.method` itself. Checked sources/files may refer to host names only when
the same `env` is passed to `compile_program`/`compile_file`; those names are
declared as `Function` for checking (runtime precedence still gives a declared
IR function priority over an env binding of the same name).

Not supported (raises `IRUnsupported`; the normal interpreter still supports
them): unresolved `import` nodes (file imports go through
`compile_file`), bare `Attr` expression nodes, record destructuring patterns
(`Point(x, y)` and nested forms are interpreter-only; enum payload variants are
fully supported), list patterns (`[]`, `[a, b]`, `[a, ...rest]`, nested lists),
match guards (`pattern if cond`) and top-level variable patterns (`x if ...`),
and all other builtins (`sql`, `ask`, `split`, `unwrap`,
`wait_user`, mail, remember/recall, ...). First-class calls are checked for
callability and arity before their arguments are evaluated and raise
`IRRuntimeError` with the interpreter's messages. The IR now enforces the same runtime limits as the
interpreter (`GAMA_L_MAX_STEPS`, `GAMA_L_MAX_EXPR_STEPS`, `GAMA_L_LOOP_LIMIT`,
`GAMA_L_MAX_OUTPUT_CHARS`, `GAMA_L_MAX_CALL_DEPTH`) with matching wording and
raises `IRRuntimeError` (a `GamaError`) on exceed. Map
literals are JSON values, so text-keyed maps are not indexable. Successful
programs produce byte-for-byte the same output as the interpreter, and runtime
error messages keep the interpreter's text and line/col.

`prove`/`verify` run on VM proof state, never on a `gamal2.Interpreter`
instance. They reuse the reference module-level canonical-JSON, hash-payload
and ledger-scan helpers, so receipts, `sha256`/`id`/`prev` links, HMAC `sig`
(`GAMA_L_PROOF_KEY`, `GAMA_L_REQUIRE_SIG`) and intent/lease fields
(`GAMA_L_PROOF_INTENT`/`GAMA_L_PROOF_LEASE`, `GAMA_L_REQUIRE_INTENT_LEASE`)
match the interpreter exactly. `GAMA_L_PROOF_LEDGER=<file>` loads and validates
the JSONL chain at startup, appends under one exclusive POSIX `flock` with a
tail re-read, and refuses to extend a malformed/forked ledger; without `fcntl`
cross-process atomicity is best-effort. Proof statements inherit the IR's
general runtime limits exactly like ordinary statements.

## Implemented

- `import "file.gm"` with cycle detection (relative to the importing file)
- module namespaces: `import "lib.gm" as math`, then `math.add(2,3)` and `math.pi`; plain imports still flatten (including top-level `let`); aliased modules expose top-level `fn` definitions, `let` constants, `record` types and `enum` declarations, evaluated at import time, and reject side-effect top-level statements (`use` is allowed as a harmless no-op). Enums are namespaced (`geo.Color`, `geo.Color.Red`); a module that declares at least one explicit `export` exposes only its exported enums through the alias.
- explicit module exports: `export fn`, `export let`, `export record` and `export enum` mark the alias-visible surface; other top-level declarations stay private but remain callable inside the module. A module with at least one `export` exposes only exported names through `import ... as`; a module with no `export` keeps the legacy behaviour (all top-level `fn`/`let`/`record`/`enum` visible). Plain imports always flatten all declarations under their original names for compatibility. `export` is only allowed at top level; duplicate export names and renamed targets that collide with another top-level name are rejected. Renamed exports use `export <decl> ... as <Ident>`: for `fn` the canonical position is in the header after `effects` (`export fn add(a: Int, b: Int) -> Int as plus { ... }`), while `let`/`record`/`enum` use a trailing clause (`export let pi = 3.14 as PI`, `export record Point { ... } as Pt`, `export enum Color { ... } as Hue`). The alias exposes only the new name (`geo.plus`, `geo.PI`, `geo.Pt`, `geo.Hue`); module code keeps using the original names. Selective re-export lists are also supported: `export { add, pi as PI, Point, Color }` names local top-level `fn`/`let`/`record`/`enum` declarations, optionally renaming each entry on the alias surface (`geo.PI`) while the module and plain imports keep the original name (`pi`). Entries are comma- or newline-separated with an optional trailing comma; `export {}` is rejected. A module with a list (alone or mixed with declaration-level exports) exposes exactly the union of its exported names and hides every unlisted top-level declaration. The list may only name declarations of that same module: imported aliases (`import ... as inner`) and plain-imported names are not re-exportable and produce `GamaError`, as do unknown names, duplicate entries, duplicate alias targets, and alias targets that collide with an existing top-level name.
- `let` with optional type annotation, `:=` assignment
- `fn` and SPEC `make` with typed parameters, defaults, return type, `effects { ... }`
- generic type parameters on named functions with type erasure: `fn id<T>(x: T) -> T { return x }`, `fn pair<A, B>(a: A, b: B) -> Text { ... }`. Type parameters are valid in parameter, return and `let` annotations and are erased to `Any` by the checker/runtime, so a call accepts any argument type. The checker infers a call result from the arguments when the return type is a type parameter (`id(5): Int`, `id("x"): Text`) and otherwise erases to `Any`; non-type-parameter positions keep normal arity/type checks. Duplicate type parameters and duplicate parameter names are rejected. Generic functions remain ordinary first-class named functions (recursion, defaults, higher-order use, and IR compilation with erasure all work). Limits: no higher-kinded bounds; union and intersection (`T: A & B`) bounds and named-function bounds are supported, and generic record/enum bounds are covered by their declarations. Generic lambdas are covered separately. The IR backend supports named-function-as-value for plain and generic functions (type erasure applies to the latter)
- explicit call type arguments on local generic functions: `id<Int>(5)`, `pair<Int, Text>(...)`; the checker validates type-argument count and names, argument compatibility uses the explicit bindings, and the runtime/IR ignore them (erasure). Imported/namespaced generic calls and nested type arguments are not recognized; formatter round-trips explicit arguments.
- union generic bounds: `fn add<T: Int | Decimal>(a: T, b: T) -> T`, `fn show<T: Text | Bool>(x: T) -> Text`; inferred/explicit arguments match any alternative, unknown/duplicate alternatives rejected, formatter round-trips `T: A | B`.
- intersection generic bounds: `fn tag<T: Named & Aged>(x: T) -> Text`; every component must be satisfied (records structurally require all fields of each bound), explicit/inferred args checked, formatter round-trips `T: A & B`, IR erases.
- nested record `with` paths: `o with { inner.x = 9 }` deep-copies only records on the updated path; multiple paths merge, original/aliases unchanged; formatter/IR supported.
- generic record/enum bounds: `record Box<T: Text> { value: T }`, `enum Opt<T: Int> { Some(T), None }`; annotation and constructor inference enforce the nominal bound, aliases rename bound refs, formatter/IR erase.
- return type inference when `->` is omitted: all lexical `return` expressions merge (Int/Decimal -> Decimal; Any absorbs; incompatible -> GamaTypeError; no return -> Any; recursion -> Any); inferred results are visible to callers, methods, lambdas and forward references.
- effect inference when `effects` is omitted: direct and callee effects are computed as a least fixpoint; explicit `effects {}` stays an empty contract and explicit sets still reject uncovered effects; inferred sets stay out-of-band so formatting is unchanged.
- LSP inferred signatures: hover/signature help show `(inferred: T)` and inferred effects when annotations are omitted.
- generic records with type erasure: `record Box<T> { value: T }`, `record Pair<A, B> { first: A, second: B }`, constructors `Box(5)` / `Pair("x", 2)`, annotations `Box<Int>` / `Pair<Text, Int>` (arity checked) and explicit constructor arguments `Box<Int>(5)`. Type parameters are usable in field types and method signatures (`fn get(self: Box<T>) -> T`); they erase to `Any`, while concrete fields keep their type. A `let` annotation binds type parameters for the constructor check (`let b: Box<Int> = Box("x")` is rejected, while erased `Box("x")` is accepted), nested refs (`Box<Box<Int>>`) erase to the nominal base, duplicate type parameters/field names are rejected, namespaced records work (`geo.Box<Int>`, `geo.Box(5)`), and formatter/IR erase explicitly (the IR also compiles method-bearing records). Limits: no bounds, no method-level type parameters (methods use only the record's parameters), and no explicit type arguments on namespaced constructors (`geo.Box<Int>(5)`; annotate the binding instead)
- top-level type aliases: `type UserId = Int`, `type IntBox = Box<Int>`, `type PointList = List<Point>`; aliases are transparent and expanded recursively by the checker in `let` annotations, parameters, returns, record fields, enum payloads and generic arguments. Generic targets are arity-checked (`Box<Int, Text>` and bare `Box` are rejected), duplicate/unknown/cyclic aliases and collisions with builtin types/records/enums are rejected, aliases may be exported and renamed (`export type UserId = Int as Id`), exposed through selective `export { ... }` lists and aliased imports (`geo.Id`), the formatter round-trips them and the IR emits a `NOP` (complete erasure). Union aliases `type Scalar = Int | Decimal` / `type MaybeText = Text | Bool` expand to a union: a value is accepted when it is compatible with at least one branch for `let`, parameters, record fields and enum payloads/patterns; a union return accepts any branch. Union-to-union assignment uses branch inclusion, while narrowing a union value back to a single type is rejected (no flow-sensitive narrowing) except for the existing `Int`/`Decimal` interchangeability. Branches may be base types, records, enums, non-union aliases, or generic record refs (erased to the nominal base); unknown/duplicate branches, nested union aliases and union arity misuse are rejected. Generic aliases `type Boxed<T> = Box<T>` are supported with arity checks and substitution (`Boxed<Int>` -> `Box<Int>`, nested uses included); bounds on alias parameters are not supported
- first-class functions and lambdas: `fn(x: Int) -> Int { ... }`, `let f = ...`, `f(41)`, passing functions as values, named functions as values after definition, IIFE `(fn...)(41)`, and recursive lambdas
- `if` / `else` blocks, `return`
- `for x in list/text`, `while cond`, `break`/`continue`, list/text indexing
- `Ok`/`Err` Result values and `match` arms (`Ok(v)`, `Err(e)`, `_`)
- `expr?` Result propagation: unwraps `Ok`, returns `Err` from the enclosing function; the function must return `Result`
- core builtins: `unwrap`, `unwrap_or`, `is_ok`, `is_err`; `split`, `join`, `contains`, `trim`, `upper`, `lower`, `replace`, `starts_with`, `ends_with`; `keys`, `values`, `has`; numeric `abs`, `min`, `max`, `floor`, `ceil`; text/list `reverse`, `slice`, `index_of`, `sort`; conversion `to_int` (`to_text` is already covered by `text`)
- literals: text, int, decimal, bool, null, list, map
- arithmetic/comparison/boolean operators; a newline immediately after a binary operator continues the expression on the next line (statement boundaries still require the operator to be on the same line)
- pipes: `filter`, `select`, `sum`, `limit`, `count`, `map`
- `ask` (offline mock, or OpenAI-compatible `GAMA_L_ASK_URL`), `sql` (demo table, or read-only SQLite via `GAMA_L_DB`), `remember/recall`
- `wait_user` (non-interactive returns `Denied`; falsy)
- guarded `sql_write` (INSERT/UPDATE/DELETE; needs `GAMA_L_ALLOW_WRITE=1`, rejects DDL/stacked statements)
- guarded SMTP `mail` (needs `GAMA_L_MAIL_ENABLED=1`; config via `GAMA_L_SMTP_*`)
- SPEC syntax aliases: `make`, `ask "..." with context`, `remember a.b = v`, `recall a.b`, `wait user "..."`, `use sql`
- `round(value, digits)`
- stdlib semantics: `abs` preserves Int/Decimal; `min`/`max` take one or more numbers; `floor`/`ceil` return Int; `reverse`/`slice` work on Text and List (`slice(seq, start, end)` is half-open with explicit `0 <= start <= end <= len`, no negative indices); `index_of` returns the first index or `-1`; `sort` returns a new ascending list and requires all numbers or all Text (stable, as Python's sort); `to_int` accepts Int, an integral Decimal, or Text of an optional sign plus digits (surrounding whitespace is trimmed)
- `ask` uses `gama_ai` when `GAMA_L_AI_URL` is set; config via `GAMA_L_AI_KEY`,
  `GAMA_L_AI_MODEL`, `GAMA_L_AI_ALLOW_HTTP=1` for local HTTP; HTTPS by default,
  retries 429/5xx, 1 MiB response cap; redirects are refused
- `prove` (hash-chained receipt, optional HMAC via `GAMA_L_PROOF_KEY`, optional persistent JSONL ledger via `GAMA_L_PROOF_LEDGER`, optional intent/lease binding via `GAMA_L_PROOF_INTENT`/`GAMA_L_PROOF_LEASE`) and `verify` (tamper detection, intent/lease mismatch rejection; `GAMA_L_REQUIRE_INTENT_LEASE=1` requires both env values and a match). Concurrent appends to one ledger are serialized (POSIX `flock`) across tail-read, hash and append, so parallel runs extend a single chain; a forked or malformed ledger is refused by `prove` and reported by `verify`
- runtime limits via `GAMA_L_MAX_CALL_DEPTH` (default 200; the effective cap is additionally clamped to `(sys.getrecursionlimit() - 100) // 4`, so an explicit higher value cannot exhaust the CPython stack — deep recursion raises `GamaError`, never `RecursionError`) and `GAMA_L_MAX_OUTPUT_CHARS` (default 1000000)
- optional global step limit via `GAMA_L_MAX_STEPS` (unset, blank, malformed, or negative = disabled): counts every executed statement dispatch (including nested block and function-body statements) plus one tick per `for`/`while` body iteration; expression evaluation and the function call itself are not counted separately. On exceed raises `GamaRuntimeError` with the count, the limit, and the source line/col. `GAMA_L_LOOP_LIMIT` (default 1000000) still bounds `while` iterations independently; a present but blank or non-integer value raises `GamaRuntimeError` instead of leaking `ValueError`
- optional expression step limit via `GAMA_L_MAX_EXPR_STEPS` (unset, blank, malformed, or negative = disabled): counts every `Interpreter.expr` evaluation; nested calls count individually (e.g. `1 + 2` is three steps: the `Binary` plus both `Literal`s). On exceed raises `GamaRuntimeError` with the count, the limit, and the source line/col. It is independent of `GAMA_L_MAX_STEPS`, call depth, output, and loop limits, and remains counted when the cap is disabled
- the standalone bytecode IR (`gama_ir.py`) enforces all five limits with interpreter parity: `STEP`/`LOOP_TICK`/`EXPR_STEP` VM ops reproduce statement-dispatch, loop-iteration and expression-node counting (including default-argument and pipe-stage evaluation and the extra receiver lookup for dotted method calls), `GAMA_L_LOOP_LIMIT` keeps strict integer parsing, and `GAMA_L_MAX_OUTPUT_CHARS`/`GAMA_L_MAX_CALL_DEPTH` reuse the interpreter's defaults and effective call-depth cap. Exceeding a limit raises `IRRuntimeError` (a `GamaError`) with the interpreter's wording and line/col. `RunResult` exposes `steps`, `expr_steps` and `out_chars` so counts can be compared directly. Scope difference: the IR counts compiled statement/expression nodes (one per emitted node, in evaluation order); constructs outside its subset are rejected at compile time, so they are never counted
- standalone bytecode IR prototype (`gama_ir.py`) for a documented subset: statements/expressions, `if`/`else if`, `while`, `for`, list/map literals and indexing, `break`/`continue`, `fn`, `Ok`/`Err` + `match`, `expr?` Result propagation inside a `Result`/`Any` function, and the core builtins `len`/`text`/`number`/`round`; also records/enums, field access and enum variants; plain and aliased imports resolve through `compile_file` (`m.fn(...)`, `m.pi`, `m.Point(1, 2)`, `let p: m.Point`, `m.Color.Red`, payload constructors `m.Option.Some(5)` and enum matches with payload bindings work); record copy/`with` updates (`p with { x = 3 }`) compile to a new same-type value (shallow, declaration order) and in-place field assignment (`p.x := 3`, nested `l.a.x := 5`, namespaced `geo.origin.x := 7`) mutates the receiver in place with interpreter-matching value evaluation order and runtime errors; generic named functions compile like ordinary functions (type parameters erased); pipe expressions (`filter`/`select`/`sum`/`limit`/`count`/`map`) compile to a single `PIPE` instruction that preserves the interpreter's evaluation order, row scoping and error messages; generic enums compile with type erasure (annotations and payload type arguments are not statically enforced by the IR, but enum identity, variants and payload arity are preserved); record methods compile with a per-nominal method table (receiver bound to `self`, defaults, method-to-method calls, recursion, nested receivers `o.inner.m()` and namespaced `geo.Point` methods) and dispatch only after named functions/enum constructors, matching the interpreter; first-class functions/lambdas, named function values and calls through values compile with closure capture, and host-injected Python callables (`run_program`/`run_file`/`compile_file` `env=...`) dispatch with evaluated args, signature-based arity, wrapped host exceptions and normal call-depth accounting; record inheritance (`Child extends Parent`) flattens fields/methods and dispatches dynamically; runtime limits (`GAMA_L_MAX_STEPS`, `GAMA_L_MAX_EXPR_STEPS`, loop/output/call-depth) match the interpreter; opt-in via `gamal2 ir run|compile` (see above), the interpreter and default `run` backend are unchanged
- enums: nullary `enum Color { Red, Green, Blue }` and payload `enum Option { Some(Int, Text), None }`; values are `Enum.Variant` (nullary) or `Variant(args...)` / `Enum.Variant(args...)` (one or more payloads), annotation `let x: Option = Some(5)`, nominal equality (cross-enum `==` is a type error), and `match` arms `Enum.Variant`, `Variant`, `Variant(x)`, `Enum.Variant(x)`, `_`. Payload printing is deterministic: `Enum.Variant(<field-value>)` where the payload is rendered like a record field value (text JSON-quoted, decimals plain fixed form, `null` for null) and nullary variants print `Enum.Variant`. Wrong payload type/arity, unknown or ambiguous constructors, and nullary/payload pattern mismatches raise `GamaTypeError`. Generic enums (`enum Option<T> { Some(T), None }`) are supported: type parameters and nested type refs are valid payload types and annotations (`Option<Int>`, `Option<Option<Int>>`) with recursive arity checking; type parameters erase to `Any` while concrete payloads keep their checks, and namespaced generic enums (`geo.Option<Int>`, `geo.Option.Some(5)`) work. Limits: a bare constructor must be unique across enums (otherwise qualify `Enum.Variant(...)`); nullary variants cannot be called with `()`; payload variants cannot be used as a bare value; enums remain top-level and distinct from records/functions
- nominal record (struct) types: `record Point { x: Int, y: Int }`, positional construction `Point(1, 2)`, named construction `Point { x: 3, y: 4 }` (local record names only), typed fields, dotted access `p.x` (including nested `l.a.x`), in-place field assignment `p.x := 3` / `l.a.x := 5`, copy/`with` updates `p with { x = 3, y = 4 }`, `let p: Point = ...`, and nominal/value equality. Constructors enforce arity, missing/extra/duplicate fields and field types (`GamaTypeError`); unknown field access is rejected statically when the base type is a record. Assignment resolves the longest dotted variable prefix as the receiver and mutates the record in place (aliases observe the update; namespaced constants such as `geo.origin.x := 7` work). `p with { ... }` type-checks the receiver as a record, rejects unknown/duplicate fields and wrong value types, and returns a new record; the receiver and its aliases are untouched. Unchanged nested record values are shallow-copied (shared), so mutating one through the copy is visible through the receiver, while a replaced field is independent. Wrong field/type, a non-record receiver and an unknown base variable are `GamaTypeError`. Printing is deterministic: declaration-order fields, `Name { x: 1, y: "text" }`, decimals with their plain fixed form, and text JSON-quoted. Record destructuring patterns are supported in `match`: `Point(x, y)` binds the flattened declaration/inheritance field order (inherited fields first), `_` skips a field, and nested patterns such as `Outer(Point(a, b), n)` are allowed when the field type is a record. The checker requires a known record name, exact positional arity, nested pattern/field type agreement and unique binding names, and gives each binding its field type in the arm body. Matching is exact-nominal: a child value does not match a parent pattern (it falls through), and `_` is the usual fallback. The formatter round-trips these patterns; the IR backend rejects them with `IRUnsupported`. Records are exported through aliased imports (`geo.Point`) and their type names are namespaced; positional construction `geo.Point(1, 2)` and `with` updates such as `geo.origin with { x = 1 }` work, while named construction with a dotted name (`geo.Point { ... }`) is not accepted by the parser. Gaps: no overloaded/generic methods; inheritance (`record Child extends Parent`) is supported nominally; static functions are declared as `fn Point.origin(...)`, constructor calls are not first-class values, `record` is contextual (not a reserved word), named construction requires a local declaration, the IR backend now supports records/enums for the documented subset (copy/`with` is supported with the same shallow semantics, and in-place field assignment mutates the receiver with interpreter parity), and LSP has record/enum completion, hover, definition, references and rename
- list patterns and match guards: `match xs { [] => ..., [a] => ..., [a, b] => ..., [a, ...rest] => ..., _ => ... }` and `match n { x if x > 0 => ..., _ => ... }`. A fixed list pattern requires the exact length; `...rest` must be last, requires a binding name (or `_` to skip), matches any longer tail and binds a fresh `List`; nested lists (`[[a], b]`), literal items (`0`, `"x"`, `true`, `false`, `null`), `_` and variable items are supported. The checker requires the subject to be `List`/`Any` for list arms, rejects duplicate binding names across items, nested lists and the rest binding, and types item bindings as `Any` and `rest` as `List` (List has no element type parameter); literal matching is type-aware, so `true` does not match `1`. Guards are parsed after the pattern, evaluated after all bindings are installed (so `if len(rest) > 0` works), must be `Bool`, and a false guard falls through to the next arm; a final guard miss raises the normal `match: no arm matched`. A bare lower-case label is a variable pattern binding the whole subject (needed for scalar guards); qualified and upper-case labels keep their enum/record meaning. Record destructuring patterns are unchanged. The formatter round-trips all forms; the IR backend rejects list patterns, guards and variable patterns with `IRUnsupported` (existing Ok/Err/enum arms are unchanged).
- LSP completion/hover for records/enums (fields, variants, declarations) and VS Code grammar/snippets
- reproducible benchmark suite (`scripts/bench.py`) for interpreter vs IR and core operations
- record methods: `record Point { x: Int; y: Int; fn sum(self: Point) -> Int { return self.x + self.y } }`, call `p.sum()`, `self` bound to the receiver, method-to-method calls, defaults, recursion and receiver mutation; per nominal record; methods may use the record's type parameters but cannot declare their own; no overloading; static functions are declared as `fn Point.origin()`; inheritance is supported nominally; the IR compiles these with interpreter parity (including unchecked unknown-method/arity/non-record errors)
- generic lambdas: `fn<T>(x: T) -> T { ... }`, `fn<A, B>(...)`, erasure, higher-order use; explicit type args supported for `let NAME = fn<...>` only
- generic type bounds on named functions: `fn show<T: Text>(x: T) -> Text`, `fn numeric<T: Int>(...)`; one nominal bound per parameter (base type, record or enum), checked at inferred/explicit calls, erased at runtime
- record static functions: `fn Point.origin() -> Point`, `fn Point.of(x: Int) -> Point`, call `Point.origin()`; no receiver/self, namespaced `geo.Point.of`; value methods unchanged
- record inheritance: `record Dog extends Animal { ... }`; inherited fields come first, positional construction takes parent then child fields, methods are inherited with exact-signature overrides, `Child` is a nominal subtype of `Parent` in let/params/returns/fields, and equality stays nominal. Cycles/unknown parents/duplicate fields are rejected; the IR compiles inheritance with interpreter parity (inherited methods, overrides and dynamic dispatch on the actual value). LSP completion/hover/symbols show inherited members and `extends`.
- structural record compatibility (static checks only): a record value `S` is compatible with a target record `T` when `S` declares every `T` field with a compatible type; extra `S` fields are allowed, missing/wrong-type fields are rejected. It applies where nominal compatibility was already used: `let` annotations, function arguments and returns, record field values (positional and named construction, `with`, field assignment), method arguments and generic-constructor checks. Field types recurse (nested records compose) and cyclic record graphs are coinductive. Runtime stays nominal: values keep their record type for printing and equality (`Point3 == Point` remains a static type error), while field access and method calls use the static target type (a `Point3` value held in a `Point` binding runs `Point`'s method). Namespaced records work (`geo.Point3` used as `geo.Point`). Limits: fields only (methods on the value are irrelevant), generic record types are compared after erasure, and there is no coercion or value slicing. The bytecode IR performs no type checking of its own: it accepts programs the checker allows and preserves the same field and static-method dispatch at runtime (so unknown-method/arity errors still surface only at runtime)
- AST-based formatter for the current language (comments and multi-line strings preserved; idempotent; records/enums included; deterministic width wrapping at 88 columns by default, configurable via `fmt --width`, covering expressions, declarations and `fn`/control-flow headers)
- static type errors and static effect violations
- CLI `run`, `check`, `fmt`, `test`, `pkg`, `lsp`, `ir`

## Benchmark (reproducible, offline)

`scripts/bench.py` measures the existing parse/check/run pipeline without
modifying the interpreter, IR prototype, or CLI. It uses only the Python
standard library and never touches the network.

```bash
python3 scripts/bench.py --runs 5 --seed 0
python3 scripts/bench.py --runs 5 --json
```

What is measured (separately, so the numbers add up):

- `parse_ms` — lexer + parser only (`gamal2.parse`).
- `check_ms` — static checker on a pre-parsed AST (parse excluded).
- `run_ms` — tree-walking interpreter on a checked AST (parse/check excluded).
- IR (only for the documented IR subset) — `compile_ms` and `run_ms` are
  reported separately; `speedup` is interpreter `run` median / IR `run` median.
  Programs outside the IR subset are listed with the concrete
  `IRUnsupported` reason, not skipped or faked. Where IR runs, its output is
  compared against the interpreter's output (`outputs_match`).
- Startup/import — inside-child import times for `gamal2` and `gamal2+gama_ir`,
  plus cold Python process startup.
- Memory — `resource.ru_maxrss` from a fresh child after importing both
  modules (process high-water mark, not per-program memory; unavailable on
  platforms without `resource`).
- Formatter — end-to-end `gamal2.fmt_source` on a mixed sample.

Methodology: `--runs N` timed samples per measurement and `--warmup W` untimed
iterations; median/min/max (and raw samples in JSON). Program execution order is
shuffled with `--seed` (results are sorted by name); the same seed/arguments
produce the same structure and IR outputs. JSON includes `schema`, `tool`,
`runs`, `warmup`, `seed`, one entry per program, and explicit `notes`.

Limits (do not overclaim): the programs are small and are not a representative
workload or a general language-performance claim; timings are single-machine
micro-benchmarks and include CPython overhead; the IR is an opt-in prototype,
not the default backend; peak RSS is a whole-process high-water mark; and a
faster number here is not a conformance or correctness result. Use more runs
and a quiet machine for comparisons, and read medians rather than single
samples.

## Tests

```bash
python3 -m unittest discover -s tests -v   # 3470/3470
python3 gamal2.py doctor --json                # 8/8 checks
../../scripts/ci_local.sh                      # py_compile + tests + doctor + VS Code JSON/smoke
```

Conformance coverage: arithmetic/pipes (20 cases), type errors, effect
violations (including `sql.write`/`mail` stubs), HITL returns `Denied`,
proof/tamper, duplicate proof keys, optional HMAC proof signatures (`GAMA_L_PROOF_KEY`; `GAMA_L_REQUIRE_SIG=1` requires them),
imports/cycles/diamonds, multi-table SQLite schema checks, git/path package
dependencies, LSP completion/hover/definition/references/symbols/tokens/code actions/
rename/formatting/didChange, SPEC syntax aliases/defaults, loops/indexing,
records (construction/access/naming/equality/namespaces), Result/match, VS Code LSP client,
HTTP/local registry, AI connector, guarded SQL writes and SMTP mail, offline
`ask` = `Unavailable`, 11 examples, canonical `fmt` check/write, doctor, and
reproducible release metadata.

Conformance: see `CONFORMANCE.md` (SPEC 2.0 T1–T10: **PASS 10/10**, including optional cross-run proof ledger).

## Not implemented yet

- deployed/public package registry (the HTTP server is implemented and testable)
- external intent/lease registry with expiry/revocation (env-bound intent/lease receipts are implemented; there is no external registry)
- full expression-level/bidirectional type inference (return/effect inference and structural compatibility are implemented); higher-kinded bounds
- formatter remaining edges: a comment *before* a binary operator has no legal source position (the grammar only breaks after an operator), so the defensive renderer keeps it after the operator; comments in a commented `fn` effects set use source order (uncommented sets stay sorted); indivisible tokens longer than the width still overflow
- VS Code Marketplace signing/publishing (VSIX packaging is reproducible locally)
- publishing a built release by default: the release workflow has secret-gated,
  opt-in PyPI/Marketplace steps, but tag pushes only build and upload artifacts

The old v0.1 transpiler remains in `gama-lang/` for reference. Do not call this
full 2.0 until the remaining conformance tests and connectors are done.
