@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.
@@ -0,0 +1,1070 @@
1
+ /**
2
+ * `nish/json-format` — the string formats of JSON Schema draft 2020-12
3
+ * (§7.3 of the validation vocabulary), each a predicate, and one dispatcher
4
+ * that answers valid, invalid or unknown for a format named in a schema.
5
+ *
6
+ * import { jsonFormatId, jsonFormatCheck, isEmail, JSON_FORMAT_VALID } from "nish/json-format"
7
+ *
8
+ * isEmail("joe.bloggs@example.com") // true
9
+ * const id = jsonFormatId("date-time") // resolve the name once, when the schema is read
10
+ * jsonFormatCheck(id, "1963-06-19T08:30:06Z") // JSON_FORMAT_VALID, for each instance
11
+ * jsonFormatValidate("idn-email", "x@y") // JSON_FORMAT_UNKNOWN: not checked here
12
+ *
13
+ * **What each format is.** Each predicate is the grammar of the RFC the
14
+ * specification names, applied to the whole string:
15
+ *
16
+ * - `date-time`, `date` and `time` are RFC 3339 §5.6: `T` and `Z` in either
17
+ * case, a fraction of any length, an offset of at most 23:59, the month's
18
+ * length in the year's calendar, and a second of 60 only where the time is
19
+ * 23:59 in UTC once the offset is taken off.
20
+ * - `duration` is RFC 3339 Appendix A as written: units in order, none
21
+ * skipped (`P1Y1D` is invalid, since a year may be followed only by a
22
+ * month), a `T` before any hour, minute or second and at least one after
23
+ * it, and weeks alone.
24
+ * - `email` is RFC 5321's `Mailbox`: a dot-string or a quoted string of at
25
+ * most 64 octets, then a domain of at most 255 that is a `hostname` or an
26
+ * address literal (`[127.0.0.1]`, `[IPv6:::1]`, or a tag and its content).
27
+ * - `hostname` is RFC 1123: labels of 1 to 63 letters, digits and hyphens
28
+ * with no hyphen at either end, 253 octets in all, and no trailing dot. An
29
+ * A-label (`xn--`) is checked as letters and digits; its Punycode is not
30
+ * decoded.
31
+ * - `ipv4` is RFC 2673's dotted quad: four decimal parts of 0 to 255, with no
32
+ * leading zero. `ipv6` is RFC 4291 §2.2: at most one `::`, groups of 1 to 4
33
+ * hex digits, an IPv4 tail counted as two, and no zone or prefix length.
34
+ * - `uuid` is RFC 4122's 8-4-4-4-12 hex digits, in either case, of any
35
+ * version or variant.
36
+ * - `uri` and `uri-reference` are RFC 3986 §3 and §4.1; `iri` and
37
+ * `iri-reference` are RFC 3987 §2.2, the same grammar with any `ucschar`
38
+ * where an unreserved character goes and `iprivate` in the query.
39
+ * - `uri-template` is RFC 6570 §2, any level.
40
+ * - `regex` is a pattern `nish/regex` compiles (see "What is not checked").
41
+ *
42
+ * **Why the URI grammar is checked here and not by `nish/net/url`.**
43
+ * `urlParse` reads an absolute URL a client is about to fetch, and refuses
44
+ * what two readers could take for different hosts: a percent-encoded host
45
+ * byte, a numeric host that is not a dotted-decimal IPv4 address, an
46
+ * IPvFuture literal, a port above 65535, any byte past ASCII, and a reference
47
+ * with no scheme (`std/net/url.ts`). Each of those is a valid RFC 3986 URI,
48
+ * IRI or relative reference, which this module must answer valid. It also
49
+ * allocates the parts it answers. So this module walks the ABNF itself, over
50
+ * the string, allocating nothing.
51
+ *
52
+ * **What is not checked.** `jsonFormatId` answers -1, and the dispatcher
53
+ * `JSON_FORMAT_UNKNOWN`, for `idn-email`, `idn-hostname`, `json-pointer`,
54
+ * `relative-json-pointer` and any other name: an unknown format is an
55
+ * annotation, which a validator accepts (§7.2.3). `regex` is answered by
56
+ * `regexCompile`, which is stricter than ECMA-262: it refuses lookaround and
57
+ * backreferences, which it does not implement, and reads the pattern with the
58
+ * `u` flag's rules, so a lone `{` or `]` is an error. Such a pattern is valid
59
+ * ECMA-262 and is answered invalid here, a false negative.
60
+ *
61
+ * **What a call costs.** Every check but `regex` is linear in the length of
62
+ * its string: each unit is read a small fixed number of times (at most four, in
63
+ * a URI's authority, which is found and then split at `@` and `:`). It
64
+ * allocates nothing and cannot panic. `jsonFormatId` compares the
65
+ * name with up to 15 strings; resolve it once per schema, not per instance.
66
+ * `isRegex` compiles the pattern, which allocates the program `nish/regex`
67
+ * builds. That is not linear in the pattern's length, since a counted repeat
68
+ * copies its body, but `nish/regex`'s limits bound it: `(a{1000}){100}` is
69
+ * refused at 100,000 instructions (`REGEX_MAX_INSTRUCTIONS`) or a 4 MiB slot
70
+ * table, whichever comes first.
71
+ *
72
+ * Offsets are code units: UTF-8 bytes natively and UTF-16 units under Node.
73
+ * The checks answer the same on both, since every non-ASCII character is
74
+ * decoded where a grammar allows one and refused everywhere else.
75
+ */
76
+ import { regexCompile } from "nish/regex"
77
+
78
+ /** The dispatcher's answer for a string the format accepts. */
79
+ export const JSON_FORMAT_VALID: i32 = 1
80
+ /** The dispatcher's answer for a string the format refuses. */
81
+ export const JSON_FORMAT_INVALID: i32 = 0
82
+ /** The dispatcher's answer for a format this module does not check. */
83
+ export const JSON_FORMAT_UNKNOWN: i32 = -1
84
+
85
+ /** The id of `date-time`, for `jsonFormatCheck`. */
86
+ export const JSON_FORMAT_DATE_TIME: i32 = 0
87
+ /** The id of `date`. */
88
+ export const JSON_FORMAT_DATE: i32 = 1
89
+ /** The id of `time`. */
90
+ export const JSON_FORMAT_TIME: i32 = 2
91
+ /** The id of `duration`. */
92
+ export const JSON_FORMAT_DURATION: i32 = 3
93
+ /** The id of `email`. */
94
+ export const JSON_FORMAT_EMAIL: i32 = 4
95
+ /** The id of `hostname`. */
96
+ export const JSON_FORMAT_HOSTNAME: i32 = 5
97
+ /** The id of `ipv4`. */
98
+ export const JSON_FORMAT_IPV4: i32 = 6
99
+ /** The id of `ipv6`. */
100
+ export const JSON_FORMAT_IPV6: i32 = 7
101
+ /** The id of `uuid`. */
102
+ export const JSON_FORMAT_UUID: i32 = 8
103
+ /** The id of `uri`. */
104
+ export const JSON_FORMAT_URI: i32 = 9
105
+ /** The id of `uri-reference`. */
106
+ export const JSON_FORMAT_URI_REFERENCE: i32 = 10
107
+ /** The id of `iri`. */
108
+ export const JSON_FORMAT_IRI: i32 = 11
109
+ /** The id of `iri-reference`. */
110
+ export const JSON_FORMAT_IRI_REFERENCE: i32 = 12
111
+ /** The id of `uri-template`. */
112
+ export const JSON_FORMAT_URI_TEMPLATE: i32 = 13
113
+ /** The id of `regex`. */
114
+ export const JSON_FORMAT_REGEX: i32 = 14
115
+
116
+ // The URI grammar's character classes, as `jfUriUnit` reads them. Each names
117
+ // the ASCII set of one RFC 3986 rule; the IRI forms add `ucschar`, and the
118
+ // query alone `iprivate`.
119
+ const JF_REG_NAME: i32 = 0 // unreserved / sub-delims
120
+ const JF_USERINFO: i32 = 1 // unreserved / sub-delims / ":"
121
+ const JF_SEGMENT: i32 = 2 // pchar: unreserved / sub-delims / ":" / "@"
122
+ const JF_PATH: i32 = 3 // pchar / "/"
123
+ const JF_FRAGMENT: i32 = 4 // pchar / "/" / "?"
124
+ const JF_QUERY: i32 = 5 // the fragment's set, and `iprivate` in an IRI
125
+
126
+ /**
127
+ * The code unit of `s` at `i`, or -1 past its end, so that a grammar can look
128
+ * one unit ahead without testing the length first.
129
+ */
130
+ const jfUnitAt = (s: string, i: i32): i32 => (i < toI32(s.length) ? toI32(s.charCodeAt(i)) : -1)
131
+
132
+ const jfIsDigit = (c: i32): boolean => c >= 48 && c <= 57
133
+
134
+ const jfIsAlpha = (c: i32): boolean => (c >= 65 && c <= 90) || (c >= 97 && c <= 122)
135
+
136
+ const jfIsHex = (c: i32): boolean => jfIsDigit(c) || (c >= 65 && c <= 70) || (c >= 97 && c <= 102)
137
+
138
+ /** `c` as a lower-case ASCII letter, so that `T` and `t` compare alike. */
139
+ const jfLower = (c: i32): i32 => (c >= 65 && c <= 90 ? c + 32 : c)
140
+
141
+ /** The value of the `n` decimal digits of `s` at `at`, or -1 if any is not one. */
142
+ const jfDigits = (s: string, at: i32, n: i32): i32 => {
143
+ let value: i32 = 0
144
+ let i: i32 = 0
145
+ while (i < n) {
146
+ const c = jfUnitAt(s, at + i)
147
+ if (!jfIsDigit(c)) {
148
+ return -1
149
+ }
150
+ value = value * 10 + (c - 48)
151
+ i += 1
152
+ }
153
+ return value
154
+ }
155
+
156
+ /** The number of days in `month` (1 to 12) of the Gregorian `year`. */
157
+ const jfDaysInMonth = (year: i32, month: i32): i32 => {
158
+ if (month === 2) {
159
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
160
+ return leap ? 29 : 28
161
+ }
162
+ return month === 4 || month === 6 || month === 9 || month === 11 ? 30 : 31
163
+ }
164
+
165
+ /** Whether `s` holds RFC 3339's `full-date`, `YYYY-MM-DD`, at `at`. */
166
+ const jfFullDateAt = (s: string, at: i32): boolean => {
167
+ const year = jfDigits(s, at, 4)
168
+ const month = jfDigits(s, at + 5, 2)
169
+ const day = jfDigits(s, at + 8, 2)
170
+ return (
171
+ year >= 0 &&
172
+ jfUnitAt(s, at + 4) === 45 &&
173
+ jfUnitAt(s, at + 7) === 45 &&
174
+ month >= 1 &&
175
+ month <= 12 &&
176
+ day >= 1 &&
177
+ day <= jfDaysInMonth(year, month)
178
+ )
179
+ }
180
+
181
+ /**
182
+ * Whether `s` from `at` to its end is RFC 3339's `full-time`:
183
+ * `HH:MM:SS[.frac]` and `Z` or `±HH:MM`.
184
+ *
185
+ * A leap second is the last second of a UTC day, so `23:59:60Z` and
186
+ * `15:59:60-08:00` are valid and `22:59:60Z` is not: the offset is taken off
187
+ * the local minute of the day and what is left must be 23:59.
188
+ */
189
+ const jfFullTimeFrom = (s: string, at: i32): boolean => {
190
+ const hour = jfDigits(s, at, 2)
191
+ const minute = jfDigits(s, at + 3, 2)
192
+ const second = jfDigits(s, at + 6, 2)
193
+ if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 60) {
194
+ return false
195
+ }
196
+ if (jfUnitAt(s, at + 2) !== 58 || jfUnitAt(s, at + 5) !== 58) {
197
+ return false
198
+ }
199
+ let i = at + 8
200
+ if (jfUnitAt(s, i) === 46) {
201
+ i += 1
202
+ const start = i
203
+ while (jfIsDigit(jfUnitAt(s, i))) {
204
+ i += 1
205
+ }
206
+ if (i === start) {
207
+ return false
208
+ }
209
+ }
210
+ const sign = jfUnitAt(s, i)
211
+ let offset: i32 = 0
212
+ if (jfLower(sign) === 122) {
213
+ i += 1
214
+ } else if (sign === 43 || sign === 45) {
215
+ const offsetHour = jfDigits(s, i + 1, 2)
216
+ const offsetMinute = jfDigits(s, i + 4, 2)
217
+ if (
218
+ offsetHour < 0 ||
219
+ offsetHour > 23 ||
220
+ offsetMinute < 0 ||
221
+ offsetMinute > 59 ||
222
+ jfUnitAt(s, i + 3) !== 58
223
+ ) {
224
+ return false
225
+ }
226
+ offset = offsetHour * 60 + offsetMinute
227
+ if (sign === 45) {
228
+ offset = -offset
229
+ }
230
+ i += 6
231
+ } else {
232
+ return false
233
+ }
234
+ if (i !== toI32(s.length)) {
235
+ return false
236
+ }
237
+ // The UTC minute of the day is in -1439 to 2878 before the wrap.
238
+ return second !== 60 || (hour * 60 + minute - offset + 1440) % 1440 === 1439
239
+ }
240
+
241
+ /** Whether `s` is an RFC 3339 `date-time`, `1963-06-19T08:30:06.283185Z`. */
242
+ export const isDateTime = (s: string): boolean =>
243
+ jfFullDateAt(s, 0) && jfLower(jfUnitAt(s, 10)) === 116 && jfFullTimeFrom(s, 11)
244
+
245
+ /** Whether `s` is an RFC 3339 `full-date`, `1963-06-19`. */
246
+ export const isDate = (s: string): boolean => toI32(s.length) === 10 && jfFullDateAt(s, 0)
247
+
248
+ /** Whether `s` is an RFC 3339 `full-time`, `08:30:06Z` or `08:30:06+01:00`. */
249
+ export const isTime = (s: string): boolean => jfFullTimeFrom(s, 0)
250
+
251
+ /** The place of unit `u` in `units`, or -1 if it is not one of them. */
252
+ const jfUnitIndex = (u: i32, units: string): i32 => {
253
+ let k: i32 = 0
254
+ while (k < toI32(units.length)) {
255
+ if (toI32(units.charCodeAt(k)) === u) {
256
+ return k
257
+ }
258
+ k += 1
259
+ }
260
+ return -1
261
+ }
262
+
263
+ /**
264
+ * Whether `s` is an RFC 3339 Appendix A `duration`, `P3Y6M4DT12H30M5S`.
265
+ *
266
+ * The ABNF lets each unit be followed only by the next one (`dur-year =
267
+ * 1*DIGIT "Y" [dur-month]`), so the units of each half must be consecutive
268
+ * in `YMD` and `HMS` order: `P1Y1D` and `PT1H1S` are invalid as written,
269
+ * though ISO 8601 allows them.
270
+ */
271
+ export const isDuration = (s: string): boolean => {
272
+ const length: i32 = toI32(s.length)
273
+ if (jfUnitAt(s, 0) !== 80) {
274
+ return false
275
+ }
276
+ let i: i32 = 1
277
+ let inTime = false
278
+ let last: i32 = -1
279
+ let count: i32 = 0
280
+ let timeCount: i32 = 0
281
+ while (i < length) {
282
+ if (jfUnitAt(s, i) === 84) {
283
+ if (inTime) {
284
+ return false
285
+ }
286
+ inTime = true
287
+ last = -1
288
+ i += 1
289
+ continue
290
+ }
291
+ const start = i
292
+ while (jfIsDigit(jfUnitAt(s, i))) {
293
+ i += 1
294
+ }
295
+ const unit = jfUnitAt(s, i)
296
+ if (i === start || unit < 0) {
297
+ return false
298
+ }
299
+ i += 1
300
+ if (unit === 87) {
301
+ // `dur-week` is the whole duration.
302
+ return !inTime && count === 0 && i === length
303
+ }
304
+ const k = jfUnitIndex(unit, inTime ? "HMS" : "YMD")
305
+ if (k < 0 || (last >= 0 && k !== last + 1)) {
306
+ return false
307
+ }
308
+ last = k
309
+ count += 1
310
+ if (inTime) {
311
+ timeCount += 1
312
+ }
313
+ }
314
+ return count > 0 && (!inTime || timeCount > 0)
315
+ }
316
+
317
+ /** Whether `s[from, to)` is a dotted-quad IPv4 address. */
318
+ const jfIpv4Range = (s: string, from: i32, to: i32): boolean => {
319
+ let i = from
320
+ let part: i32 = 0
321
+ while (part < 4) {
322
+ if (part > 0) {
323
+ if (i >= to || jfUnitAt(s, i) !== 46) {
324
+ return false
325
+ }
326
+ i += 1
327
+ }
328
+ const start = i
329
+ let value: i32 = 0
330
+ while (i < to && i - start < 3 && jfIsDigit(jfUnitAt(s, i))) {
331
+ value = value * 10 + (jfUnitAt(s, i) - 48)
332
+ i += 1
333
+ }
334
+ const n = i - start
335
+ if (n === 0 || value > 255 || (n > 1 && jfUnitAt(s, start) === 48)) {
336
+ return false
337
+ }
338
+ part += 1
339
+ }
340
+ return i === to
341
+ }
342
+
343
+ /** Whether `s[from, to)` is an RFC 4291 IPv6 address, with no zone or prefix. */
344
+ const jfIpv6Range = (s: string, from: i32, to: i32): boolean => {
345
+ let i = from
346
+ let groups: i32 = 0
347
+ let elided = false
348
+ if (i + 1 < to && jfUnitAt(s, i) === 58 && jfUnitAt(s, i + 1) === 58) {
349
+ elided = true
350
+ i += 2
351
+ }
352
+ while (i < to) {
353
+ const start = i
354
+ while (i < to && jfIsHex(jfUnitAt(s, i))) {
355
+ i += 1
356
+ }
357
+ if (i < to && jfUnitAt(s, i) === 46) {
358
+ // An IPv4 tail ends the address and fills two groups.
359
+ if (!jfIpv4Range(s, start, to)) {
360
+ return false
361
+ }
362
+ groups += 2
363
+ break
364
+ }
365
+ if (i === start || i - start > 4) {
366
+ return false
367
+ }
368
+ groups += 1
369
+ if (i === to) {
370
+ break
371
+ }
372
+ if (jfUnitAt(s, i) !== 58) {
373
+ return false
374
+ }
375
+ i += 1
376
+ if (i < to && jfUnitAt(s, i) === 58) {
377
+ if (elided) {
378
+ return false
379
+ }
380
+ elided = true
381
+ i += 1
382
+ } else if (i === to) {
383
+ return false
384
+ }
385
+ }
386
+ return elided ? groups <= 7 : groups === 8
387
+ }
388
+
389
+ /** Whether `s` is a dotted-quad IPv4 address, `192.168.0.1`. */
390
+ export const isIpv4 = (s: string): boolean => jfIpv4Range(s, 0, toI32(s.length))
391
+
392
+ /** Whether `s` is an IPv6 address, `::ffff:192.168.0.1`. */
393
+ export const isIpv6 = (s: string): boolean => jfIpv6Range(s, 0, toI32(s.length))
394
+
395
+ const jfIsLdh = (c: i32): boolean => jfIsAlpha(c) || jfIsDigit(c) || c === 45
396
+
397
+ /** Whether `s[from, to)` is an RFC 1123 host name. */
398
+ const jfHostnameRange = (s: string, from: i32, to: i32): boolean => {
399
+ if (to <= from || to - from > 253) {
400
+ return false
401
+ }
402
+ let start = from
403
+ let i = from
404
+ while (i <= to) {
405
+ if (i === to || jfUnitAt(s, i) === 46) {
406
+ const n = i - start
407
+ if (n === 0 || n > 63 || jfUnitAt(s, start) === 45 || jfUnitAt(s, i - 1) === 45) {
408
+ return false
409
+ }
410
+ start = i + 1
411
+ } else if (!jfIsLdh(jfUnitAt(s, i))) {
412
+ return false
413
+ }
414
+ i += 1
415
+ }
416
+ return true
417
+ }
418
+
419
+ /** Whether `s` is a host name, `www.example.com`. */
420
+ export const isHostname = (s: string): boolean => jfHostnameRange(s, 0, toI32(s.length))
421
+
422
+ /** Whether `c` is RFC 5322's `atext`: a letter, a digit or one of ``!#$%&'*+-/=?^_`{|}~``. */
423
+ const jfIsAtext = (c: i32): boolean =>
424
+ jfIsAlpha(c) ||
425
+ jfIsDigit(c) ||
426
+ c === 33 ||
427
+ (c >= 35 && c <= 39) ||
428
+ c === 42 ||
429
+ c === 43 ||
430
+ c === 45 ||
431
+ c === 47 ||
432
+ c === 61 ||
433
+ c === 63 ||
434
+ (c >= 94 && c <= 96) ||
435
+ (c >= 123 && c <= 126)
436
+
437
+ /**
438
+ * The end of the RFC 5321 `Local-part` at the start of `s`, a dot-string or a
439
+ * quoted string, or -1 when it is neither.
440
+ */
441
+ const jfLocalPartEnd = (s: string): i32 => {
442
+ let i: i32 = 0
443
+ if (jfUnitAt(s, 0) === 34) {
444
+ i = 1
445
+ while (true) {
446
+ const c = jfUnitAt(s, i)
447
+ if (c === 34) {
448
+ return i + 1
449
+ }
450
+ if (c === 92) {
451
+ const quoted = jfUnitAt(s, i + 1)
452
+ if (quoted < 32 || quoted > 126) {
453
+ return -1
454
+ }
455
+ i += 2
456
+ } else if (c === 32 || c === 33 || (c >= 35 && c <= 91) || (c >= 93 && c <= 126)) {
457
+ i += 1
458
+ } else {
459
+ return -1
460
+ }
461
+ }
462
+ }
463
+ while (true) {
464
+ const start = i
465
+ while (jfIsAtext(jfUnitAt(s, i))) {
466
+ i += 1
467
+ }
468
+ if (i === start) {
469
+ return -1
470
+ }
471
+ if (jfUnitAt(s, i) !== 46) {
472
+ return i
473
+ }
474
+ i += 1
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Whether `s[from, to)` is RFC 5321's general address literal content,
480
+ * `Standardized-tag ":" 1*dcontent`, for a tag other than `IPv6`.
481
+ */
482
+ const jfGeneralLiteral = (s: string, from: i32, to: i32): boolean => {
483
+ let i = from
484
+ while (i < to && jfIsLdh(jfUnitAt(s, i))) {
485
+ i += 1
486
+ }
487
+ if (i === from || jfUnitAt(s, i - 1) === 45 || i >= to || jfUnitAt(s, i) !== 58) {
488
+ return false
489
+ }
490
+ i += 1
491
+ if (i === to) {
492
+ return false
493
+ }
494
+ while (i < to) {
495
+ const c = jfUnitAt(s, i)
496
+ if (c < 33 || (c > 90 && c < 94) || c > 126) {
497
+ return false
498
+ }
499
+ i += 1
500
+ }
501
+ return true
502
+ }
503
+
504
+ /** Whether `s` is an RFC 5321 mailbox, `joe.bloggs@example.com`. */
505
+ export const isEmail = (s: string): boolean => {
506
+ const length: i32 = toI32(s.length)
507
+ const at = jfLocalPartEnd(s)
508
+ if (at < 0 || at > 64 || jfUnitAt(s, at) !== 64) {
509
+ return false
510
+ }
511
+ const from = at + 1
512
+ if (length - from > 255) {
513
+ return false
514
+ }
515
+ if (jfUnitAt(s, from) !== 91) {
516
+ return jfHostnameRange(s, from, length)
517
+ }
518
+ if (jfUnitAt(s, length - 1) !== 93) {
519
+ return false
520
+ }
521
+ const inner = from + 1
522
+ const end = length - 1
523
+ if (
524
+ end - inner > 5 &&
525
+ jfLower(jfUnitAt(s, inner)) === 105 &&
526
+ jfLower(jfUnitAt(s, inner + 1)) === 112 &&
527
+ jfLower(jfUnitAt(s, inner + 2)) === 118 &&
528
+ jfUnitAt(s, inner + 3) === 54 &&
529
+ jfUnitAt(s, inner + 4) === 58
530
+ ) {
531
+ return jfIpv6Range(s, inner + 5, end)
532
+ }
533
+ return jfIpv4Range(s, inner, end) || jfGeneralLiteral(s, inner, end)
534
+ }
535
+
536
+ /** Whether `s` is a UUID, `2eb8aa08-aa98-11ea-b4aa-73b441d16380`. */
537
+ export const isUuid = (s: string): boolean => {
538
+ if (toI32(s.length) !== 36) {
539
+ return false
540
+ }
541
+ let i: i32 = 0
542
+ while (i < 36) {
543
+ const c = jfUnitAt(s, i)
544
+ if (i === 8 || i === 13 || i === 18 || i === 23 ? c !== 45 : !jfIsHex(c)) {
545
+ return false
546
+ }
547
+ i += 1
548
+ }
549
+ return true
550
+ }
551
+
552
+ /** Whether host-side code units are UTF-8 bytes, as natively, or UTF-16 units, as under Node. */
553
+ const jfUnitsAreBytes = (): boolean => toI32("é".length) === 2
554
+
555
+ /** Whether `cp` is RFC 3987's `iprivate`. */
556
+ const jfIsPrivate = (cp: i32): boolean =>
557
+ (cp >= 0xe000 && cp <= 0xf8ff) || (cp >= 0xf0000 && (cp & 0xffff) <= 0xfffd)
558
+
559
+ /** Whether `cp` is RFC 3987's `ucschar`. */
560
+ const jfIsUcschar = (cp: i32): boolean => {
561
+ if (cp < 0x10000) {
562
+ return (cp >= 0xa0 && cp <= 0xd7ff) || (cp >= 0xf900 && cp <= 0xfdcf) || (cp >= 0xfdf0 && cp <= 0xffef)
563
+ }
564
+ // Planes 1 to 13 bar their last two code points; plane 14 starts at E1000.
565
+ return cp < 0xf0000 && (cp & 0xffff) <= 0xfffd && (cp < 0xe0000 || cp >= 0xe1000)
566
+ }
567
+
568
+ /**
569
+ * The code units of the non-ASCII character of `s` at `i`, when it is a
570
+ * `ucschar`, or an `iprivate` where `privateOk`; else 0, for a character
571
+ * outside those ranges or units that do not encode one.
572
+ */
573
+ const jfUcsWidth = (s: string, i: i32, privateOk: boolean): i32 => {
574
+ const lead = jfUnitAt(s, i)
575
+ let cp: i32 = 0
576
+ let width: i32 = 0
577
+ if (jfUnitsAreBytes()) {
578
+ // UTF-8: no overlong form, no surrogate, nothing past U+10FFFF.
579
+ const b1 = jfUnitAt(s, i + 1)
580
+ let low: i32 = 0x80
581
+ let high: i32 = 0xbf
582
+ if (lead >= 0xc2 && lead <= 0xdf) {
583
+ width = 2
584
+ cp = lead & 0x1f
585
+ } else if (lead >= 0xe0 && lead <= 0xef) {
586
+ width = 3
587
+ cp = lead & 0x0f
588
+ low = lead === 0xe0 ? 0xa0 : 0x80
589
+ high = lead === 0xed ? 0x9f : 0xbf
590
+ } else if (lead >= 0xf0 && lead <= 0xf4) {
591
+ width = 4
592
+ cp = lead & 0x07
593
+ low = lead === 0xf0 ? 0x90 : 0x80
594
+ high = lead === 0xf4 ? 0x8f : 0xbf
595
+ } else {
596
+ return 0
597
+ }
598
+ if (b1 < low || b1 > high) {
599
+ return 0
600
+ }
601
+ cp = (cp << 6) | (b1 & 0x3f)
602
+ let k: i32 = 2
603
+ while (k < width) {
604
+ const b = jfUnitAt(s, i + k)
605
+ if (b < 0x80 || b > 0xbf) {
606
+ return 0
607
+ }
608
+ cp = (cp << 6) | (b & 0x3f)
609
+ k += 1
610
+ }
611
+ } else if (lead >= 0xd800 && lead <= 0xdbff) {
612
+ const trail = jfUnitAt(s, i + 1)
613
+ if (trail < 0xdc00 || trail > 0xdfff) {
614
+ return 0
615
+ }
616
+ width = 2
617
+ cp = ((lead - 0xd800) << 10) + (trail - 0xdc00) + 0x10000
618
+ } else if (lead >= 0xdc00 && lead <= 0xdfff) {
619
+ return 0
620
+ } else {
621
+ width = 1
622
+ cp = lead
623
+ }
624
+ return jfIsUcschar(cp) || (privateOk && jfIsPrivate(cp)) ? width : 0
625
+ }
626
+
627
+ const jfIsUnreserved = (c: i32): boolean =>
628
+ jfIsAlpha(c) || jfIsDigit(c) || c === 45 || c === 46 || c === 95 || c === 126
629
+
630
+ /** Whether `c` is RFC 3986's `sub-delims`: ``!$&'()*+,;=``. */
631
+ const jfIsSubDelim = (c: i32): boolean => c === 33 || c === 36 || (c >= 38 && c <= 44) || c === 59 || c === 61
632
+
633
+ /** Whether the ASCII `c` belongs to the URI character class `set` (a `JF_` constant). */
634
+ const jfInSet = (c: i32, set: i32): boolean => {
635
+ if (jfIsUnreserved(c) || jfIsSubDelim(c)) {
636
+ return true
637
+ }
638
+ if (set === JF_REG_NAME) {
639
+ return false
640
+ }
641
+ if (c === 58) {
642
+ return true
643
+ }
644
+ if (set === JF_USERINFO) {
645
+ return false
646
+ }
647
+ if (c === 64) {
648
+ return true
649
+ }
650
+ if (set === JF_SEGMENT) {
651
+ return false
652
+ }
653
+ return c === 47 || (set !== JF_PATH && c === 63)
654
+ }
655
+
656
+ /**
657
+ * The code units of one character of the class `set` at `i` of `s`: 1 for an
658
+ * ASCII member, 3 for `%` and two hex digits, the character's width for one
659
+ * past ASCII where `iri` allows it, and 0 where none fits.
660
+ */
661
+ const jfUriUnit = (s: string, i: i32, set: i32, iri: boolean): i32 => {
662
+ const c = jfUnitAt(s, i)
663
+ if (c < 0) {
664
+ return 0
665
+ }
666
+ if (c === 37) {
667
+ return jfIsHex(jfUnitAt(s, i + 1)) && jfIsHex(jfUnitAt(s, i + 2)) ? 3 : 0
668
+ }
669
+ if (c < 0x80) {
670
+ return jfInSet(c, set) ? 1 : 0
671
+ }
672
+ return iri ? jfUcsWidth(s, i, set === JF_QUERY) : 0
673
+ }
674
+
675
+ /** Whether every character of `s[from, to)` belongs to the URI class `set`. */
676
+ const jfUriRun = (s: string, from: i32, to: i32, set: i32, iri: boolean): boolean => {
677
+ let i = from
678
+ while (i < to) {
679
+ const w = jfUriUnit(s, i, set, iri)
680
+ if (w === 0) {
681
+ return false
682
+ }
683
+ i += w
684
+ }
685
+ return i === to
686
+ }
687
+
688
+ /** The first index in `[from, to)` whose unit is `a` or `b`, or `to`. */
689
+ const jfFind = (s: string, from: i32, to: i32, a: i32, b: i32): i32 => {
690
+ let i = from
691
+ while (i < to) {
692
+ const u = jfUnitAt(s, i)
693
+ if (u === a || u === b) {
694
+ return i
695
+ }
696
+ i += 1
697
+ }
698
+ return to
699
+ }
700
+
701
+ /**
702
+ * Whether `s[from, to)` is RFC 3986's `IP-literal` content: an IPv6 address
703
+ * or `IPvFuture`, `"v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )`.
704
+ */
705
+ const jfIpLiteral = (s: string, from: i32, to: i32): boolean => {
706
+ if (jfLower(jfUnitAt(s, from)) !== 118) {
707
+ return jfIpv6Range(s, from, to)
708
+ }
709
+ let i = from + 1
710
+ while (i < to && jfIsHex(jfUnitAt(s, i))) {
711
+ i += 1
712
+ }
713
+ if (i === from + 1 || i >= to || jfUnitAt(s, i) !== 46 || i + 1 === to) {
714
+ return false
715
+ }
716
+ return jfUriRun(s, i + 1, to, JF_USERINFO, false)
717
+ }
718
+
719
+ /** Whether `s[from, to)` is an RFC 3986 `authority`: `[ userinfo "@" ] host [ ":" port ]`. */
720
+ const jfAuthority = (s: string, from: i32, to: i32, iri: boolean): boolean => {
721
+ // `userinfo` cannot hold an `@`, so the first one ends it.
722
+ const at = jfFind(s, from, to, 64, 64)
723
+ let host = from
724
+ if (at < to) {
725
+ if (!jfUriRun(s, from, at, JF_USERINFO, iri)) {
726
+ return false
727
+ }
728
+ host = at + 1
729
+ }
730
+ let port: i32 = 0
731
+ if (jfUnitAt(s, host) === 91) {
732
+ const close = jfFind(s, host, to, 93, 93)
733
+ if (close === to || !jfIpLiteral(s, host + 1, close)) {
734
+ return false
735
+ }
736
+ port = close + 1
737
+ } else {
738
+ // A `reg-name` cannot hold a `:`, so the first one starts the port; an
739
+ // IPv4 address is a `reg-name` too, so it needs no check of its own.
740
+ port = jfFind(s, host, to, 58, 58)
741
+ if (!jfUriRun(s, host, port, JF_REG_NAME, iri)) {
742
+ return false
743
+ }
744
+ }
745
+ if (port === to) {
746
+ return true
747
+ }
748
+ if (jfUnitAt(s, port) !== 58) {
749
+ return false
750
+ }
751
+ let i = port + 1
752
+ while (i < to) {
753
+ if (!jfIsDigit(jfUnitAt(s, i))) {
754
+ return false
755
+ }
756
+ i += 1
757
+ }
758
+ return true
759
+ }
760
+
761
+ /**
762
+ * Whether `s` is a URI, or with `reference` a URI reference, and with `iri`
763
+ * the IRI forms of either: RFC 3986 §3 and §4.1, and RFC 3987 §2.2.
764
+ */
765
+ const jfUriCheck = (s: string, iri: boolean, reference: boolean): boolean => {
766
+ const length: i32 = toI32(s.length)
767
+ let i: i32 = 0
768
+ if (jfIsAlpha(jfUnitAt(s, 0))) {
769
+ let j: i32 = 1
770
+ while (true) {
771
+ const c = jfUnitAt(s, j)
772
+ if (!(jfIsAlpha(c) || jfIsDigit(c) || c === 43 || c === 45 || c === 46)) {
773
+ break
774
+ }
775
+ j += 1
776
+ }
777
+ if (jfUnitAt(s, j) === 58) {
778
+ i = j + 1
779
+ }
780
+ }
781
+ if (i === 0 && !reference) {
782
+ return false
783
+ }
784
+ const pathEnd = jfFind(s, i, length, 63, 35)
785
+ if (jfUnitAt(s, i) === 47 && jfUnitAt(s, i + 1) === 47) {
786
+ const authorityEnd = jfFind(s, i + 2, pathEnd, 47, 47)
787
+ if (!jfAuthority(s, i + 2, authorityEnd, iri)) {
788
+ return false
789
+ }
790
+ i = authorityEnd
791
+ } else if (i === 0) {
792
+ // `path-noscheme`: a relative path's first segment cannot hold a `:`,
793
+ // or it would read as a scheme.
794
+ const firstSegmentEnd = jfFind(s, i, pathEnd, 47, 47)
795
+ if (jfFind(s, i, firstSegmentEnd, 58, 58) < firstSegmentEnd) {
796
+ return false
797
+ }
798
+ }
799
+ if (!jfUriRun(s, i, pathEnd, JF_PATH, iri)) {
800
+ return false
801
+ }
802
+ i = pathEnd
803
+ if (jfUnitAt(s, i) === 63) {
804
+ const queryEnd = jfFind(s, i + 1, length, 35, 35)
805
+ if (!jfUriRun(s, i + 1, queryEnd, JF_QUERY, iri)) {
806
+ return false
807
+ }
808
+ i = queryEnd
809
+ }
810
+ if (jfUnitAt(s, i) === 35) {
811
+ return jfUriRun(s, i + 1, length, JF_FRAGMENT, iri)
812
+ }
813
+ return true
814
+ }
815
+
816
+ /** Whether `s` is an absolute URI, `http://example.com/a?b#c`. */
817
+ export const isUri = (s: string): boolean => jfUriCheck(s, false, false)
818
+
819
+ /** Whether `s` is a URI or a relative reference, `../a?b#c`. */
820
+ export const isUriReference = (s: string): boolean => jfUriCheck(s, false, true)
821
+
822
+ /** Whether `s` is an absolute IRI, `http://ƒøø.ßår/?∂éœ=πîx`. */
823
+ export const isIri = (s: string): boolean => jfUriCheck(s, true, false)
824
+
825
+ /** Whether `s` is an IRI or a relative IRI reference. */
826
+ export const isIriReference = (s: string): boolean => jfUriCheck(s, true, true)
827
+
828
+ /** The code units of RFC 6570's `varchar` at `i` of `s`, `ALPHA / DIGIT / "_" / pct-encoded`, or 0. */
829
+ const jfVarchar = (s: string, i: i32): i32 => {
830
+ const c = jfUnitAt(s, i)
831
+ if (jfIsAlpha(c) || jfIsDigit(c) || c === 95) {
832
+ return 1
833
+ }
834
+ return c === 37 && jfIsHex(jfUnitAt(s, i + 1)) && jfIsHex(jfUnitAt(s, i + 2)) ? 3 : 0
835
+ }
836
+
837
+ /**
838
+ * The end of the RFC 6570 `expression` whose `{` is at `open`, or -1 when the
839
+ * text there is not one: an operator, then `varspec`s separated by commas,
840
+ * each a `varname` and a `:` prefix of 1 to 9999 or a `*`.
841
+ */
842
+ const jfExpressionEnd = (s: string, open: i32): i32 => {
843
+ let i = open + 1
844
+ const op = jfUnitAt(s, i)
845
+ if (jfUnitIndex(op, "+#./;?&=,!@|") >= 0) {
846
+ i += 1
847
+ }
848
+ while (true) {
849
+ let w = jfVarchar(s, i)
850
+ if (w === 0) {
851
+ return -1
852
+ }
853
+ i += w
854
+ while (true) {
855
+ if (jfUnitAt(s, i) === 46) {
856
+ w = jfVarchar(s, i + 1)
857
+ if (w === 0) {
858
+ return -1
859
+ }
860
+ i += 1 + w
861
+ } else {
862
+ w = jfVarchar(s, i)
863
+ if (w === 0) {
864
+ break
865
+ }
866
+ i += w
867
+ }
868
+ }
869
+ const modifier = jfUnitAt(s, i)
870
+ if (modifier === 58) {
871
+ const first = jfUnitAt(s, i + 1)
872
+ if (first < 49 || first > 57) {
873
+ return -1
874
+ }
875
+ i += 2
876
+ let more: i32 = 0
877
+ while (more < 3 && jfIsDigit(jfUnitAt(s, i))) {
878
+ i += 1
879
+ more += 1
880
+ }
881
+ } else if (modifier === 42) {
882
+ i += 1
883
+ }
884
+ const next = jfUnitAt(s, i)
885
+ if (next === 125) {
886
+ return i + 1
887
+ }
888
+ if (next !== 44) {
889
+ return -1
890
+ }
891
+ i += 1
892
+ }
893
+ }
894
+
895
+ /**
896
+ * Whether the ASCII `c` is RFC 6570's `literals`: anything but a control,
897
+ * space and ``"'%<>\^`{|}``.
898
+ */
899
+ const jfIsTemplateLiteral = (c: i32): boolean =>
900
+ c === 33 ||
901
+ c === 35 ||
902
+ c === 36 ||
903
+ c === 38 ||
904
+ (c >= 40 && c <= 59) ||
905
+ c === 61 ||
906
+ (c >= 63 && c <= 91) ||
907
+ c === 93 ||
908
+ c === 95 ||
909
+ (c >= 97 && c <= 122) ||
910
+ c === 126
911
+
912
+ /** Whether `s` is an RFC 6570 URI template, `http://example.com/{term:1}/{term}`. */
913
+ export const isUriTemplate = (s: string): boolean => {
914
+ const length: i32 = toI32(s.length)
915
+ let i: i32 = 0
916
+ while (i < length) {
917
+ const c = jfUnitAt(s, i)
918
+ if (c === 123) {
919
+ i = jfExpressionEnd(s, i)
920
+ if (i < 0) {
921
+ return false
922
+ }
923
+ } else if (c === 37) {
924
+ if (!jfIsHex(jfUnitAt(s, i + 1)) || !jfIsHex(jfUnitAt(s, i + 2))) {
925
+ return false
926
+ }
927
+ i += 3
928
+ } else if (c < 0x80) {
929
+ if (!jfIsTemplateLiteral(c)) {
930
+ return false
931
+ }
932
+ i += 1
933
+ } else {
934
+ const w = jfUcsWidth(s, i, true)
935
+ if (w === 0) {
936
+ return false
937
+ }
938
+ i += w
939
+ }
940
+ }
941
+ return true
942
+ }
943
+
944
+ /**
945
+ * Whether `s` is a regular expression `nish/regex` compiles with no flags.
946
+ * Lookaround, backreferences and what the `u` flag refuses are answered
947
+ * false, though ECMA-262 accepts them (see the module header).
948
+ */
949
+ export const isRegex = (s: string): boolean => regexCompile(s, "").isOk()
950
+
951
+ /**
952
+ * The id of the format `name` for `jsonFormatCheck`, one of the
953
+ * `JSON_FORMAT_*` ids, or -1 for a format this module does not check.
954
+ */
955
+ export const jsonFormatId = (name: string): i32 => {
956
+ if (name === "date-time") {
957
+ return JSON_FORMAT_DATE_TIME
958
+ }
959
+ if (name === "date") {
960
+ return JSON_FORMAT_DATE
961
+ }
962
+ if (name === "time") {
963
+ return JSON_FORMAT_TIME
964
+ }
965
+ if (name === "duration") {
966
+ return JSON_FORMAT_DURATION
967
+ }
968
+ if (name === "email") {
969
+ return JSON_FORMAT_EMAIL
970
+ }
971
+ if (name === "hostname") {
972
+ return JSON_FORMAT_HOSTNAME
973
+ }
974
+ if (name === "ipv4") {
975
+ return JSON_FORMAT_IPV4
976
+ }
977
+ if (name === "ipv6") {
978
+ return JSON_FORMAT_IPV6
979
+ }
980
+ if (name === "uuid") {
981
+ return JSON_FORMAT_UUID
982
+ }
983
+ if (name === "uri") {
984
+ return JSON_FORMAT_URI
985
+ }
986
+ if (name === "uri-reference") {
987
+ return JSON_FORMAT_URI_REFERENCE
988
+ }
989
+ if (name === "iri") {
990
+ return JSON_FORMAT_IRI
991
+ }
992
+ if (name === "iri-reference") {
993
+ return JSON_FORMAT_IRI_REFERENCE
994
+ }
995
+ if (name === "uri-template") {
996
+ return JSON_FORMAT_URI_TEMPLATE
997
+ }
998
+ if (name === "regex") {
999
+ return JSON_FORMAT_REGEX
1000
+ }
1001
+ return -1
1002
+ }
1003
+
1004
+ /** Whether `value` passes the format `id`, which is one of the `JSON_FORMAT_*` ids. */
1005
+ const jfPasses = (id: i32, value: string): boolean => {
1006
+ if (id === JSON_FORMAT_DATE_TIME) {
1007
+ return isDateTime(value)
1008
+ }
1009
+ if (id === JSON_FORMAT_DATE) {
1010
+ return isDate(value)
1011
+ }
1012
+ if (id === JSON_FORMAT_TIME) {
1013
+ return isTime(value)
1014
+ }
1015
+ if (id === JSON_FORMAT_DURATION) {
1016
+ return isDuration(value)
1017
+ }
1018
+ if (id === JSON_FORMAT_EMAIL) {
1019
+ return isEmail(value)
1020
+ }
1021
+ if (id === JSON_FORMAT_HOSTNAME) {
1022
+ return isHostname(value)
1023
+ }
1024
+ if (id === JSON_FORMAT_IPV4) {
1025
+ return isIpv4(value)
1026
+ }
1027
+ if (id === JSON_FORMAT_IPV6) {
1028
+ return isIpv6(value)
1029
+ }
1030
+ if (id === JSON_FORMAT_UUID) {
1031
+ return isUuid(value)
1032
+ }
1033
+ if (id === JSON_FORMAT_URI) {
1034
+ return isUri(value)
1035
+ }
1036
+ if (id === JSON_FORMAT_URI_REFERENCE) {
1037
+ return isUriReference(value)
1038
+ }
1039
+ if (id === JSON_FORMAT_IRI) {
1040
+ return isIri(value)
1041
+ }
1042
+ if (id === JSON_FORMAT_IRI_REFERENCE) {
1043
+ return isIriReference(value)
1044
+ }
1045
+ if (id === JSON_FORMAT_URI_TEMPLATE) {
1046
+ return isUriTemplate(value)
1047
+ }
1048
+ return isRegex(value)
1049
+ }
1050
+
1051
+ /**
1052
+ * `value` checked against the format `id` from `jsonFormatId`:
1053
+ * `JSON_FORMAT_VALID` or `JSON_FORMAT_INVALID`, or `JSON_FORMAT_UNKNOWN`
1054
+ * for an id that names no format, -1 included.
1055
+ */
1056
+ export const jsonFormatCheck = (id: i32, value: string): i32 => {
1057
+ if (id < JSON_FORMAT_DATE_TIME || id > JSON_FORMAT_REGEX) {
1058
+ return JSON_FORMAT_UNKNOWN
1059
+ }
1060
+ return jfPasses(id, value) ? JSON_FORMAT_VALID : JSON_FORMAT_INVALID
1061
+ }
1062
+
1063
+ /**
1064
+ * `value` checked against the format named `name`: `JSON_FORMAT_VALID`,
1065
+ * `JSON_FORMAT_INVALID`, or `JSON_FORMAT_UNKNOWN` for a format this module
1066
+ * does not check. A validator that checks many instances against one schema
1067
+ * resolves the name once with `jsonFormatId` and calls `jsonFormatCheck`.
1068
+ */
1069
+ export const jsonFormatValidate = (name: string, value: string): i32 =>
1070
+ jsonFormatCheck(jsonFormatId(name), value)