jsonatapy 2.2.8__tar.gz → 2.2.10__tar.gz

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.
Files changed (72) hide show
  1. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.gitignore +1 -0
  2. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/architecture.md +6 -3
  3. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/CHANGELOG.md +238 -0
  4. jsonatapy-2.2.10/CONTRIBUTORS.md +2 -0
  5. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/Cargo.lock +32 -13
  6. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/Cargo.toml +3 -2
  7. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/PKG-INFO +27 -6
  8. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/README.md +26 -5
  9. jsonatapy-2.2.10/SECURITY.md +47 -0
  10. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/bindings/c/README.md +7 -1
  11. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/bindings/c/examples/smoke.c +21 -0
  12. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/bindings/c/jsonata.h +13 -0
  13. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/pyproject.toml +2 -2
  14. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/ast_transform.rs +41 -144
  15. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/bin/jsonata/error_format.rs +1 -9
  16. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/bin/jsonata/main.rs +14 -6
  17. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/builtins.rs +188 -256
  18. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/capi.rs +77 -37
  19. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/compiler.rs +12 -17
  20. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/datetime.rs +2 -20
  21. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/evaluator.rs +663 -1287
  22. jsonatapy-2.2.10/src/expression.rs +180 -0
  23. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/functions.rs +128 -290
  24. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/lazy.rs +93 -28
  25. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/lib.rs +56 -85
  26. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/signature.rs +45 -136
  27. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/value.rs +42 -29
  28. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/vm.rs +33 -71
  29. jsonatapy-2.2.10/tests/lambda_closure_suite.rs +586 -0
  30. jsonatapy-2.2.8/src/parser/README.md +0 -321
  31. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.gitmodules +0 -0
  32. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/.gitignore +0 -0
  33. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/conventions.md +0 -0
  34. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/core.md +0 -0
  35. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/memory_maintenance.md +0 -0
  36. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/suggested_commands.md +0 -0
  37. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/task_completion.md +0 -0
  38. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/memories/tech_stack.md +0 -0
  39. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/.serena/project.yml +0 -0
  40. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/LICENSE +0 -0
  41. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/benches/evaluator_bench.rs +0 -0
  42. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/examples/evaluator_demo.rs +0 -0
  43. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/examples/host_functions.rs +0 -0
  44. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/examples/parser_demo.rs +0 -0
  45. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/examples/simd_json_bench.rs +0 -0
  46. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/__init__.py +0 -0
  47. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/__main__.py +0 -0
  48. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/_cli/__init__.py +0 -0
  49. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/_cli/bindings.py +0 -0
  50. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/_cli/error_format.py +0 -0
  51. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/_cli/mcp_server.py +0 -0
  52. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/_cli/resolve.py +0 -0
  53. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/_cli/run.py +0 -0
  54. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/python/jsonatapy/py.typed +0 -0
  55. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/ast.rs +0 -0
  56. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/bin/jsonata/bindings.rs +0 -0
  57. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/bin/jsonata/resolve.rs +0 -0
  58. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/src/parser.rs +0 -0
  59. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/study/cli_fixtures.json +0 -0
  60. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/study/cli_fixtures_testdata.json +0 -0
  61. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/study/cli_spec.md +0 -0
  62. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/cli_fixtures_test.rs +0 -0
  63. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/cli_test.rs +0 -0
  64. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/datetime_picture_suite.rs +0 -0
  65. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/fixtures/builtin_arity.json +0 -0
  66. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/fixtures/builtin_differential.json +0 -0
  67. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/fixtures/builtin_signatures.json +0 -0
  68. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/fixtures/fastpath_differential.json +0 -0
  69. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/fixtures/fastpath_known_divergences.json +0 -0
  70. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/host_functions_test.rs +0 -0
  71. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/integration_test.rs +0 -0
  72. {jsonatapy-2.2.8 → jsonatapy-2.2.10}/tests/parent_and_focus_binding_suite.rs +0 -0
@@ -71,3 +71,4 @@ benchmarks/javascript/package-lock.json
71
71
  # Temporary files
72
72
  *.tmp
73
73
  *.bak
74
+ *.exe
@@ -45,9 +45,12 @@ site (grep `unwrap_or(JValue::Null)` / `JValue::Null =>` near field-access code)
45
45
  ### Compilation pipeline (bytecode fast path)
46
46
  `evaluator::try_compile_expr(ast)` → `CompiledExpr` (IR) → `compiler::BytecodeCompiler::compile`
47
47
  → `BytecodeProgram` (flat `Vec<Instr>`) → `peephole()` folds (`PushData+GetField` →
48
- `GetDataField`, `GetVar+GetField` → `GetVarField`, elides `Not+Not`) → `vm::Vm::new(bc).run(...)`.
49
- `JsonataExpression` (lib.rs) caches `bytecode: OnceCell<Option<BytecodeProgram>>`; all 4 evaluate
50
- entrypoints try the VM first and fall back to the tree-walker.
48
+ `GetDataField`, `GetVar+GetField` → `GetVarField`, elides `Not+Not`) →
49
+ `vm::Vm::with_options(bc, options).run(...)`.
50
+ `JsonataExpression` (lib.rs) caches `bytecode: OnceCell<Option<BytecodeProgram>>`; all 5 evaluate
51
+ entrypoints funnel through `run_eval`, which tries the VM first and falls back to the tree-walker.
52
+ The `vm`/`compiler` modules exist only under the `python`, `capi`, or `bench` features (cfg-gated
53
+ in lib.rs) — a default-feature build, including the Rust CLI, always uses the tree-walker.
51
54
 
