@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,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/json-equal` — whether two JSON values are equal by JSON Schema's
|
|
3
|
+
* rule, a canonical text and a hash that agree with it, and the first
|
|
4
|
+
* repeated element of an array, which is what `const`, `enum` and
|
|
5
|
+
* `uniqueItems` ask.
|
|
6
|
+
*
|
|
7
|
+
* import { jsonEqual, jsonEqualFirstDuplicate } from "nish/json-equal"
|
|
8
|
+
*
|
|
9
|
+
* const doc = new JsonDocument(new JsonLimits())
|
|
10
|
+
* const a = doc.parseText('[{"x":1,"y":[true]},{"y":[true],"x":1.0}]')
|
|
11
|
+
* jsonEqual(doc, doc.at(a, 0), doc, doc.at(a, 1)) // true
|
|
12
|
+
* jsonEqualFirstDuplicate(doc, a) // 1
|
|
13
|
+
*
|
|
14
|
+
* **The rule.** Two values are equal when they are the same kind and: two
|
|
15
|
+
* numbers have the same mathematical value (`1`, `1.0` and `1e0` are equal;
|
|
16
|
+
* `nish/json-number` decides, never through `f64`), two strings have the
|
|
17
|
+
* same string, two booleans the same value, two arrays the same length and
|
|
18
|
+
* equal elements in order, and two objects the same keys with equal values,
|
|
19
|
+
* in whatever order the members are written. `null` equals only `null`, and
|
|
20
|
+
* `true` is not `1`. A number whose exponent is past
|
|
21
|
+
* `JSON_NUMBER_MAX_EXPONENT` cannot be read exactly, and equals only a number
|
|
22
|
+
* written with the same text.
|
|
23
|
+
*
|
|
24
|
+
* **No depth limit.** Every walk keeps its own stack in an array, so a tree
|
|
25
|
+
* built with `push` and `set` deeper than any parser allows compares as well
|
|
26
|
+
* as a flat one, and nothing recurses.
|
|
27
|
+
*
|
|
28
|
+
* **What a call costs.** `jsonEqual` stops at the first difference. Up to
|
|
29
|
+
* there it is linear in the values' size, except that each pair of objects
|
|
30
|
+
* reads every key (one string each) and sorts them, `n log n` for `n`
|
|
31
|
+
* members; it also allocates one string per string value, its stack, and
|
|
32
|
+
* whatever `jsonNumberCompare` allocates per number pair. `jsonEqualCanonical`
|
|
33
|
+
* builds the canonical text, about the size of the value written as JSON,
|
|
34
|
+
* and sorts keys the same way. `jsonEqualHash` is FNV-1a of that text's
|
|
35
|
+
* units: its bytes natively, its code units under Node. Every order and
|
|
36
|
+
* length below is in those units too.
|
|
37
|
+
* `jsonEqualFirstDuplicate` makes the canonical text of every element up to
|
|
38
|
+
* the first repeat and keeps them in a `Map`.
|
|
39
|
+
*
|
|
40
|
+
* **The hash is not keyed.** FNV-1a (public domain) is the same in every
|
|
41
|
+
* process, so an attacker can make many values that share a hash, and a
|
|
42
|
+
* table keyed by `jsonEqualHash` alone degrades to a list. It is for a
|
|
43
|
+
* filter or a fingerprint; `jsonEqualFirstDuplicate` keys its `Map` by the
|
|
44
|
+
* canonical text, which the `Map`'s own keyed hash spreads.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { jsonNumberCanonical, jsonNumberCompare } from "nish/json-number"
|
|
48
|
+
import {
|
|
49
|
+
JSON_VALUE_ARRAY,
|
|
50
|
+
JSON_VALUE_BOOLEAN,
|
|
51
|
+
JSON_VALUE_NONE,
|
|
52
|
+
JSON_VALUE_NULL,
|
|
53
|
+
JSON_VALUE_NUMBER,
|
|
54
|
+
JSON_VALUE_OBJECT,
|
|
55
|
+
JSON_VALUE_STRING,
|
|
56
|
+
JsonDocument,
|
|
57
|
+
} from "nish/json-value"
|
|
58
|
+
|
|
59
|
+
/** On the canonical walk's stack, in place of a value: write the next key on the key stack. */
|
|
60
|
+
const JSON_EQUAL_KEY_TASK: i32 = -1
|
|
61
|
+
|
|
62
|
+
/** Whether `a` sorts before `b`, unit by unit (byte or code unit) and a prefix first. */
|
|
63
|
+
const jsonEqualBefore = (a: string, b: string): boolean => {
|
|
64
|
+
for (let k: i32 = 0; k < toI32(a.length) && k < toI32(b.length); k++) {
|
|
65
|
+
const x: i32 = toI32(a.charCodeAt(k))
|
|
66
|
+
const y: i32 = toI32(b.charCodeAt(k))
|
|
67
|
+
if (x !== y) {
|
|
68
|
+
return x < y
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return toI32(a.length) < toI32(b.length)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Guarded reads and writes for indexes that stay inside their arrays by the
|
|
75
|
+
// algorithm's reasoning rather than by a test the compiler can see.
|
|
76
|
+
|
|
77
|
+
/** `a[i]`, or `0` outside `a`. */
|
|
78
|
+
const jsonEqualAt = (a: i32[], i: i32): i32 => (i >= 0 && i < toI32(a.length) ? a[i] : 0)
|
|
79
|
+
|
|
80
|
+
/** `keys[i]`, or `""` outside `keys`. */
|
|
81
|
+
const jsonEqualKeyAt = (keys: string[], i: i32): string => (i >= 0 && i < toI32(keys.length) ? keys[i] : "")
|
|
82
|
+
|
|
83
|
+
/** `a[i] = x`, nothing outside `a`. */
|
|
84
|
+
const jsonEqualSetAt = (a: i32[], i: i32, x: i32): void => {
|
|
85
|
+
if (i >= 0 && i < toI32(a.length)) {
|
|
86
|
+
a[i] = x
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The member positions of object `v` in key order, with every key put
|
|
92
|
+
* in `keys` by its position. A bottom-up merge sort: `n log n` comparisons and
|
|
93
|
+
* no recursion. Keys in one object are distinct, so the order is total.
|
|
94
|
+
*/
|
|
95
|
+
const jsonEqualSortedMembers = (doc: JsonDocument, v: i32, keys: string[]): i32[] => {
|
|
96
|
+
const n: i32 = doc.length(v)
|
|
97
|
+
let order: i32[] = []
|
|
98
|
+
let spare: i32[] = []
|
|
99
|
+
for (let i: i32 = 0; i < n; i++) {
|
|
100
|
+
keys.push(doc.keyAt(v, i))
|
|
101
|
+
order.push(i)
|
|
102
|
+
spare.push(i)
|
|
103
|
+
}
|
|
104
|
+
// A document holds at most 2^22 values, so the widths below never come
|
|
105
|
+
// near overflowing.
|
|
106
|
+
let width: i32 = 1
|
|
107
|
+
while (width < n) {
|
|
108
|
+
let lo: i32 = 0
|
|
109
|
+
while (lo < n) {
|
|
110
|
+
const mid: i32 = lo + width < n ? lo + width : n
|
|
111
|
+
const hi: i32 = mid + width < n ? mid + width : n
|
|
112
|
+
let i: i32 = lo
|
|
113
|
+
let j: i32 = mid
|
|
114
|
+
for (let k: i32 = lo; k < hi; k++) {
|
|
115
|
+
const left: i32 = jsonEqualAt(order, i)
|
|
116
|
+
const right: i32 = jsonEqualAt(order, j)
|
|
117
|
+
const takeLeft: boolean =
|
|
118
|
+
i < mid && (j >= hi || !jsonEqualBefore(jsonEqualKeyAt(keys, right), jsonEqualKeyAt(keys, left)))
|
|
119
|
+
jsonEqualSetAt(spare, k, takeLeft ? left : right)
|
|
120
|
+
if (takeLeft) {
|
|
121
|
+
i += 1
|
|
122
|
+
} else {
|
|
123
|
+
j += 1
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
lo = hi
|
|
127
|
+
}
|
|
128
|
+
const done: i32[] = spare
|
|
129
|
+
spare = order
|
|
130
|
+
order = done
|
|
131
|
+
width = width * 2
|
|
132
|
+
}
|
|
133
|
+
return order
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Whether two number texts are equal by value, or by text when either is past the exponent bound. */
|
|
137
|
+
const jsonEqualNumbers = (a: string, b: string): boolean => {
|
|
138
|
+
const c: i32 = jsonNumberCompare(a, b)
|
|
139
|
+
return c < -1 ? a === b : c === 0
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Whether value `a` of document `da` equals value `b` of document `db` by
|
|
144
|
+
* JSON Schema's rule (the module header): numbers by value, objects whatever
|
|
145
|
+
* their member order. The two may be one document. `false` when either
|
|
146
|
+
* handle names no value. Iterative, so any depth compares.
|
|
147
|
+
*/
|
|
148
|
+
export const jsonEqual = (da: JsonDocument, a: i32, db: JsonDocument, b: i32): boolean => {
|
|
149
|
+
const left: i32[] = [a]
|
|
150
|
+
const right: i32[] = [b]
|
|
151
|
+
while (left.length > 0) {
|
|
152
|
+
const x: i32 = jsonEqualAt(left, toI32(left.length) - 1)
|
|
153
|
+
const y: i32 = jsonEqualAt(right, toI32(right.length) - 1)
|
|
154
|
+
left.pop()
|
|
155
|
+
right.pop()
|
|
156
|
+
const kind: i32 = da.kind(x)
|
|
157
|
+
if (kind === JSON_VALUE_NONE || kind !== db.kind(y)) {
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
if (kind === JSON_VALUE_BOOLEAN) {
|
|
161
|
+
if (da.boolean(x) !== db.boolean(y)) {
|
|
162
|
+
return false
|
|
163
|
+
}
|
|
164
|
+
} else if (kind === JSON_VALUE_NUMBER) {
|
|
165
|
+
if (!jsonEqualNumbers(da.numberText(x), db.numberText(y))) {
|
|
166
|
+
return false
|
|
167
|
+
}
|
|
168
|
+
} else if (kind === JSON_VALUE_STRING) {
|
|
169
|
+
if (!db.stringEquals(y, da.string(x))) {
|
|
170
|
+
return false
|
|
171
|
+
}
|
|
172
|
+
} else if (kind === JSON_VALUE_ARRAY || kind === JSON_VALUE_OBJECT) {
|
|
173
|
+
const n: i32 = da.length(x)
|
|
174
|
+
if (n !== db.length(y)) {
|
|
175
|
+
return false
|
|
176
|
+
}
|
|
177
|
+
if (kind === JSON_VALUE_ARRAY) {
|
|
178
|
+
for (let i: i32 = 0; i < n; i++) {
|
|
179
|
+
left.push(da.at(x, i))
|
|
180
|
+
right.push(db.at(y, i))
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
const keysX: string[] = []
|
|
184
|
+
const keysY: string[] = []
|
|
185
|
+
const orderX: i32[] = jsonEqualSortedMembers(da, x, keysX)
|
|
186
|
+
const orderY: i32[] = jsonEqualSortedMembers(db, y, keysY)
|
|
187
|
+
for (let i: i32 = 0; i < n; i++) {
|
|
188
|
+
const mx: i32 = jsonEqualAt(orderX, i)
|
|
189
|
+
const my: i32 = jsonEqualAt(orderY, i)
|
|
190
|
+
if (jsonEqualKeyAt(keysX, mx) !== jsonEqualKeyAt(keysY, my)) {
|
|
191
|
+
return false
|
|
192
|
+
}
|
|
193
|
+
left.push(da.at(x, mx))
|
|
194
|
+
right.push(db.at(y, my))
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Appends `s` to `out` whole, so every unit of it is kept. */
|
|
203
|
+
const jsonEqualPut = (out: string[], s: string): void => {
|
|
204
|
+
out.push(s)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Appends `s` as the canonical form writes a string or a key: its length, `:`, the string. */
|
|
208
|
+
const jsonEqualPutText = (out: string[], s: string): void => {
|
|
209
|
+
jsonEqualPut(out, `${toI32(s.length)}:`)
|
|
210
|
+
jsonEqualPut(out, s)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Appends number `x`'s canonical form. Past the exponent bound a number is its
|
|
215
|
+
* own text, tagged apart so that it never meets a canonical one, which is
|
|
216
|
+
* `jsonEqual`'s fallback too. A function of its own so that the texts it makes
|
|
217
|
+
* are released when it returns rather than at the end of the walk.
|
|
218
|
+
*/
|
|
219
|
+
const jsonEqualPutNumber = (out: string[], doc: JsonDocument, x: i32): void => {
|
|
220
|
+
const text: string = doc.numberText(x)
|
|
221
|
+
const canonical: string = jsonNumberCanonical(text)
|
|
222
|
+
jsonEqualPut(out, canonical === "" ? `x${text};` : `n${canonical};`)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** The canonical text of value `v` (`jsonEqualCanonical`), or `""` for a handle that names no value. */
|
|
226
|
+
const jsonEqualCanonicalText = (doc: JsonDocument, v: i32): string => {
|
|
227
|
+
const out: string[] = []
|
|
228
|
+
if (doc.kind(v) === JSON_VALUE_NONE) {
|
|
229
|
+
return ""
|
|
230
|
+
}
|
|
231
|
+
const tasks: i32[] = [v]
|
|
232
|
+
const keyStack: string[] = []
|
|
233
|
+
while (tasks.length > 0) {
|
|
234
|
+
const x: i32 = jsonEqualAt(tasks, toI32(tasks.length) - 1)
|
|
235
|
+
tasks.pop()
|
|
236
|
+
if (x === JSON_EQUAL_KEY_TASK) {
|
|
237
|
+
jsonEqualPutText(out, jsonEqualKeyAt(keyStack, toI32(keyStack.length) - 1))
|
|
238
|
+
keyStack.pop()
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
const kind: i32 = doc.kind(x)
|
|
242
|
+
if (kind === JSON_VALUE_NULL) {
|
|
243
|
+
jsonEqualPut(out, "z")
|
|
244
|
+
} else if (kind === JSON_VALUE_BOOLEAN) {
|
|
245
|
+
jsonEqualPut(out, doc.boolean(x) ? "t" : "f")
|
|
246
|
+
} else if (kind === JSON_VALUE_NUMBER) {
|
|
247
|
+
jsonEqualPutNumber(out, doc, x)
|
|
248
|
+
} else if (kind === JSON_VALUE_STRING) {
|
|
249
|
+
jsonEqualPut(out, "s")
|
|
250
|
+
jsonEqualPutText(out, doc.string(x))
|
|
251
|
+
} else if (kind === JSON_VALUE_ARRAY) {
|
|
252
|
+
const n: i32 = doc.length(x)
|
|
253
|
+
jsonEqualPut(out, `a${n}:`)
|
|
254
|
+
for (let i: i32 = n - 1; i >= 0; i--) {
|
|
255
|
+
tasks.push(doc.at(x, i))
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
const n: i32 = doc.length(x)
|
|
259
|
+
jsonEqualPut(out, `o${n}:`)
|
|
260
|
+
const keys: string[] = []
|
|
261
|
+
const order: i32[] = jsonEqualSortedMembers(doc, x, keys)
|
|
262
|
+
// Pushed last member first, each value before its key, so the key pops first.
|
|
263
|
+
for (let i: i32 = n - 1; i >= 0; i--) {
|
|
264
|
+
const m: i32 = jsonEqualAt(order, i)
|
|
265
|
+
tasks.push(doc.at(x, m))
|
|
266
|
+
tasks.push(JSON_EQUAL_KEY_TASK)
|
|
267
|
+
keyStack.push(jsonEqualKeyAt(keys, m))
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return out.join("")
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* The canonical text of value `v`: the same text for exactly the values
|
|
276
|
+
* `jsonEqual` calls equal, so it is a key for a `Map` or `Set` of values. It
|
|
277
|
+
* is not JSON: each value is a tag and what it holds, `z`, `t` and `f` for
|
|
278
|
+
* null, true and false, `n` and a `jsonNumberCanonical` text and `;` for a
|
|
279
|
+
* number, `s`, a length, `:` and the string itself, `a` and a count and `:`
|
|
280
|
+
* before an array's elements, and `o` and a count and `:` before an object's
|
|
281
|
+
* members in key order, each key a length, `:` and the key (orders and lengths
|
|
282
|
+
* as the module header says).
|
|
283
|
+
* `""` for a handle that names no value.
|
|
284
|
+
*/
|
|
285
|
+
export const jsonEqualCanonical = (doc: JsonDocument, v: i32): string => jsonEqualCanonicalText(doc, v)
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* A 64-bit hash of value `v` that agrees with `jsonEqual`: equal values hash
|
|
289
|
+
* the same. FNV-1a of the canonical text's units (its code units under
|
|
290
|
+
* Node, so a string past U+00FF hashes differently there), unkeyed (the
|
|
291
|
+
* module header says what that means for a table). The offset basis for a handle that names no
|
|
292
|
+
* value.
|
|
293
|
+
*/
|
|
294
|
+
export const jsonEqualHash = (doc: JsonDocument, v: i32): u64 => {
|
|
295
|
+
const text: string = jsonEqualCanonicalText(doc, v)
|
|
296
|
+
// FNV-1a's 64-bit prime and offset basis (0xcbf29ce484222325, past what a
|
|
297
|
+
// literal writes exactly), public domain. A module constant cannot be a
|
|
298
|
+
// `u64`, so they are made here from 16-bit pieces, every one through
|
|
299
|
+
// `toU64`: under Node a `u64` is a BigInt, which no plain number may meet
|
|
300
|
+
// and which does not wrap, so the product is masked to 64 bits (a no-op
|
|
301
|
+
// natively).
|
|
302
|
+
const quarter: u64 = toU64(65535)
|
|
303
|
+
const half: u64 = (quarter << toU64(16)) | quarter
|
|
304
|
+
const all: u64 = (half << toU64(32)) | half
|
|
305
|
+
const prime: u64 = (toU64(256) << toU64(32)) | toU64(435)
|
|
306
|
+
const upper: u64 = (toU64(52210) << toU64(16)) | toU64(40164)
|
|
307
|
+
const lower: u64 = (toU64(33826) << toU64(16)) | toU64(8997)
|
|
308
|
+
let h: u64 = (upper << toU64(32)) | lower
|
|
309
|
+
for (let i: i32 = 0; i < toI32(text.length); i++) {
|
|
310
|
+
h = ((h ^ toU64(toI32(text.charCodeAt(i)))) * prime) & all
|
|
311
|
+
}
|
|
312
|
+
return h
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The position of the first element of array `v` equal (`jsonEqual`) to an
|
|
317
|
+
* earlier one, or `-1` when every element is distinct or `v` is not an array:
|
|
318
|
+
* JSON Schema's `uniqueItems` holds exactly when this is `-1`. Linear in the
|
|
319
|
+
* elements read, through a `Map` of their canonical texts.
|
|
320
|
+
*/
|
|
321
|
+
export const jsonEqualFirstDuplicate = (doc: JsonDocument, v: i32): i32 => {
|
|
322
|
+
if (doc.kind(v) !== JSON_VALUE_ARRAY) {
|
|
323
|
+
return -1
|
|
324
|
+
}
|
|
325
|
+
const seen = new Map<string, i32>()
|
|
326
|
+
const n: i32 = doc.length(v)
|
|
327
|
+
for (let i: i32 = 0; i < n; i++) {
|
|
328
|
+
const text: string = jsonEqualCanonical(doc, doc.at(v, i))
|
|
329
|
+
if (seen.has(text)) {
|
|
330
|
+
return i
|
|
331
|
+
}
|
|
332
|
+
seen.set(text, i)
|
|
333
|
+
}
|
|
334
|
+
return -1
|
|
335
|
+
}
|