jsonatapy 2.2.4__tar.gz → 2.2.6__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 (62) hide show
  1. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/CHANGELOG.md +62 -0
  2. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/Cargo.lock +1 -1
  3. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/Cargo.toml +2 -1
  4. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/PKG-INFO +31 -6
  5. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/README.md +30 -5
  6. jsonatapy-2.2.6/bindings/c/README.md +169 -0
  7. jsonatapy-2.2.6/bindings/c/examples/smoke.c +191 -0
  8. jsonatapy-2.2.6/bindings/c/jsonata.h +139 -0
  9. jsonatapy-2.2.6/examples/host_functions.rs +113 -0
  10. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/pyproject.toml +1 -1
  11. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/__init__.py +73 -0
  12. jsonatapy-2.2.6/src/capi.rs +891 -0
  13. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/evaluator.rs +148 -0
  14. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/lib.rs +191 -7
  15. jsonatapy-2.2.6/tests/host_functions_test.rs +188 -0
  16. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.gitignore +0 -0
  17. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.gitmodules +0 -0
  18. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/.gitignore +0 -0
  19. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/architecture.md +0 -0
  20. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/conventions.md +0 -0
  21. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/core.md +0 -0
  22. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/memory_maintenance.md +0 -0
  23. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/suggested_commands.md +0 -0
  24. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/task_completion.md +0 -0
  25. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/tech_stack.md +0 -0
  26. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/project.yml +0 -0
  27. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/LICENSE +0 -0
  28. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/benches/evaluator_bench.rs +0 -0
  29. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/examples/evaluator_demo.rs +0 -0
  30. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/examples/parser_demo.rs +0 -0
  31. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/examples/simd_json_bench.rs +0 -0
  32. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/__main__.py +0 -0
  33. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/_cli/__init__.py +0 -0
  34. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/_cli/bindings.py +0 -0
  35. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/_cli/error_format.py +0 -0
  36. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/_cli/mcp_server.py +0 -0
  37. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/_cli/resolve.py +0 -0
  38. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/_cli/run.py +0 -0
  39. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/py.typed +0 -0
  40. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/ast.rs +0 -0
  41. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/ast_transform.rs +0 -0
  42. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/bin/jsonata/bindings.rs +0 -0
  43. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/bin/jsonata/error_format.rs +0 -0
  44. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/bin/jsonata/main.rs +0 -0
  45. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/bin/jsonata/resolve.rs +0 -0
  46. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/compiler.rs +0 -0
  47. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/datetime.rs +0 -0
  48. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/functions.rs +0 -0
  49. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/lazy.rs +0 -0
  50. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/parser/README.md +0 -0
  51. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/parser.rs +0 -0
  52. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/signature.rs +0 -0
  53. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/value.rs +0 -0
  54. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/vm.rs +0 -0
  55. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/study/cli_fixtures.json +0 -0
  56. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/study/cli_fixtures_testdata.json +0 -0
  57. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/study/cli_spec.md +0 -0
  58. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/tests/cli_fixtures_test.rs +0 -0
  59. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/tests/cli_test.rs +0 -0
  60. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/tests/datetime_picture_suite.rs +0 -0
  61. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/tests/integration_test.rs +0 -0
  62. {jsonatapy-2.2.4 → jsonatapy-2.2.6}/tests/parent_and_focus_binding_suite.rs +0 -0
@@ -19,6 +19,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
19
 
20
20
  ### Security
21
21
 
