jsonatapy 2.2.4__tar.gz → 2.2.5__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 (60) hide show
  1. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/CHANGELOG.md +27 -0
  2. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/Cargo.lock +1 -1
  3. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/Cargo.toml +2 -1
  4. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/PKG-INFO +19 -1
  5. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/README.md +18 -0
  6. jsonatapy-2.2.5/bindings/c/README.md +160 -0
  7. jsonatapy-2.2.5/bindings/c/examples/smoke.c +133 -0
  8. jsonatapy-2.2.5/bindings/c/jsonata.h +104 -0
  9. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/pyproject.toml +1 -1
  10. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/__init__.py +5 -0
  11. jsonatapy-2.2.5/src/capi.rs +543 -0
  12. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/lib.rs +38 -5
  13. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.gitignore +0 -0
  14. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.gitmodules +0 -0
  15. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/.gitignore +0 -0
  16. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/architecture.md +0 -0
  17. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/conventions.md +0 -0
  18. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/core.md +0 -0
  19. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/memory_maintenance.md +0 -0
  20. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/suggested_commands.md +0 -0
  21. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/task_completion.md +0 -0
  22. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/memories/tech_stack.md +0 -0
  23. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/.serena/project.yml +0 -0
  24. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/LICENSE +0 -0
  25. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/benches/evaluator_bench.rs +0 -0
  26. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/examples/evaluator_demo.rs +0 -0
  27. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/examples/parser_demo.rs +0 -0
  28. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/examples/simd_json_bench.rs +0 -0
  29. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/__main__.py +0 -0
  30. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/_cli/__init__.py +0 -0
  31. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/_cli/bindings.py +0 -0
  32. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/_cli/error_format.py +0 -0
  33. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/_cli/mcp_server.py +0 -0
  34. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/_cli/resolve.py +0 -0
  35. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/_cli/run.py +0 -0
  36. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/python/jsonatapy/py.typed +0 -0
  37. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/ast.rs +0 -0
  38. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/ast_transform.rs +0 -0
  39. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/bin/jsonata/bindings.rs +0 -0
  40. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/bin/jsonata/error_format.rs +0 -0
  41. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/bin/jsonata/main.rs +0 -0
  42. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/bin/jsonata/resolve.rs +0 -0
  43. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/compiler.rs +0 -0
  44. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/datetime.rs +0 -0
  45. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/evaluator.rs +0 -0
  46. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/functions.rs +0 -0
  47. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/lazy.rs +0 -0
  48. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/parser/README.md +0 -0
  49. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/parser.rs +0 -0
  50. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/signature.rs +0 -0
  51. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/value.rs +0 -0
  52. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/src/vm.rs +0 -0
  53. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/study/cli_fixtures.json +0 -0
  54. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/study/cli_fixtures_testdata.json +0 -0
  55. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/study/cli_spec.md +0 -0
  56. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/tests/cli_fixtures_test.rs +0 -0
  57. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/tests/cli_test.rs +0 -0
  58. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/tests/datetime_picture_suite.rs +0 -0
  59. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/tests/integration_test.rs +0 -0
  60. {jsonatapy-2.2.4 → jsonatapy-2.2.5}/tests/parent_and_focus_binding_suite.rs +0 -0
@@ -19,6 +19,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
19
 
20
20
  ### Security
21
21
 
