@jarenjs/core 0.9.2
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/ARCHITECTURE.md +800 -0
- package/LICENSE +21 -0
- package/README.md +68 -0
- package/dist/types/array.d.ts +28 -0
- package/dist/types/bigint.d.ts +5 -0
- package/dist/types/dates.d.ts +79 -0
- package/dist/types/float.d.ts +32 -0
- package/dist/types/function.d.ts +22 -0
- package/dist/types/index.d.ts +119 -0
- package/dist/types/integer.d.ts +24 -0
- package/dist/types/math/float64.d.ts +121 -0
- package/dist/types/math/index.d.ts +5 -0
- package/dist/types/math/int32.d.ts +41 -0
- package/dist/types/math/vec2f64.d.ts +346 -0
- package/dist/types/math/vec2i32.d.ts +43 -0
- package/dist/types/math/vec3f64.d.ts +61 -0
- package/dist/types/number.d.ts +39 -0
- package/dist/types/object.d.ts +44 -0
- package/dist/types/scan.d.ts +64 -0
- package/dist/types/string.d.ts +65 -0
- package/dist/types/text/base64.d.ts +4 -0
- package/dist/types/text/basic.d.ts +5 -0
- package/dist/types/text/email.d.ts +3 -0
- package/dist/types/text/host.d.ts +14 -0
- package/dist/types/text/i18n.d.ts +13 -0
- package/dist/types/text/identifiers.d.ts +5 -0
- package/dist/types/text/index.d.ts +8 -0
- package/dist/types/text/iregexp.d.ts +36 -0
- package/dist/types/text/misc.d.ts +4 -0
- package/dist/types/text/punycode.d.ts +86 -0
- package/package.json +101 -0
- package/src/array.js +57 -0
- package/src/bigint.js +30 -0
- package/src/dates.js +371 -0
- package/src/float.js +107 -0
- package/src/function.js +56 -0
- package/src/index.js +223 -0
- package/src/integer.js +77 -0
- package/src/math/float64.js +316 -0
- package/src/math/index.js +5 -0
- package/src/math/int32.js +235 -0
- package/src/math/vec2f64.js +706 -0
- package/src/math/vec2i32.js +250 -0
- package/src/math/vec3f64.js +225 -0
- package/src/number.js +63 -0
- package/src/object.js +240 -0
- package/src/scan.js +96 -0
- package/src/string.js +194 -0
- package/src/text/base64.js +54 -0
- package/src/text/basic.js +23 -0
- package/src/text/email.js +61 -0
- package/src/text/host.js +335 -0
- package/src/text/i18n.js +294 -0
- package/src/text/identifiers.js +27 -0
- package/src/text/index.js +11 -0
- package/src/text/iregexp.js +308 -0
- package/src/text/misc.js +19 -0
- package/src/text/punycode.js +407 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// I-Regexp: An Interoperable Regular Expression Format
|
|
2
|
+
// https://www.rfc-editor.org/rfc/rfc9485.html
|
|
3
|
+
//
|
|
4
|
+
// A complete RFC 9485 validator and translator to ECMAScript RegExp,
|
|
5
|
+
// used by the JSONPath engine (json/path.js) for the match()/search()
|
|
6
|
+
// function extensions of RFC 9535.
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
isDigitCode,
|
|
10
|
+
} from '../scan.js';
|
|
11
|
+
|
|
12
|
+
// single-character escapes allowed by RFC 9485: \( \) \* \+ \- \. \? \[ \\ \] \^ \n \r \t \{ \| \}
|
|
13
|
+
const IREGEXP_SINGLE_ESC = '()*+-.?[\\]^nrt{|}';
|
|
14
|
+
|
|
15
|
+
// Unicode general categories allowed in \p{...} / \P{...} (RFC 9485):
|
|
16
|
+
// the key is the major category, the value the allowed subcategory letters
|
|
17
|
+
const IREGEXP_CATEGORIES = {
|
|
18
|
+
L: 'lmotu',
|
|
19
|
+
M: 'cen',
|
|
20
|
+
N: 'dlo',
|
|
21
|
+
P: 'cdefios',
|
|
22
|
+
Z: 'lps',
|
|
23
|
+
S: 'ckmo',
|
|
24
|
+
C: 'cfno',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validate an I-Regexp (RFC 9485) against its complete ABNF grammar and
|
|
29
|
+
* translate it to an equivalent ECMAScript pattern (RFC 9485 section 5.3):
|
|
30
|
+
*
|
|
31
|
+
* - unescaped dots outside character classes become [^\n\r]
|
|
32
|
+
* - '\-' outside a character class becomes '-' (not a valid ECMAScript
|
|
33
|
+
* escape under the 'u' flag)
|
|
34
|
+
* - unescaped '^' and '$' (grammatically NormalChars) pass through
|
|
35
|
+
* unchanged: the RFC's own ECMAScript/PCRE/RE2/Ruby conversions
|
|
36
|
+
* (sections 5.3/5.4) leave them alone, which gives them anchor
|
|
37
|
+
* semantics, and the official JSONPath compliance test suite expects
|
|
38
|
+
* exactly that; write '\^' for a literal caret and '[$]' for a
|
|
39
|
+
* literal dollar ('\$' is not a valid I-Regexp escape)
|
|
40
|
+
*
|
|
41
|
+
* I-Regexp deliberately excludes lookaround, backreferences, lazy
|
|
42
|
+
* quantifiers, multi-character escapes (\d \s \w), and inline flags; per
|
|
43
|
+
* RFC 9535 sections 2.4.6/2.4.7 a nonconforming pattern makes
|
|
44
|
+
* match()/search() yield LogicalFalse, so this returns null for them.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} pattern - The I-Regexp pattern
|
|
47
|
+
* @returns {string|null} The ECMAScript pattern source, or null when invalid
|
|
48
|
+
*/
|
|
49
|
+
export function translateIRegexp(pattern) {
|
|
50
|
+
const n = pattern.length;
|
|
51
|
+
let i = 0;
|
|
52
|
+
let out = '';
|
|
53
|
+
|
|
54
|
+
// reads the code point at i; -1 marks a lone surrogate (not a
|
|
55
|
+
// Unicode scalar value, so never valid in an I-Regexp)
|
|
56
|
+
function codePoint() {
|
|
57
|
+
const c = pattern.charCodeAt(i);
|
|
58
|
+
if (c >= 0xD800 && c <= 0xDFFF) {
|
|
59
|
+
if (c >= 0xDC00 || i + 1 >= n)
|
|
60
|
+
return -1;
|
|
61
|
+
const d = pattern.charCodeAt(i + 1);
|
|
62
|
+
return (d >= 0xDC00 && d <= 0xDFFF) ? pattern.codePointAt(i) : -1;
|
|
63
|
+
}
|
|
64
|
+
return c;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function emitCodePoint(cp) {
|
|
68
|
+
const width = cp > 0xFFFF ? 2 : 1;
|
|
69
|
+
out += pattern.slice(i, i + width);
|
|
70
|
+
i += width;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// NormalChar = %x00-27 / "," / "-" / %x2F-3E / %x40-5A / %x5E-7A / %x7E-D7FF / %xE000-10FFFF
|
|
74
|
+
function isNormalChar(cp) {
|
|
75
|
+
return cp <= 0x27
|
|
76
|
+
|| cp === 0x2C || cp === 0x2D
|
|
77
|
+
|| (cp >= 0x2F && cp <= 0x3E)
|
|
78
|
+
|| (cp >= 0x40 && cp <= 0x5A)
|
|
79
|
+
|| (cp >= 0x5E && cp <= 0x7A)
|
|
80
|
+
|| cp >= 0x7E; // codePoint() already excluded surrogates
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// CCchar = %x00-2C / %x2E-5A / %x5E-D7FF / %xE000-10FFFF (or SingleCharEsc)
|
|
84
|
+
function isCCchar(cp) {
|
|
85
|
+
return cp !== 0x2D && cp !== 0x5B && cp !== 0x5C && cp !== 0x5D;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// "\" already consumed; SingleCharEsc / catEsc / complEsc
|
|
89
|
+
function parseEscape(inClass) {
|
|
90
|
+
if (i >= n)
|
|
91
|
+
return false;
|
|
92
|
+
const ch = pattern[i];
|
|
93
|
+
if (ch === 'p' || ch === 'P') {
|
|
94
|
+
i++;
|
|
95
|
+
if (pattern[i] !== '{')
|
|
96
|
+
return false;
|
|
97
|
+
i++;
|
|
98
|
+
const sub = IREGEXP_CATEGORIES[pattern[i]];
|
|
99
|
+
if (sub === undefined)
|
|
100
|
+
return false;
|
|
101
|
+
let prop = pattern[i];
|
|
102
|
+
i++;
|
|
103
|
+
if (pattern[i] !== '}') {
|
|
104
|
+
if (i >= n || !sub.includes(pattern[i]))
|
|
105
|
+
return false;
|
|
106
|
+
prop += pattern[i];
|
|
107
|
+
i++;
|
|
108
|
+
if (pattern[i] !== '}')
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
i++;
|
|
112
|
+
out += '\\' + ch + '{' + prop + '}';
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (!IREGEXP_SINGLE_ESC.includes(ch))
|
|
116
|
+
return false;
|
|
117
|
+
// '\-' is a valid I-Regexp escape but not a valid ECMAScript 'u'
|
|
118
|
+
// escape outside a character class
|
|
119
|
+
out += (ch === '-' && !inClass) ? '-' : '\\' + ch;
|
|
120
|
+
i++;
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// CCE1 = ( CCchar [ "-" CCchar ] ) / charClassEsc
|
|
125
|
+
function parseCCE1() {
|
|
126
|
+
let rangeStart = false; // \p{...} cannot start a range
|
|
127
|
+
if (pattern.charCodeAt(i) === 0x5C) { // backslash
|
|
128
|
+
i++;
|
|
129
|
+
const isCat = pattern[i] === 'p' || pattern[i] === 'P';
|
|
130
|
+
if (!parseEscape(true))
|
|
131
|
+
return false;
|
|
132
|
+
rangeStart = !isCat;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
const cp = codePoint();
|
|
136
|
+
if (cp < 0 || !isCCchar(cp))
|
|
137
|
+
return false;
|
|
138
|
+
emitCodePoint(cp);
|
|
139
|
+
rangeStart = true;
|
|
140
|
+
}
|
|
141
|
+
// optional range: "-" CCchar (a trailing "-]" belongs to the class)
|
|
142
|
+
if (rangeStart && pattern[i] === '-' && i + 1 < n && pattern[i + 1] !== ']') {
|
|
143
|
+
out += '-';
|
|
144
|
+
i++;
|
|
145
|
+
if (pattern.charCodeAt(i) === 0x5C) {
|
|
146
|
+
i++;
|
|
147
|
+
return pattern[i] !== 'p' && pattern[i] !== 'P' && parseEscape(true);
|
|
148
|
+
}
|
|
149
|
+
const cp = codePoint();
|
|
150
|
+
if (cp < 0 || !isCCchar(cp))
|
|
151
|
+
return false;
|
|
152
|
+
emitCodePoint(cp);
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// charClassExpr = "[" [ "^" ] ( "-" / CCE1 ) *CCE1 [ "-" ] "]"
|
|
158
|
+
// (with the extra RFC 9485 restriction that "[^]" is not allowed)
|
|
159
|
+
function parseCharClassExpr() {
|
|
160
|
+
out += '[';
|
|
161
|
+
i++; // consume '['
|
|
162
|
+
if (pattern[i] === '^') {
|
|
163
|
+
out += '^';
|
|
164
|
+
i++;
|
|
165
|
+
}
|
|
166
|
+
if (pattern[i] === '-') {
|
|
167
|
+
out += '\\-';
|
|
168
|
+
i++;
|
|
169
|
+
}
|
|
170
|
+
else if (i >= n || pattern[i] === ']' || !parseCCE1()) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
for (;;) {
|
|
174
|
+
if (i >= n)
|
|
175
|
+
return false;
|
|
176
|
+
const ch = pattern[i];
|
|
177
|
+
if (ch === ']') {
|
|
178
|
+
out += ']';
|
|
179
|
+
i++;
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
if (ch === '-') { // only valid as the trailing "-]"
|
|
183
|
+
if (pattern[i + 1] !== ']')
|
|
184
|
+
return false;
|
|
185
|
+
out += '\\-]';
|
|
186
|
+
i += 2;
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
if (!parseCCE1())
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// atom = NormalChar / charClass / ( "(" i-regexp ")" )
|
|
195
|
+
function parseAtom() {
|
|
196
|
+
const ch = pattern[i];
|
|
197
|
+
if (ch === '(') {
|
|
198
|
+
out += '(';
|
|
199
|
+
i++;
|
|
200
|
+
if (!parseAlternation())
|
|
201
|
+
return false;
|
|
202
|
+
if (pattern[i] !== ')')
|
|
203
|
+
return false;
|
|
204
|
+
out += ')';
|
|
205
|
+
i++;
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
if (ch === '.') { // matches any character except \n and \r
|
|
209
|
+
out += '[^\\n\\r]';
|
|
210
|
+
i++;
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
if (ch === '\\') {
|
|
214
|
+
i++;
|
|
215
|
+
return parseEscape(false);
|
|
216
|
+
}
|
|
217
|
+
if (ch === '[')
|
|
218
|
+
return parseCharClassExpr();
|
|
219
|
+
const cp = codePoint();
|
|
220
|
+
if (cp < 0 || !isNormalChar(cp))
|
|
221
|
+
return false;
|
|
222
|
+
emitCodePoint(cp);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// piece = atom [ quantifier ]
|
|
227
|
+
function parsePiece() {
|
|
228
|
+
if (!parseAtom())
|
|
229
|
+
return false;
|
|
230
|
+
const ch = pattern[i];
|
|
231
|
+
if (ch === '*' || ch === '+' || ch === '?') {
|
|
232
|
+
out += ch;
|
|
233
|
+
i++;
|
|
234
|
+
}
|
|
235
|
+
else if (ch === '{') { // range-quantifier = "{" QuantExact [ "," [ QuantExact ] ] "}"
|
|
236
|
+
let j = i + 1;
|
|
237
|
+
const first = j;
|
|
238
|
+
while (j < n && isDigitCode(pattern.charCodeAt(j)))
|
|
239
|
+
j++;
|
|
240
|
+
if (j === first)
|
|
241
|
+
return false;
|
|
242
|
+
if (pattern[j] === ',') {
|
|
243
|
+
j++;
|
|
244
|
+
while (j < n && isDigitCode(pattern.charCodeAt(j)))
|
|
245
|
+
j++;
|
|
246
|
+
}
|
|
247
|
+
if (pattern[j] !== '}')
|
|
248
|
+
return false;
|
|
249
|
+
out += pattern.slice(i, j + 1);
|
|
250
|
+
i = j + 1;
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// branch = *piece
|
|
256
|
+
function parseBranch() {
|
|
257
|
+
while (i < n) {
|
|
258
|
+
const ch = pattern[i];
|
|
259
|
+
if (ch === '|' || ch === ')')
|
|
260
|
+
return true;
|
|
261
|
+
if (!parsePiece())
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// i-regexp = branch *( "|" branch )
|
|
268
|
+
function parseAlternation() {
|
|
269
|
+
if (!parseBranch())
|
|
270
|
+
return false;
|
|
271
|
+
while (pattern[i] === '|') {
|
|
272
|
+
out += '|';
|
|
273
|
+
i++;
|
|
274
|
+
if (!parseBranch())
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return (parseAlternation() && i === n) ? out : null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Compile an I-Regexp into an ECMAScript RegExp.
|
|
285
|
+
* @param {string} pattern - The I-Regexp pattern
|
|
286
|
+
* @param {boolean} [fullMatch=false] - Anchor the whole pattern (match() semantics, true) or leave it free (search() semantics, false)
|
|
287
|
+
* @returns {RegExp|null} null when the pattern is not a valid I-Regexp
|
|
288
|
+
*/
|
|
289
|
+
export function compileIRegexp(pattern, fullMatch = false) {
|
|
290
|
+
const translated = translateIRegexp(pattern);
|
|
291
|
+
if (translated === null)
|
|
292
|
+
return null;
|
|
293
|
+
try {
|
|
294
|
+
return new RegExp(fullMatch ? `^(?:${translated})$` : translated, 'u');
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Validates a pattern against the complete I-Regexp (RFC 9485) grammar.
|
|
303
|
+
* @param {string} pattern - The I-Regexp pattern to validate
|
|
304
|
+
* @returns {boolean} True when the pattern is a valid I-Regexp
|
|
305
|
+
*/
|
|
306
|
+
export function isValidIRegexp(pattern) {
|
|
307
|
+
return typeof pattern === 'string' && translateIRegexp(pattern) !== null;
|
|
308
|
+
}
|
package/src/text/misc.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const CONST_REGPEXP_ISBN10 = /^(?:ISBN(?:-10)?:?\ *((?=\d{1,5}([ -]?)\d{1,7}\2?\d{1,6}\2?\d)(?:\d\2*){9}[\dX]))$/i;
|
|
2
|
+
export function isValidISBN10(str) {
|
|
3
|
+
return CONST_REGPEXP_ISBN10.test(str);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const CONST_REGEXP_ISBN13 = /^(?:ISBN(?:-13)?:?\ *(97(?:8|9)([ -]?)(?=\d{1,5}\2?\d{1,7}\2?\d{1,6}\2?\d)(?:\d\2*){9}\d))$/i;
|
|
7
|
+
export function isValidISBN13(str) {
|
|
8
|
+
return CONST_REGEXP_ISBN13.test(str);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const CONST_REGEXP_COUNTRY_ALPHA2 = /^(AF|AX|AL|DZ|AS|AD|AO|AI|AQ|AG|AR|AM|AW|AU|AT|AZ|BS|BH|BD|BB|BY|BE|BZ|BJ|BM|BT|BO|BQ|BA|BW|BV|BR|IO|BN|BG|BF|BI|KH|CM|CA|CV|KY|CF|TD|CL|CN|CX|CC|CO|KM|CG|CD|CK|CR|CI|HR|CU|CW|CY|CZ|DK|DJ|DM|DO|EC|EG|SV|GQ|ER|EE|ET|FK|FO|FJ|FI|FR|GF|PF|TF|GA|GM|GE|DE|GH|GI|GR|GL|GD|GP|GU|GT|GG|GN|GW|GY|HT|HM|VA|HN|HK|HU|IS|IN|ID|IR|IQ|IE|IM|IL|IT|JM|JP|JE|JO|KZ|KE|KI|KP|KR|KW|KG|LA|LV|LB|LS|LR|LY|LI|LT|LU|MO|MK|MG|MW|MY|MV|ML|MT|MH|MQ|MR|MU|YT|MX|FM|MD|MC|MN|ME|MS|MA|MZ|MM|NA|NR|NP|NL|NC|NZ|NI|NE|NG|NU|NF|MP|NO|OM|PK|PW|PS|PA|PG|PY|PE|PH|PN|PL|PT|PR|QA|RE|RO|RU|RW|BL|SH|KN|LC|MF|PM|VC|WS|SM|ST|SA|SN|RS|SC|SL|SG|SX|SK|SI|SB|SO|ZA|GS|SS|ES|LK|SD|SR|SJ|SZ|SE|CH|SY|TW|TJ|TZ|TH|TL|TG|TK|TO|TT|TN|TR|TM|TC|TV|UG|UA|AE|GB|US|UM|UY|UZ|VU|VE|VN|VG|VI|WF|EH|YE|ZM|ZW|XK)/i;
|
|
12
|
+
export function isValidCountryAlpha2(str) {
|
|
13
|
+
return CONST_REGEXP_COUNTRY_ALPHA2.test(str);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const CONST_REGEXP_IBAN = /^([A-Z]{2}[ '+'\\\\'+'-]?[0-9]{2})(?=(?:[ '+'\\\\'+'-]?[A-Z0-9]){9,30}\$)((?:[ '+'\\\\'+'-]?[A-Z0-9]{3,5}){2,7})([ '+'\\\\'+'-]?[A-Z0-9]{1,3})?\$/i;
|
|
17
|
+
export function isValidIBAN(str) {
|
|
18
|
+
return CONST_REGEXP_IBAN.test(str);
|
|
19
|
+
}
|