@amritk/nish-x86_64-linux 0.27.2 → 0.29.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.
Files changed (54) hide show
  1. package/INSTALL.md +16 -8
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +118 -26
  4. package/package.json +1 -1
  5. package/runtime/core/host.d.ts +2 -1
  6. package/runtime/core/host.mjs +4 -0
  7. package/runtime/core.d.ts +47 -0
  8. package/runtime/core.mjs +67 -0
  9. package/runtime/nish.d.ts +176 -0
  10. package/runtime/nish.h +120 -6
  11. package/runtime/nish.mjs +24 -4
  12. package/runtime/runtime-checksum.c +270 -0
  13. package/runtime/runtime-net.c +386 -5
  14. package/runtime/runtime-os.c +59 -8
  15. package/runtime/runtime-parallel.c +81 -5
  16. package/runtime/runtime-sandbox.c +479 -0
  17. package/runtime/runtime-sha256.c +239 -0
  18. package/runtime/runtime-shm.c +541 -0
  19. package/runtime/runtime.c +827 -791
  20. package/scripts/build.sh +79 -28
  21. package/std/README.md +25 -13
  22. package/std/collections.ts +5 -5
  23. package/std/compress/checksum.ts +88 -75
  24. package/std/compress/deflate.ts +224 -0
  25. package/std/compress/gzip.ts +52 -0
  26. package/std/crypto/bcrypt.ts +816 -280
  27. package/std/crypto/hmac.ts +305 -2
  28. package/std/crypto/jwt.ts +1661 -0
  29. package/std/json-equal.ts +335 -0
  30. package/std/json-format.ts +1070 -0
  31. package/std/json-number.ts +428 -0
  32. package/std/json-pointer.ts +245 -0
  33. package/std/json-typed.ts +34 -0
  34. package/std/json-value.ts +510 -17
  35. package/std/json.ts +35 -6
  36. package/std/net/handler.ts +1348 -0
  37. package/std/net/http-client.ts +97 -16
  38. package/std/net/http-compress.ts +301 -16
  39. package/std/net/http1-client.ts +104 -19
  40. package/std/net/http1-server.ts +127 -24
  41. package/std/net/http1.ts +24 -4
  42. package/std/net/outbound.ts +409 -0
  43. package/std/net/postgres.ts +405 -0
  44. package/std/net/prefork.ts +344 -20
  45. package/std/net/websocket.ts +295 -14
  46. package/std/regex.ts +613 -52
  47. package/std/secret.ts +1 -1
  48. package/std/sync/ring.ts +587 -0
  49. package/std/sync/shm.ts +284 -0
  50. package/std/term-io.ts +35 -8
  51. package/std/term-rate.ts +13 -1
  52. package/std/threads.ts +35 -3
  53. package/std/yaml-json.ts +841 -0
  54. /package/runtime/{LICENSE-ryu → LICENSE-zmij} +0 -0