52
55
  Key correctness rules baked into the compiler/VM (violate these and specific test-suite groups
53
56
  regress):
@@ -19,6 +19,244 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
19
 
20
20
  ### Security
21
21
 
22
+ ## [2.2.10] - 2026-09-24
23
+
24
+ ### Added
25
+ - `SECURITY.md` security policy: security fixes ship as a new patch release of the latest
26
+ minor line (currently 2.2.x) with no backports, and vulnerabilities are reported privately
27
+ through the repository's Security tab rather than in public issues. (#171)
28
+ - Benchmark tables gain a per-row **jsonata-core (pure Rust)** column, measured by a new
29
+ `benchmarks/rust` harness that evaluates this project's engine as a Rust library with no
30
+ Python boundary. The column previously labelled `jsonatapy (rust)` is relabelled
31
+ `jsonatapy (json I/O)`: it was always the `evaluate_json` path of the same engine, not a
32
+ separate Rust implementation. (#162)
33
+ - The README's performance section now shows an auto-generated chart of the "Realistic
34
+ Workload" benchmarks, regenerated alongside `docs/performance.md` on each release. (#165)
35
+ - `CONTRIBUTORS.md`. (#167)
36
+
37
+ ### Changed
38
+ - Updated Rust dependencies: `simd-json` 0.18.0 → 0.18.1 (#163), `indexmap` 2.14.0 → 2.14.2
39
+ (#168), and `clap` 4.6.6 → 4.6.7, used only by the `jsonata` CLI (#169).
40
+ - CodeQL now runs through GitHub's CodeQL default setup, covering Rust, Python, C/C++,
41
+ JavaScript/TypeScript and GitHub Actions, instead of CodeQL jobs in the Security Scanning
42
+ workflow. (#170, #171)
43
+ - The Security Scanning workflow now audits the resolved `uv.lock` with `pip-audit`, not just
44
+ the packages installed on the runner, and fails on a vulnerable locked dependency. It also
45
+ lists `cargo-audit` warnings such as yanked crates, without failing the build on them. (#170)
46
+
47
+ ### Deprecated
48
+
49
+ ### Removed
50
+
51
+ ### Fixed
52
+ - The default docs URL (`/stable/`) served the previous release's performance page: the
53
+ release job deployed docs from the release tag, which never contains that release's own
54
+ benchmark numbers. Release docs now take `docs/performance.md` from `main` after the
55
+ benchmark job records it, and the docs workflow can republish the released docs line
56
+ without a new release. (#164)
57
+ - Resolved all 18 open CodeQL code-quality alerts in the benchmark and test scripts, and
58
+ implemented the previously stubbed `test_compile_invalid_expression` test. (#170)
59
+
60
+ ### Security
61
+ - `Cargo.lock`: `chacha20` 0.10.1 → 0.10.2. 0.10.1 was yanked; it reaches the build through
62
+ `rand` and was compiled into the 2.2.9 Python wheels and CLI binaries. (#170)
63
+ - `uv.lock`: `anyio` 4.14.1 → 4.15.1 (CVE-2026-63374, CVE-2026-64847, CVE-2026-63349),
64
+ `cryptography` 49.0.0 → 50.0.1 (PYSEC-2026-3552), and `mkdocs-material` 9.7.6 → 9.7.7
65
+ (PYSEC-2026-3864). These are development and documentation dependencies only; the published
66
+ `jsonatapy` package has no Python dependencies and was not affected. (#170)
67
+
68
+ ## [2.2.9] "Perform-ata" - 2026-08-31
69
+
70
+ ### Changed
71
+ - Lambda values now carry their closure directly — `JValue::Lambda(Rc<StoredLambda>)`
72
+ instead of a `lambda_id` name tag resolved through a per-scope side table (#157).
73
+ The `Rc` provides the closure's lifetime, so the side table (`Scope.lambdas`),
74
+ the escape-analysis GC (`extract_lambda_ids`/`collect_lambda_ids` walking every
75
+ block/lambda result, `pop_scope_preserving_lambdas`) and the dual-map handling in
76
+ `unbind`/`clear_current_scope` are gone, along with the O(result size) walk on
77
+ every scope pop. Recursion is late-bound letrec: a closure defined as
78
+ `$f := function ...` records its own name and binds it to itself at each
79
+ invocation, so no `Rc` cycles exist and closures cannot leak (guarded by a
80
+ live-count test). Free-variable capture remains a by-value snapshot at
81
+ definition time, now applied uniformly to lambda-valued variables too.
82
+ Behavioral fixes (all matching jsonata-js, pinned in
83
+ `tests/lambda_closure_suite.rs`): a transform bound to a variable is callable
84
+ and pipeable (`$t := |...|...|; $t(x)`, `x ~> $t`); a partial application
85
+ escaping the scope that defined its target function works; an alias
86
+ (`$g := $f`) survives `$f` being rebound; calling a name rebound to a
87
+ non-function raises T1006 instead of silently invoking the stale lambda; a
88
+ closure captured inside another escaping closure's environment no longer
89
+ dangles. Function equality is now closure identity rather than id-string
90
+ equality. Remaining known divergences from jsonata-js's live-frame capture
91
+ are documented in the same suite's `divergent_from_reference` module.
92
+ - Partial application is a typed representation instead of a stringly-typed
93
+ protocol: `StoredLambda` carries an optional `PartialApplication { target,
94
+ bound_args, placeholder_positions, total_args }`, replacing the
95
+ `"__partial_call:name:bool:n"` marker-string body that was parsed at every
96
+ invocation and the `__bound_arg_N` / `__placeholder_positions` /
97
+ `__total_args` / `__partial_target` values smuggled through `captured_env`.
98
+ A user-defined target is a captured closure (`PartialTarget::Lambda`);
99
+ builtins/host functions stay name-resolved at call time
100
+ (`PartialTarget::Named`), preserving call-time shadowing. Partials no longer
101
+ snapshot the entire environment at creation (the old
102
+ `capture_current_environment()` call was only ever a smuggling container),
103
+ and one behavior corner now matches jsonata-js: rebinding a user function
104
+ after partially applying it no longer changes what the partial calls
105
+ (pinned in `tests/lambda_closure_suite.rs` with eight other partial pins).
106
+
107
+ ### Added
108
+ - `jsonata_set_limits` on the C ABI: wall-clock timeout (D1012), max AST recursion
109
+ depth (D1011), and max sequence length (D2015) — the same three guardrails the
110
+ Python bindings expose. C embedders previously had no way to bound a runaway
111
+ expression at all (the cleanup review's finding); 0 = unlimited, resettable per
112
+ handle. Covered by the C/C++ smoke tests and a Rust-side capi test.
113
+ - `jsonata_core::Expression` — a compile-once API for Rust callers that runs
114
+ compilable expressions on the bytecode VM, exactly like the Python and C
115
+ bindings always have. Until now the VM was unreachable from the pure-Rust
116
+ surface: `Evaluator` always tree-walks, so Rust (and CLI) callers silently
117
+ got the slower path. `Expression::compile(src)?.evaluate(&data)` lowers to
118
+ bytecode lazily and falls back to the tree-walker for non-compilable
119
+ expressions; bindings and host functions still go through `Evaluator`. The
120
+ dispatch now lives in one place (`expression::run_compiled`), shared by the
121
+ Rust API, the Python bindings' `run_eval`, and the C ABI (which previously
122
+ carried a hand-copied duplicate of it), and the CLI uses it when no `--arg`
123
+ bindings are given. The `vm`/`compiler` modules are unconditionally compiled
124
+ again (the cfg gates added earlier in this cycle are gone — the default
125
+ build now has a real consumer).
126
+
127
+ ### Changed
128
+ - Python→Rust data conversion — the cost that dominates `evaluate(dict)` on array-heavy
129
+ inputs — is faster on two fronts. `lazy::convert` now dispatches on the exact type object
130
+ and iterates lists via borrowed references instead of pyo3's instance-check chain
131
+ (subclasses still take the original chain, so semantics, error types included, are
132
+ unchanged; pinned by `tests/python/test_convert_fastpath.py`). And the published wheels
133
+ now use mimalloc as the global allocator (new opt-in `mimalloc` cargo feature, enabled in
134
+ `pyproject.toml`), roughly halving the per-list/per-object allocation cost that JValue's
135
+ Rc-per-container representation makes the conversion floor. Measured together on the
136
+ benchmark suite's shapes: "Nested Array Access" (`data[1][1][1][1]`, the one row where
137
+ jsonata-js was still ahead) drops ~35% (12.0µs → 7.8µs on the dev container), the four
138
+ `lazy_check.py` gate rows drop 10–17%, and string-heavy conversion roughly halves. The
139
+ structural fix for the nested-array row — lazy list views — remains deferred (see the
140
+ 2026-07-12 lazy-views spec's Limitations).
141
+ - The `vm` and `compiler` modules (the bytecode pipeline) are now cfg-gated on the features
142
+ that actually consume them (`python`, `capi`, `bench`) — they were compiled but unreachable
143
+ in a default-feature build, producing 12 permanent dead-code warnings that CI's
144
+ `--all-features` clippy never saw. A default `cargo build` is now warning-clean, and both
145
+ modules carry a header note naming their consumers. List and dict *subclasses* now take the
146
+ boundary converter's fast path too (`PyList_Check`/`PyDict_Check`; iteration was already
147
+ C-level for them, so behavior is unchanged), which let the duplicate slow-path container
148
+ loops be deleted.
149
+
150
+ ### Changed (evaluator internals)
151
+ - The vestigial `*_is_explicit_null` plumbing is gone: since the null/undefined split
152
+ (#32) a runtime `JValue::Null` is always an explicit null, yet the flags were still
153
+ computed and threaded through ~20 signatures — nine arithmetic/comparison wrapper
154
+ methods, the `CompiledExpr::ExplicitNull` variant (now `Literal(Null)`), a
155
+ `PushExplicitNull` instruction, and per-instruction bool pairs on the VM's
156
+ arithmetic/comparison opcodes — with both terminal consumers ignoring them (~200
157
+ lines removed). Arithmetic and ordered comparison now have exactly one
158
+ implementation each, shared by the tree-walker, the compiled path, and the VM; as
159
+ part of that, the compiled/VM paths' T2010/T2009 error texts now match the
160
+ tree-walker's richer messages ("Cannot compare X and Y", operator symbol in T2009)
161
+ instead of the generic "Type mismatch in comparison" — same codes, better text, and
162
+ the two engines no longer disagree. `Evaluator::is_truthy` likewise now delegates to
163
+ the shared `compiled_is_truthy` instead of maintaining a twin (#111 had to be fixed
164
+ in both).
165
+ - More single-definition consolidation: the two verbatim 45-line signature-error
166
+ translation blocks (direct vs TCO lambda invocation) are one `coerce_lambda_args`
167
+ helper; the five copies of jsonata-js's `hofFuncArgs` argument shaping are two
168
+ helpers (`hof_array_call_args` for $map/$filter/$single, `hof_object_call_args` for
169
+ $sift/$each); the six copy-pasted encoding builtin arms share `unary_string_encoding`.
170
+ `signature.rs` dropped its dead `return_type` field and test-only constructors, and
171
+ a builtin signature that fails to parse now panics at cache build instead of leaving
172
+ that builtin silently unvalidated. `ast_transform.rs`'s 157-line module header — a
173
+ stale task journal whose ~20 line references were all wrong — is a 30-line statement
174
+ of the actual recursion/drop invariants, and `push_ast_node_children`'s leaf arm
175
+ lists every variant explicitly instead of `_ => {}`, so a future `AstNode` variant
176
+ cannot silently opt out of the stack-overflow-on-drop guard.
177
+ - Five hand-maintained parallel lists of builtin names (the pure set, the arity table,
178
+ the compilable set, the signature table, and a test's own literal copy) are one
179
+ sorted `builtins::BUILTINS` spec table; `is_pure_builtin`, `is_compilable_builtin`,
180
+ `builtin_arity`, and `signature::builtin_signature` all derive from it, and the
181
+ jsonata-js fixture drift tests still police the data. Adding a builtin is now one
182
+ row plus its dispatch arm. Membership and values are unchanged (the table was
183
+ generated mechanically from the lists it replaces).
184
+ - The bare-`AstNode::Name` arm of `evaluate_internal_impl` — a private field-access
185
+ copy that had drifted (kept nulls, never flattened, no tuple or lazy-dict handling)
186
+ and that instrumentation shows no expression in any suite reaches — now delegates
187
+ to `compiled_field_step`, the semantics owner for a single field step.
188
+
189
+ ### Deprecated
190
+
191
+ ### Removed
192
+ - Repository hygiene: the checked-in `rustup-init.exe` (12.9 MB Windows installer that
193
+ dominated clone size and permanently tripped the file-size lint), the orphaned root
194
+ `node_modules/jsonata/` copy (~830 KB; nothing referenced it — the benchmark harness
195
+ installs its own under `benchmarks/javascript/`, and the reference implementation is the
196
+ `tests/jsonata-js` submodule), and the stale `src/parser/README.md` (every structural
197
+ claim in it — line counts, ranges, even the crate name in its example — was wrong).
198
+ - Dead code that survived the 2.2.8 builtins consolidation: `functions::numeric::{max,
199
+ min, average}` and `functions::object::{keys, lookup}` had no callers and encoded
200
+ pre-#109 semantics the engine no longer has (`Null` instead of `Undefined` on empty
201
+ input, no nested-array recursion) — a trap for anyone fixing a `$max`/`$lookup` bug in
202
+ the wrong place. Their tests, which pinned the wrong behavior, went with them. Also
203
+ removed the unused `datetime::parse_iso8601`. Technically these were `pub` items of the
204
+ crate, so strict semver would call this a breaking change; they were never used by the
205
+ engine itself.
206
+
207
+ ### Fixed
208
+ - Two families of tree-walker drift found by unifying the remaining field-extraction
209
+ loops onto `compiled_field_step` (each pinned on BOTH engines in
210
+ `tests/python/test_field_step_and_arraygroup.py` with jsonata-js reference outputs):
211
+ the single-step-Name fast path skipped null-valued fields and returned Null on empty
212
+ (`p` over `[{"p":null},{"p":2}]` was `2` on the tree-walker, `[null,2]` on the VM and
213
+ in the reference — the engines disagreed); and the `.[...]` array-group constructor
214
+ kept undefined elements as null (`foo.blah.[baz]` gave `[[..],[null],[null]]` instead
215
+ of `[[..],[],[]]`, masked downstream by that null-skip), while its empty results
216
+ conflated "constructed empty array over a single value" (kept: `{"a":1}.[b]` is `[]`)
217
+ with "mapped over an empty array" (undefined: `emptyarr.[b]`). Both tree-walker
218
+ Name-step loops (the fast path and the general step loop's inner loop) now delegate
219
+ to `compiled_field_step`, deleting ~100 lines of drifted copies; the reference
220
+ suite's `array-constructor/case013`/`case014` now pass by the correct route. The
221
+ fast path's tuple branch (a third copy, with the same null-skip drift) is deleted
222
+ outright — tuple streams fall through to the general step loop's single tuple
223
+ implementation, and `$#$i.p` over `[{"p":null},{"p":2}]` is now `[null,2]` on both
224
+ engines, matching the reference.
225
+ - Errors carry their JSONata spec code at the front of the message again: the
226
+ `FunctionError` wrapping used to bury codes behind prose prefixes ("Runtime
227
+ error: D3030: Cannot convert 'x' to number"), so the C ABI, the Rust CLI, and
228
+ the Python CLI each grew a different classifier and disagreed on whether the
229
+ same error was coded. The inner message now passes through unwrapped
230
+ ("D3030: ..."), `EvaluatorError::code()` / `evaluator::error_code_prefix` is
231
+ the one definition of "coded error" (prefix-anchored), and the C ABI and both
232
+ CLIs classify with it — the same failure now presents identically in every
233
+ binary. The C ABI's mid-string code scan is retired (it existed only to see
234
+ through the now-removed prose prefixes).
235
+ - Number stringification now matches jsonata-js exactly (verified against the pinned
236
+ reference with a 307-case randomized differential over scalars, containers,
237
+ prettified output, and `&` concatenation). Three divergences fixed: non-integers
238
+ were truncated to 14 significant digits instead of rounded to 15 (`$string(1/3)`
239
+ was `0.33333333333333`, now `0.333333333333333` — the old formatter counted the
240
+ `0` of a leading `0.` as significant); integer-valued floats above 2^53 printed
241
+ their exact i64 digits instead of the float's shortest round-trip decimal; and
242
+ numbers nested in containers went through serde with different rules again.
243
+ `$string` now stringifies through its own writer implementing the reference's
244
+ replacer (functions → `""`, non-integers → `toPrecision(15)`), and one
245
+ `value::js_number_to_string` defines JS number printing (plain digits in
246
+ [1e-6, 1e21), exponential with `+` outside, `-0` as `0`) for `$string`, concat,
247
+ `Display`, and `$join`. Pinned by `tests/python/test_number_stringification.py`.
248
+ - `$var.field` over an array containing nested-array elements now recurses into
249
+ them like jsonata-js's `lookup`: `($v := [[{"p":1}],{"p":2}]; $v.p)` is `[1,2]`
250
+ (previously `2` — the tree-walker's two-step fast path hand-rolled its mapping
251
+ loop and silently skipped non-object elements; it now delegates to the shared
252
+ field step). Found while consolidating the seven divergent field-extraction
253
+ loops the cleanup review flagged; pinned by `tests/python/test_var_field_nested.py`.
254
+ - Regex literals now honor the `m` (multiline) flag in `$match` and the `~>`
255
+ chain-pipe, matching `$split`/`$replace` and jsonata-js — the two paths
256
+ previously translated only `i` (see `tests/python/test_regex_flags.py`).
257
+
258
+ ### Security
259
+
22
260
  ## [2.2.8] "Conform-ata" - 2026-08-25
23
261
 
24
262
  Primarily a conformance release, with documentation work on the Rust crate alongside it. Most
@@ -0,0 +1,2 @@
1
+ # Contributors
2
+ - [Maria](https://github.com/Tangerinedream13)
@@ -189,9 +189,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
189
189
 
190
190
  [[package]]
191
191
  name = "chacha20"
192
- version = "0.10.1"
192
+ version = "0.10.2"
193
193
  source = "registry+https://github.com/rust-lang/crates.io-index"
194
- checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
194
+ checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
195
195
  dependencies = [
196
196
  "cfg-if",
197
197
  "cpufeatures",
@@ -240,9 +240,9 @@ dependencies = [
240
240
 
241
241
  [[package]]
242
242
  name = "clap"
243
- version = "4.6.6"
243
+ version = "4.6.7"
244
244
  source = "registry+https://github.com/rust-lang/crates.io-index"
245
- checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
245
+ checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
246
246
  dependencies = [
247
247
  "clap_builder",
248
248
  "clap_derive",
@@ -250,9 +250,9 @@ dependencies = [
250
250
 
251
251
  [[package]]
252
252
  name = "clap_builder"
253
- version = "4.6.6"
253
+ version = "4.6.7"
254
254
  source = "registry+https://github.com/rust-lang/crates.io-index"
255
- checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
255
+ checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
256
256
  dependencies = [
257
257
  "anstream",
258
258
  "anstyle",
@@ -262,9 +262,9 @@ dependencies = [
262
262
 
263
263
  [[package]]
264
264
  name = "clap_derive"
265
- version = "4.6.4"
265
+ version = "4.6.7"
266
266
  source = "registry+https://github.com/rust-lang/crates.io-index"
267
- checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
267
+ checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
268
268
  dependencies = [
269
269
  "heck",
270
270
  "proc-macro2",
@@ -544,9 +544,9 @@ dependencies = [
544
544
 
545
545
  [[package]]
546
546
  name = "indexmap"
547
- version = "2.14.0"
547
+ version = "2.14.2"
548
548
  source = "registry+https://github.com/rust-lang/crates.io-index"
549
- checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
549
+ checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
550
550
  dependencies = [
551
551
  "equivalent",
552
552
  "hashbrown 0.17.1",
@@ -586,7 +586,7 @@ dependencies = [
586
586
 
587
587
  [[package]]
588
588
  name = "jsonata-core"
589
- version = "2.2.8"
589
+ version = "2.2.10"
590
590
  dependencies = [
591
591
  "assert_cmd",
592
592
  "base64",
@@ -594,6 +594,7 @@ dependencies = [
594
594
  "clap",
595
595
  "criterion",
596
596
  "indexmap",
597
+ "mimalloc",
597
598
  "num-traits",
598
599
  "percent-encoding",
599
600
  "predicates",
@@ -614,6 +615,15 @@ version = "0.2.186"
614
615
  source = "registry+https://github.com/rust-lang/crates.io-index"
615
616
  checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
616
617
 
618
+ [[package]]
619
+ name = "libmimalloc-sys"
620
+ version = "0.1.49"
621
+ source = "registry+https://github.com/rust-lang/crates.io-index"
622
+ checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9"
623
+ dependencies = [
624
+ "cc",
625
+ ]
626
+
617
627
  [[package]]
618
628
  name = "linux-raw-sys"
619
629
  version = "0.12.1"
@@ -632,6 +642,15 @@ version = "2.8.3"
632
642
  source = "registry+https://github.com/rust-lang/crates.io-index"
633
643
  checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
634
644
 
645
+ [[package]]
646
+ name = "mimalloc"
647
+ version = "0.1.52"
648
+ source = "registry+https://github.com/rust-lang/crates.io-index"
649
+ checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862"
650
+ dependencies = [
651
+ "libmimalloc-sys",
652
+ ]
653
+
635
654
  [[package]]
636
655
  name = "normalize-line-endings"
637
656
  version = "0.3.0"
@@ -1113,9 +1132,9 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
1113
1132
 
1114
1133
  [[package]]
1115
1134
  name = "simd-json"
1116
- version = "0.18.0"
1135
+ version = "0.18.1"
1117
1136
  source = "registry+https://github.com/rust-lang/crates.io-index"
1118
- checksum = "5b4c2bab24c48682e93509b5afc39f1f6ef2fbc1ebccab917c8b0534b3007ae4"
1137
+ checksum = "6ebe141eb4a23ecc4d5de93fa5b139ac65dc07c38037a2fe9a8fb7c9d36bd832"
1119
1138
  dependencies = [
1120
1139
  "halfbrown",
1121
1140
  "ref-cast",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "jsonata-core"
3
- version = "2.2.8"
3
+ version = "2.2.10"
4
4
  edition = "2021"
5
5
  authors = ["txjmb <txjmb@users.noreply.github.com>"]
6
6
  description = "High-performance Rust implementation of JSONata query and transformation language"
@@ -29,7 +29,6 @@ exclude = [
29
29
  "CLAUDE.MD",
30
30
  "pyproject.toml",
31
31
  "uv.lock",
32
- "rustup-init.exe",
33
32
  ]
34
33
 
35
34
  [lib]
@@ -56,6 +55,7 @@ rand = "0.10"
56
55
  stacker = "0.1"
57
56
  simd-json = { version = "0.18", optional = true }
58
57
  clap = { version = "4.6", features = ["derive"], optional = true }
58
+ mimalloc = { version = "0.1.52", optional = true }
59
59
 
60
60
  [features]
61
61
  default = ["simd"]
@@ -64,6 +64,7 @@ python = ["dep:pyo3"]
64
64
  cli = ["dep:clap"]
65
65
  bench = [] # exposes _bench facade for Criterion benchmarks
66
66
  capi = []
67
+ mimalloc = ["dep:mimalloc"]
67
68
 
68
69
  [dev-dependencies]
69
70
  criterion = "0.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jsonatapy
3
- Version: 2.2.8
3
+ Version: 2.2.10
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -66,7 +66,26 @@ Project-URL: Repository, https://github.com/txjmb/jsonata-core
66
66
  [![JSONata conformance](https://img.shields.io/badge/JSONata%20conformance-1686%2F1686-brightgreen.svg)](https://github.com/jsonata-js/jsonata/tree/master/test/test-suite)
67
67
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
68
68
 
69
- > ### ⚠️ Upgrading to 2.2.8 "Conform-ata" — please test before you deploy
69
+ <!-- BEGIN generated performance chart -->
70
+ <!-- Regenerated from the latest benchmark run by
71
+ benchmarks/python/generate_readme_chart.py. Do not hand-edit this block
72
+ or the SVGs it points at; edit the generator instead. -->
73
+ <a href="https://txjmb.github.io/jsonata-core/stable/performance/">
74
+ <picture>
75
+ <source media="(prefers-color-scheme: dark)"
76
+ srcset="https://raw.githubusercontent.com/txjmb/jsonata-core/main/docs/assets/realistic-workload-dark.svg">
77
+ <img width="880"
78
+ alt="Realistic-workload benchmark on a 100-product dataset, lower is better: jsonatapy is 7.5x faster than the jsonata-js reference on the geometric mean of 5 e-commerce queries, and the pure-Rust jsonata-core engine is faster still."
79
+ src="https://raw.githubusercontent.com/txjmb/jsonata-core/main/docs/assets/realistic-workload-light.svg">
80
+ </picture>
81
+ </a>
82
+
83
+ *Click the chart for the full category-by-category tables, including jsonata-python
84
+ and jsonata-rs, which are left off the chart because at 68 ms–9 s they would
85
+ flatten everything else into a sliver.*
86
+ <!-- END generated performance chart -->
87
+
88
+ > ### ⚠️ Upgrading to 2.2.8 "Conform-ata"+ — please test before you deploy
70
89
  >
71
90
  > 2.2.8 is primarily a **conformance** release: it corrects a number of places where this
72
91
  > library disagreed with the jsonata-js reference. These issues were found during routine quality checks and documentation improvements. The test harness was inadvertently missing some edge cases. Many of those corrections **change the
@@ -97,7 +116,7 @@ JSONata implementation in Python, so the goal was to port JSONata to Rust (with
97
116
  for Python) and see how fast it could go. The answer: faster than V8 for most expression
98
117
  workloads, and faster than the next pure-Rust implementation. The rust versions are published on crates.io, and the python wheels on pypi. There is also a command-line binary and Python command-line available (works great with uvx) for use in scripting. The Python library is also usable in a command-line fashion, and a C-compatible library is available for those who want to easily use jsonata in C/C++.
99
118
 
100
- Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ (1686/1686 passing for last build of this project) tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
119
+ Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, expressive and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ (1686/1686 passing for last build of this project) tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
101
120
 
102
121
  Release versions will follow the reference jsonata-js project major and minor release numbers, but not necessarily patches. This will make it easier for adopters of this library to understand each release's JSONata API compatibility. As an example, 2.2.8 should be compliant with 2.2.x jsonata-js tests, but may have fixes specific to this library. If a patch release for jsonata-js is relevant for this project, it will be included in a patch release that may or may not follow the patch numbers of the upstream project. "Major" versions of this library that don't include any updates to follow jsonata-js conformance will be "named" in release notes/changelog.
103
122
 
@@ -137,10 +156,10 @@ let result = Evaluator::new().evaluate(&ast, &data)?;
137
156
  ```toml
138
157
  # Cargo.toml
139
158
  [dependencies]
140
- jsonata-core = "2.2.8" # pure Rust, no Python dependency
159
+ jsonata-core = "2.2.9" # pure Rust, no Python dependency
141
160
 
142
161
  # Optional: disable SIMD for constrained targets
143
- jsonata-core = { version = "2.2.8", default-features = false }
162
+ jsonata-core = { version = "2.2.9", default-features = false }
144
163
  ```
145
164
 
146
165
  ---
@@ -225,7 +244,9 @@ See [official JSONata docs](https://docs.jsonata.org/) for the full language ref
225
244
 
226
245
  ## Performance
227
246
 
228
- `jsonata-core` passes **1682/1682** JSONata reference tests and is the fastest JSONata
247
+
248
+
249
+ `jsonata-core` passes **1686/1686** JSONata reference tests and is the fastest JSONata
229
250
  implementation available in either Rust or Python:
230
251
 
231
252
  - **~6x faster on average** than the JavaScript reference implementation (V8), across all
@@ -17,7 +17,26 @@
17
17
  [![JSONata conformance](https://img.shields.io/badge/JSONata%20conformance-1686%2F1686-brightgreen.svg)](https://github.com/jsonata-js/jsonata/tree/master/test/test-suite)
18
18
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
19
19
 
20
- > ### ⚠️ Upgrading to 2.2.8 "Conform-ata" — please test before you deploy
20
+ <!-- BEGIN generated performance chart -->
21
+ <!-- Regenerated from the latest benchmark run by
22
+ benchmarks/python/generate_readme_chart.py. Do not hand-edit this block
23
+ or the SVGs it points at; edit the generator instead. -->
24
+ <a href="https://txjmb.github.io/jsonata-core/stable/performance/">
25
+ <picture>
26
+ <source media="(prefers-color-scheme: dark)"
27
+ srcset="https://raw.githubusercontent.com/txjmb/jsonata-core/main/docs/assets/realistic-workload-dark.svg">
28
+ <img width="880"
29
+ alt="Realistic-workload benchmark on a 100-product dataset, lower is better: jsonatapy is 7.5x faster than the jsonata-js reference on the geometric mean of 5 e-commerce queries, and the pure-Rust jsonata-core engine is faster still."
30
+ src="https://raw.githubusercontent.com/txjmb/jsonata-core/main/docs/assets/realistic-workload-light.svg">
31
+ </picture>
32
+ </a>
33
+
34
+ *Click the chart for the full category-by-category tables, including jsonata-python
35
+ and jsonata-rs, which are left off the chart because at 68 ms–9 s they would
36
+ flatten everything else into a sliver.*
37
+ <!-- END generated performance chart -->
38
+
39
+ > ### ⚠️ Upgrading to 2.2.8 "Conform-ata"+ — please test before you deploy
21
40
  >
22
41
  > 2.2.8 is primarily a **conformance** release: it corrects a number of places where this
23
42
  > library disagreed with the jsonata-js reference. These issues were found during routine quality checks and documentation improvements. The test harness was inadvertently missing some edge cases. Many of those corrections **change the
@@ -48,7 +67,7 @@ JSONata implementation in Python, so the goal was to port JSONata to Rust (with
48
67
  for Python) and see how fast it could go. The answer: faster than V8 for most expression
49
68
  workloads, and faster than the next pure-Rust implementation. The rust versions are published on crates.io, and the python wheels on pypi. There is also a command-line binary and Python command-line available (works great with uvx) for use in scripting. The Python library is also usable in a command-line fashion, and a C-compatible library is available for those who want to easily use jsonata in C/C++.
50
69
 
51
- Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ (1686/1686 passing for last build of this project) tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
70
+ Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, expressive and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ (1686/1686 passing for last build of this project) tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
52
71
 
53
72
  Release versions will follow the reference jsonata-js project major and minor release numbers, but not necessarily patches. This will make it easier for adopters of this library to understand each release's JSONata API compatibility. As an example, 2.2.8 should be compliant with 2.2.x jsonata-js tests, but may have fixes specific to this library. If a patch release for jsonata-js is relevant for this project, it will be included in a patch release that may or may not follow the patch numbers of the upstream project. "Major" versions of this library that don't include any updates to follow jsonata-js conformance will be "named" in release notes/changelog.
54
73
 
@@ -88,10 +107,10 @@ let result = Evaluator::new().evaluate(&ast, &data)?;
88
107
  ```toml
89
108
  # Cargo.toml
90
109
  [dependencies]
91
- jsonata-core = "2.2.8" # pure Rust, no Python dependency
110
+ jsonata-core = "2.2.9" # pure Rust, no Python dependency
92
111
 
93
112
  # Optional: disable SIMD for constrained targets
94
- jsonata-core = { version = "2.2.8", default-features = false }
113
+ jsonata-core = { version = "2.2.9", default-features = false }
95
114
  ```
96
115
 
97
116
  ---
@@ -176,7 +195,9 @@ See [official JSONata docs](https://docs.jsonata.org/) for the full language ref
176
195
 
177
196
  ## Performance
178
197
 
179
- `jsonata-core` passes **1682/1682** JSONata reference tests and is the fastest JSONata
198
+
199
+
200
+ `jsonata-core` passes **1686/1686** JSONata reference tests and is the fastest JSONata
180
201
  implementation available in either Rust or Python:
181
202
 
182
203
  - **~6x faster on average** than the JavaScript reference implementation (V8), across all
@@ -0,0 +1,47 @@
1
+ # Security Policy
2
+
3
+ This policy covers the **jsonata-core** Rust crate, the **jsonatapy** Python
4
+ package, the `jsonata` command-line tool, and the C API in `bindings/c`, all
5
+ published from this repository.
6
+
7
+ ## Supported Versions
8
+
9
+ Security fixes are released as a new patch version of the latest minor line,
10
+ published to [crates.io](https://crates.io/crates/jsonata-core) and
11
+ [PyPI](https://pypi.org/project/jsonatapy/). Older lines do not receive
12
+ backports; upgrade to the latest release to pick up a fix.
13
+
14
+ | Version | Supported |
15
+ | ------- | --------- |
16
+ | 2.2.x (latest patch) | ✅ |
17
+ | < 2.2 | ❌ |
18
+
19
+ ## Reporting a Vulnerability
20
+
21
+ **Please do not report security vulnerabilities through public GitHub issues,
22
+ discussions, or pull requests.**
23
+
24
+ Report them privately instead: go to the repository's
25
+ [**Security** tab](https://github.com/txjmb/jsonata-core/security) and choose
26
+ **Report a vulnerability**. Only the maintainers can see the report.
27
+
28
+ Please include:
29
+
30
+ - The affected component (Rust crate, Python package, CLI, or C API) and version
31
+ - A minimal reproduction: the JSONata expression and input data that trigger
32
+ the issue
33
+ - What happens (crash, hang, memory growth, wrong result) and what you expected
34
+ - Your assessment of the impact, if you have one
35
+
36
+ What to expect:
37
+
38
+ - We aim to acknowledge your report within 5 business days, and will keep you
39
+ updated as we investigate.
40
+ - If the report is accepted, we fix it in a new patch release, then publish a
41
+ GitHub Security Advisory and request a CVE where appropriate. We coordinate
42
+ the disclosure date with you and credit you in the advisory unless you prefer
43
+ to stay anonymous.
44
+ - If the report is declined, we explain why.
45
+
46
+ Please give us a reasonable chance to release a fix before disclosing the issue
47
+ publicly.
@@ -141,7 +141,13 @@ Rust library rebuilds with your project.)
141
141
  deliberately (e.g. a frozen `$now`, or a disabled `$eval`). `user_data`
142
142
  must outlive the handle. Registering a host function routes evaluation
143
143
  through the tree-walker, like variable bindings.
144
- 8. **Panics:** internal engine panics are caught at the boundary and
144
+ 8. **Guardrails:** `jsonata_set_limits(expr, timeout_ms, max_stack_depth,
145
+ max_sequence_length)` bounds wall-clock time (D1012), AST recursion
146
+ depth (D1011), and query-result sequence length (D2015) for all
147
+ subsequent evaluations on that handle — the same three limits the
148
+ Python bindings expose. 0 means "unlimited" (the default); call again
149
+ to change or reset.
150
+ 9. **Panics:** internal engine panics are caught at the boundary and
145
151
  surface as errors prefixed `internal error:` — they will not abort
146
152
  your process.
147
153
 
@@ -112,6 +112,27 @@ int main(void) {
112
112
  jsonata_free_expr(e);
113
113
  }
114
114
 
115
+ /* evaluation guardrails */
116
+ {
117
+ JsonataExpr *e = jsonata_compile("$map([1..100000], function($x) { $x })");
118
+ CHECK(jsonata_set_limits(NULL, 0, 0, 0) == -1, "set_limits NULL handle -> -1");
119
+ CHECK(jsonata_set_limits(e, 0, 0, 10) == 0, "set_limits succeeds");
120
+ char *r = jsonata_evaluate(e, "{}");
121
+ char *err = take_error();
122
+ char *code = jsonata_last_error_code();
123
+ CHECK(r == NULL && err != NULL, "sequence limit -> NULL + message");
124
+ CHECK(code != NULL && strcmp(code, "D2015") == 0,
125
+ "sequence limit -> spec code D2015");
126
+ jsonata_free_string(err);
127
+ jsonata_free_string(code);
128
+ /* lifting the limit makes the same handle work again */
129
+ CHECK(jsonata_set_limits(e, 0, 0, 0) == 0, "set_limits reset succeeds");
130
+ char *r2 = jsonata_evaluate(e, "{}");
131
+ CHECK(r2 != NULL, "evaluate succeeds after limit reset");
132
+ jsonata_free_string(r2);
133
+ jsonata_free_expr(e);
134
+ }
135
+
115
136
  /* variable binding */
116
137
  {
117
138
  JsonataExpr *e = jsonata_compile("$sum($xs) + n");