rjq 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5556b3edad7d8c167e8cc364b1f40f071be01d783125d1a9308bfde9a2eb2884
4
+ data.tar.gz: d40945b36cd710c8d07acf7885c82db3299f6e903830ef16884ec5cc275e8f09
5
+ SHA512:
6
+ metadata.gz: d4046883d388b8e1d7bb169ac8e55c374cb79a010ebb6060707bc40cf91df8dd89085c0597b9a95ced0fdad50bbd279050f3d3c6742eaa18cc9a1a0e0308ccee
7
+ data.tar.gz: 53c472f7c37ead61b345b8168e7495e92a928b9cefe6dba3f9619bca41d41e51ffcc860a9ad4f985dcef84d2ae6fc064ec1e2ba9ca3fef698420bc6ff7a97815
data/ARCHITECTURE.md ADDED
@@ -0,0 +1,38 @@
1
+ # Architecture
2
+
3
+ The execution path is deliberately split into explicit phases:
4
+
5
+ 1. The lexer records token offsets and source names.
6
+ 2. The parser builds filter and module-directive AST nodes.
7
+ 3. `ModuleLoader` resolves dependencies through an injected resolver, evaluates constant metadata, and constructs namespaced definitions.
8
+ 4. `BytecodeCompiler` lowers the executable AST to instructions and constants.
9
+ 5. `SemanticAnalyzer` validates function and builtin names and arities before input is consumed.
10
+ 6. `VM` executes enumerator-backed value streams.
11
+ 7. `Runtime` coordinates incremental input records, input builtins, filenames, line numbers, and output budgets.
12
+ 8. `JSON::Dumper` writes values directly to an IO without recursively building container output strings.
13
+
14
+ ## Streaming model
15
+
16
+ `JSON::InputBuffer` reads fixed-size chunks. The normal parser yields complete top-level values; the stream parser yields path events within containers. `Runtime::InputQueue` adds one-record lookahead for `input` and `inputs`. Owned file handles are closed on completion, failure, and downstream early termination.
17
+
18
+ Array constructors, JSON slurp, raw slurp, sorting, grouping, uniqueness, and other jq aggregation boundaries collect by definition. Generators such as `range`, `recurse`, `repeat`, `while`, `until`, and `inputs` remain lazy so `first` and `limit` apply backpressure.
19
+
20
+ ## Number model
21
+
22
+ `Rjq::Number` retains the original decimal literal and its binary64 value. Exact untouched literals use decimal components for equality and ordering without expanding large exponents. Arithmetic returns computed Ruby numeric values, which the dumper renders separately from untouched lexemes.
23
+
24
+ ## Modules
25
+
26
+ Production code has no fixture-name fallback. `ModuleResolver` accepts explicit paths, canonicalizes real paths, and limits file size. `ModuleLoader` limits dependency depth, detects canonical cycles, caches parsed modules, handles data imports, and applies namespace rewriting to AST calls rather than source strings. Tests inject their own fixture resolver.
27
+
28
+ ## Resource boundaries
29
+
30
+ JSON parsing uses jq's 256-container depth limit. Module bytes and dependency depth are limited. Embedded callers can
31
+ set `max_outputs` and `input_chunk_size`; `input_max_depth` can lower the parser depth limit. `max_number_digits` and
32
+ `max_string_bytes` are optional, unlimited by default, and stop oversized tokens incrementally before numeric
33
+ conversion or decoded-string growth. Public runtime and compiler APIs reject unknown options and invalid limits at
34
+ construction. Filter parsing is capped at 256 nested expressions by default. Continuation-free user calls in tail position use an
35
+ explicit VM trampoline; non-tail calls can be bounded with `max_call_depth`. An optional bytecode-instruction budget is
36
+ charged lazily, and execution limits bypass jq-level error handlers so filters cannot disable host safety policy.
37
+ `max_replay_cache` optionally bounds the memoized values needed to replay effectful index filters.
38
+ Cyclic Ruby values and invalid JSON value types are rejected before copying or output.
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.1.0 - 2026-08-13
6
+
7
+ - Initial release
data/COMPATIBILITY.md ADDED
@@ -0,0 +1,48 @@
1
+ # Compatibility
2
+
3
+ `rjq` targets a practical subset of jq 1.7.1. Compatibility is tested at two levels:
4
+
5
+ - The bundled, adapted `jq.test` and `onig.test` fixtures exercise 447 and 40 cases. Expected JSON is decoded by Ruby's independent stdlib parser and compared without rjq's parser or value comparator. `%%FAIL` cases verify error categories instead of accepting any error.
6
+ - The differential suite invokes an independently installed jq 1.7.1 executable and compares stdout bytes, normalized stderr, exit status, output count, and order. Run it with `JQ_BIN=/path/to/jq-1.7.1 bundle exec rake differential`.
7
+
8
+ The fixture source tag, upstream commit, adaptation marker, and file checksums are recorded in `spec/fixtures/jq/manifest.json`.
9
+
10
+ ## Compatibility areas
11
+
12
+ | Area | Current contract |
13
+ | --- | --- |
14
+ | JSON numbers | Untouched decimal literals retain their lexeme; computed values use binary64-style rendering. |
15
+ | Filter streams | Pipes, commas, branching, interpolation, filter arguments, slices, reduce, foreach, range, recurse, repeat, while, and until preserve multiple outputs. |
16
+ | Errors | Compile, runtime, input parse, `halt`, `halt_error`, and `-e` statuses follow the jq status classes documented below. |
17
+ | Input | Top-level JSON and `--stream` parse incrementally. Multiple files form one logical stream; only slurp and aggregating filters intentionally collect. |
18
+ | Modules | Directives are parsed syntax, paths are canonicalized, fixture resolution is test-only, and traversal, symlink, size, depth, and cycle checks are enforced. |
19
+ | Filter locations | `$__loc__` and disassembly use filter-source positions, including module and interpolation sources. `-f` filenames are reported as canonical absolute paths. Input filenames and line numbers remain separate. |
20
+ | Regex | Ruby's regular-expression engine is used. jq's `m`, `s`, and `p` mode mapping is preserved; advanced Oniguruma behavior can differ. Ruby API callers may set `regexp_timeout` on runtimes that support per-expression timeouts. |
21
+ | Date/time | UTC conversion is host-timezone independent; platform date ranges can still differ. |
22
+ | Math | jq 1.7.1 builtin names and arities are declared. Bessel, fused multiply-add, and IEEE remainder use the platform C math library through `fiddle` when available; scaling uses a portable implementation. |
23
+
24
+ ## Known differences
25
+
26
+ - This is not libjq and does not claim complete source, diagnostic-text, regex-engine, module-layout, or performance compatibility.
27
+ - Regex flag `l`, Unicode offsets, and advanced engine constructs can differ because Ruby Regexp is not jq's bundled Oniguruma build. In particular, zero-width matches on multibyte UTF-8 strings advance by codepoint in rjq and by byte in jq, so `split/2` can contain a different number of empty fields.
28
+ - Ruby Regexp rejects some variable-length lookbehind expressions that jq's Oniguruma accepts. These remain controlled regular-expression errors rather than loading a second native regex engine.
29
+ - `fma` and IEEE remainder require their exact native symbols and raise controlled runtime errors when unavailable. `scalb` and `scalbln` use a portable `ldexp`-based implementation with jq-compatible truncation, saturation, signed zero, and non-finite handling.
30
+ - rjq extensions include `@base32`, `@base32d`, `dateadd`, `datesub`, `ascii`, `to_number`, and compatibility aliases such as `leaf_paths`. They remain callable but are excluded from jq's `builtins` result; embedders can inspect `Rjq::Builtins::EXTENSION_ARITIES`.
31
+ - `get_jq_origin` reports the rjq installation root. Default module search paths are rjq's actual expanded paths, not jq's symbolic `$ORIGIN` entries.
32
+ - JSON object keys supplied through the Ruby API must be strings. Cyclic Ruby arrays and hashes are rejected.
33
+ - `env`, `now`, local time, module loading, file arguments, and diagnostic builtins access process capabilities unless the caller avoids those features or injects controlled options.
34
+
35
+ ## Exit statuses
36
+
37
+ | Condition | Status |
38
+ | --- | ---: |
39
+ | Option or system usage error | 2 |
40
+ | Filter compile error | 3 |
41
+ | `-e` with no output | 4 |
42
+ | Runtime or input JSON error | 5 |
43
+ | `halt` | 0 |
44
+ | `halt_error(n)` | `n` |
45
+
46
+ ## Ruby API values
47
+
48
+ Inputs and outputs are composed of `nil`, booleans, `Numeric`, UTF-8 `String`, `Array`, and `Hash` with string keys. Assignment operations copy affected values. Mutable strings are copied at constant and deep-copy boundaries. A compiled program may be reused sequentially; concurrent reuse has not yet been declared a stable guarantee.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,30 @@
1
+ # Contributing
2
+
3
+ Install dependencies and run the complete local gate:
4
+
5
+ ```sh
6
+ bundle install
7
+ bundle exec rake
8
+ ```
9
+
10
+ When jq 1.7.1 is available, also run the independent byte-level comparison:
11
+
12
+ ```sh
13
+ JQ_BIN=/path/to/jq-1.7.1 bundle exec rake differential
14
+ ```
15
+
16
+ Build and smoke-test the gem before changing packaging:
17
+
18
+ ```sh
19
+ gem build rjq.gemspec
20
+ gem install ./rjq-*.gem
21
+ rjq -nc '{installed: true}'
22
+ ```
23
+
24
+ Changes to semantics should include a focused regression test and, where jq behavior is the contract, a differential case. Avoid deriving both expected and actual values through the same parser or comparator. Preserve partial output and verify the terminal error/status separately.
25
+
26
+ The bundled jq fixtures are adapted regression inputs. If they change, update `spec/fixtures/jq/manifest.json` deliberately and record the upstream tag or commit, checksum, and reason in the change.
27
+
28
+ ## Release
29
+
30
+ Update `Rjq::VERSION` and `CHANGELOG.md` in one reviewed change. After CI passes, create and push a matching `vX.Y.Z` tag. `.github/workflows/release.yml` verifies the tag, reruns the gate, and publishes with RubyGems trusted publishing. The repository and `release.yml` workflow must first be registered as a RubyGems trusted publisher using the `release` environment.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yudai Takada
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # rjq
2
+
3
+ `rjq` is a Ruby JSON processor implementing a practical jq 1.7.1-compatible subset with a command line and Ruby API.
4
+
5
+ The runtime uses its own incremental JSON parser and writer instead of Ruby's `json` library. The gem has no project-specific native extension; Bessel math functions call the platform C math library through `fiddle`.
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add it to your application's Gemfile by executing:
10
+
11
+ ```sh
12
+ bundle add rjq
13
+ ```
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ ```sh
18
+ gem install rjq
19
+ ```
20
+
21
+ From a checkout, install dependencies and run the executable directly:
22
+
23
+ ```sh
24
+ bundle install
25
+ bin/rjq --version
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ Use `rjq` from the command line:
31
+
32
+ ```sh
33
+ rjq '.foo | map(select(.active)) | .[].name' <<'JSON'
34
+ {"foo":[{"name":"Ada","active":true},{"name":"Linus","active":false}]}
35
+ JSON
36
+ ```
37
+
38
+ Useful CLI examples:
39
+
40
+ ```sh
41
+ rjq -c '[range(3)]' <<< 'null'
42
+ rjq -r '.message' <<< '{"message":"hello"}'
43
+ rjq --arg name Ada '{"hello": $name}' <<< 'null'
44
+ rjq -n '[inputs]' <<< $'1\n2\n3'
45
+ ```
46
+
47
+ Use `rjq` as a Ruby library:
48
+
49
+ ```ruby
50
+ require "rjq"
51
+
52
+ Rjq.run(".foo | .[]", {"foo" => [1, 2, 3]}).to_a
53
+ # => [1, 2, 3]
54
+
55
+ # Optional resource limits for untrusted JSON input and filters:
56
+ Rjq.run_stream(".", io: input_io, opts: {
57
+ input_chunk_size: 16_384,
58
+ input_max_depth: 128,
59
+ max_number_digits: 1_000,
60
+ max_string_bytes: 1_048_576,
61
+ max_filter_depth: 128,
62
+ max_call_depth: 64,
63
+ max_instructions: 1_000_000,
64
+ max_replay_cache: 10_000,
65
+ max_outputs: 10_000,
66
+ regexp_timeout: 0.1
67
+ }).to_a
68
+ ```
69
+
70
+ `max_number_digits` counts all digits in a JSON number literal, including fractional and exponent digits.
71
+ `max_string_bytes` limits each decoded JSON string (including object keys) by UTF-8 byte size. Both default to
72
+ `nil` (unlimited) for jq compatibility. Invalid or unknown Ruby API options are rejected before compilation or input
73
+ processing. Passing `nil` for `stderr` or `module_resolver` selects the normal process stderr or default module
74
+ resolver, respectively. Option hashes and their `variables` and `library_path` containers are copied before lazy
75
+ execution begins.
76
+
77
+ Filter nesting defaults to 256. User-function calls in continuation-free tail positions are trampolined, so bounded
78
+ countdown-style recursion does not consume the Ruby stack. `max_call_depth` and `max_instructions` are unlimited by default; the latter counts
79
+ bytecode instructions actually demanded by the consumer. Call and instruction limits are host safety boundaries and
80
+ cannot be caught by jq `try` or `?`. The same three controls are available to the CLI as
81
+ `--max-filter-depth`, `--max-call-depth`, and `--max-instructions`.
82
+ `max_replay_cache` (also available as `--max-replay-cache`) bounds values retained when a generated index filter must
83
+ be replayed for multiple upstream values; it is unlimited by default for jq compatibility.
84
+
85
+ Inspect compiled bytecode:
86
+
87
+ ```ruby
88
+ puts Rjq.compile(".foo | .[]").disasm
89
+ ```
90
+
91
+ ## Compatibility
92
+
93
+ The current implementation passes the bundled, adapted jq 1.7.1 regression fixtures:
94
+
95
+ ```sh
96
+ ruby script/official_compat.rb
97
+ # checked=447 failures=0
98
+
99
+ ruby script/official_compat.rb spec/fixtures/jq/onig.test
100
+ # checked=40 failures=0
101
+ ```
102
+
103
+ The same fixtures are integrated into RSpec under `spec/compat`, so CI runs them as normal tests. Expected JSON uses Ruby's independent stdlib parser and comparator, and `%%FAIL` cases check diagnostic categories. The adapted fixtures remain a broad regression corpus rather than proof of complete jq compatibility.
104
+
105
+ An independent differential suite invokes a checksum-pinned jq 1.7.1 executable and compares stdout bytes, normalized stderr, exit status, output count, and ordering:
106
+
107
+ ```sh
108
+ JQ_BIN=/path/to/jq-1.7.1 bundle exec rake differential
109
+ ```
110
+
111
+ Supported areas include:
112
+
113
+ - jq values, ordering, truthiness, and numeric edge cases
114
+ - incremental JSON parser and direct writer, including jq-style `NaN`, `Infinity`, and `-0`
115
+ - jq-compatible `--stream` input, including close markers, and `--stream-errors` parse-error arrays
116
+ - field/index/slice access, iteration, pipes, commas, conditionals, `try/catch`, labels and breaks
117
+ - bindings, structured bindings, functions, local `def`, filter arguments, and recursive functions
118
+ - path expressions, assignment/update operators, `del`, `getpath`, `setpath`, `delpaths`
119
+ - core, array, string, math, date/time, format, SQL-style, stream, and regex builtins
120
+ - `jq.test` success cases, `%%FAIL` rejection cases, and `onig.test` success cases
121
+
122
+ See [COMPATIBILITY.md](COMPATIBILITY.md) for the compatibility contract, known differences, exit statuses, extensions, and accepted Ruby value types.
123
+
124
+ ## Bytecode VM
125
+
126
+ `Rjq.compile` parses filters into an AST, compiles the executable filter body into a bytecode `Rjq::Program`, then runs that program through `Rjq::VM`.
127
+
128
+ The VM is a stack machine with explicit instructions such as:
129
+
130
+ - `load_input`, `load_const`, `field`, `index_const`, `index_filter`, `slice_const`, `each`
131
+ - `path`, `pipe`, `append`, `array`, `object`, `call`, `unary`, `binary`, `branch`
132
+ - `try`, `reduce`, `foreach`, `label`, `break`, `assign`, `scoped_def`, `recurse`
133
+
134
+ All parsed AST nodes used by the runtime are lowered to bytecode. Path expressions, update assignment,
135
+ `try/catch`, local `def`, recursion, and module metadata are handled by VM instructions and runtime context
136
+ rather than an `eval_node` fallback.
137
+
138
+ The VM stores instruction results as enumerator-backed streams on the stack. `each`, `pipe`, comma-style append,
139
+ branching, binary cross-products, object construction, `reduce`, `foreach`, local function calls, filter arguments,
140
+ recursion, `range`, `repeat`, `while`, `until`, and input builtins preserve continuations lazily. Array constructors,
141
+ slurp, and aggregating builtins collect only at the jq semantic boundaries that require a complete value. Input files
142
+ and stream events are read incrementally and owned file handles close when downstream evaluation stops early.
143
+
144
+ See [ARCHITECTURE.md](ARCHITECTURE.md) for execution phases, number semantics, module resolution, streaming, and resource boundaries.
145
+
146
+ ## Development
147
+
148
+ After checking out the repo, run:
149
+
150
+ ```sh
151
+ bundle install
152
+ bundle exec rake
153
+ ```
154
+
155
+ This runs:
156
+
157
+ - RSpec, including `spec/compat`
158
+ - `script/compat_probe.rb`
159
+
160
+ Individual checks:
161
+
162
+ ```sh
163
+ bundle exec rake spec
164
+ bundle exec rake compat
165
+ bundle exec rake differential
166
+ ruby script/official_compat.rb
167
+ ruby script/official_compat.rb spec/fixtures/jq/onig.test
168
+ ```
169
+
170
+ Run the benchmark suite:
171
+
172
+ ```sh
173
+ bundle exec ruby benchmark/jq_compare.rb
174
+ ```
175
+
176
+ If `jq` is available on `PATH`, the benchmark prints a Markdown comparison table and checks stdout equality. Increase the sample size with:
177
+
178
+ ```sh
179
+ ITERATIONS=50 bundle exec ruby benchmark/jq_compare.rb
180
+ ```
181
+
182
+ GitHub Actions runs Ruby 3.1 through 4.0 plus experimental Ruby head, macOS, Windows, musl, minimum dependencies,
183
+ coverage thresholds, the checksum-pinned jq 1.7.1 differential suite, and a built-gem install smoke test. A scheduled
184
+ workflow records median and p95 process benchmarks and rejects output mismatches.
185
+
186
+ ## Contributing
187
+
188
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ydah/rjq. See [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md).
189
+
190
+ ## License
191
+
192
+ The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
data/SECURITY.md ADDED
@@ -0,0 +1,10 @@
1
+ # Security policy
2
+
3
+ Please report suspected vulnerabilities privately through GitHub's security advisory interface for this repository. Do not open a public issue with exploit details before a fix is available.
4
+
5
+ Supported releases are the current `main` branch and the latest published gem version.
6
+
7
+ Security-sensitive boundaries include module path resolution, symlink and traversal checks, incremental JSON parsing, regex resource consumption, cyclic host values, and process capabilities exposed by file, environment, time, and diagnostic builtins.
8
+
9
+ Current safeguards include canonical module roots, module byte/depth/cycle limits, a 256-level JSON parse limit, cyclic-value rejection, incremental input, optional output budgets, and early file closure. Ruby Regexp can still be exposed to expensive patterns; callers processing untrusted patterns should use process-level time and memory limits.
10
+
data/bin/rjq ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path('../lib', __dir__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+
7
+ require 'rjq/cli'
8
+
9
+ exit Rjq::CLI.new(ARGV, stdin: $stdin, stdout: $stdout, stderr: $stderr).run