Changelog: tramaj-py
On Fri, 25 Sep 2026, by @lucasdicioccio, 972 words, 0 code snippets, 1 links, 0images.
Changelog: tramaj-py
Release history of tramaj-py, the Python implementation, standard library only. Releases happen when there are
major changes rather than on a schedule; each entry below is one version.
Every implementation has its own page like this one, and each page is
tagged so the site builds one Atom feed per implementation:
#changelog #python #tramaj-py
The 0.2.0 entry has breaking changes to numbers: Migrating to integers and floats says what to do about each.
tramaj-py 0.3.02026-10-24
`?shape` and `~name` markers, a symbol or term key of `has`/`lookup` is `NotConcrete`, and parse errors that point at an unclosed opener. On PyPI, as tramaj.
- changed Breaking, symbolic mode only: a symbol or a term given as the key of `has` or `lookup` is `NotConcrete` (v3-symbols section 1.5), where it answered `false` or the fallback, reading an unknown key as a missing one. A concrete key keeps the tolerant behaviour: a missing key, an out-of-range index or a wrong-shaped container still answers `false` or the fallback.
- added `?shape(key, shape)` allocates a structure of symbols with one key and one site (v3-symbols section 1.2, decisions section 19). Each `~name` or `~name(e)` marker in the shape is a symbol `[key, "name"]` or `[key, "name", e]` at the form's site, and `{~name}` is short for `{name: ~name}`. It lowers at parse time, so the AST and the envelope are unchanged. A marker outside a shape or in a key, a bare marker under a lambda in the shape, a marker name used twice, a shape with no marker and a wrong argument count are parse errors. The corpus runner provides the `shape` profile.
- changed An unclosed `(`, `[`, `{`, string or interpolation is reported where the closer was looked for, naming its opener and where it is, instead of mostly at char 0 as `expected an expression`. Every parse error now gives a 1-based line and column after its offset: `at char 8 (line 1, column 9): expected ')' to close the '(' of .div at line 1, column 5`. Every program that parsed still parses to the same AST. `ParseError` gains `offset`, `line`, `column` and `detail`; `str(e)` is the message above.
tramaj-py 0.2.02026-10-09
Language v5: two number types, arithmetic, sorting and number formatting. On PyPI, as tramaj.
- changed Breaking: numbers are two types, an integer and a float (decisions section 18), and they are the two Python types: an `int` is an integer and a `float` a float, in the context and in the result. A literal and a JSON number are typed by their text: `3` is an integer, `3.0` and `3e0` are floats. Integers cover the signed 64-bit range; Python's own are unbounded, so one outside the range is refused and never rounded: a parse error as a literal, a `TypeMismatch` in the context, a `NotRepresentable` as a result.
- changed Breaking: a number keeps its type when written. `str(1.0)` is `1.0` where it was `1`, and `compact_json`, `pretty_json` and the CLI write a float with a fraction or an exponent. `node_from_json` rejects an integer outside the range and a float too large for a double.
- changed Breaking: `eq` never equates an integer with a float, and so neither does `json_equal`. `lt`/`lte`/`gt`/`gte` take two integers or two floats; a mixed pair is a `TypeMismatch`. An array index is an integer: `has` and `lookup` no longer take `1.0` for `1`.
- changed Breaking: `int` and `float` are type primitives and `number` is no longer one (v4-types section 1). `"$term"` is a reserved object key, next to `"$sym"` and `"$type"`. `normalize_number` and `MAX_SAFE_INTEGER` are gone from `jsonval`; `VNumber` is replaced by `VInt` and `VFloat`.
- added The arithmetic profile (reference section 11), ten builtins: `sum`, `product`, `negate`, `inverse`, `quotient`, `floor-quotient`, `modulo`, `floor`, `real` and `round`. It is an option of each evaluation, off by default: `run_program(..., arithmetic=True)`, and `--arithmetic` on the command line. Without it the ten names are unbound. `round` (decisions section 20) gives the nearest integer, ties away from zero, unlike Python's own.
- added Terms: over a symbol an arithmetic builtin builds `{"$term": op, "arguments": [...]}` with its operands as written (v3-symbols section 1.9), and a well-formed term can be seeded back in symbolic mode.
- added `arithmetic_ops` and `deep_arithmetic_ops`, the scope-aware analysis of which arithmetic names a program references free; `python -m tramaj analyze arithmetic` prints the deep one. `parse_json` reads JSON text with each number typed by its text. The `NotRepresentable` error kind.
- added The corpus runner declares `int-float`, `arithmetic`, `int64`, `sort`, `format-number` and `round`, and turns arithmetic on only for the cases that list it.
- added `sort-by(list, fn)` and `sort-by-descending(list, fn)` (decisions section 20): a stable sort by key, in every profile. The key function runs once per element, in index order; the keys of one call are all integers, all floats or all strings, and strings compare by code point.
- changed Breaking: `sort-by` and `sort-by-descending` are special-form names lowering to one new AST constructor, `ast.SortBy`. A program can no longer bind one and call it as an ordinary name, and a wrong argument count is a parse error.
- added `format-number(x, decimals, group)` (decisions section 20), an ordinary builtin of every profile: positional decimal text with 0 to 20 decimals and an optional group separator. It rounds the exact value of the number, ties away from zero, on `fractions.Fraction`, and never writes an exponent or a negative zero.
tramaj-py 0.1.02026-09-27
Python port: parser, evaluator (concrete and symbolic modes), Node JSON codec, static analyses and a python -m tramaj CLI. Standard library only, Python 3.9+.
- added Hand-written port checked against `corpus/cases` by `tests/test_corpus.py`: every case passes in both modes.
- added Object destructuring patterns in `@` bindings and lambda parameters (decisions section 17): a parser lowering to plain `Let`/`Lambda`, checked by shared corpus cases 100-118.