22
+ ## [2.2.5] - 2026-07-14
23
+
24
+ ### Added
25
+ - C API (`capi` cargo feature): use the engine from C, C++, or any language with C interop.
26
+ Eight functions, JSON text in/out (`jsonata_compile`, `jsonata_evaluate`, `jsonata_bind_var`,
27
+ `jsonata_free_expr`, `jsonata_free_string`, `jsonata_last_error_message`,
28
+ `jsonata_last_error_code`, `jsonata_version`), thread-local error slot, and engine panics
29
+ caught at the boundary instead of aborting the host process. Ships with a hand-written
30
+ header (`bindings/c/jsonata.h`), build/link/CMake documentation (`bindings/c/README.md`),
31
+ and a CI-gated smoke test compiled as both C and C++. Build with
32
+ `cargo build --release --features capi`.
33
+
34
+ ### Fixed
35
+ - Performance regression in v2.2.4 on small/fast expressions (issue #74): the
36
+ `JSONATAPY_FORCE_TREE_WALKER` test toggle read the environment variable on every
37
+ evaluation (both `evaluate()` and `evaluate_json()`), costing ~100-200ns per call —
38
+ 10-30% of a sub-microsecond expression. The toggle is now a process-wide atomic seeded
39
+ from the environment once at import (whole-process forcing works unchanged) and flippable
40
+ via a private test hook. Small-expression benchmarks recover 5-16%; the remaining few
41
+ percent vs v2.2.3 on tiny payloads is the documented cost of lazy conversion (which makes
42
+ realistic workloads up to 48% faster, see 2.2.4 notes).
43
+
44
+ ### Changed
45
+ - Benchmark tooling: PR benchmark comments and release regression issues now state their
46
+ comparison baseline explicitly (which commit/release, recorded when, on which runner),
47
+ and the vs-jsonata-js comparison is labeled as such.
48
+
22
49
  ## [2.2.4] - 2026-07-13
23
50
 
24
51
  ### Added
@@ -586,7 +586,7 @@ dependencies = [
586
586
 
587
587
  [[package]]
588
588
  name = "jsonata-core"
589
- version = "2.2.4"
589
+ version = "2.2.5"
590
590
  dependencies = [
591
591
  "assert_cmd",
592
592
  "base64",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "jsonata-core"
3
- version = "2.2.4"
3
+ version = "2.2.5"
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"
@@ -63,6 +63,7 @@ simd = ["dep:simd-json"]
63
63
  python = ["dep:pyo3"]
64
64
  cli = ["dep:clap"]
65
65
  bench = [] # exposes _bench facade for Criterion benchmarks
66
+ capi = []
66
67
 
67
68
  [dev-dependencies]
68
69
  criterion = "0.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jsonatapy
3
- Version: 2.2.4
3
+ Version: 2.2.5
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -151,6 +151,24 @@ echo '{"orders":[{"product":"Laptop","price":1200}]}' | jsonatapy 'orders[price
151
151
 
152
152
  See [CLI Reference](docs/cli.md) for the full flag/exit-code contract.
153
153
 
154
+ ## C / C++ quick start
155
+
156
+ The engine exposes a small C ABI (8 functions, JSON text in/out), usable
157
+ from C, C++, or any language with C interop:
158
+
159
+ ```c
160
+ JsonataExpr *expr = jsonata_compile("$sum(items.price)");
161
+ char *result = jsonata_evaluate(expr, "{\"items\":[{\"price\":2},{\"price\":3}]}");
162
+ // result: "5"
163
+ jsonata_free_string(result);
164
+ jsonata_free_expr(expr);
165
+ ```
166
+
167
+ Build with `cargo build --release --features capi` and include
168
+ [`bindings/c/jsonata.h`](bindings/c/jsonata.h). See the
169
+ [C API guide](bindings/c/README.md) for linking (gcc/clang, Makefile,
170
+ CMake), the memory/threading contract, and error handling.
171
+
154
172
  ---
155
173
 
156
174
  ## What is JSONata?
@@ -102,6 +102,24 @@ echo '{"orders":[{"product":"Laptop","price":1200}]}' | jsonatapy 'orders[price
102
102
 
103
103
  See [CLI Reference](docs/cli.md) for the full flag/exit-code contract.
104
104
 
105
+ ## C / C++ quick start
106
+
107
+ The engine exposes a small C ABI (8 functions, JSON text in/out), usable
108
+ from C, C++, or any language with C interop:
109
+
110
+ ```c
111
+ JsonataExpr *expr = jsonata_compile("$sum(items.price)");
112
+ char *result = jsonata_evaluate(expr, "{\"items\":[{\"price\":2},{\"price\":3}]}");
113
+ // result: "5"
114
+ jsonata_free_string(result);
115
+ jsonata_free_expr(expr);
116
+ ```
117
+
118
+ Build with `cargo build --release --features capi` and include
119
+ [`bindings/c/jsonata.h`](bindings/c/jsonata.h). See the
120
+ [C API guide](bindings/c/README.md) for linking (gcc/clang, Makefile,
121
+ CMake), the memory/threading contract, and error handling.
122
+
105
123
  ---
106
124
 
107
125
  ## What is JSONata?
@@ -0,0 +1,160 @@
1
+ # jsonata-core C API
2
+
3
+ Use [JSONata](https://jsonata.org) — the JSON query and transformation
4
+ language — from C, C++, or any language with C interop, backed by
5
+ [jsonata-core](https://github.com/txjmb/jsonata-core), a high-performance
6
+ Rust implementation with 100% reference-suite compatibility against
7
+ jsonata-js.
8
+
9
+ The API is deliberately small: JSON text in, JSON text out, eight
10
+ functions. See [`jsonata.h`](jsonata.h) for the authoritative contract.
11
+
12
+ ```c
13
+ #include "jsonata.h"
14
+ #include <stdio.h>
15
+
16
+ int main(void) {
17
+ JsonataExpr *expr = jsonata_compile("$sum(order.items.(price * qty))");
18
+ if (!expr) {
19
+ char *err = jsonata_last_error_message();
20
+ fprintf(stderr, "compile failed: %s\n", err);
21
+ jsonata_free_string(err);
22
+ return 1;
23
+ }
24
+
25
+ char *result = jsonata_evaluate(expr,
26
+ "{\"order\":{\"items\":[{\"price\":9.99,\"qty\":2},{\"price\":5,\"qty\":1}]}}");
27
+ if (result) {
28
+ printf("total: %s\n", result); /* total: 24.98 */
29
+ jsonata_free_string(result);
30
+ } else {
31
+ char *err = jsonata_last_error_message();
32
+ if (err) { /* error */
33
+ fprintf(stderr, "evaluate failed: %s\n", err);
34
+ jsonata_free_string(err);
35
+ } else { /* JSONata "undefined" — no match */
36
+ printf("no result\n");
37
+ }
38
+ }
39
+
40
+ jsonata_free_expr(expr);
41
+ return 0;
42
+ }
43
+ ```
44
+
45
+ ## Building the shared library
46
+
47
+ Requires a Rust toolchain (rustc 1.70+, [rustup.rs](https://rustup.rs)):
48
+
49
+ ```sh
50
+ git clone https://github.com/txjmb/jsonata-core
51
+ cd jsonata-core
52
+ cargo build --release --features capi
53
+ ```
54
+
55
+ This produces the shared library in `target/release/`:
56
+
57
+ | Platform | Library |
58
+ |---|---|
59
+ | Linux | `libjsonata_core.so` |
60
+ | macOS | `libjsonata_core.dylib` |
61
+ | Windows | `jsonata_core.dll` (+ `jsonata_core.dll.lib` import lib) |
62
+
63
+ The only header you need is `bindings/c/jsonata.h`.
64
+
65
+ ## Compiling and linking
66
+
67
+ **C** (gcc/clang):
68
+
69
+ ```sh
70
+ gcc -Wall -o myapp myapp.c -I/path/to/jsonata-core/bindings/c \
71
+ -L/path/to/jsonata-core/target/release -ljsonata_core
72
+ LD_LIBRARY_PATH=/path/to/jsonata-core/target/release ./myapp
73
+ ```
74
+
75
+ **C++**: the header is C++-safe (`extern "C"` guarded); compile and link
76
+ exactly as above with `g++`/`clang++`.
77
+
78
+ For deployment, install the library somewhere on the loader path (or set
79
+ `rpath`): `-Wl,-rpath,/opt/myapp/lib` at link time avoids
80
+ `LD_LIBRARY_PATH` at run time.
81
+
82
+ **Makefile snippet:**
83
+
84
+ ```make
85
+ JSONATA_DIR := /path/to/jsonata-core
86
+ CFLAGS += -I$(JSONATA_DIR)/bindings/c
87
+ LDFLAGS += -L$(JSONATA_DIR)/target/release -Wl,-rpath,$(JSONATA_DIR)/target/release
88
+ LDLIBS += -ljsonata_core
89
+
90
+ myapp: myapp.o
91
+ $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
92
+ ```
93
+
94
+ **CMake snippet:**
95
+
96
+ ```cmake
97
+ set(JSONATA_DIR /path/to/jsonata-core)
98
+
99
+ add_library(jsonata_core SHARED IMPORTED)
100
+ set_target_properties(jsonata_core PROPERTIES
101
+ IMPORTED_LOCATION ${JSONATA_DIR}/target/release/libjsonata_core.so
102
+ INTERFACE_INCLUDE_DIRECTORIES ${JSONATA_DIR}/bindings/c)
103
+
104
+ target_link_libraries(myapp PRIVATE jsonata_core)
105
+ ```
106
+
107
+ (Optionally wrap the `cargo build` in a CMake `add_custom_command` so the
108
+ Rust library rebuilds with your project.)
109
+
110
+ ## API rules (the short version)
111
+
112
+ 1. **Ownership:** every `char*` the library returns is yours — free it with
113
+ `jsonata_free_string()`. The single exception is `jsonata_version()`
114
+ (static; never free). Never free library strings with your own
115
+ `free()` — allocators may differ.
116
+ 2. **NULL from `jsonata_evaluate` is ambiguous by design:** check
117
+ `jsonata_last_error_message()`. Non-NULL → error. NULL → the JSONata
118
+ result was *undefined* (a normal outcome — e.g. a path that matched
119
+ nothing). This mirrors JSONata semantics, where `undefined` and `null`
120
+ are distinct.
121
+ 3. **Error codes:** `jsonata_last_error_code()` returns the JSONata spec
122
+ code (`"T2002"`, `"S0201"`, `"D3030"`, …) when the failure has one,
123
+ NULL otherwise. Error source positions are not exposed (the engine
124
+ does not currently track them).
125
+ 4. **Threading:** one `JsonataExpr*` per thread — create, use, and free a
126
+ handle on the same thread. Handles are cheap; compile per thread rather
127
+ than sharing. Different threads with their own handles are fine, and
128
+ the error slot is thread-local.
129
+ 5. **Reuse handles:** `jsonata_compile` once, `jsonata_evaluate` many
130
+ times — the expression is JIT-compiled to bytecode on first evaluation
131
+ and reused. Compilation is the expensive step.
132
+ 6. **Variables:** `jsonata_bind_var(expr, "x", "[1,2,3]")` binds `$x` for
133
+ all subsequent evaluations on that handle (values are JSON text;
134
+ re-binding replaces).
135
+ 7. **Panics:** internal engine panics are caught at the boundary and
136
+ surface as errors prefixed `internal error:` — they will not abort
137
+ your process.
138
+
139
+ ## Smoke test
140
+
141
+ [`examples/smoke.c`](examples/smoke.c) exercises the full API surface and
142
+ doubles as usage documentation. Build and run it from the repo root:
143
+
144
+ ```sh
145
+ cargo build --release --features capi
146
+ gcc -Wall -Wextra -Werror -o smoke bindings/c/examples/smoke.c \
147
+ -Ltarget/release -ljsonata_core
148
+ LD_LIBRARY_PATH=target/release ./smoke
149
+ ```
150
+
151
+ CI compiles and runs it (as both C and C++) on every change to the C ABI.
152
+
153
+ ## Related
154
+
155
+ - [jsonatapy](https://pypi.org/project/jsonatapy/) — the Python binding of
156
+ this engine (PyPI, zero runtime dependencies).
157
+ - [`jsonata` CLI](../../docs/) — a jq-style command-line interface to the
158
+ same engine.
159
+ - [jsonata-js](https://github.com/jsonata-js/jsonata) — the reference
160
+ implementation this engine tracks (1682/1682 reference tests passing).
@@ -0,0 +1,133 @@
1
+ /*
2
+ * smoke.c — compile-and-run smoke test for the jsonata-core C ABI.
3
+ *
4
+ * Exercises every function in jsonata.h against the real library and exits
5
+ * non-zero on the first failure. CI builds and runs this on every PR that
6
+ * touches the C ABI; see bindings/c/README.md for the manual build/run
7
+ * commands.
8
+ */
9
+
10
+ #include <stdio.h>
11
+ #include <stdlib.h>
12
+ #include <string.h>
13
+
14
+ #include "../jsonata.h"
15
+
16
+ static int failures = 0;
17
+
18
+ #define CHECK(cond, name) \
19
+ do { \
20
+ if (cond) { \
21
+ printf("PASS %s\n", name); \
22
+ } else { \
23
+ printf("FAIL %s (line %d)\n", name, __LINE__); \
24
+ failures++; \
25
+ } \
26
+ } while (0)
27
+
28
+ static char *take_error(void) { return jsonata_last_error_message(); }
29
+
30
+ int main(void) {
31
+ /* version */
32
+ const char *v = jsonata_version();
33
+ CHECK(v != NULL && strlen(v) > 0, "version non-empty");
34
+
35
+ /* simple path */
36
+ {
37
+ JsonataExpr *e = jsonata_compile("user.name");
38
+ CHECK(e != NULL, "compile simple path");
39
+ char *r = jsonata_evaluate(e, "{\"user\":{\"name\":\"Alice\"}}");
40
+ CHECK(r != NULL && strcmp(r, "\"Alice\"") == 0, "evaluate simple path");
41
+ jsonata_free_string(r);
42
+ jsonata_free_expr(e);
43
+ }
44
+
45
+ /* object construction + arithmetic */
46
+ {
47
+ JsonataExpr *e = jsonata_compile("{\"n\": a + b}");
48
+ char *r = jsonata_evaluate(e, "{\"a\":1,\"b\":2}");
49
+ CHECK(r != NULL && strcmp(r, "{\"n\":3}") == 0, "object result");
50
+ jsonata_free_string(r);
51
+ jsonata_free_expr(e);
52
+ }
53
+
54
+ /* undefined result: NULL with EMPTY error slot */
55
+ {
56
+ JsonataExpr *e = jsonata_compile("missing.path");
57
+ char *r = jsonata_evaluate(e, "{\"a\":1}");
58
+ char *err = take_error();
59
+ CHECK(r == NULL && err == NULL, "undefined -> NULL + empty error");
60
+ jsonata_free_string(err);
61
+ jsonata_free_expr(e);
62
+ }
63
+
64
+ /* parse error: NULL handle + message */
65
+ {
66
+ JsonataExpr *e = jsonata_compile("a.b[");
67
+ char *err = take_error();
68
+ CHECK(e == NULL && err != NULL && strlen(err) > 0,
69
+ "parse error -> NULL + message");
70
+ jsonata_free_string(err);
71
+ }
72
+
73
+ /* coded evaluation error: message + spec code */
74
+ {
75
+ JsonataExpr *e = jsonata_compile("$number(b)");
76
+ char *r = jsonata_evaluate(e, "{\"b\":[1]}");
77
+ char *err = take_error();
78
+ char *code = jsonata_last_error_code();
79
+ CHECK(r == NULL && err != NULL, "eval error -> NULL + message");
80
+ CHECK(code != NULL && strcmp(code, "D3030") == 0,
81
+ "eval error -> spec code D3030");
82
+ jsonata_free_string(err);
83
+ jsonata_free_string(code);
84
+ jsonata_free_expr(e);
85
+ }
86
+
87
+ /* variable binding */
88
+ {
89
+ JsonataExpr *e = jsonata_compile("$sum($xs) + n");
90
+ int rc = jsonata_bind_var(e, "$xs", "[1,2,3]");
91
+ CHECK(rc == 0, "bind_var succeeds");
92
+ char *r = jsonata_evaluate(e, "{\"n\":10}");
93
+ CHECK(r != NULL && strcmp(r, "16") == 0, "evaluate with bound var");
94
+ jsonata_free_string(r);
95
+ jsonata_free_expr(e);
96
+ }
97
+
98
+ /* invalid input JSON: NULL + uncoded message */
99
+ {
100
+ JsonataExpr *e = jsonata_compile("a");
101
+ char *r = jsonata_evaluate(e, "{not json");
102
+ char *err = take_error();
103
+ char *code = jsonata_last_error_code();
104
+ CHECK(r == NULL && err != NULL && strstr(err, "invalid input JSON") != NULL,
105
+ "invalid input JSON -> message");
106
+ CHECK(code == NULL, "invalid input JSON -> no spec code");
107
+ jsonata_free_string(err);
108
+ jsonata_free_string(code);
109
+ jsonata_free_expr(e);
110
+ }
111
+
112
+ /* multibyte UTF-8 round trip */
113
+ {
114
+ JsonataExpr *e = jsonata_compile("$uppercase(name)");
115
+ char *r = jsonata_evaluate(e, "{\"name\":\"h\xC3\xA9llo \xE2\x9C\x93\"}");
116
+ CHECK(r != NULL && strcmp(r, "\"H\xC3\x89LLO \xE2\x9C\x93\"") == 0,
117
+ "multibyte UTF-8 round trip");
118
+ jsonata_free_string(r);
119
+ jsonata_free_expr(e);
120
+ }
121
+
122
+ /* NULL tolerance of free functions */
123
+ jsonata_free_expr(NULL);
124
+ jsonata_free_string(NULL);
125
+ CHECK(1, "free(NULL) is a no-op");
126
+
127
+ if (failures == 0) {
128
+ printf("SMOKE OK (jsonata-core %s)\n", v);
129
+ return 0;
130
+ }
131
+ printf("SMOKE FAILED: %d failure(s)\n", failures);
132
+ return 1;
133
+ }
@@ -0,0 +1,104 @@
1
+ /*
2
+ * jsonata.h — C API for jsonata-core, a high-performance Rust implementation
3
+ * of the JSONata query and transformation language (https://jsonata.org).
4
+ *
5
+ * Implemented by src/capi.rs (build with `cargo build --release --features
6
+ * capi`, link against the produced libjsonata_core.{so,dylib} /
7
+ * jsonata_core.dll). This header is hand-maintained; CI compiles
8
+ * bindings/c/examples/smoke.c against it to keep the two in sync.
9
+ *
10
+ * ── Contract ────────────────────────────────────────────────────────────
11
+ *
12
+ * Strings. All strings crossing the boundary are UTF-8, NUL-terminated.
13
+ * JSON documents and results cross as JSON text; there is no C-visible
14
+ * structured value model. Every char* RETURNED by this library is owned by
15
+ * the caller and must be released with jsonata_free_string() — except
16
+ * jsonata_version(), which returns a static string that must never be
17
+ * freed.
18
+ *
19
+ * Errors. Failures are reported through a thread-local "last error" slot:
20
+ * - jsonata_compile() returns NULL and sets the slot on failure.
21
+ * - jsonata_evaluate() returns NULL in TWO cases, disambiguated by the
22
+ * slot: if jsonata_last_error_message() returns non-NULL it was an
23
+ * error; if it returns NULL the JSONata result was *undefined* (a
24
+ * legitimate result — e.g. a path that matched nothing).
25
+ * - jsonata_bind_var() returns 0 on success, -1 on failure (slot set).
26
+ * Successful calls clear the slot. jsonata_last_error_code() additionally
27
+ * extracts the JSONata specification error code ("T2002", "S0201",
28
+ * "D3030", ...) when the failure has one; errors without a spec code
29
+ * (e.g. invalid input JSON) yield NULL. Error source positions are not
30
+ * available — the engine does not currently track them.
31
+ *
32
+ * Threading. A JsonataExpr* must be created, used, and freed on a single
33
+ * thread (the engine's value model is reference-counted without atomics).
34
+ * Different threads may each use their own handles concurrently; the error
35
+ * slot is thread-local, so per-thread error reporting never races.
36
+ *
37
+ * Panics. Internal engine panics are caught at this boundary and reported
38
+ * as errors (message prefixed "internal error:"); they neither unwind into
39
+ * the caller nor abort the host process.
40
+ */
41
+
42
+ #ifndef JSONATA_CORE_H
43
+ #define JSONATA_CORE_H
44
+
45
+ #ifdef __cplusplus
46
+ extern "C" {
47
+ #endif
48
+
49
+ /* An opaque compiled-expression handle. Not thread-safe; see header notes. */
50
+ typedef struct JsonataExpr JsonataExpr;
51
+
52
+ /*
53
+ * Compile a JSONata expression (UTF-8).
54
+ * Returns a handle, or NULL on failure (check jsonata_last_error_message).
55
+ * Free with jsonata_free_expr().
56
+ */
57
+ JsonataExpr *jsonata_compile(const char *expr_utf8);
58
+
59
+ /*
60
+ * Evaluate the compiled expression against a JSON document (UTF-8 JSON
61
+ * text). Returns the result as JSON text (caller frees with
62
+ * jsonata_free_string), or NULL — meaning *undefined* if
63
+ * jsonata_last_error_message() is NULL, otherwise an error.
64
+ * The handle may be reused for any number of evaluations; the expression
65
+ * is compiled to bytecode lazily on first use.
66
+ */
67
+ char *jsonata_evaluate(JsonataExpr *expr, const char *json_utf8);
68
+
69
+ /*
70
+ * Bind a variable ($name) on the expression from a JSON value (UTF-8 JSON
71
+ * text). Applies to every subsequent jsonata_evaluate() on this handle.
72
+ * A leading '$' in name is accepted and stripped; re-binding an existing
73
+ * name replaces its value. Returns 0 on success, -1 on error (slot set).
74
+ */
75
+ int jsonata_bind_var(JsonataExpr *expr, const char *name,
76
+ const char *json_value_utf8);
77
+
78
+ /* Free a handle returned by jsonata_compile(). NULL is a no-op. */
79
+ void jsonata_free_expr(JsonataExpr *expr);
80
+
81
+ /* Free a string returned by this library. NULL is a no-op. */
82
+ void jsonata_free_string(char *s);
83
+
84
+ /*
85
+ * The last error message on this thread, as a fresh copy (caller frees
86
+ * with jsonata_free_string), or NULL if the last call succeeded.
87
+ */
88
+ char *jsonata_last_error_message(void);
89
+
90
+ /*
91
+ * The JSONata spec code of the last error on this thread ("T2002", ...),
92
+ * as a fresh copy (caller frees with jsonata_free_string), or NULL when
93
+ * there is no error or the error carries no spec code.
94
+ */
95
+ char *jsonata_last_error_code(void);
96
+
97
+ /* The jsonata-core version, e.g. "2.2.4". Static string — do NOT free. */
98
+ const char *jsonata_version(void);
99
+
100
+ #ifdef __cplusplus
101
+ } /* extern "C" */
102
+ #endif
103
+
104
+ #endif /* JSONATA_CORE_H */
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "jsonatapy"
7
- version = "2.2.4"
7
+ version = "2.2.5"
8
8
  description = "High-performance Python/Rust implementation of JSONata query and transformation language"
9
9
  authors = [
10
10
  {name = "txjmb", email = "txjmb@users.noreply.github.com"}
@@ -28,6 +28,11 @@ from ._jsonatapy import (
28
28
  __jsonata_version__,
29
29
  __version__,
30
30
  )
31
+
32
+ # Private test hooks (not public API): runtime toggle forcing the
33
+ # tree-walking evaluator; seeded from JSONATAPY_FORCE_TREE_WALKER at import.
34
+ from ._jsonatapy import _get_force_tree_walker as _get_force_tree_walker
35
+ from ._jsonatapy import _set_force_tree_walker as _set_force_tree_walker
31
36
  from ._jsonatapy import (
32
37
  compile as _compile,
33
38
  )