@@ -0,0 +1,428 @@
1
+ /**
2
+ * `nish/json-number` — JSON number text compared and divided exactly, the
3
+ * arithmetic JSON Schema's `minimum`, `maximum`, `exclusiveMinimum`,
4
+ * `exclusiveMaximum`, `multipleOf`, `const` and `enum` ask for.
5
+ *
6
+ * import { jsonNumberCompare, jsonNumberMultipleOf } from "nish/json-number"
7
+ *
8
+ * jsonNumberCompare("1.0", "1e0") // 0: the same value
9
+ * jsonNumberCompare("0.1", "0.10000000000000001") // -1, which f64 calls equal
10
+ * jsonNumberMultipleOf("0.3", "0.1") // 1, which f64 calls 0
11
+ * jsonNumberMultipleOf("1e1000000000", "2") // JSON_NUMBER_ERR_GAP
12
+ *
13
+ * **Never through `f64`.** A number is read from its text as a sign, its
14
+ * significant digits (leading and trailing zeros dropped) and the decimal
15
+ * exponent of its last digit, and every answer is computed on those, so
16
+ * `9007199254740993` is not `9007199254740992`, `0.1 * 3` is not involved in
17
+ * whether `0.3` is a multiple of `0.1`, and `1e400` is a number like any
18
+ * other rather than an infinity. The text is a JSON number, RFC 8259's
19
+ * grammar exactly (what `JsonDocument.numberText` answers); anything else is
20
+ * `JSON_NUMBER_ERR_SYNTAX`.
21
+ *
22
+ * **What a call costs.** `jsonNumberCompare` reads each text once and
23
+ * allocates two records of a few fields, nothing in proportion to the text:
24
+ * time is linear in the texts' lengths. `jsonNumberCanonical` allocates the
25
+ * string it answers, at most the text's length plus an exponent.
26
+ * `jsonNumberMultipleOf` divides the value's digits, followed by as many
27
+ * zeros as its exponent is above the divisor's (the gap), by the divisor's
28
+ * digits: one step per digit of that dividend when the divisor has at most 18
29
+ * significant digits, and one per digit of the dividend per digit of the
30
+ * divisor otherwise, which then also allocates the remainder, one byte per
31
+ * divisor digit. A step of that long division is a shift, up to ten
32
+ * comparisons and up to nine subtractions of a byte each, so about twenty
33
+ * byte operations.
34
+ *
35
+ * **The bounds are stated.** An exponent is at most `JSON_NUMBER_MAX_EXPONENT`
36
+ * in size, which keeps every exponent sum far inside `i64`, and past
37
+ * it a text is `JSON_NUMBER_ERR_EXPONENT`, unless every digit is zero:
38
+ * `0e9999999999999999` is zero. A division that would take more
39
+ * than `JSON_NUMBER_MAX_STEPS` steps is `JSON_NUMBER_ERR_GAP` before it
40
+ * starts: `1e1000000000` is 12 bytes, and whether it is a multiple of `3`
41
+ * would otherwise be a billion steps, the cheap input and expensive answer an
42
+ * attacker sends to a validator. A JSON Schema validator reports a refused
43
+ * comparison as the instance failing, and says why.
44
+ *
45
+ * Every answer is an `i32`, so a program reads them the same under
46
+ * `--number-mode f64`; a refusal is a `JSON_NUMBER_ERR_*` code, each below
47
+ * `-1`, so that it is never one of `jsonNumberCompare`'s answers.
48
+ */
49
+
50
+ /** The text is not a JSON number. */
51
+ export const JSON_NUMBER_ERR_SYNTAX: i32 = -10
52
+ /** The text's exponent is larger in size than `JSON_NUMBER_MAX_EXPONENT`. */
53
+ export const JSON_NUMBER_ERR_EXPONENT: i32 = -11
54
+ /** `jsonNumberMultipleOf` was asked about a divisor of zero. */
55
+ export const JSON_NUMBER_ERR_DIVISOR: i32 = -12
56
+ /** `jsonNumberMultipleOf` would take more than `JSON_NUMBER_MAX_STEPS` steps. */
57
+ export const JSON_NUMBER_ERR_GAP: i32 = -13
58
+
59
+ /** The largest exponent, in size, a text may write: 10^15, so a sum of two stays far inside `i64`. */
60
+ export const JSON_NUMBER_MAX_EXPONENT: i64 = 1000000000000000
61
+ /** The most digit steps one `jsonNumberMultipleOf` may take, 2^20 (the module header says what a step costs). */
62
+ export const JSON_NUMBER_MAX_STEPS: i64 = 1048576
63
+
64
+ /** The most divisor digits the `u64` remainder holds: 10^18 times ten, plus nine, is below 2^64. */
65
+ const JSON_NUMBER_SMALL_DIVISOR: i32 = 18
66
+
67
+ const JSON_NUMBER_PLUS: i32 = 43
68
+ const JSON_NUMBER_MINUS: i32 = 45
69
+ const JSON_NUMBER_DOT: i32 = 46
70
+ const JSON_NUMBER_DIGIT_0: i32 = 48
71
+ const JSON_NUMBER_DIGIT_9: i32 = 57
72
+ const JSON_NUMBER_UPPER_E: i32 = 69
73
+ const JSON_NUMBER_LOWER_E: i32 = 101
74
+
75
+ /** The byte at `i` of `s`, or `-1` past its end. */
76
+ const jsonNumberByte = (s: string, i: i32): i32 => (i < toI32(s.length) ? toI32(s.charCodeAt(i)) : -1)
77
+
78
+ const jsonNumberIsDigit = (c: i32): boolean => c >= JSON_NUMBER_DIGIT_0 && c <= JSON_NUMBER_DIGIT_9
79
+
80
+ /**
81
+ * One number text read as a sign, significant digits and an exponent. The
82
+ * digits are not copied: `digit(k)` reads the k-th of them from the text,
83
+ * stepping over the point.
84
+ */
85
+ class JsonNumberParts {
86
+ text: string
87
+ /** `0` when the text is a number, else a `JSON_NUMBER_ERR_*` code. */
88
+ code: i32
89
+ negative: boolean
90
+ /** Whether every digit is zero, `-0` and `0.0e5` included. */
91
+ zero: boolean
92
+ /** Where the integer part's digits start, and how many there are. */
93
+ intStart: i32
94
+ intLen: i32
95
+ /** Where the fraction's digits start, after the point, and how many there are. */
96
+ fracStart: i32
97
+ fracLen: i32
98
+ /** The first significant digit's place among the integer and fraction digits run together. */
99
+ first: i32
100
+ /** How many significant digits there are, from the first non-zero one to the last. */
101
+ count: i32
102
+ /** The decimal exponent of the first significant digit: `3` for `1234`, `-2` for `0.05`. */
103
+ lead: i64
104
+ /** The decimal exponent of the last significant digit: `0` for `1234`, `2` for `1200`, `-2` for `0.05`. */
105
+ last: i64
106
+
107
+ constructor(text: string) {
108
+ this.text = text
109
+ this.code = 0
110
+ this.negative = false
111
+ this.zero = true
112
+ this.intStart = 0
113
+ this.intLen = 0
114
+ this.fracStart = 0
115
+ this.fracLen = 0
116
+ this.first = 0
117
+ this.count = 0
118
+ this.lead = toI64(0)
119
+ this.last = toI64(0)
120
+ this.code = this.read()
121
+ }
122
+
123
+ /** The k-th significant digit, `0 <= k < count`, as `0` to `9`. */
124
+ digit(k: i32): i32 {
125
+ return this.placeDigit(this.first + k)
126
+ }
127
+
128
+ /** The digit at place `p` of the integer and fraction digits run together. */
129
+ placeDigit(p: i32): i32 {
130
+ const at: i32 = p < this.intLen ? this.intStart + p : this.fracStart + (p - this.intLen)
131
+ return toI32(this.text.charCodeAt(at)) - JSON_NUMBER_DIGIT_0
132
+ }
133
+
134
+ /** Reads the text against RFC 8259's number grammar; answers `0` or a code. */
135
+ read(): i32 {
136
+ const s: string = this.text
137
+ const n: i32 = toI32(s.length)
138
+ let i: i32 = 0
139
+ if (jsonNumberByte(s, i) === JSON_NUMBER_MINUS) {
140
+ this.negative = true
141
+ i += 1
142
+ }
143
+ this.intStart = i
144
+ const lead: i32 = jsonNumberByte(s, i)
145
+ if (!jsonNumberIsDigit(lead)) {
146
+ return JSON_NUMBER_ERR_SYNTAX
147
+ }
148
+ i += 1
149
+ if (lead !== JSON_NUMBER_DIGIT_0) {
150
+ while (jsonNumberIsDigit(jsonNumberByte(s, i))) {
151
+ i += 1
152
+ }
153
+ }
154
+ this.intLen = i - this.intStart
155
+ if (jsonNumberByte(s, i) === JSON_NUMBER_DOT) {
156
+ i += 1
157
+ this.fracStart = i
158
+ while (jsonNumberIsDigit(jsonNumberByte(s, i))) {
159
+ i += 1
160
+ }
161
+ this.fracLen = i - this.fracStart
162
+ if (this.fracLen === 0) {
163
+ return JSON_NUMBER_ERR_SYNTAX
164
+ }
165
+ }
166
+ let exponent: i64 = toI64(0)
167
+ // An exponent past the bound is refused only once the digits are known
168
+ // not all to be zero: `0e9999999999` is zero, as any other zero is.
169
+ let big: boolean = false
170
+ const e: i32 = jsonNumberByte(s, i)
171
+ if (e === JSON_NUMBER_LOWER_E || e === JSON_NUMBER_UPPER_E) {
172
+ i += 1
173
+ let minus: boolean = false
174
+ const sign: i32 = jsonNumberByte(s, i)
175
+ if (sign === JSON_NUMBER_PLUS || sign === JSON_NUMBER_MINUS) {
176
+ minus = sign === JSON_NUMBER_MINUS
177
+ i += 1
178
+ }
179
+ if (!jsonNumberIsDigit(jsonNumberByte(s, i))) {
180
+ return JSON_NUMBER_ERR_SYNTAX
181
+ }
182
+ while (jsonNumberIsDigit(jsonNumberByte(s, i))) {
183
+ // Adding stops past the bound, so a thousand-digit exponent neither
184
+ // overflows nor is mistaken for a small one.
185
+ if (!big) {
186
+ exponent = exponent * toI64(10) + toI64(jsonNumberByte(s, i) - JSON_NUMBER_DIGIT_0)
187
+ big = exponent > JSON_NUMBER_MAX_EXPONENT
188
+ }
189
+ i += 1
190
+ }
191
+ if (minus) {
192
+ exponent = -exponent
193
+ }
194
+ }
195
+ if (i !== n) {
196
+ return JSON_NUMBER_ERR_SYNTAX
197
+ }
198
+ const places: i32 = this.intLen + this.fracLen
199
+ let first: i32 = 0
200
+ while (first < places && this.placeDigit(first) === 0) {
201
+ first += 1
202
+ }
203
+ if (first === places) {
204
+ return 0
205
+ }
206
+ if (big) {
207
+ return JSON_NUMBER_ERR_EXPONENT
208
+ }
209
+ let last: i32 = places - 1
210
+ while (this.placeDigit(last) === 0) {
211
+ last -= 1
212
+ }
213
+ this.zero = false
214
+ this.first = first
215
+ this.count = last - first + 1
216
+ this.lead = exponent + toI64(this.intLen - 1 - first)
217
+ this.last = exponent + toI64(this.intLen - 1 - last)
218
+ return 0
219
+ }
220
+ }
221
+
222
+ /** `-1`, `0` or `1` as `a`'s size is below, equal to or above `b`'s, signs aside. */
223
+ const jsonNumberCompareSize = (a: JsonNumberParts, b: JsonNumberParts): i32 => {
224
+ if (a.lead !== b.lead) {
225
+ return a.lead < b.lead ? -1 : 1
226
+ }
227
+ const n: i32 = a.count > b.count ? a.count : b.count
228
+ for (let k: i32 = 0; k < n; k++) {
229
+ const x: i32 = k < a.count ? a.digit(k) : 0
230
+ const y: i32 = k < b.count ? b.digit(k) : 0
231
+ if (x !== y) {
232
+ return x < y ? -1 : 1
233
+ }
234
+ }
235
+ return 0
236
+ }
237
+
238
+ /** The sign of `p`: `0` for zero, else `-1` or `1`. */
239
+ const jsonNumberSign = (p: JsonNumberParts): i32 => {
240
+ if (p.zero) {
241
+ return 0
242
+ }
243
+ return p.negative ? -1 : 1
244
+ }
245
+
246
+ /**
247
+ * `-1`, `0` or `1` as the value of the JSON number text `a` is below, equal
248
+ * to or above that of `b`, exactly: `1`, `1.0`, `1e0` and `10e-1` are equal,
249
+ * and `-0` equals `0`. A text that is not a number, or whose exponent is past
250
+ * `JSON_NUMBER_MAX_EXPONENT`, is that `JSON_NUMBER_ERR_*` code, `a`'s first.
251
+ * Linear in the two lengths; allocates two small records.
252
+ */
253
+ export const jsonNumberCompare = (a: string, b: string): i32 => {
254
+ const x = new JsonNumberParts(a)
255
+ if (x.code !== 0) {
256
+ return x.code
257
+ }
258
+ const y = new JsonNumberParts(b)
259
+ if (y.code !== 0) {
260
+ return y.code
261
+ }
262
+ const sx: i32 = jsonNumberSign(x)
263
+ const sy: i32 = jsonNumberSign(y)
264
+ if (sx !== sy) {
265
+ return sx < sy ? -1 : 1
266
+ }
267
+ if (sx === 0) {
268
+ return 0
269
+ }
270
+ const size: i32 = jsonNumberCompareSize(x, y)
271
+ return sx > 0 ? size : -size
272
+ }
273
+
274
+ /**
275
+ * The one text every spelling of a number's value has: `0` for zero (`-0`
276
+ * too), else an optional `-`, the significant digits, `e` and the exponent of
277
+ * the last digit, so `1.50`, `15e-1` and `0.015e2` are all `15e-1`, and `100`
278
+ * is `1e2`. Two texts have the same canonical text exactly when
279
+ * `jsonNumberCompare` calls them equal, which makes it a key for a set or a
280
+ * hash. `""` for a text that is not a number or whose exponent is past
281
+ * `JSON_NUMBER_MAX_EXPONENT`. Allocates the answer.
282
+ */
283
+ export const jsonNumberCanonical = (text: string): string => {
284
+ const p = new JsonNumberParts(text)
285
+ if (p.code !== 0) {
286
+ return ""
287
+ }
288
+ if (p.zero) {
289
+ return "0"
290
+ }
291
+ const bytes: u8[] = []
292
+ if (p.negative) {
293
+ bytes.push(toU8(JSON_NUMBER_MINUS))
294
+ }
295
+ for (let k: i32 = 0; k < p.count; k++) {
296
+ bytes.push(toU8(p.digit(k) + JSON_NUMBER_DIGIT_0))
297
+ }
298
+ const digits: string = String.fromBytes(bytes, 0, toI32(bytes.length))
299
+ return `${digits}e${p.last}`
300
+ }
301
+
302
+ /**
303
+ * Whether the low digits of the remainder, `rem[1, n]`, most significant
304
+ * first, are at least the divisor's `n` digits.
305
+ */
306
+ const jsonNumberAtLeast = (rem: u8[], d: JsonNumberParts): boolean => {
307
+ for (let k: i32 = 1; k < toI32(rem.length); k++) {
308
+ const x: i32 = toI32(rem[k])
309
+ const y: i32 = d.digit(k - 1)
310
+ if (x !== y) {
311
+ return x > y
312
+ }
313
+ }
314
+ return true
315
+ }
316
+
317
+ /**
318
+ * Whether the dividend (the value's digits, then zeros to `total` digits)
319
+ * leaves no remainder divided by the divisor's digits, more than 18 of them.
320
+ * The remainder is one decimal digit per byte, one byte more than the divisor
321
+ * has; each step shifts a digit in and takes the divisor away at most nine
322
+ * times.
323
+ */
324
+ const jsonNumberDividesLong = (v: JsonNumberParts, d: JsonNumberParts, total: i64): boolean => {
325
+ const n: i32 = d.count
326
+ const rem: u8[] = []
327
+ for (let k: i32 = 0; k <= n; k++) {
328
+ rem.push(0)
329
+ }
330
+ for (let step: i64 = toI64(0); step < total; step++) {
331
+ // The remainder is below the divisor, so its top byte is zero and the
332
+ // shift loses nothing.
333
+ for (let k: i32 = 0; k + 1 < toI32(rem.length); k++) {
334
+ rem[k] = rem[k + 1]
335
+ }
336
+ let next: i32 = 0
337
+ if (step < toI64(v.count)) {
338
+ next = v.digit(toI32(step))
339
+ }
340
+ rem[toI32(rem.length) - 1] = toU8(next)
341
+ // Below ten divisors now: a non-zero top byte is n + 1 digits against n,
342
+ // so certainly at least the divisor; otherwise the low n digits decide.
343
+ while (rem[0] > 0 || jsonNumberAtLeast(rem, d)) {
344
+ let borrow: i32 = 0
345
+ for (let k: i32 = toI32(rem.length) - 1; k >= 1; k--) {
346
+ let x: i32 = toI32(rem[k]) - d.digit(k - 1) - borrow
347
+ borrow = 0
348
+ if (x < 0) {
349
+ x += 10
350
+ borrow = 1
351
+ }
352
+ rem[k] = toU8(x)
353
+ }
354
+ rem[0] = toU8(toI32(rem[0]) - borrow)
355
+ }
356
+ }
357
+ for (let k: i32 = 0; k < toI32(rem.length); k++) {
358
+ if (rem[k] !== 0) {
359
+ return false
360
+ }
361
+ }
362
+ return true
363
+ }
364
+
365
+ /**
366
+ * The same for a divisor of at most 18 digits, whose remainder fits a `u64`
367
+ * with a digit shifted in: one multiply and one remainder per step.
368
+ */
369
+ const jsonNumberDividesShort = (v: JsonNumberParts, d: JsonNumberParts, total: i64): boolean => {
370
+ let divisor: u64 = toU64(0)
371
+ for (let k: i32 = 0; k < d.count; k++) {
372
+ divisor = divisor * toU64(10) + toU64(d.digit(k))
373
+ }
374
+ let rem: u64 = toU64(0)
375
+ for (let step: i64 = toI64(0); step < total; step++) {
376
+ const digit: u64 = step < toI64(v.count) ? toU64(v.digit(toI32(step))) : toU64(0)
377
+ rem = (rem * toU64(10) + digit) % divisor
378
+ }
379
+ return rem === toU64(0)
380
+ }
381
+
382
+ /**
383
+ * `1` when the value of the JSON number text `value` is a whole multiple of
384
+ * that of `divisor`, exactly, else `0`: `0.3` is a multiple of `0.1` and
385
+ * `0.0075` of `0.0001`, `4.5` is not one of `2`, and zero is one of anything.
386
+ * Signs are ignored. JSON Schema's `multipleOf` is `1` here.
387
+ *
388
+ * Refused, each with its `JSON_NUMBER_ERR_*` code: a text that is not a
389
+ * number or whose exponent is past `JSON_NUMBER_MAX_EXPONENT` (`value`'s
390
+ * first), a `divisor` of zero, and a division of more than
391
+ * `JSON_NUMBER_MAX_STEPS` steps, which is `JSON_NUMBER_ERR_GAP`: the value's
392
+ * digits plus the gap between the two last digits' exponents, times the
393
+ * divisor's digits when it has more than 18.
394
+ */
395
+ export const jsonNumberMultipleOf = (value: string, divisor: string): i32 => {
396
+ const v = new JsonNumberParts(value)
397
+ if (v.code !== 0) {
398
+ return v.code
399
+ }
400
+ const d = new JsonNumberParts(divisor)
401
+ if (d.code !== 0) {
402
+ return d.code
403
+ }
404
+ if (d.zero) {
405
+ return JSON_NUMBER_ERR_DIVISOR
406
+ }
407
+ if (v.zero) {
408
+ return 1
409
+ }
410
+ // value = V * 10^v.last and divisor = D * 10^d.last, neither V nor D ending
411
+ // in a zero. A value whose last digit is finer than the divisor's would need
412
+ // D * 10^k to divide V for some k above zero, and then ten would divide V.
413
+ const gap: i64 = v.last - d.last
414
+ if (gap < toI64(0)) {
415
+ return 0
416
+ }
417
+ const total: i64 = toI64(v.count) + gap
418
+ const short: boolean = d.count <= JSON_NUMBER_SMALL_DIVISOR
419
+ // A module constant is a plain number under Node, where an `i64` is a
420
+ // BigInt: comparing the two is allowed, dividing them is not.
421
+ if (total > (short ? JSON_NUMBER_MAX_STEPS : toI64(JSON_NUMBER_MAX_STEPS) / toI64(d.count))) {
422
+ return JSON_NUMBER_ERR_GAP
423
+ }
424
+ if (short) {
425
+ return jsonNumberDividesShort(v, d, total) ? 1 : 0
426
+ }
427
+ return jsonNumberDividesLong(v, d, total) ? 1 : 0
428
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * `nish/json-pointer` — RFC 6901 JSON Pointers: parsed into their tokens,
3
+ * escaped, resolved against a `JsonDocument`, and checked, with JSON Schema's
4
+ * relative JSON Pointer beside them.
5
+ *
6
+ * import { jsonPointerParse, jsonPointerResolve, isRelativeJsonPointer } from "nish/json-pointer"
7
+ *
8
+ * const doc = new JsonDocument(new JsonLimits())
9
+ * const root = doc.parseText('{"a/b":[10,20],"~":true}')
10
+ * jsonPointerParse("/a~1b/1") // ["a/b", "1"]
11
+ * doc.i64(jsonPointerResolve(doc, root, "/a~1b/1")) // 20
12
+ * jsonPointerResolve(doc, root, "/a~1b/-") // JSON_POINTER_NONE
13
+ * jsonPointerResolve(doc, root, "a") // JSON_POINTER_ERR_SYNTAX
14
+ * isRelativeJsonPointer("1/a~1b") // true
15
+ *
16
+ * **The grammar.** A pointer is empty, naming the whole document, or a run
17
+ * of `/`-led tokens, each any bytes but `/`, with `~0` for `~` and `~1` for
18
+ * `/` and no other `~`. A token names an object's member by its key, bytes
19
+ * for bytes, and an array's element by an index written `0` or without a
20
+ * leading zero; `-`, the place after the last element, names nothing, as
21
+ * does an index past the end, a token on a scalar, and a key not there. The
22
+ * pointer is the decoded string, not a URI fragment: `#/a` is not one.
23
+ *
24
+ * A relative JSON Pointer (draft-bhutton-relative-json-pointer-00, which
25
+ * JSON Schema 2020-12's `relative-json-pointer` format names) is a
26
+ * non-negative integer written without a leading zero, then either `#`, or
27
+ * an optional index manipulation (`+` or `-` and another such integer) and
28
+ * a JSON Pointer: `0#`, `1/a`, `0+1/a`, `2-1`, not `0+1#`. Only its form is
29
+ * checked here: resolving one needs the place it is relative to, which a
30
+ * validator walking an instance holds.
31
+ *
32
+ * **What a call costs.** `isJsonPointer` and `isRelativeJsonPointer` read
33
+ * the text once and allocate nothing. `jsonPointerParse` allocates each
34
+ * token and the array. `jsonPointerResolve` reads the pointer once and
35
+ * allocates each token it decodes; a member is found by `JsonDocument.get`,
36
+ * which steps through the object's members, so a step costs the object's
37
+ * size.
38
+ */
39
+
40
+ import { JSON_VALUE_ARRAY, JSON_VALUE_NONE, JSON_VALUE_OBJECT, JsonDocument } from "nish/json-value"
41
+
42
+ /** `jsonPointerResolve`: the pointer is well formed and names nothing in the document. */
43
+ export const JSON_POINTER_NONE: i32 = -1
44
+ /** `jsonPointerResolve`: the text is not a JSON Pointer. */
45
+ export const JSON_POINTER_ERR_SYNTAX: i32 = -2
46
+
47
+ const JSON_POINTER_HASH: i32 = 35
48
+ const JSON_POINTER_PLUS: i32 = 43
49
+ const JSON_POINTER_MINUS: i32 = 45
50
+ const JSON_POINTER_SLASH: i32 = 47
51
+ const JSON_POINTER_DIGIT_0: i32 = 48
52
+ const JSON_POINTER_DIGIT_1: i32 = 49
53
+ const JSON_POINTER_DIGIT_9: i32 = 57
54
+ const JSON_POINTER_TILDE: i32 = 126
55
+
56
+ /** The byte at `i` of `s`, or `-1` past its end. */
57
+ const jsonPointerByte = (s: string, i: i32): i32 =>
58
+ i >= 0 && i < toI32(s.length) ? toI32(s.charCodeAt(i)) : -1
59
+
60
+ /**
61
+ * Whether `s[from, end)` is a JSON Pointer: empty, or `/` and then tokens in
62
+ * which every `~` is followed by `0` or `1`.
63
+ */
64
+ const jsonPointerWellFormed = (s: string, from: i32): boolean => {
65
+ const n: i32 = toI32(s.length)
66
+ if (from < n && jsonPointerByte(s, from) !== JSON_POINTER_SLASH) {
67
+ return false
68
+ }
69
+ for (let i: i32 = from; i < n; i++) {
70
+ if (jsonPointerByte(s, i) === JSON_POINTER_TILDE) {
71
+ const next: i32 = jsonPointerByte(s, i + 1)
72
+ if (next !== JSON_POINTER_DIGIT_0 && next !== JSON_POINTER_DIGIT_1) {
73
+ return false
74
+ }
75
+ }
76
+ }
77
+ return true
78
+ }
79
+
80
+ /** Whether `s` is an RFC 6901 JSON Pointer (the module header's grammar). Allocates nothing. */
81
+ export const isJsonPointer = (s: string): boolean => jsonPointerWellFormed(s, 0)
82
+
83
+ /**
84
+ * Where the non-negative integer that starts at `from` of `s` ends: past
85
+ * `0`, or past a digit run led by `1`-`9`. `-1` when no digit is there.
86
+ */
87
+ const jsonPointerIntegerEnd = (s: string, from: i32): i32 => {
88
+ const first: i32 = jsonPointerByte(s, from)
89
+ if (first < JSON_POINTER_DIGIT_0 || first > JSON_POINTER_DIGIT_9) {
90
+ return -1
91
+ }
92
+ let i: i32 = from + 1
93
+ if (first !== JSON_POINTER_DIGIT_0) {
94
+ while (jsonPointerByte(s, i) >= JSON_POINTER_DIGIT_0 && jsonPointerByte(s, i) <= JSON_POINTER_DIGIT_9) {
95
+ i += 1
96
+ }
97
+ }
98
+ return i
99
+ }
100
+
101
+ /**
102
+ * Whether `s` is a relative JSON Pointer: a non-negative integer with no
103
+ * leading zero (`0`, `12`, not `01`, `+1` or `-1`), then `#`, or an optional
104
+ * `+` or `-` and another such integer and then a JSON Pointer, which may be
105
+ * empty. Allocates nothing.
106
+ */
107
+ export const isRelativeJsonPointer = (s: string): boolean => {
108
+ let i: i32 = jsonPointerIntegerEnd(s, 0)
109
+ if (i < 0) {
110
+ return false
111
+ }
112
+ const next: i32 = jsonPointerByte(s, i)
113
+ if (next === JSON_POINTER_HASH) {
114
+ return i + 1 === toI32(s.length)
115
+ }
116
+ if (next === JSON_POINTER_PLUS || next === JSON_POINTER_MINUS) {
117
+ i = jsonPointerIntegerEnd(s, i + 1)
118
+ if (i < 0) {
119
+ return false
120
+ }
121
+ }
122
+ return jsonPointerWellFormed(s, i)
123
+ }
124
+
125
+ /**
126
+ * The token of well-formed pointer `p` that starts at `from` and ends before
127
+ * `end`, with `~1` and `~0` decoded. The runs between escapes are copied
128
+ * whole, so no byte (no code unit under Node) is narrowed.
129
+ */
130
+ const jsonPointerToken = (p: string, from: i32, end: i32): string => {
131
+ const parts: string[] = []
132
+ let run: i32 = from
133
+ let i: i32 = from
134
+ while (i < end) {
135
+ if (jsonPointerByte(p, i) === JSON_POINTER_TILDE) {
136
+ parts.push(p.slice(run, i))
137
+ parts.push(jsonPointerByte(p, i + 1) === JSON_POINTER_DIGIT_1 ? "/" : "~")
138
+ i += 2
139
+ run = i
140
+ } else {
141
+ i += 1
142
+ }
143
+ }
144
+ parts.push(p.slice(run, end))
145
+ return parts.join("")
146
+ }
147
+
148
+ /** Where the token that starts at `from` of pointer `p` ends: the next `/`, or the end. */
149
+ const jsonPointerTokenEnd = (p: string, from: i32): i32 => {
150
+ let i: i32 = from
151
+ while (i < toI32(p.length) && jsonPointerByte(p, i) !== JSON_POINTER_SLASH) {
152
+ i += 1
153
+ }
154
+ return i
155
+ }
156
+
157
+ /**
158
+ * The reference tokens of JSON Pointer `p`, decoded: `[]` for `""`,
159
+ * `["a/b", "", "~"]` for `/a~1b//~0`. `null` when `p` is not a JSON Pointer.
160
+ */
161
+ export const jsonPointerParse = (p: string): string[] | null => {
162
+ if (!isJsonPointer(p)) {
163
+ return null
164
+ }
165
+ const tokens: string[] = []
166
+ let i: i32 = 0
167
+ while (i < toI32(p.length)) {
168
+ const end: i32 = jsonPointerTokenEnd(p, i + 1)
169
+ tokens.push(jsonPointerToken(p, i + 1, end))
170
+ i = end
171
+ }
172
+ return tokens
173
+ }
174
+
175
+ /**
176
+ * `token` written as one token of a JSON Pointer: `~` as `~0` and `/` as
177
+ * `~1`, so `"a/b"` is `a~1b`. Everything else is copied whole.
178
+ */
179
+ export const jsonPointerEscape = (token: string): string => {
180
+ const parts: string[] = []
181
+ let run: i32 = 0
182
+ for (let i: i32 = 0; i < toI32(token.length); i++) {
183
+ const c: i32 = jsonPointerByte(token, i)
184
+ if (c === JSON_POINTER_TILDE || c === JSON_POINTER_SLASH) {
185
+ parts.push(token.slice(run, i))
186
+ parts.push(c === JSON_POINTER_TILDE ? "~0" : "~1")
187
+ run = i + 1
188
+ }
189
+ }
190
+ parts.push(token.slice(run))
191
+ return parts.join("")
192
+ }
193
+
194
+ /**
195
+ * The element an array token names in an array of `length` elements: its
196
+ * index, or `-1` for `-`, a leading zero, anything but digits, or an index at
197
+ * or past the end. Digits stop counting once they pass `length`, so a token
198
+ * of any length is read without overflow.
199
+ */
200
+ const jsonPointerIndex = (p: string, from: i32, end: i32, length: i32): i32 => {
201
+ if (from === end || (end - from > 1 && jsonPointerByte(p, from) === JSON_POINTER_DIGIT_0)) {
202
+ return -1
203
+ }
204
+ let value: i32 = 0
205
+ for (let i: i32 = from; i < end; i++) {
206
+ const c: i32 = jsonPointerByte(p, i)
207
+ if (c < JSON_POINTER_DIGIT_0 || c > JSON_POINTER_DIGIT_9) {
208
+ return -1
209
+ }
210
+ value = value * 10 + (c - JSON_POINTER_DIGIT_0)
211
+ if (value >= length) {
212
+ return -1
213
+ }
214
+ }
215
+ return value
216
+ }
217
+
218
+ /**
219
+ * The value JSON Pointer `p` names in `root` of `doc`: `root` itself for
220
+ * `""`, else each token taken in turn, as the module header says.
221
+ * `JSON_POINTER_NONE` when the pointer names nothing there (or `root` names
222
+ * no value), and `JSON_POINTER_ERR_SYNTAX` when `p` is not a JSON Pointer.
223
+ */
224
+ export const jsonPointerResolve = (doc: JsonDocument, root: i32, p: string): i32 => {
225
+ if (!isJsonPointer(p)) {
226
+ return JSON_POINTER_ERR_SYNTAX
227
+ }
228
+ let at: i32 = root
229
+ let i: i32 = 0
230
+ while (doc.kind(at) !== JSON_VALUE_NONE && i < toI32(p.length)) {
231
+ const end: i32 = jsonPointerTokenEnd(p, i + 1)
232
+ const kind: i32 = doc.kind(at)
233
+ if (kind === JSON_VALUE_OBJECT) {
234
+ at = doc.get(at, jsonPointerToken(p, i + 1, end))
235
+ } else if (kind === JSON_VALUE_ARRAY) {
236
+ // `-` is a single byte that is not a digit, so `jsonPointerIndex` answers `-1` for it.
237
+ const index: i32 = jsonPointerIndex(p, i + 1, end, doc.length(at))
238
+ at = index < 0 ? JSON_POINTER_NONE : doc.at(at, index)
239
+ } else {
240
+ at = JSON_POINTER_NONE
241
+ }
242
+ i = end
243
+ }
244
+ return doc.kind(at) === JSON_VALUE_NONE ? JSON_POINTER_NONE : at
245
+ }