22
+ ## [2.2.6] - 2026-07-20
23
+
24
+ ### Added
25
+ - Host-callable custom functions (Rust core): `Evaluator::register_fn` and
26
+ `Evaluator::register_fn_override` let a host register native functions callable
27
+ from an expression as `$name(...)` — the equivalent of jsonata-js's
28
+ `registerFunction`. Functions are plain closures
29
+ (`Fn(&[JValue]) -> Result<JValue, EvaluatorError>`) and resolve after the
30
+ expression's own bindings/lambdas and before built-ins. `register_fn` rejects
31
+ collisions with built-ins; `register_fn_override` allows deliberately replacing
32
+ the impure built-ins (`$now`, `$millis`, `$random`, `$eval`) for determinism
33
+ injection or sandboxing. Evaluation stays synchronous. See
34
+ `examples/host_functions.rs` and the Rust crate docs.
35
+ - Host-callable custom functions (Python binding): `JsonataExpression.register(name,
36
+ func)` and `.register_override(name, func)` expose the above to Python. The callable
37
+ receives already-evaluated positional arguments and must return a JSON-compatible
38
+ value synchronously; an `async def` (coroutine) is rejected at call time with
39
+ guidance to await I/O outside jsonata and pass results via `bindings`. Collision and
40
+ compilable-builtin-override rules are validated at `register()` time.
41
+ - Host-callable custom functions (C ABI): `jsonata_register_fn(expr, name, fn, user_data)`
42
+ and `jsonata_register_fn_override(...)` expose the feature to C and any language with C
43
+ interop. The callback receives its arguments as a JSON array string and returns a JSON
44
+ result string (jsonata copies it; the host retains ownership), or NULL to signal an error.
45
+ See `bindings/c/jsonata.h`, `bindings/c/README.md`, and `bindings/c/examples/smoke.c`.
46
+
47
+ ### Changed
48
+
49
+ ### Deprecated
50
+
51
+ ### Removed
52
+
53
+ ### Fixed
54
+
55
+ ### Security
56
+
57
+ ## [2.2.5] - 2026-07-14
58
+
59
+ ### Added
60
+ - C API (`capi` cargo feature): use the engine from C, C++, or any language with C interop.
61
+ Eight functions, JSON text in/out (`jsonata_compile`, `jsonata_evaluate`, `jsonata_bind_var`,
62
+ `jsonata_free_expr`, `jsonata_free_string`, `jsonata_last_error_message`,
63
+ `jsonata_last_error_code`, `jsonata_version`), thread-local error slot, and engine panics
64
+ caught at the boundary instead of aborting the host process. Ships with a hand-written
65
+ header (`bindings/c/jsonata.h`), build/link/CMake documentation (`bindings/c/README.md`),
66
+ and a CI-gated smoke test compiled as both C and C++. Build with
67
+ `cargo build --release --features capi`.
68
+
69
+ ### Fixed
70
+ - Performance regression in v2.2.4 on small/fast expressions (issue #74): the
71
+ `JSONATAPY_FORCE_TREE_WALKER` test toggle read the environment variable on every
72
+ evaluation (both `evaluate()` and `evaluate_json()`), costing ~100-200ns per call —
73
+ 10-30% of a sub-microsecond expression. The toggle is now a process-wide atomic seeded
74
+ from the environment once at import (whole-process forcing works unchanged) and flippable
75
+ via a private test hook. Small-expression benchmarks recover 5-16%; the remaining few
76
+ percent vs v2.2.3 on tiny payloads is the documented cost of lazy conversion (which makes
77
+ realistic workloads up to 48% faster, see 2.2.4 notes).
78
+
79
+ ### Changed
80
+ - Benchmark tooling: PR benchmark comments and release regression issues now state their
81
+ comparison baseline explicitly (which commit/release, recorded when, on which runner),
82
+ and the vs-jsonata-js comparison is labeled as such.
83
+
22
84
  ## [2.2.4] - 2026-07-13
23
85
 
24
86
  ### 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.6"
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.6"
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.6
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -47,19 +47,26 @@ Project-URL: Documentation, https://github.com/txjmb/jsonata-core
47
47
  Project-URL: Homepage, https://github.com/txjmb/jsonata-core
48
48
  Project-URL: Repository, https://github.com/txjmb/jsonata-core
49
49
 
50
- # jsonata-core + jsonatapy
50
+ # jsonata-core (rust) + jsonatapy + jsonata C-ABI library + jsonata cli
51
+ #### Pypi stats
52
+ [![jsonatapy Downloads Last Month](https://assets.piptrends.com/get-last-month-downloads-badge/jsonatapy.svg 'jsonatapy Downloads Last Month by pip Trends')](https://piptrends.com/package/jsonatapy) [![jsonatapy Downloads Last Week](https://assets.piptrends.com/get-last-week-downloads-badge/jsonatapy.svg 'jsonatapy Downloads Last Week by pip Trends')](https://piptrends.com/package/jsonatapy) [![jsonatapy Average Daily Downloads](https://assets.piptrends.com/get-average-downloads-badge/jsonatapy.svg 'jsonatapy Average Daily Downloads by pip Trends')](https://piptrends.com/package/jsonatapy)
51
53
 
52
- High-performance [JSONata](https://jsonata.org/) implementation in Rust, with Python bindings.
54
+ #### Crates.io stats
55
+ ![downloads](https://shieldcn.dev/crates/d/jsonata-core.svg)
56
+
57
+ High-performance [JSONata](https://jsonata.org/) implementation in Rust, with Python binding and C ABI/library. If you use this library, please add a github star!
53
58
 
54
59
  Much of this project was built using Claude Code with significant human oversight. There was no performant
55
60
  JSONata implementation in Python, so the goal was to port JSONata to Rust (with a PyO3 wrapper
56
61
  for Python) and see how fast it could go. The answer: faster than V8 for most expression
57
- workloads, and faster than the next pure-Rust implementation. The rust versions are published on crates.io, and the python wheels on pypi.
62
+ 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++.
58
63
 
59
- 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+ 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.
64
+ 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+ (1682/1682 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.
60
65
 
61
66
  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.1.7 should be compliant with 2.1.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.
62
67
 
68
+ This project currently chooses not to implement async, because it has limited value for most of the most common use-cases. We
69
+
63
70
  [![Crates.io](https://img.shields.io/crates/v/jsonata-core.svg)](https://crates.io/crates/jsonata-core)
64
71
  [![PyPI version](https://badge.fury.io/py/jsonatapy.svg)](https://pypi.org/project/jsonatapy/)
65
72
  [![Python versions](https://img.shields.io/pypi/pyversions/jsonatapy.svg)](https://pypi.org/project/jsonatapy/)
@@ -141,7 +148,7 @@ Supports Python 3.10, 3.11, 3.12, 3.13, 3.14 on Linux, macOS (Intel & ARM), and
141
148
 
142
149
  ## Command-line quick start
143
150
 
144
- Both packages also ship a CLI, `jq`-shaped, with an identical contract:
151
+ Both packages also ship a binary CLI and a Python-based CLI, `jq`-shaped, with an identical contract:
145
152
 
146
153
  ```bash
147
154
  pip install jsonatapy
@@ -151,6 +158,24 @@ echo '{"orders":[{"product":"Laptop","price":1200}]}' | jsonatapy 'orders[price
151
158
 
152
159
  See [CLI Reference](docs/cli.md) for the full flag/exit-code contract.
153
160
 
161
+ ## C / C++ quick start
162
+
163
+ The engine exposes a small C ABI (8 functions, JSON text in/out), usable
164
+ from C, C++, or any language with C interop:
165
+
166
+ ```c
167
+ JsonataExpr *expr = jsonata_compile("$sum(items.price)");
168
+ char *result = jsonata_evaluate(expr, "{\"items\":[{\"price\":2},{\"price\":3}]}");
169
+ // result: "5"
170
+ jsonata_free_string(result);
171
+ jsonata_free_expr(expr);
172
+ ```
173
+
174
+ Build with `cargo build --release --features capi` and include
175
+ [`bindings/c/jsonata.h`](bindings/c/jsonata.h). See the
176
+ [C API guide](bindings/c/README.md) for linking (gcc/clang, Makefile,
177
+ CMake), the memory/threading contract, and error handling.
178
+
154
179
  ---
155
180
 
156
181
  ## What is JSONata?
@@ -1,16 +1,23 @@
1
- # jsonata-core + jsonatapy
1
+ # jsonata-core (rust) + jsonatapy + jsonata C-ABI library + jsonata cli
2
+ #### Pypi stats
3
+ [![jsonatapy Downloads Last Month](https://assets.piptrends.com/get-last-month-downloads-badge/jsonatapy.svg 'jsonatapy Downloads Last Month by pip Trends')](https://piptrends.com/package/jsonatapy) [![jsonatapy Downloads Last Week](https://assets.piptrends.com/get-last-week-downloads-badge/jsonatapy.svg 'jsonatapy Downloads Last Week by pip Trends')](https://piptrends.com/package/jsonatapy) [![jsonatapy Average Daily Downloads](https://assets.piptrends.com/get-average-downloads-badge/jsonatapy.svg 'jsonatapy Average Daily Downloads by pip Trends')](https://piptrends.com/package/jsonatapy)
2
4
 
3
- High-performance [JSONata](https://jsonata.org/) implementation in Rust, with Python bindings.
5
+ #### Crates.io stats
6
+ ![downloads](https://shieldcn.dev/crates/d/jsonata-core.svg)
7
+
8
+ High-performance [JSONata](https://jsonata.org/) implementation in Rust, with Python binding and C ABI/library. If you use this library, please add a github star!
4
9
 
5
10
  Much of this project was built using Claude Code with significant human oversight. There was no performant
6
11
  JSONata implementation in Python, so the goal was to port JSONata to Rust (with a PyO3 wrapper
7
12
  for Python) and see how fast it could go. The answer: faster than V8 for most expression
8
- workloads, and faster than the next pure-Rust implementation. The rust versions are published on crates.io, and the python wheels on pypi.
13
+ 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++.
9
14
 
10
- 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+ 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.
15
+ 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+ (1682/1682 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.
11
16
 
12
17
  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.1.7 should be compliant with 2.1.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.
13
18
 
19
+ This project currently chooses not to implement async, because it has limited value for most of the most common use-cases. We
20
+
14
21
  [![Crates.io](https://img.shields.io/crates/v/jsonata-core.svg)](https://crates.io/crates/jsonata-core)
15
22
  [![PyPI version](https://badge.fury.io/py/jsonatapy.svg)](https://pypi.org/project/jsonatapy/)
16
23
  [![Python versions](https://img.shields.io/pypi/pyversions/jsonatapy.svg)](https://pypi.org/project/jsonatapy/)
@@ -92,7 +99,7 @@ Supports Python 3.10, 3.11, 3.12, 3.13, 3.14 on Linux, macOS (Intel & ARM), and
92
99
 
93
100
  ## Command-line quick start
94
101
 
95
- Both packages also ship a CLI, `jq`-shaped, with an identical contract:
102
+ Both packages also ship a binary CLI and a Python-based CLI, `jq`-shaped, with an identical contract:
96
103
 
97
104
  ```bash
98
105
  pip install jsonatapy
@@ -102,6 +109,24 @@ echo '{"orders":[{"product":"Laptop","price":1200}]}' | jsonatapy 'orders[price
102
109
 
103
110
  See [CLI Reference](docs/cli.md) for the full flag/exit-code contract.
104
111
 
112
+ ## C / C++ quick start
113
+
114
+ The engine exposes a small C ABI (8 functions, JSON text in/out), usable
115
+ from C, C++, or any language with C interop:
116
+
117
+ ```c
118
+ JsonataExpr *expr = jsonata_compile("$sum(items.price)");
119
+ char *result = jsonata_evaluate(expr, "{\"items\":[{\"price\":2},{\"price\":3}]}");
120
+ // result: "5"
121
+ jsonata_free_string(result);
122
+ jsonata_free_expr(expr);
123
+ ```
124
+
125
+ Build with `cargo build --release --features capi` and include
126
+ [`bindings/c/jsonata.h`](bindings/c/jsonata.h). See the
127
+ [C API guide](bindings/c/README.md) for linking (gcc/clang, Makefile,
128
+ CMake), the memory/threading contract, and error handling.
129
+
105
130
  ---
106
131
 
107
132
  ## What is JSONata?
@@ -0,0 +1,169 @@
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. **Host functions:** `jsonata_register_fn(expr, "name", fn, user_data)`
136
+ exposes a C callback to the expression as `$name(...)`. The callback
137
+ receives its arguments as a JSON array string and returns a JSON result
138
+ string that jsonata copies (you keep ownership — do not expect jsonata to
139
+ free it); returning NULL signals an error. A name colliding with a
140
+ built-in is rejected; `jsonata_register_fn_override` replaces a built-in
141
+ deliberately (e.g. a frozen `$now`, or a disabled `$eval`). `user_data`
142
+ must outlive the handle. Registering a host function routes evaluation
143
+ through the tree-walker, like variable bindings.
144
+ 8. **Panics:** internal engine panics are caught at the boundary and
145
+ surface as errors prefixed `internal error:` — they will not abort
146
+ your process.
147
+
148
+ ## Smoke test
149
+
150
+ [`examples/smoke.c`](examples/smoke.c) exercises the full API surface and
151
+ doubles as usage documentation. Build and run it from the repo root:
152
+
153
+ ```sh
154
+ cargo build --release --features capi
155
+ gcc -Wall -Wextra -Werror -o smoke bindings/c/examples/smoke.c \
156
+ -Ltarget/release -ljsonata_core
157
+ LD_LIBRARY_PATH=target/release ./smoke
158
+ ```
159
+
160
+ CI compiles and runs it (as both C and C++) on every change to the C ABI.
161
+
162
+ ## Related
163
+
164
+ - [jsonatapy](https://pypi.org/project/jsonatapy/) — the Python binding of
165
+ this engine (PyPI, zero runtime dependencies).
166
+ - [`jsonata` CLI](../../docs/) — a jq-style command-line interface to the
167
+ same engine.
168
+ - [jsonata-js](https://github.com/jsonata-js/jsonata) — the reference
169
+ implementation this engine tracks (1682/1682 reference tests passing).
@@ -0,0 +1,191 @@
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
+ /*
31
+ * Host function: given the JSON array ["<name>"], returns "hello <name>".
32
+ * Uses a static buffer — valid until the next call, which is all jsonata needs
33
+ * (it copies the result before returning to the expression).
34
+ */
35
+ static char host_buf[256];
36
+ static const char *greet_cb(void *user_data, const char *args_json) {
37
+ (void)user_data;
38
+ const char *open = strchr(args_json, '"');
39
+ if (!open) return NULL;
40
+ const char *start = open + 1;
41
+ const char *end = strchr(start, '"');
42
+ if (!end) return NULL;
43
+ int len = (int)(end - start);
44
+ snprintf(host_buf, sizeof(host_buf), "\"hello %.*s\"", len, start);
45
+ return host_buf;
46
+ }
47
+
48
+ /* Host override for $now(): a frozen timestamp (static string, always valid). */
49
+ static const char *frozen_now_cb(void *user_data, const char *args_json) {
50
+ (void)user_data;
51
+ (void)args_json;
52
+ return "\"2020-01-01T00:00:00.000Z\"";
53
+ }
54
+
55
+ int main(void) {
56
+ /* version */
57
+ const char *v = jsonata_version();
58
+ CHECK(v != NULL && strlen(v) > 0, "version non-empty");
59
+
60
+ /* simple path */
61
+ {
62
+ JsonataExpr *e = jsonata_compile("user.name");
63
+ CHECK(e != NULL, "compile simple path");
64
+ char *r = jsonata_evaluate(e, "{\"user\":{\"name\":\"Alice\"}}");
65
+ CHECK(r != NULL && strcmp(r, "\"Alice\"") == 0, "evaluate simple path");
66
+ jsonata_free_string(r);
67
+ jsonata_free_expr(e);
68
+ }
69
+
70
+ /* object construction + arithmetic */
71
+ {
72
+ JsonataExpr *e = jsonata_compile("{\"n\": a + b}");
73
+ char *r = jsonata_evaluate(e, "{\"a\":1,\"b\":2}");
74
+ CHECK(r != NULL && strcmp(r, "{\"n\":3}") == 0, "object result");
75
+ jsonata_free_string(r);
76
+ jsonata_free_expr(e);
77
+ }
78
+
79
+ /* undefined result: NULL with EMPTY error slot */
80
+ {
81
+ JsonataExpr *e = jsonata_compile("missing.path");
82
+ char *r = jsonata_evaluate(e, "{\"a\":1}");
83
+ char *err = take_error();
84
+ CHECK(r == NULL && err == NULL, "undefined -> NULL + empty error");
85
+ jsonata_free_string(err);
86
+ jsonata_free_expr(e);
87
+ }
88
+
89
+ /* parse error: NULL handle + message */
90
+ {
91
+ JsonataExpr *e = jsonata_compile("a.b[");
92
+ char *err = take_error();
93
+ CHECK(e == NULL && err != NULL && strlen(err) > 0,
94
+ "parse error -> NULL + message");
95
+ jsonata_free_string(err);
96
+ }
97
+
98
+ /* coded evaluation error: message + spec code */
99
+ {
100
+ JsonataExpr *e = jsonata_compile("$number(b)");
101
+ char *r = jsonata_evaluate(e, "{\"b\":[1]}");
102
+ char *err = take_error();
103
+ char *code = jsonata_last_error_code();
104
+ CHECK(r == NULL && err != NULL, "eval error -> NULL + message");
105
+ CHECK(code != NULL && strcmp(code, "D3030") == 0,
106
+ "eval error -> spec code D3030");
107
+ jsonata_free_string(err);
108
+ jsonata_free_string(code);
109
+ jsonata_free_expr(e);
110
+ }
111
+
112
+ /* variable binding */
113
+ {
114
+ JsonataExpr *e = jsonata_compile("$sum($xs) + n");
115
+ int rc = jsonata_bind_var(e, "$xs", "[1,2,3]");
116
+ CHECK(rc == 0, "bind_var succeeds");
117
+ char *r = jsonata_evaluate(e, "{\"n\":10}");
118
+ CHECK(r != NULL && strcmp(r, "16") == 0, "evaluate with bound var");
119
+ jsonata_free_string(r);
120
+ jsonata_free_expr(e);
121
+ }
122
+
123
+ /* invalid input JSON: NULL + uncoded message */
124
+ {
125
+ JsonataExpr *e = jsonata_compile("a");
126
+ char *r = jsonata_evaluate(e, "{not json");
127
+ char *err = take_error();
128
+ char *code = jsonata_last_error_code();
129
+ CHECK(r == NULL && err != NULL && strstr(err, "invalid input JSON") != NULL,
130
+ "invalid input JSON -> message");
131
+ CHECK(code == NULL, "invalid input JSON -> no spec code");
132
+ jsonata_free_string(err);
133
+ jsonata_free_string(code);
134
+ jsonata_free_expr(e);
135
+ }
136
+
137
+ /* multibyte UTF-8 round trip */
138
+ {
139
+ JsonataExpr *e = jsonata_compile("$uppercase(name)");
140
+ char *r = jsonata_evaluate(e, "{\"name\":\"h\xC3\xA9llo \xE2\x9C\x93\"}");
141
+ CHECK(r != NULL && strcmp(r, "\"H\xC3\x89LLO \xE2\x9C\x93\"") == 0,
142
+ "multibyte UTF-8 round trip");
143
+ jsonata_free_string(r);
144
+ jsonata_free_expr(e);
145
+ }
146
+
147
+ /* host function registration + call */
148
+ {
149
+ JsonataExpr *e = jsonata_compile("$greet(name)");
150
+ int rc = jsonata_register_fn(e, "greet", greet_cb, NULL);
151
+ CHECK(rc == 0, "register_fn succeeds");
152
+ char *r = jsonata_evaluate(e, "{\"name\":\"Ada\"}");
153
+ CHECK(r != NULL && strcmp(r, "\"hello Ada\"") == 0, "host function call");
154
+ jsonata_free_string(r);
155
+ jsonata_free_expr(e);
156
+ }
157
+
158
+ /* host function override of an impure built-in ($now) */
159
+ {
160
+ JsonataExpr *e = jsonata_compile("$now()");
161
+ int rc = jsonata_register_fn_override(e, "now", frozen_now_cb, NULL);
162
+ CHECK(rc == 0, "register_fn_override succeeds");
163
+ char *r = jsonata_evaluate(e, "null");
164
+ CHECK(r != NULL && strcmp(r, "\"2020-01-01T00:00:00.000Z\"") == 0,
165
+ "override $now");
166
+ jsonata_free_string(r);
167
+ jsonata_free_expr(e);
168
+ }
169
+
170
+ /* collision with a built-in is rejected */
171
+ {
172
+ JsonataExpr *e = jsonata_compile("$sum(x)");
173
+ int rc = jsonata_register_fn(e, "sum", greet_cb, NULL);
174
+ char *err = take_error();
175
+ CHECK(rc == -1 && err != NULL, "register_fn collision rejected");
176
+ jsonata_free_string(err);
177
+ jsonata_free_expr(e);
178
+ }
179
+
180
+ /* NULL tolerance of free functions */
181
+ jsonata_free_expr(NULL);
182
+ jsonata_free_string(NULL);
183
+ CHECK(1, "free(NULL) is a no-op");
184
+
185
+ if (failures == 0) {
186
+ printf("SMOKE OK (jsonata-core %s)\n", v);
187
+ return 0;
188
+ }
189
+ printf("SMOKE FAILED: %d failure(s)\n", failures);
190
+ return 1;
191
+ }