json5-ruby 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 +7 -0
- data/LICENSE.txt +21 -0
- data/NOTICE +6 -0
- data/README.md +161 -0
- data/SECURITY.md +9 -0
- data/docs/conformance-matrix.md +90 -0
- data/docs/conformance-report.md +29 -0
- data/grammar/json5.y +74 -0
- data/lib/json5/ascii_classification.rb +31 -0
- data/lib/json5/diagnostic.rb +70 -0
- data/lib/json5/document.rb +20 -0
- data/lib/json5/document_nodes.rb +99 -0
- data/lib/json5/document_parser.rb +291 -0
- data/lib/json5/ecma_string.rb +106 -0
- data/lib/json5/errors.rb +67 -0
- data/lib/json5/generated_parser.ibex.json +196 -0
- data/lib/json5/generated_parser.rb +10066 -0
- data/lib/json5/input.rb +283 -0
- data/lib/json5/lexer.rb +599 -0
- data/lib/json5/limits.rb +47 -0
- data/lib/json5/line_map.rb +92 -0
- data/lib/json5/number_value.rb +119 -0
- data/lib/json5/object_value.rb +28 -0
- data/lib/json5/parser.rb +301 -0
- data/lib/json5/public_token_names.rb +43 -0
- data/lib/json5/ruby_value_builder.rb +236 -0
- data/lib/json5/token.rb +26 -0
- data/lib/json5/unicode_tables.rb +42 -0
- data/lib/json5/version.rb +7 -0
- data/lib/json5.rb +47 -0
- data/tool/sources.yml +21 -0
- data/tool/unicode/README.md +19 -0
- data/tool/unicode/UNICODE-LICENSE.txt +39 -0
- data/tool/unicode/checksums.txt +1 -0
- metadata +91 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 54db9905ae19b2ec82f8db3fb27732ce3332b04daf7515918939fcabb5fe2170
|
|
4
|
+
data.tar.gz: '05096708a51dd8e323c174164d7fa03e09079a05e21254e96caf3d9d8863ef64'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7c19286aaa870ced8fb58f3f5bf81214a0af14455ed30689f36e0dd47d373ab827b0aafe72c5c6b49f8b02a3cbaefbc5b7f8d874f8279f365ebb1958054691cd
|
|
7
|
+
data.tar.gz: 52ef17204f488be48a0d6b31773e2c65025337f76d24867a5582400376658784143b8271f22063ea38449cc69fb15f15d40d467394ef4e715b4e175e531d5608
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
data/NOTICE
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
This project includes generated parser artifacts produced with Ibex 0.3.0.
|
|
2
|
+
Ibex is used during development and is not a runtime dependency.
|
|
3
|
+
|
|
4
|
+
UnicodeData-3.0.0.txt is Copyright © 1991-2000 Unicode, Inc. and is
|
|
5
|
+
redistributed under the Unicode License V3 included in
|
|
6
|
+
tool/unicode/UNICODE-LICENSE.txt.
|
data/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# json5-ruby
|
|
2
|
+
|
|
3
|
+
`json5-ruby` is a Pure Ruby JSON5 1.0.0 parser. Its syntactic parser is
|
|
4
|
+
generated from `grammar/json5.y` with Ibex 0.3.0; Ibex and ibex-runtime are
|
|
5
|
+
development/build dependencies only.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "json5-ruby"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
require "json5"
|
|
17
|
+
|
|
18
|
+
JSON5.parse(<<~JSON5)
|
|
19
|
+
{
|
|
20
|
+
unquoted: 'value',
|
|
21
|
+
hex: 0x2a,
|
|
22
|
+
values: [true, null,],
|
|
23
|
+
}
|
|
24
|
+
JSON5
|
|
25
|
+
# {"unquoted"=>"value", "hex"=>42, "values"=>[true, nil]}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The parser accepts comments, single-quoted strings, trailing commas,
|
|
29
|
+
IdentifierName keys, hexadecimal numbers, `Infinity`, and `NaN`. Input must be
|
|
30
|
+
a valid UTF-8 or US-ASCII Ruby `String`; other encodings are not implicitly
|
|
31
|
+
transcoded.
|
|
32
|
+
|
|
33
|
+
`JSON5.parse` returns Ruby values by default. Use `number_mode: :float` or
|
|
34
|
+
`:lexeme`, `string_mode: :code_units` for exact UTF-16 code units, and
|
|
35
|
+
`duplicate_keys: :first`, `:error`, or `:preserve` when the default last-value
|
|
36
|
+
policy is not appropriate. Lone surrogates in values require code-unit mode or
|
|
37
|
+
`parse_document`; `duplicate_keys: :preserve` also retains lone-surrogate
|
|
38
|
+
member names as `JSON5::ECMAString` keys because Ruby UTF-8 strings cannot
|
|
39
|
+
represent them losslessly.
|
|
40
|
+
|
|
41
|
+
Set `warn_duplicate_keys: true` to emit `duplicate_key` diagnostics for
|
|
42
|
+
duplicates discarded by the `:last` or `:first` policies.
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
document = JSON5.parse_document("{ /* keep me */ answer: 0x2a, }")
|
|
46
|
+
document.root.raw
|
|
47
|
+
# "{ /* keep me */ answer: 0x2a, }"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`Document` nodes retain byte spans, raw lexemes, decoded code units, and
|
|
51
|
+
leading/trailing trivia. Member nodes expose trivia before `:` and around the
|
|
52
|
+
comma/closing separator; array nodes expose the corresponding element
|
|
53
|
+
separator trivia. The source is copied and frozen by default.
|
|
54
|
+
|
|
55
|
+
## Limits and diagnostics
|
|
56
|
+
|
|
57
|
+
`JSON5::Limits.default` protects input size, nesting depth, string and number
|
|
58
|
+
length, comment length, and container entries. Pass `limits:
|
|
59
|
+
JSON5::Limits.unbounded` only for trusted input. Diagnostics can be collected in
|
|
60
|
+
an array or sent to a callable; by default warnings are sent to `Warning.warn`.
|
|
61
|
+
Unescaped U+2028 and U+2029 in strings produce diagnostics.
|
|
62
|
+
|
|
63
|
+
The defaults are 64 MiB input, 512 nesting levels, 16 Mi UTF-16 string code
|
|
64
|
+
units, 1 MiB number tokens, 16 MiB comments, and 10,000,000 entries per
|
|
65
|
+
container. Construct `JSON5::Limits` with individual keyword overrides when an
|
|
66
|
+
application needs a different boundary; a `nil` field disables that one limit.
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
warnings = []
|
|
70
|
+
JSON5.parse("'line\u2028separator'", diagnostics: warnings)
|
|
71
|
+
warnings.first.code
|
|
72
|
+
# "unescaped_line_separator_in_string"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Development
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
bundle install
|
|
79
|
+
bundle exec rake
|
|
80
|
+
bundle exec rake generated
|
|
81
|
+
bundle exec rake conformance
|
|
82
|
+
bundle exec rake fuzz
|
|
83
|
+
bundle exec rake security
|
|
84
|
+
bundle exec rake package
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`bundle exec rake release:check` runs all deterministic release gates above. The
|
|
88
|
+
generated and conformance tasks are read-only: intentional updates use
|
|
89
|
+
`bundle exec rake generated:update` and `bundle exec rake conformance:update`,
|
|
90
|
+
followed by the corresponding read-only check. `rake generated` regenerates into
|
|
91
|
+
a temporary directory, runs Ibex's own `--check` against that raw output, then
|
|
92
|
+
compares the canonicalized parser, manifest, automaton, verifier report, metrics,
|
|
93
|
+
and Unicode table byte-for-byte with the committed artifacts. The package gate verifies that
|
|
94
|
+
runtime dependencies are empty, inspects gem contents, installs into a clean
|
|
95
|
+
temporary gem home, and smoke-tests the installed artifact.
|
|
96
|
+
|
|
97
|
+
Performance thresholds are evaluated only between results captured on the same
|
|
98
|
+
fixture, Ruby runtime, and machine. Capture before/after files with
|
|
99
|
+
`benchmark/run.rb`, then run an explicit threshold gate:
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
ruby benchmark/run.rb --iterations=25 --warmup=5 --output=before.json
|
|
103
|
+
# apply the candidate change, using the same host and Ruby
|
|
104
|
+
ruby benchmark/run.rb --iterations=25 --warmup=5 --output=after.json
|
|
105
|
+
bundle exec rake "benchmark:check[before.json,after.json,5,5]"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The final two arguments are the maximum allowed median-runtime and allocation
|
|
109
|
+
regression percentages. The comparison requires identical fixture/result
|
|
110
|
+
checksums, Ruby/OS/CPU/compiler/host metadata, warmup, batch, and iteration
|
|
111
|
+
settings, YJIT state, and relevant environment knobs; incompatible or invalid
|
|
112
|
+
results are rejected. GitHub-hosted CI uploads a report-only artifact because
|
|
113
|
+
those runners do not provide a stable performance environment. Maintainers run
|
|
114
|
+
the `Benchmark Gate` workflow at the immutable `benchmark-builder-v12` tag with
|
|
115
|
+
a reviewed full candidate SHA. The base is fixed by
|
|
116
|
+
`benchmark/baseline.commit`; callers cannot substitute it.
|
|
117
|
+
`benchmark/trusted_builder.commit` pins the workflow revision that supplies the
|
|
118
|
+
trusted harness, comparator, manifest, and fixtures on a one-job `ephemeral` +
|
|
119
|
+
`json5-benchmark` self-hosted runner. It covers value, lexer, document, and error
|
|
120
|
+
paths, uses predeclared balanced before/after process rounds, enforces both 5%
|
|
121
|
+
thresholds on every round and their aggregate, and checks 1x/2x/4x linearity. Never attach
|
|
122
|
+
the label to a persistent runner: candidate parser code is executed, so the
|
|
123
|
+
runner must be isolated, one-time, and kept on a stable power/performance
|
|
124
|
+
profile. The runner must be Linux, provide Bubblewrap at `/usr/bin/bwrap`, and
|
|
125
|
+
provide Ruby from `/opt/hostedtoolcache`. Candidate code runs without network or
|
|
126
|
+
inherited credentials in isolated user, mount, PID, IPC, UTS, cgroup, and
|
|
127
|
+
network namespaces; its host inputs are read-only and only an ephemeral `/tmp`
|
|
128
|
+
is writable.
|
|
129
|
+
|
|
130
|
+
The workflow artifact is a tar archive containing aggregate `evidence.json`, a
|
|
131
|
+
SHA-256 file manifest, and all raw measurements. A separate GitHub-hosted job
|
|
132
|
+
revalidates it and creates a GitHub artifact attestation bound to the pinned
|
|
133
|
+
builder workflow. The signed contents bind the exact candidate release commit.
|
|
134
|
+
Before release, set `JSON5_BENCHMARK_EVIDENCE` to the downloaded `.tar.gz` file;
|
|
135
|
+
the release task verifies that attestation before it accepts the fixed base,
|
|
136
|
+
exact release/harness commit, complete manifest coverage, passing comparisons,
|
|
137
|
+
and passing linearity evidence. This verification requires GitHub CLI network
|
|
138
|
+
access.
|
|
139
|
+
|
|
140
|
+
`release:check` also refuses uncommitted or untracked files, so commit intended
|
|
141
|
+
generated evidence before running the final release gate.
|
|
142
|
+
|
|
143
|
+
The generated parser, Automaton IR, verification report, metrics, grammar,
|
|
144
|
+
source manifest, fixed Unicode 3.0.0 input, and conformance matrix are checked
|
|
145
|
+
in. See `docs/conformance-matrix.md` for the coverage map and the
|
|
146
|
+
design/work-plan documents under `.idea/` for the implementation boundary and
|
|
147
|
+
release checklist.
|
|
148
|
+
|
|
149
|
+
Security issues should be reported privately as described in [SECURITY.md](SECURITY.md).
|
|
150
|
+
Conformance scope and known limitations are recorded in
|
|
151
|
+
[docs/conformance-report.md](docs/conformance-report.md).
|
|
152
|
+
|
|
153
|
+
## Compatibility
|
|
154
|
+
|
|
155
|
+
The public API is `JSON5`; `Json5` remains an alias for the initial gem
|
|
156
|
+
scaffold. The supported Ruby version is 3.2 or newer. This project does not
|
|
157
|
+
execute input as Ruby code and never converts member names to symbols.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT. See [LICENSE.txt](LICENSE.txt).
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
Please do not disclose security vulnerabilities in public issues or pull
|
|
4
|
+
requests. Report them privately to the project maintainer through the contact
|
|
5
|
+
address listed in the gem metadata, including a description, reproduction
|
|
6
|
+
steps, affected versions, and any suggested mitigation.
|
|
7
|
+
|
|
8
|
+
Please allow reasonable time for triage and coordinated disclosure. Do not
|
|
9
|
+
include secrets or unrelated personal data in a report.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# JSON5 1.0.0 conformance matrix
|
|
2
|
+
|
|
3
|
+
The matrix tracks the behavior required by the JSON5 1.0.0 specification and the
|
|
4
|
+
ECMAScript 5.1 lexical rules used by it.
|
|
5
|
+
|
|
6
|
+
Fixture IDs are stable evidence identifiers. Their source files, typed expected
|
|
7
|
+
results, and provenance are recorded in `spec/fixtures/conformance/cases.yml`.
|
|
8
|
+
|
|
9
|
+
| ID | Spec section | Requirement | Positive tests | Negative tests | Implementation | Status |
|
|
10
|
+
|---|---|---|---|---|---|---|
|
|
11
|
+
| TEXT-001 | JSON5Text | empty input is invalid because one value is required | - | `TEXT-EMPTY-001` | `grammar/json5.y` | covered |
|
|
12
|
+
| TEXT-002 | JSON5Text | trailing whitespace and comments are accepted | `TEXT-TRIVIA-001` | - | `lib/json5/lexer.rb` | covered |
|
|
13
|
+
| TEXT-003 | JSON5Text | a second top-level value is rejected | - | `TEXT-TRAILING-001` | `lib/json5/parser.rb` | covered |
|
|
14
|
+
| TEXT-004 | JSON5Text | punctuation suffix tokens after a value are rejected | - | `TEXT-SUFFIX-001` | `lib/json5/parser.rb` | covered |
|
|
15
|
+
| VALUE-001 | JSON5Value | object, array, string, number, Boolean, and null are top-level values | `TEXT-TOPLEVEL-001`<br>`VALUE-STRING-001`<br>`VALUE-BOOLEAN-001`<br>`VALUE-NULL-001`<br>`VALUE-TOPLEVEL-001` | - | `grammar/json5.y` | covered |
|
|
16
|
+
| OBJ-001 | JSON5Object / JSON5Array | empty containers are accepted | `OBJ-EMPTY-001` | - | `grammar/json5.y` | covered |
|
|
17
|
+
| OBJ-002 | JSON5Member / JSON5ElementList | one member or element is accepted | `OBJ-SINGLE-001` | - | `grammar/json5.y` | covered |
|
|
18
|
+
| OBJ-003 | JSON5MemberList / JSON5ElementList | multiple members or elements are accepted | `OBJ-MULTIPLE-001` | - | `grammar/json5.y` | covered |
|
|
19
|
+
| OBJ-004 | JSON5Object / JSON5Array | one trailing comma is accepted | `OBJECT-GRAMMAR-001`<br>`ARRAY-TRAILING-001` | - | `grammar/json5.y` | covered |
|
|
20
|
+
| OBJ-005 | JSON5Object / JSON5Array | a second trailing comma is rejected | - | `OBJECT-DOUBLE-COMMA-001`<br>`ARR-DOUBLE-COMMA-001` | `grammar/json5.y` | covered |
|
|
21
|
+
| ARR-001 | JSON5Array | array holes are rejected | - | `ARRAY-HOLE-001` | `grammar/json5.y` | covered |
|
|
22
|
+
| OBJ-006 | JSON5MemberName | quoted and unquoted member names are accepted | `OBJ-NAMES-001` | - | `grammar/json5.y` | covered |
|
|
23
|
+
| OBJ-007 | JSON5MemberName | literal keyword spellings are accepted as member names | `OBJECT-KEYWORDS-001` | - | `grammar/json5.y` | covered |
|
|
24
|
+
| DUP-001 | API duplicate policy | last, first, error, and preserve policies are fixed | `OBJECT-DUPLICATE-001`<br>`DUP-POLICIES-001` | - | `lib/json5/ruby_value_builder.rb` | covered |
|
|
25
|
+
| WS-001 | Whitespace | U+0009, U+000A, U+000B, U+000C, U+000D, and U+0020 are accepted | `WS-ASCII-001` | - | `lib/json5/lexer.rb` | covered |
|
|
26
|
+
| WS-002 | Whitespace | U+00A0, U+2028, U+2029, and U+FEFF are accepted | `WS-UNICODE-001` | - | `lib/json5/lexer.rb` | covered |
|
|
27
|
+
| WS-003 | Whitespace | every pinned Unicode 3.0 Zs character is accepted | `NUMBER-WHITESPACE-001`<br>`WS-ALL-ZS-001` | - | `lib/json5/unicode_tables.rb` | covered |
|
|
28
|
+
| COMMENT-001 | SingleLineComment | line comments terminate at LF, CRLF, CR, LS, PS, or EOF | `COMMENT-EOL-001` | - | `lib/json5/lexer.rb` | covered |
|
|
29
|
+
| COMMENT-002 | MultiLineComment | a closed block comment is trivia | `COMMENT-SIMPLE-001` | - | `lib/json5/lexer.rb` | covered |
|
|
30
|
+
| COMMENT-003 | MultiLineComment | nested-looking text closes at the first closing delimiter | `COMMENT-BLOCK-001` | - | `lib/json5/lexer.rb` | covered |
|
|
31
|
+
| COMMENT-004 | MultiLineComment | an unclosed block comment is rejected | - | `COMMENT-UNCLOSED-001` | `lib/json5/lexer.rb` | covered |
|
|
32
|
+
| COMMENT-005 | Comments | a slash that does not start a comment is rejected | - | `COMMENT-SLASH-001` | `lib/json5/lexer.rb` | covered |
|
|
33
|
+
| COMMENT-006 | Lexical token boundaries | comments inside identifiers and numbers are rejected | - | `COMMENT-TOKEN-BOUNDARY-001` | `lib/json5/lexer.rb` | covered |
|
|
34
|
+
| IDENT-001 | IdentifierStart | ASCII letters, dollar signs, and underscores are accepted | `IDENT-ASCII-001` | - | `lib/json5/lexer.rb` | covered |
|
|
35
|
+
| IDENT-002 | IdentifierPart | decimal digits are accepted only after the start | `IDENT-DIGIT-001` | - | `lib/json5/unicode_tables.rb` | covered |
|
|
36
|
+
| IDENT-003 | IdentifierPart | combining marks are accepted only after the start | `IDENT-COMBINING-001` | - | `lib/json5/unicode_tables.rb` | covered |
|
|
37
|
+
| IDENT-004 | IdentifierPart | ZWNJ and ZWJ are accepted only after the start | `IDENT-ZWNJ-ZWJ-001` | - | `lib/json5/unicode_tables.rb` | covered |
|
|
38
|
+
| IDENT-005 | UnicodeEscapeSequence | four-digit Unicode escapes are accepted by position | `IDENT-UNICODE-ESCAPE-001` | - | `lib/json5/lexer.rb` | covered |
|
|
39
|
+
| IDENT-006 | UnicodeEscapeSequence | invalid hex count and alternate escape forms are rejected | - | `IDENT-ESCAPE-LENGTH-001` | `lib/json5/lexer.rb` | covered |
|
|
40
|
+
| IDENT-007 | IdentifierName | decoded escape code units are revalidated as start or part | - | `IDENT-ESCAPE-BOUNDARY-001` | `lib/json5/unicode_tables.rb` | covered |
|
|
41
|
+
| IDENT-008 | IdentifierName / JSON5Value | escaped keywords are member names but not literal values | `IDENT-ESCAPED-KEYWORD-001` | `IDENT-ESCAPED-KEYWORD-002` | `lib/json5/lexer.rb` | covered |
|
|
42
|
+
| IDENT-009 | IdentifierName / JSON5Value | truex and Infinityx are identifiers by maximal munch | - | `IDENT-KEYWORD-SUFFIX-001`<br>`IDENT-KEYWORD-SUFFIX-COMPLETE-001` | `lib/json5/lexer.rb` | covered |
|
|
43
|
+
| IDENT-010 | JSON5MemberName | arbitrary strings are accepted as quoted keys | `IDENT-QUOTED-KEY-001` | - | `grammar/json5.y` | covered |
|
|
44
|
+
| IDENT-011 | IdentifierName | pinned Unicode letters are accepted as member names | `IDENT-UNICODE-001` | - | `lib/json5/unicode_tables.rb` | covered |
|
|
45
|
+
| STRING-001 | StringLiteral | single- and double-quoted strings are accepted | `STRING-QUOTES-001`<br>`STRING-QUOTE-PAIR-001` | - | `lib/json5/lexer.rb` | covered |
|
|
46
|
+
| STRING-002 | StringLiteral | mismatched delimiters are rejected | - | `STRING-MISMATCH-001` | `lib/json5/lexer.rb` | covered |
|
|
47
|
+
| STRING-003 | EscapeSequence | every named escape decodes to its specified code unit | `STRING-NAMED-ESCAPES-001` | - | `lib/json5/lexer.rb` | covered |
|
|
48
|
+
| STRING-004 | EscapeSequence | null escape is accepted and a following decimal digit is rejected | `STRING-ESCAPE-001` | `STRING-NULL-DIGIT-001` | `lib/json5/lexer.rb` | covered |
|
|
49
|
+
| STRING-005 | HexEscapeSequence | exactly two hexadecimal digits are required | `STRING-HEX-ESCAPE-001` | `STRING-ESCAPE-MALFORMED-001` | `lib/json5/lexer.rb` | covered |
|
|
50
|
+
| STRING-006 | UnicodeEscapeSequence | exactly four hexadecimal digits decode one UTF-16 code unit | `STRING-UNICODE-ESCAPE-001` | - | `lib/json5/lexer.rb` | covered |
|
|
51
|
+
| STRING-007 | NonEscapeCharacter | non-special escape characters and escaped slash are accepted | `STRING-NONESCAPE-001` | - | `lib/json5/lexer.rb` | covered |
|
|
52
|
+
| STRING-008 | EscapeSequence | decimal escapes one through nine are rejected | - | `STRING-DECIMAL-ESCAPE-001` | `lib/json5/lexer.rb` | covered |
|
|
53
|
+
| STRING-009 | LineContinuation | LF, CR, CRLF, LS, and PS continuations are accepted | `STRING-CONTINUATION-001` | - | `lib/json5/lexer.rb` | covered |
|
|
54
|
+
| STRING-010 | StringLiteral | unescaped LF and CR line terminators are rejected | - | `STRING-RAW-LINE-001` | `lib/json5/lexer.rb` | covered |
|
|
55
|
+
| STRING-011 | StringLiteral / diagnostics | unescaped U+2028 and U+2029 are accepted with warnings | `STRING-LS-001`<br>`STRING-PS-001` | - | `lib/json5/diagnostic.rb` | covered |
|
|
56
|
+
| STRING-012 | UTF-16 conversion | lone surrogates are preserved in code-unit mode and valid pairs combine | `STRING-LONE-SURROGATE-001`<br>`STRING-SURROGATE-PAIR-001` | `STRING-LONE-SURROGATE-002` | `lib/json5/ecma_string.rb` | covered |
|
|
57
|
+
| STRING-013 | StringLiteral | unescaped NUL and non-line control characters are preserved | `STRING-CONTROL-001` | - | `lib/json5/lexer.rb` | covered |
|
|
58
|
+
| STRING-014 | StringLiteral | unterminated quotes and terminal backslashes are rejected | - | `STRING-UNTERMINATED-001` | `lib/json5/lexer.rb` | covered |
|
|
59
|
+
| NUMBER-001 | DecimalIntegerLiteral | zero and non-zero integers are accepted | `NUMBER-INTEGER-001` | - | `lib/json5/lexer.rb` | covered |
|
|
60
|
+
| NUMBER-002 | DecimalIntegerLiteral | decimal integers with a leading zero are rejected | - | `NUMBER-LEADING-ZERO-001` | `lib/json5/lexer.rb` | covered |
|
|
61
|
+
| NUMBER-003 | DecimalLiteral | 1.0, .1, 1., and 1.e2 shapes are accepted | `NUMBER-DECIMAL-001`<br>`NUMBER-DECIMAL-SHAPES-001` | - | `lib/json5/lexer.rb` | covered |
|
|
62
|
+
| NUMBER-004 | ExponentPart | lower/upper E and absent/plus/minus exponent signs are accepted | `NUMBER-EXPONENT-CASE-001` | - | `lib/json5/lexer.rb` | covered |
|
|
63
|
+
| NUMBER-005 | ExponentPart | an exponent without decimal digits is rejected | - | `NUMBER-EXPONENT-001` | `lib/json5/lexer.rb` | covered |
|
|
64
|
+
| NUMBER-006 | HexIntegerLiteral | lower- and upper-case hexadecimal prefixes are accepted | `NUMBER-HEX-001` | - | `lib/json5/lexer.rb` | covered |
|
|
65
|
+
| NUMBER-007 | HexIntegerLiteral | a hexadecimal prefix without valid digits is rejected | - | `NUMBER-MALFORMED-HEX-001` | `lib/json5/lexer.rb` | covered |
|
|
66
|
+
| NUMBER-008 | NumericLiteral | explicit plus and minus are accepted on finite number forms | `NUMBER-FINITE-SIGNS-001` | - | `lib/json5/lexer.rb` | covered |
|
|
67
|
+
| NUMBER-009 | NumericLiteral | Infinity and NaN accept absent, plus, and minus signs atomically | `NUMBER-INFINITY-001`<br>`NUMBER-NAN-001`<br>`NUMBER-SIGNED-001`<br>`NUMBER-SPECIAL-SIGNS-COMPLETE-001` | - | `lib/json5/lexer.rb` | covered |
|
|
68
|
+
| NUMBER-010 | NumericLiteral | case-mismatched Infinity and NaN spellings are rejected | - | `NUMBER-SPECIAL-CASE-001` | `lib/json5/lexer.rb` | covered |
|
|
69
|
+
| NUMBER-011 | NumericLiteral | invalid identifier suffixes cannot follow numeric or special values | - | `NUMBER-TOKEN-BOUNDARY-001` | `lib/json5/lexer.rb` | covered |
|
|
70
|
+
| NUMBER-012 | NumericLiteral | whitespace or comments cannot separate a sign from its body | - | `NUMBER-COMMENT-SIGN-001`<br>`NUMBER-SIGN-TRIVIA-001` | `lib/json5/lexer.rb` | covered |
|
|
71
|
+
| NUMBER-013 | Numeric conversion | minus-zero sign and binary64 bit pattern are preserved | `NUMBER-NEGATIVE-ZERO-001`<br>`NUMBER-FLOAT-BITS-001` | - | `lib/json5/number_value.rb` | covered |
|
|
72
|
+
| NUMBER-014 | Numeric conversion / binary64 | rounding, overflow, and underflow follow fixed binary64 oracles | `NUMBER-HEX-ROUNDING-001`<br>`NUMBER-BINARY64-BOUNDARY-001` | - | `lib/json5/number_value.rb` | covered |
|
|
73
|
+
| ENC-001 | Input encoding | invalid UTF-8 is rejected | - | `ENC-INVALID-001` | `lib/json5/input.rb` | covered |
|
|
74
|
+
| UNICODE-001 | ECMAScript 5.1 IdentifierName | pinned Unicode 3.0.0 predicates match every BMP code point | `UNICODE-BMP-001` | - | `lib/json5/unicode_tables.rb` | covered |
|
|
75
|
+
| LIMIT-001 | API limits | configured limits fail early | - | `LIMIT-BOUNDARY-001` | `lib/json5/limits.rb` | covered |
|
|
76
|
+
| INPUT-001 | API load | load enforces limits even when a reader ignores chunk size | - | `INPUT-LIMIT-001` | `lib/json5/parser.rb` | covered |
|
|
77
|
+
| API-001 | API option validation | load and Document reject invalid or unsupported options | - | `API-OPTIONS-001` | `lib/json5/parser.rb` | covered |
|
|
78
|
+
| ERROR-001 | Error model | parser and builder errors expose source context | - | `ERROR-CONTEXT-001` | `lib/json5/errors.rb` | covered |
|
|
79
|
+
| ERROR-002 | Error model | EOF diagnostics never extend beyond the source | - | `ERROR-EOF-001` | `lib/json5/lexer.rb` | covered |
|
|
80
|
+
| PERF-001 | Resource safety | literal token context is released after reduction | - | `PERF-TOKEN-RETENTION-001` | `lib/json5/ruby_value_builder.rb` | covered |
|
|
81
|
+
| DOC-001 | Document mode | lossless document spans and trivia | `DOC-LOSSLESS-001` | - | `lib/json5/document_parser.rb` | covered |
|
|
82
|
+
| DIFF-001 | Strict JSON subset | values agree with the standard-library JSON parser | `JSON-DIFFERENTIAL-001` | - | `lib/json5/parser.rb` | covered |
|
|
83
|
+
|
|
84
|
+
## Fuzz regressions
|
|
85
|
+
|
|
86
|
+
The deterministic generator configuration and invalid-mutation registry live in
|
|
87
|
+
`spec/fixtures/conformance/fuzz.yml`. When a seed finds a failure, follow the
|
|
88
|
+
minimization and stable-fixture procedure in `spec/fixtures/conformance/README.md`;
|
|
89
|
+
the generated source is never accepted or rejected solely because another JSON5
|
|
90
|
+
implementation behaves the same way.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# JSON5 conformance report
|
|
2
|
+
|
|
3
|
+
Generated from `docs/conformance-matrix.md`.
|
|
4
|
+
|
|
5
|
+
- JSON5 specification: 1.0.0 (`36f9418ed339580e362eb370fd084a235e361b8e`)
|
|
6
|
+
- ECMAScript lexical baseline: 5.1 / Unicode 3.0.0
|
|
7
|
+
- Ibex: 0.3.0, embedded Pure Ruby parser
|
|
8
|
+
- Supported Ruby CI matrix: 3.2, 3.3, 3.4, 4.0
|
|
9
|
+
- Matrix rows: 72
|
|
10
|
+
- Covered: 72
|
|
11
|
+
- Planned: 0
|
|
12
|
+
- Blocked: 0
|
|
13
|
+
- Generated automaton conflicts: 0
|
|
14
|
+
- Strict Automaton IR verification: valid
|
|
15
|
+
- Specification-derived fixtures: 33 (22 positive, 11 negative, 2 diagnostic-bearing)
|
|
16
|
+
- Linked executable evidence examples: 62
|
|
17
|
+
- Deterministic fuzz seeds: 12648430, 1592594996, 3735928559
|
|
18
|
+
- Deterministic fuzz iterations: 80 per seed (240 total)
|
|
19
|
+
- Deterministic fuzz corpus SHA-256: `52102866735c93ce7a3efbec11b512862c071ea43e2a30728903e4f8e3f0f156`
|
|
20
|
+
- Single-edit invalid mutations: 9
|
|
21
|
+
- Source provenance: `tool/sources.yml`; pinned Unicode SHA-256 verified
|
|
22
|
+
- Official JSON5 corpus: not vendored; coverage is specification-derived
|
|
23
|
+
- External differential: not used as the conformance oracle
|
|
24
|
+
- Known limitations: none within the documented strict JSON5 1.0.0 scope
|
|
25
|
+
- Performance baseline: `benchmark/results/baseline.json`
|
|
26
|
+
|
|
27
|
+
The report is evidence for the repository's current test suite. Performance
|
|
28
|
+
results remain machine-specific. The matrix does not by itself constitute a
|
|
29
|
+
claim that every third-party corpus or extension is supported.
|
data/grammar/json5.y
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
class JSON5::GeneratedParser
|
|
2
|
+
token STRING NUMBER IDENTIFIER_NAME TRUE FALSE NULL INFINITY NAN
|
|
3
|
+
start json5_text
|
|
4
|
+
expect 0
|
|
5
|
+
options no_result_var
|
|
6
|
+
rule
|
|
7
|
+
json5_text
|
|
8
|
+
: value { result = val[0] }
|
|
9
|
+
|
|
10
|
+
value
|
|
11
|
+
: object { result = val[0] }
|
|
12
|
+
| array { result = val[0] }
|
|
13
|
+
| STRING { result = @builder.string(val[0]) }
|
|
14
|
+
| NUMBER { result = @builder.number(val[0]) }
|
|
15
|
+
| TRUE { result = @builder.literal(val[0], true) }
|
|
16
|
+
| FALSE { result = @builder.literal(val[0], false) }
|
|
17
|
+
| NULL { result = @builder.literal(val[0], nil) }
|
|
18
|
+
| INFINITY { result = @builder.number(val[0]) }
|
|
19
|
+
| NAN { result = @builder.number(val[0]) }
|
|
20
|
+
|
|
21
|
+
object
|
|
22
|
+
: '{' '}' { result = @builder.object([], val[0], val[1]) }
|
|
23
|
+
| '{' members '}' { result = @builder.object(val[1], val[0], val[2]) }
|
|
24
|
+
| '{' members ',' '}' { result = @builder.object(val[1], val[0], val[3], val[2]) }
|
|
25
|
+
|
|
26
|
+
members
|
|
27
|
+
: member { result = @builder.member_list(val[0]) }
|
|
28
|
+
| members ',' member { result = @builder.append_member(val[0], val[2], val[1]) }
|
|
29
|
+
|
|
30
|
+
member
|
|
31
|
+
: member_name ':' value { result = @builder.member(val[0], val[1], val[2]) }
|
|
32
|
+
|
|
33
|
+
member_name
|
|
34
|
+
: STRING { result = @builder.member_name(val[0]) }
|
|
35
|
+
| IDENTIFIER_NAME { result = @builder.member_name(val[0]) }
|
|
36
|
+
| TRUE { result = @builder.member_name(val[0]) }
|
|
37
|
+
| FALSE { result = @builder.member_name(val[0]) }
|
|
38
|
+
| NULL { result = @builder.member_name(val[0]) }
|
|
39
|
+
| INFINITY { result = @builder.member_name(val[0]) }
|
|
40
|
+
| NAN { result = @builder.member_name(val[0]) }
|
|
41
|
+
|
|
42
|
+
array
|
|
43
|
+
: '[' ']' { result = @builder.array([], val[0], val[1]) }
|
|
44
|
+
| '[' elements ']' { result = @builder.array(val[1], val[0], val[2]) }
|
|
45
|
+
| '[' elements ',' ']' { result = @builder.array(val[1], val[0], val[3], val[2]) }
|
|
46
|
+
|
|
47
|
+
elements
|
|
48
|
+
: value { result = @builder.element_list(val[0]) }
|
|
49
|
+
| elements ',' value { result = @builder.append_element(val[0], val[2], val[1]) }
|
|
50
|
+
end
|
|
51
|
+
---- inner
|
|
52
|
+
def initialize(lexer, builder)
|
|
53
|
+
@lexer = lexer
|
|
54
|
+
@builder = builder
|
|
55
|
+
super()
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def parse_tokens
|
|
59
|
+
do_parse
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def next_token
|
|
63
|
+
token = @lexer.next_token
|
|
64
|
+
@builder.observe_token(token)
|
|
65
|
+
return false if token.kind == :EOF
|
|
66
|
+
|
|
67
|
+
payload = if token.kind.is_a?(String)
|
|
68
|
+
@builder.retain_punctuation_tokens? ? token : nil
|
|
69
|
+
else
|
|
70
|
+
token.value
|
|
71
|
+
end
|
|
72
|
+
[token.kind, payload]
|
|
73
|
+
end
|
|
74
|
+
---- footer
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSON5
|
|
4
|
+
module ASCIIClassification
|
|
5
|
+
WHITESPACE = 1 << 0
|
|
6
|
+
DIGIT = 1 << 1
|
|
7
|
+
HEX_DIGIT = 1 << 2
|
|
8
|
+
IDENTIFIER_START = 1 << 3
|
|
9
|
+
IDENTIFIER_PART = 1 << 4
|
|
10
|
+
QUOTE = 1 << 5
|
|
11
|
+
PUNCTUATOR = 1 << 6
|
|
12
|
+
SLASH = 1 << 7
|
|
13
|
+
BACKSLASH = 1 << 8
|
|
14
|
+
LINE_TERMINATOR = 1 << 9
|
|
15
|
+
|
|
16
|
+
TABLE = Array.new(256, 0).tap do |table|
|
|
17
|
+
[0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x20].each { |byte| table[byte] |= WHITESPACE }
|
|
18
|
+
(0x30..0x39).each { |byte| table[byte] |= DIGIT | HEX_DIGIT | IDENTIFIER_PART }
|
|
19
|
+
(0x41..0x46).each { |byte| table[byte] |= HEX_DIGIT }
|
|
20
|
+
(0x61..0x66).each { |byte| table[byte] |= HEX_DIGIT }
|
|
21
|
+
(0x41..0x5a).each { |byte| table[byte] |= IDENTIFIER_START | IDENTIFIER_PART }
|
|
22
|
+
(0x61..0x7a).each { |byte| table[byte] |= IDENTIFIER_START | IDENTIFIER_PART }
|
|
23
|
+
[0x24, 0x5f].each { |byte| table[byte] |= IDENTIFIER_START | IDENTIFIER_PART }
|
|
24
|
+
[0x22, 0x27].each { |byte| table[byte] |= QUOTE }
|
|
25
|
+
[0x7b, 0x7d, 0x5b, 0x5d, 0x3a, 0x2c].each { |byte| table[byte] |= PUNCTUATOR }
|
|
26
|
+
table[0x2f] |= SLASH
|
|
27
|
+
table[0x5c] |= BACKSLASH
|
|
28
|
+
[0x0a, 0x0d].each { |byte| table[byte] |= LINE_TERMINATOR }
|
|
29
|
+
end.freeze
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSON5
|
|
4
|
+
class Diagnostic
|
|
5
|
+
attr_reader :severity, :code, :message, :filename, :byte_offset,
|
|
6
|
+
:end_byte_offset, :line, :column
|
|
7
|
+
|
|
8
|
+
def initialize(code:, message:, severity: :warning, filename: nil,
|
|
9
|
+
byte_offset: nil, end_byte_offset: nil, line: nil,
|
|
10
|
+
column: nil)
|
|
11
|
+
@severity = severity.to_sym
|
|
12
|
+
@code = code.to_s.dup.freeze
|
|
13
|
+
@message = message.to_s.dup.freeze
|
|
14
|
+
@filename = filename.nil? ? nil : filename.to_s.dup.freeze
|
|
15
|
+
@byte_offset = byte_offset
|
|
16
|
+
@end_byte_offset = end_byte_offset
|
|
17
|
+
@line = line
|
|
18
|
+
@column = column
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_h
|
|
23
|
+
{
|
|
24
|
+
severity: severity,
|
|
25
|
+
code: code,
|
|
26
|
+
message: message,
|
|
27
|
+
filename: filename,
|
|
28
|
+
byte_offset: byte_offset,
|
|
29
|
+
end_byte_offset: end_byte_offset,
|
|
30
|
+
line: line,
|
|
31
|
+
column: column
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class DiagnosticSink
|
|
37
|
+
def self.build(target, filename: nil)
|
|
38
|
+
new(target, filename: filename)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.write_default_warning(diagnostic)
|
|
42
|
+
Warning.warn("#{diagnostic.message} (#{diagnostic.code})\n")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def initialize(target, filename: nil)
|
|
46
|
+
@target = target
|
|
47
|
+
@filename = filename
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def emit(code:, message:, byte_offset:, end_byte_offset:, line: nil, column: nil)
|
|
51
|
+
diagnostic = Diagnostic.new(
|
|
52
|
+
code: code,
|
|
53
|
+
message: message,
|
|
54
|
+
filename: @filename,
|
|
55
|
+
byte_offset: byte_offset,
|
|
56
|
+
end_byte_offset: end_byte_offset,
|
|
57
|
+
line: line,
|
|
58
|
+
column: column
|
|
59
|
+
)
|
|
60
|
+
if @target.is_a?(Array)
|
|
61
|
+
@target << diagnostic
|
|
62
|
+
elsif @target.respond_to?(:call)
|
|
63
|
+
@target.call(diagnostic)
|
|
64
|
+
elsif @target != :ignore
|
|
65
|
+
self.class.write_default_warning(diagnostic)
|
|
66
|
+
end
|
|
67
|
+
diagnostic
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSON5
|
|
4
|
+
class Document
|
|
5
|
+
attr_reader :source, :root, :diagnostics, :trailing_trivia, :line_map
|
|
6
|
+
|
|
7
|
+
def initialize(source:, root:, diagnostics:, trailing_trivia: [])
|
|
8
|
+
@source = source
|
|
9
|
+
@root = root
|
|
10
|
+
@diagnostics = diagnostics.dup.freeze
|
|
11
|
+
@trailing_trivia = trailing_trivia.dup.freeze
|
|
12
|
+
@line_map = LineMap.new(source)
|
|
13
|
+
freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def raw
|
|
17
|
+
source
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSON5
|
|
4
|
+
ByteSpan = Data.define(:start_byte, :end_byte)
|
|
5
|
+
|
|
6
|
+
class Node
|
|
7
|
+
attr_reader :type, :source, :raw_span, :start_byte, :end_byte, :leading_trivia, :trailing_trivia
|
|
8
|
+
|
|
9
|
+
def initialize(type:, source:, start_byte:, end_byte:, leading_trivia: [], trailing_trivia: [])
|
|
10
|
+
@type = type.to_sym
|
|
11
|
+
@source = source
|
|
12
|
+
@start_byte = start_byte
|
|
13
|
+
@end_byte = end_byte
|
|
14
|
+
@raw_span = ByteSpan.new(start_byte, end_byte)
|
|
15
|
+
@leading_trivia = leading_trivia.dup.freeze
|
|
16
|
+
@trailing_trivia = trailing_trivia.dup.freeze
|
|
17
|
+
freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def raw
|
|
21
|
+
source.byteslice(start_byte...end_byte)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class StringNode < Node
|
|
26
|
+
attr_reader :code_units, :quote, :escape_spans
|
|
27
|
+
|
|
28
|
+
def initialize(code_units:, quote:, escape_spans: [], **attributes)
|
|
29
|
+
@code_units = code_units
|
|
30
|
+
@quote = quote
|
|
31
|
+
@escape_spans = escape_spans.dup.freeze
|
|
32
|
+
super(type: :string, **attributes)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class NumberNode < Node
|
|
37
|
+
attr_reader :classification, :sign
|
|
38
|
+
|
|
39
|
+
def initialize(classification:, sign:, **attributes)
|
|
40
|
+
@classification = classification
|
|
41
|
+
@sign = sign
|
|
42
|
+
super(type: :number, **attributes)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
alias lexeme raw
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class IdentifierNameNode < Node
|
|
49
|
+
attr_reader :code_units, :decoded_code_units
|
|
50
|
+
|
|
51
|
+
def initialize(code_units:, **attributes)
|
|
52
|
+
@code_units = code_units
|
|
53
|
+
@decoded_code_units = code_units
|
|
54
|
+
super(type: :identifier_name, **attributes)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class LiteralNode < Node
|
|
59
|
+
attr_reader :value
|
|
60
|
+
|
|
61
|
+
def initialize(value:, **attributes)
|
|
62
|
+
@value = value
|
|
63
|
+
super(type: :literal, **attributes)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class MemberNode < Node
|
|
68
|
+
attr_reader :name, :value, :colon_span, :colon_start_byte, :colon_end_byte, :colon_leading_trivia
|
|
69
|
+
|
|
70
|
+
def initialize(name:, value:, colon_start_byte:, colon_end_byte:, colon_leading_trivia: [], **attributes)
|
|
71
|
+
@name = name
|
|
72
|
+
@value = value
|
|
73
|
+
@colon_start_byte = colon_start_byte
|
|
74
|
+
@colon_end_byte = colon_end_byte
|
|
75
|
+
@colon_span = ByteSpan.new(colon_start_byte, colon_end_byte)
|
|
76
|
+
@colon_leading_trivia = colon_leading_trivia.dup.freeze
|
|
77
|
+
super(type: :member, **attributes)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class ObjectNode < Node
|
|
82
|
+
attr_reader :members
|
|
83
|
+
|
|
84
|
+
def initialize(members:, **attributes)
|
|
85
|
+
@members = members.dup.freeze
|
|
86
|
+
super(type: :object, **attributes)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
class ArrayNode < Node
|
|
91
|
+
attr_reader :elements, :element_trailing_trivia
|
|
92
|
+
|
|
93
|
+
def initialize(elements:, element_trailing_trivia: [], **attributes)
|
|
94
|
+
@elements = elements.dup.freeze
|
|
95
|
+
@element_trailing_trivia = element_trailing_trivia.map { |trivia| trivia.dup.freeze }.freeze
|
|
96
|
+
super(type: :array, **attributes)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|