1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
// Copyright (c) 2026 tabnas, MIT License
// The engine's error carries a code, position, hint and a formatted
// report, so it is large by design and `Result<_, TabnasError>` trips
// clippy's `result_large_err`. The engine allows the lint at its own
// crate root for the same reason; boxing here instead would make
// `parse` return a different shape from `Tabnas::parse` and from the
// TypeScript and Go ports, which is a worse trade than the lint.
//! A standard JSON grammar plugin for the `tabnas` parsing engine.
//!
//! The engine ships no grammar of its own; this crate supplies the
//! strict, standard-JSON one. The rule set (`val` / `map` / `list` /
//! `pair` / `elem`) is jsonic's "Plain JSON" grammar, the pure-JSON core
//! jsonic defines before extending it for the relaxed jsonic format.
//! Here that core is installed on its own, with the lexer restricted to
//! strict JSON and none of jsonic's extended grammar (comments, unquoted
//! keys, implicit objects/arrays, trailing commas, single/backtick
//! strings, path diving).
//!
//! This plugin is intended to be the foundation other tabnas grammar
//! plugins build on: install it first, then layer additional rules on the
//! shared `val` / `map` / `list` / `pair` / `elem` rules.
use OnceLock;
use Regex;
use json;
use ;
/// The README's Rust examples run as doctests, so a stale one fails the
/// gate rather than misleading the reader. Its `toml` and `bash` fences
/// are skipped; rustdoc runs only the `rust` ones.
/// This crate's version. It MUST equal `ts/package.json` "version": the
/// release orchestrator rewrites both, and `tests/version_test.rs` fails
/// the build if they drift. Mirrors `VERSION` in `ts/src/json.ts` and
/// `const VERSION` in `go/json.go`.
pub const VERSION: &str = "0.5.13";
/// The error a failed parse produces, re-exported so callers need not
/// depend on the engine crate directly. Mirrors the TypeScript
/// `export { TabnasError as JsonError }`.
pub use TabnasError as JsonError;
/// The name the serialized options bind the number preflight hook under.
/// Referencing it by name is the only way to reach `options.number.check`
/// from outside the engine crate: `LexCheck`'s constructors are
/// `pub(crate)`, and `Tabnas::lex_check_ref` exists for exactly this.
const NUMBER_CHECK: &str = "json-strict-number";
/// Exactly a standard JSON number.
/// The candidate literal starting at `src`, up to the next JSON
/// structural character or whitespace. That boundary is what the engine's
/// (lenient) number matcher would consider, so it is what has to be
/// judged.
///
/// `/` is a boundary too, and not for strict JSON's sake: a slash after a
/// number is invalid there whatever this returns. It is for the JSONC
/// recipe the docs describe. With comment lexing re-enabled,
/// `{"a":1/* note */}` has no space between the number and the comment,
/// so without `/` here the literal scanned to the next WHITESPACE and the
/// hook judged `1/*`, failed the pattern, and answered `Skip` -- turning
/// a valid JSONC document into `unexpected`. Stopping here lets the
/// number tokenize and leaves the comment to the lexer, which is the one
/// that knows whether comments are on.
/// Reject anything the engine's lenient number matcher would accept that
/// standard JSON does not.
///
/// This is a `check` hook rather than `options.number.exclude` for two
/// independent reasons, and both matter:
///
/// 1. **`exclude` cannot express it.** The TypeScript exclude is a
/// negative lookahead (`/^(?!-?(?:0|[1-9][0-9]*)…$)/`), and the Rust
/// engine's `exclude` is a pattern for the `regex` crate, which has no
/// lookaround at all. The positive form plus an inversion is the only
/// way to say it here, which is the shape the Go port already uses.
///
/// 2. **Out-of-range exponents.** `1e999` and `123123e100000` are
/// syntactically valid JSON, and the platform oracles disagree about
/// them: `JSON.parse` saturates to `Infinity` (so TypeScript accepts),
/// while `encoding/json` errors. `serde_json` — this runtime's oracle —
/// errors too ("number out of range"), verified, so Rust rejects them
/// with Go rather than accepting with TypeScript. AGENTS.md rule 4 is
/// per-runtime parity and names this as a deliberate, permanent
/// asymmetry. Underflow (`1e-999` -> `0`) is accepted by serde_json and
/// is left alone, exactly as Go leaves it.
///
/// Note the asymmetry is not expressible as a regex either way, which is
/// the deeper reason both ports need a predicate and TypeScript does not.
/// serde_json's own nesting limit, and therefore this port's.
///
/// `serde_json::from_str` accepts 127 levels of nesting and refuses the
/// 128th with "recursion limit exceeded"; `JSON.parse` and
/// `encoding/json` both go far deeper. That is the same shape of
/// platform disagreement as the out-of-range exponent above, and
/// per-runtime parity answers it the same way: this port follows its own
/// platform. The boundary was measured against serde_json rather than
/// read off its constant, and `tests/json_test.rs` re-measures it, so a
/// future change there shows up as a failure instead of as silent drift.
///
/// Unlike that one, it is also a crash fix. Without a limit, a 1 KB
/// source of 500 open brackets aborts the process with a stack overflow
/// rather than returning an error, which the external conformance corpus
/// exercises directly (`i_structure_500_nested_arrays`,
/// `n_structure_100000_opening_arrays`). A parser reached with untrusted
/// input must not be able to end the process.
const DEPTH_LIMIT: usize = 127;
/// How many containers are open at this point in the parse.
///
/// Unlike the number check, the depth check is not bound through the
/// grammar document: `parse_guard` takes the closure directly, under the
/// name [`DEPTH_GUARD`], and the document has nothing to reference.
///
/// Counted from the RULE NAMES rather than from `rule_stack.len()`. The
/// stack holds about three rules per level (`val`, then `map`/`list`,
/// then `pair`/`elem`), so a length-based limit would encode that ratio
/// and shift silently the first time the grammar gains an alternate.
/// Counting the container rules is the depth a reader of the document
/// would count.
///
/// The rule the loop is working on is NOT in `rule_stack`: the engine
/// hands it over separately as `context.rule`, and the stack holds only
/// its ancestors. A container is open from the moment it is that rule,
/// so it has to be counted too. Counting the ancestors alone made the
/// boundary depend on what the innermost container held: `[]` nested 127
/// deep parsed, because the 127th list was the current rule and went
/// uncounted, while `[1]` nested 127 deep was refused, because by the
/// time `1` was read all 127 lists were ancestors. serde_json accepts
/// both, and `tests/json_test.rs` now measures both shapes against it.
/// The depth guard: stop before the nesting outruns the stack.
///
/// At most, not strictly less than. `depth` already includes the
/// container the loop is inside, so the count it returns IS the nesting
/// depth of the token about to be read, and `DEPTH_LIMIT` means "this
/// many levels parse, the next one does not": the 128th container fails
/// the check on the very iteration it becomes the current rule, whether
/// it turns out to be empty or not. That is the boundary
/// `tests/json_test.rs` measures against serde_json rather than
/// asserting from this reasoning.
/// The name the depth check is installed under, as a parse guard.
///
/// A guard rather than the parse budget, because the budget is one slot
/// that a caller's `parse_budget` replaces, and the limit went with it
/// whenever a caller set a budget of its own. Nothing a caller does to the
/// budget reaches a guard. A grammar built on this one that counts depth
/// its own way installs its check under the same name to replace this one,
/// as `tabnas_jsonic` does.
const DEPTH_GUARD: &str = "depth";
/// The one serialized document carrying both the strict-JSON options and
/// the JSON rule set, mirroring `JSON_OPTIONS` + `registerJsonGrammar` in
/// `ts/src/json.ts` and `jsonOptions` + `RegisterJSONGrammar` in
/// `go/json.go`.
///
/// Options travel in the grammar document rather than through the typed
/// `Options` struct because `number.check` can only be bound by name from
/// here; everything else could go either way, and keeping them together
/// means there is one definition of "strict JSON" rather than two halves
/// that can drift.
/// Install the strict JSON options and the JSON rule set on `parser`.
///
/// This is the one entry point: `make` goes through it too, so the two
/// construction paths cannot drift apart.
///
/// ```
/// let mut parser = tabnas::Tabnas::new();
/// tabnas_json::json(&mut parser)?;
/// let value = parser.parse("[1,2,3]")?;
/// assert_eq!(value.to_string(), "[1,2,3]");
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
/// Build a standard-JSON parser instance.
///
/// Infallible by design, and it goes through [`json()`] rather than
/// duplicating the setup, so this path and installing the plugin by hand
/// cannot drift. The document is a fixed literal, so a failure here is a
/// bug in this crate rather than anything a caller did — the Go `Make`
/// panics for the same reason, with the same justification.
///
/// ```
/// let parser = tabnas_json::make();
/// let value = parser.parse("[1,2,3]")?;
/// assert_eq!(value.to_string(), "[1,2,3]");
/// assert!(parser.parse("[1,2,]").is_err());
/// # Ok::<(), tabnas_json::JsonError>(())
/// ```
/// Parse a JSON source string with the shared default parser.
///
/// The engine is built once, on first use, and reused after that. Both
/// other runtimes do the same (`sync.Once` in `go/json.go`, a lazily
/// assigned module variable in `ts/src/json.ts`), and reuse is safe here
/// for the same reason it is there: [`Tabnas::parse`] takes `&self` and
/// builds a fresh parse context per call, and `Tabnas` is `Send + Sync`,
/// so concurrent callers share one installed grammar instead of each
/// rebuilding it. `tests/json_test.rs` pins that with a threaded test.
///
/// Use [`make`] instead when the parser needs configuring: that returns a
/// fresh instance and leaves this one alone.
///
/// ```
/// let value = tabnas_json::parse(r#"{"a":[1,2]}"#)?;
/// assert_eq!(value.to_string(), r#"{"a":[1,2]}"#);
/// assert_eq!(tabnas_json::parse("{a:1}").unwrap_err().code, "unexpected");
/// # Ok::<(), tabnas_json::JsonError>(())
/// ```
/// One optional alchemy source and the entry point a host calls.
/// The package-local structural translation interface.
const TRANSLATION: TranslationParts = TranslationParts ;
/// Return JSON's immutable translation parts.
pub const
/// The plugin's manifest, `tabnas.plugin.json`, as the repository carries
/// it. Its `translate` object is what a host that translates reads: the
/// shape JSON is read as and written from (`tree`) and the render that
/// writes it, which is the `json` render alchemy carries rather than a
/// file of this repository's. It declares no loss: that render keeps every
/// value and every number's spelling, so a JSON document written from a
/// JSON tree loses nothing. The crate embeds its own copy,
/// `translate/manifest.json`, since a packaged crate holds nothing outside
/// `rs/`; `tests/translate_test.rs` holds the copy to the file.
///
/// ```
/// assert!(tabnas_json::manifest_text().contains("\"translate\""));
/// ```