@amritk/nish-x86_64-linux 0.27.2 → 0.28.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.
- package/INSTALL.md +13 -8
- package/bin/nish +0 -0
- package/docs/AI.md +19 -2
- package/package.json +1 -1
- package/runtime/core.d.ts +47 -0
- package/runtime/core.mjs +67 -0
- package/runtime/nish.d.ts +49 -0
- package/runtime/nish.h +50 -5
- package/runtime/nish.mjs +6 -0
- package/runtime/runtime-checksum.c +270 -0
- package/runtime/runtime-parallel.c +21 -0
- package/runtime/runtime-sandbox.c +470 -0
- package/runtime/runtime-sha256.c +239 -0
- package/scripts/build.sh +44 -23
- package/std/README.md +15 -10
- package/std/compress/checksum.ts +88 -75
- package/std/compress/deflate.ts +224 -0
- package/std/compress/gzip.ts +52 -0
- package/std/crypto/bcrypt.ts +816 -280
- package/std/json-equal.ts +335 -0
- package/std/json-format.ts +1070 -0
- package/std/json-number.ts +428 -0
- package/std/json-pointer.ts +245 -0
- package/std/json-value.ts +510 -17
- package/std/net/http-compress.ts +259 -16
- package/std/net/http1-client.ts +104 -19
- package/std/net/http1-server.ts +61 -12
- package/std/net/postgres.ts +405 -0
- package/std/net/websocket.ts +295 -14
- package/std/regex.ts +613 -52
- package/std/term-io.ts +35 -8
- package/std/yaml-json.ts +841 -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
|
+
}
|