@futpib/parser 1.0.3 → 1.0.6
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/.claude/settings.local.json +24 -0
- package/.github/workflows/main.yml +1 -0
- package/build/androidPackageParser.js +30 -32
- package/build/arbitraryDalvikBytecode.d.ts +3 -3
- package/build/arbitraryDalvikBytecode.js +33 -27
- package/build/arbitraryDalvikExecutable.js +55 -17
- package/build/arbitraryJava.d.ts +31 -0
- package/build/arbitraryJava.js +532 -0
- package/build/arbitraryJavaScript.d.ts +3 -0
- package/build/arbitraryJavaScript.js +263 -0
- package/build/arbitraryJavascript.d.ts +3 -0
- package/build/arbitraryJavascript.js +263 -0
- package/build/arbitraryZig.d.ts +3 -0
- package/build/arbitraryZig.js +240 -0
- package/build/arbitraryZipStream.d.ts +1 -1
- package/build/arrayParser.js +72 -13
- package/build/backsmali.d.ts +4 -3
- package/build/backsmali.js +26 -6
- package/build/bash.d.ts +89 -0
- package/build/bash.js +1 -0
- package/build/bashParser.d.ts +6 -0
- package/build/bashParser.js +335 -0
- package/build/bashParser.test.d.ts +1 -0
- package/build/bashParser.test.js +343 -0
- package/build/bashParserEdgeCases.test.d.ts +1 -0
- package/build/bashParserEdgeCases.test.js +117 -0
- package/build/dalvikBytecodeParser/addressConversion.d.ts +110 -0
- package/build/dalvikBytecodeParser/addressConversion.js +334 -0
- package/build/dalvikBytecodeParser/formatParsers.d.ts +7 -6
- package/build/dalvikBytecodeParser/formatParsers.js +13 -14
- package/build/dalvikBytecodeParser.d.ts +60 -31
- package/build/dalvikBytecodeParser.js +92 -35
- package/build/dalvikBytecodeParser.test-d.d.ts +1 -0
- package/build/dalvikBytecodeParser.test-d.js +268 -0
- package/build/dalvikBytecodeUnparser/formatUnparsers.d.ts +9 -8
- package/build/dalvikBytecodeUnparser/formatUnparsers.js +13 -12
- package/build/dalvikBytecodeUnparser.d.ts +2 -2
- package/build/dalvikBytecodeUnparser.js +23 -23
- package/build/dalvikBytecodeUnparser.test.js +7 -7
- package/build/dalvikExecutable.d.ts +3 -3
- package/build/dalvikExecutable.test-d.d.ts +1 -0
- package/build/dalvikExecutable.test-d.js +59 -0
- package/build/dalvikExecutableParser/typedNumbers.d.ts +18 -0
- package/build/dalvikExecutableParser/typedNumbers.js +3 -0
- package/build/dalvikExecutableParser.d.ts +2 -1
- package/build/dalvikExecutableParser.js +96 -77
- package/build/dalvikExecutableParser.test.js +24 -3
- package/build/dalvikExecutableParserAgainstSmaliParser.test.js +3 -0
- package/build/dalvikExecutableUnparser/poolScanners.d.ts +2 -2
- package/build/dalvikExecutableUnparser/sectionUnparsers.d.ts +3 -3
- package/build/dalvikExecutableUnparser/sectionUnparsers.js +26 -11
- package/build/dalvikExecutableUnparser.d.ts +2 -2
- package/build/dalvikExecutableUnparser.test.js +2 -1
- package/build/disjunctionParser.d.ts +5 -3
- package/build/disjunctionParser.js +79 -17
- package/build/disjunctionParser.test-d.d.ts +1 -0
- package/build/disjunctionParser.test-d.js +72 -0
- package/build/elementSwitchParser.d.ts +4 -0
- package/build/{exactElementSwitchParser.js → elementSwitchParser.js} +3 -4
- package/build/elementSwitchParser.test-d.d.ts +1 -0
- package/build/elementSwitchParser.test-d.js +44 -0
- package/build/exactSequenceParser.d.ts +4 -2
- package/build/exactSequenceParser.test-d.d.ts +1 -0
- package/build/exactSequenceParser.test-d.js +36 -0
- package/build/fetchCid.js +2 -66
- package/build/index.d.ts +25 -2
- package/build/index.js +23 -1
- package/build/index.test.js +16 -1
- package/build/inputReader.d.ts +10 -0
- package/build/inputReader.js +36 -0
- package/build/java.d.ts +502 -0
- package/build/java.js +2 -0
- package/build/javaKeyStoreParser.js +14 -17
- package/build/javaParser.d.ts +51 -0
- package/build/javaParser.js +1538 -0
- package/build/javaParser.test.d.ts +1 -0
- package/build/javaParser.test.js +1287 -0
- package/build/javaScript.d.ts +35 -0
- package/build/javaScript.js +1 -0
- package/build/javaScriptParser.d.ts +9 -0
- package/build/javaScriptParser.js +34 -0
- package/build/javaScriptUnparser.d.ts +3 -0
- package/build/javaScriptUnparser.js +4 -0
- package/build/javaScriptUnparser.test.d.ts +1 -0
- package/build/javaScriptUnparser.test.js +24 -0
- package/build/javaUnparser.d.ts +2 -0
- package/build/javaUnparser.js +519 -0
- package/build/javaUnparser.test.d.ts +1 -0
- package/build/javaUnparser.test.js +24 -0
- package/build/javascript.d.ts +35 -0
- package/build/javascript.js +1 -0
- package/build/javascriptParser.d.ts +9 -0
- package/build/javascriptParser.js +34 -0
- package/build/javascriptUnparser.d.ts +3 -0
- package/build/javascriptUnparser.js +4 -0
- package/build/javascriptUnparser.test.d.ts +1 -0
- package/build/javascriptUnparser.test.js +24 -0
- package/build/jsonParser.js +2 -12
- package/build/lazyMessageError.d.ts +3 -0
- package/build/lookaheadParser.js +60 -3
- package/build/negativeLookaheadParser.js +70 -11
- package/build/nonEmptyArrayParser.js +72 -13
- package/build/objectParser.d.ts +12 -0
- package/build/objectParser.js +31 -0
- package/build/objectParser.test-d.d.ts +1 -0
- package/build/objectParser.test-d.js +112 -0
- package/build/objectParser.test.d.ts +1 -0
- package/build/objectParser.test.js +55 -0
- package/build/optionalParser.js +69 -10
- package/build/parser.d.ts +4 -0
- package/build/parser.js +3 -1
- package/build/parser.test.js +114 -1
- package/build/parserConsumedSequenceParser.js +66 -7
- package/build/parserContext.d.ts +6 -0
- package/build/parserContext.js +20 -11
- package/build/parserError.d.ts +119 -27
- package/build/parserError.js +16 -8
- package/build/regexpParser.d.ts +2 -0
- package/build/regexpParser.js +101 -0
- package/build/regexpParser.test.d.ts +1 -0
- package/build/regexpParser.test.js +114 -0
- package/build/regularExpression.d.ts +63 -0
- package/build/regularExpression.js +1 -0
- package/build/regularExpressionParser.d.ts +3 -0
- package/build/regularExpressionParser.js +600 -0
- package/build/regularExpressionParser.test.d.ts +1 -0
- package/build/regularExpressionParser.test.js +89 -0
- package/build/separatedArrayParser.js +73 -14
- package/build/separatedNonEmptyArrayParser.js +73 -14
- package/build/sliceBoundedParser.js +62 -5
- package/build/smaliParser.d.ts +7 -7
- package/build/smaliParser.js +185 -268
- package/build/smaliParser.test.js +58 -0
- package/build/stringEscapes.d.ts +5 -0
- package/build/stringEscapes.js +244 -0
- package/build/symbolicExpression.d.ts +29 -0
- package/build/symbolicExpression.js +1 -0
- package/build/symbolicExpressionParser.d.ts +4 -0
- package/build/symbolicExpressionParser.js +123 -0
- package/build/symbolicExpressionParser.test.d.ts +1 -0
- package/build/symbolicExpressionParser.test.js +289 -0
- package/build/terminatedArrayParser.js +113 -38
- package/build/terminatedArrayParser.test.js +4 -2
- package/build/tupleParser.d.ts +7 -15
- package/build/tupleParser.js +1 -0
- package/build/unionParser.d.ts +5 -3
- package/build/unionParser.js +7 -2
- package/build/unionParser.test-d.d.ts +1 -0
- package/build/unionParser.test-d.js +72 -0
- package/build/unionParser.test.js +10 -11
- package/build/zig.d.ts +280 -0
- package/build/zig.js +2 -0
- package/build/zigParser.d.ts +3 -0
- package/build/zigParser.js +1119 -0
- package/build/zigParser.test.d.ts +1 -0
- package/build/zigParser.test.js +1590 -0
- package/build/zigUnparser.d.ts +2 -0
- package/build/zigUnparser.js +460 -0
- package/build/zigUnparser.test.d.ts +1 -0
- package/build/zigUnparser.test.js +24 -0
- package/build/zipParser.js +19 -32
- package/build/zipUnparser.js +19 -7
- package/build/zipUnparser.test.js +1 -1
- package/node_modules-@types/s-expression/index.d.ts +5 -0
- package/package.json +25 -6
- package/src/androidPackageParser.ts +33 -60
- package/src/arbitraryDalvikBytecode.ts +39 -31
- package/src/arbitraryDalvikExecutable.ts +65 -20
- package/src/arbitraryJava.ts +804 -0
- package/src/arbitraryJavaScript.ts +410 -0
- package/src/arbitraryZig.ts +380 -0
- package/src/arrayParser.ts +1 -3
- package/src/backsmali.ts +35 -4
- package/src/bash.ts +127 -0
- package/src/bashParser.test.ts +590 -0
- package/src/bashParser.ts +498 -0
- package/src/dalvikBytecodeParser/addressConversion.ts +496 -0
- package/src/dalvikBytecodeParser/formatParsers.ts +19 -29
- package/src/dalvikBytecodeParser.test-d.ts +310 -0
- package/src/dalvikBytecodeParser.ts +194 -69
- package/src/dalvikBytecodeUnparser/formatUnparsers.ts +27 -26
- package/src/dalvikBytecodeUnparser.test.ts +7 -7
- package/src/dalvikBytecodeUnparser.ts +31 -30
- package/src/dalvikExecutable.test-d.ts +132 -0
- package/src/dalvikExecutable.ts +3 -3
- package/src/dalvikExecutableParser/typedNumbers.ts +11 -0
- package/src/dalvikExecutableParser.test.ts +37 -3
- package/src/dalvikExecutableParser.test.ts.md +163 -2
- package/src/dalvikExecutableParser.test.ts.snap +0 -0
- package/src/dalvikExecutableParser.ts +121 -139
- package/src/dalvikExecutableParserAgainstSmaliParser.test.ts +4 -0
- package/src/dalvikExecutableUnparser/poolScanners.ts +6 -6
- package/src/dalvikExecutableUnparser/sectionUnparsers.ts +38 -14
- package/src/dalvikExecutableUnparser.test.ts +3 -2
- package/src/dalvikExecutableUnparser.ts +4 -4
- package/src/disjunctionParser.test-d.ts +105 -0
- package/src/disjunctionParser.ts +18 -15
- package/src/elementSwitchParser.test-d.ts +74 -0
- package/src/elementSwitchParser.ts +51 -0
- package/src/exactSequenceParser.test-d.ts +43 -0
- package/src/exactSequenceParser.ts +13 -8
- package/src/fetchCid.ts +2 -76
- package/src/index.test.ts +22 -1
- package/src/index.ts +119 -2
- package/src/inputReader.ts +53 -0
- package/src/java.ts +708 -0
- package/src/javaKeyStoreParser.ts +18 -32
- package/src/javaParser.test.ts +1592 -0
- package/src/javaParser.ts +2640 -0
- package/src/javaScript.ts +36 -0
- package/src/javaScriptParser.ts +57 -0
- package/src/javaScriptUnparser.test.ts +37 -0
- package/src/javaScriptUnparser.ts +7 -0
- package/src/javaUnparser.test.ts +37 -0
- package/src/javaUnparser.ts +640 -0
- package/src/jsonParser.ts +6 -27
- package/src/lookaheadParser.ts +2 -6
- package/src/negativeLookaheadParser.ts +1 -3
- package/src/nonEmptyArrayParser.ts +1 -3
- package/src/objectParser.test-d.ts +152 -0
- package/src/objectParser.test.ts +71 -0
- package/src/objectParser.ts +69 -0
- package/src/optionalParser.ts +1 -3
- package/src/parser.test.ts +151 -4
- package/src/parser.ts +11 -1
- package/src/parserConsumedSequenceParser.ts +2 -4
- package/src/parserContext.ts +26 -11
- package/src/parserError.ts +17 -3
- package/src/regexpParser.test.ts +264 -0
- package/src/regexpParser.ts +126 -0
- package/src/regularExpression.ts +24 -0
- package/src/regularExpressionParser.test.ts +102 -0
- package/src/regularExpressionParser.ts +920 -0
- package/src/separatedArrayParser.ts +1 -3
- package/src/separatedNonEmptyArrayParser.ts +1 -3
- package/src/sliceBoundedParser.test.ts +2 -2
- package/src/sliceBoundedParser.ts +15 -19
- package/src/smaliParser.test.ts +64 -0
- package/src/smaliParser.test.ts.md +12 -12
- package/src/smaliParser.test.ts.snap +0 -0
- package/src/smaliParser.ts +246 -534
- package/src/stringEscapes.ts +253 -0
- package/src/symbolicExpression.ts +17 -0
- package/src/symbolicExpressionParser.test.ts +466 -0
- package/src/symbolicExpressionParser.ts +190 -0
- package/src/terminatedArrayParser.test.ts +9 -6
- package/src/terminatedArrayParser.ts +25 -29
- package/src/tupleParser.ts +21 -18
- package/src/unionParser.test-d.ts +105 -0
- package/src/unionParser.test.ts +18 -17
- package/src/unionParser.ts +28 -16
- package/src/zig.ts +411 -0
- package/src/zigParser.test.ts +1693 -0
- package/src/zigParser.ts +1745 -0
- package/src/zigUnparser.test.ts +37 -0
- package/src/zigUnparser.ts +615 -0
- package/src/zipParser.ts +20 -56
- package/src/zipUnparser.test.ts +1 -1
- package/src/zipUnparser.ts +22 -7
- package/tsconfig.json +2 -2
- package/build/exactElementSwitchParser.d.ts +0 -3
- package/src/exactElementSwitchParser.ts +0 -41
package/src/parserContext.ts
CHANGED
|
@@ -36,6 +36,8 @@ export type ParserContext<Sequence, Element> = {
|
|
|
36
36
|
equals(sequenceA: Sequence, sequenceB: Sequence): boolean;
|
|
37
37
|
|
|
38
38
|
get position(): number;
|
|
39
|
+
get furthestReadPosition(): number;
|
|
40
|
+
get furthestPeekedPosition(): number;
|
|
39
41
|
peek(offset: number): Promise<Element | undefined>;
|
|
40
42
|
peekSequence(start: number, end: number): Promise<Sequence | undefined>;
|
|
41
43
|
skip(offset: number): void;
|
|
@@ -46,6 +48,7 @@ export type ParserContext<Sequence, Element> = {
|
|
|
46
48
|
lookahead(options?: LookaheadOptions): ParserContext<Sequence, Element>;
|
|
47
49
|
unlookahead(): void;
|
|
48
50
|
dispose(): void;
|
|
51
|
+
[Symbol.dispose](): void;
|
|
49
52
|
|
|
50
53
|
invariant<T>(value: T, format: ValueOrAccessor<string | string[]>, ...formatArguments: unknown[]): Exclude<T, Falsy>;
|
|
51
54
|
invariantJoin<T>(value: T, childErrors: ParserParsingFailedError[], format: ValueOrAccessor<string | string[]>, ...formatArguments: unknown[]): Exclude<T, Falsy>;
|
|
@@ -117,6 +120,14 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
|
|
|
117
120
|
return this._inputReader.position;
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
get furthestReadPosition() {
|
|
124
|
+
return this._inputReader.furthestReadPosition;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
get furthestPeekedPosition() {
|
|
128
|
+
return this._inputReader.furthestPeekedPosition;
|
|
129
|
+
}
|
|
130
|
+
|
|
120
131
|
async peek(offset: number): Promise<Element | undefined> {
|
|
121
132
|
if (
|
|
122
133
|
this._options.sliceEnd !== undefined
|
|
@@ -169,7 +180,7 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
|
|
|
169
180
|
const element = await this.peek(offset);
|
|
170
181
|
|
|
171
182
|
if (element === undefined) {
|
|
172
|
-
throw new this._options.errorsModule.ParserUnexpectedEndOfInputError('', this._depth, this.position);
|
|
183
|
+
throw new this._options.errorsModule.ParserUnexpectedEndOfInputError('', this._depth, this.position, this._inputReader.furthestReadPosition, this._inputReader.furthestPeekedPosition);
|
|
173
184
|
}
|
|
174
185
|
|
|
175
186
|
this.skip(offset + 1);
|
|
@@ -181,7 +192,7 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
|
|
|
181
192
|
const sequence = await this.peekSequence(start, end);
|
|
182
193
|
|
|
183
194
|
if (sequence === undefined) {
|
|
184
|
-
throw new this._options.errorsModule.ParserUnexpectedEndOfInputError('', this._depth, this.position);
|
|
195
|
+
throw new this._options.errorsModule.ParserUnexpectedEndOfInputError('', this._depth, this.position, this._inputReader.furthestReadPosition, this._inputReader.furthestPeekedPosition);
|
|
185
196
|
}
|
|
186
197
|
|
|
187
198
|
this.skip(end);
|
|
@@ -300,11 +311,15 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
|
|
|
300
311
|
this._parentParserContext = undefined;
|
|
301
312
|
}
|
|
302
313
|
|
|
314
|
+
[Symbol.dispose]() {
|
|
315
|
+
this.dispose();
|
|
316
|
+
}
|
|
317
|
+
|
|
303
318
|
invariant<T>(value: T, format: ValueOrAccessor<string | string[]>, ...formatArguments: unknown[]): Exclude<T, Falsy> {
|
|
304
319
|
const parserContext = this;
|
|
305
320
|
|
|
306
321
|
return customInvariant(function (lazyMessage: LazyMessage) {
|
|
307
|
-
return new parserContext._options.errorsModule.ParserParsingInvariantError(lazyMessage, parserContext._depth, parserContext.position);
|
|
322
|
+
return new parserContext._options.errorsModule.ParserParsingInvariantError(lazyMessage, parserContext._depth, parserContext.position, parserContext._inputReader.furthestReadPosition, parserContext._inputReader.furthestPeekedPosition);
|
|
308
323
|
}, value, format, ...formatArguments);
|
|
309
324
|
}
|
|
310
325
|
|
|
@@ -316,22 +331,22 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
|
|
|
316
331
|
|
|
317
332
|
if (errorJoinMode === 'none') {
|
|
318
333
|
return customInvariant(function (lazyMessage: LazyMessage) {
|
|
319
|
-
return new parserContext._options.errorsModule.ParserParsingJoinNoneError(lazyMessage, parserContext._depth, parserContext.position);
|
|
334
|
+
return new parserContext._options.errorsModule.ParserParsingJoinNoneError(lazyMessage, parserContext._depth, parserContext.position, parserContext._inputReader.furthestReadPosition, parserContext._inputReader.furthestPeekedPosition);
|
|
320
335
|
}, value, format, ...formatArguments);
|
|
321
336
|
}
|
|
322
337
|
|
|
323
338
|
if (errorJoinMode === 'furthest') {
|
|
324
339
|
return customInvariant(function (userLazyMessage: LazyMessage) {
|
|
325
|
-
let
|
|
340
|
+
let furthestChildErrorPosition = 0;
|
|
326
341
|
let furthestChildErrors: ParserParsingFailedError[] = [];
|
|
327
342
|
|
|
328
343
|
for (const childError of childErrors) {
|
|
329
|
-
if (childError.position <
|
|
344
|
+
if (childError.position < furthestChildErrorPosition) {
|
|
330
345
|
continue;
|
|
331
346
|
}
|
|
332
347
|
|
|
333
|
-
if (childError.position >
|
|
334
|
-
|
|
348
|
+
if (childError.position > furthestChildErrorPosition) {
|
|
349
|
+
furthestChildErrorPosition = childError.position;
|
|
335
350
|
furthestChildErrors = [ childError ];
|
|
336
351
|
continue;
|
|
337
352
|
}
|
|
@@ -354,7 +369,7 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
|
|
|
354
369
|
|
|
355
370
|
return furthestChildError.stack?.split('\n').map(line => ' ' + line);
|
|
356
371
|
}).join('\n'),
|
|
357
|
-
], parserContext._depth,
|
|
372
|
+
], parserContext._depth, furthestChildErrorPosition, parserContext._inputReader.furthestReadPosition, parserContext._inputReader.furthestPeekedPosition, furthestChildErrors);
|
|
358
373
|
}, value, format, ...formatArguments);
|
|
359
374
|
}
|
|
360
375
|
|
|
@@ -396,7 +411,7 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
|
|
|
396
411
|
|
|
397
412
|
return deepestChildError.stack?.split('\n').map(line => ' ' + line);
|
|
398
413
|
}).join('\n'),
|
|
399
|
-
], deepestDepth, parserContext.position, deepestChildErrors);
|
|
414
|
+
], deepestDepth, parserContext.position, parserContext._inputReader.furthestReadPosition, parserContext._inputReader.furthestPeekedPosition, deepestChildErrors);
|
|
400
415
|
}, value, format, ...formatArguments);
|
|
401
416
|
}
|
|
402
417
|
|
|
@@ -417,7 +432,7 @@ export class ParserContextImplementation<Sequence, Element> implements ParserCon
|
|
|
417
432
|
|
|
418
433
|
return childError.stack?.split('\n').map(line => ' ' + line);
|
|
419
434
|
}).join('\n'),
|
|
420
|
-
], parserContext._depth, parserContext.position, childErrors);
|
|
435
|
+
], parserContext._depth, parserContext.position, parserContext._inputReader.furthestReadPosition, parserContext._inputReader.furthestPeekedPosition, childErrors);
|
|
421
436
|
}, value, format, ...formatArguments);
|
|
422
437
|
}
|
|
423
438
|
|
package/src/parserError.ts
CHANGED
|
@@ -23,6 +23,8 @@ type ParserErrorInterface = ParserError;
|
|
|
23
23
|
export interface ParserParsingFailedError extends ParserErrorInterface {
|
|
24
24
|
depth: number;
|
|
25
25
|
position: number;
|
|
26
|
+
furthestReadPosition: number;
|
|
27
|
+
furthestPeekedPosition: number;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export function isParserParsingFailedError(value: unknown): value is ParserParsingFailedError {
|
|
@@ -34,6 +36,10 @@ export function isParserParsingFailedError(value: unknown): value is ParserParsi
|
|
|
34
36
|
&& typeof value.depth === 'number'
|
|
35
37
|
&& 'position' in value
|
|
36
38
|
&& typeof value.position === 'number'
|
|
39
|
+
&& 'furthestReadPosition' in value
|
|
40
|
+
&& typeof value.furthestReadPosition === 'number'
|
|
41
|
+
&& 'furthestPeekedPosition' in value
|
|
42
|
+
&& typeof value.furthestPeekedPosition === 'number'
|
|
37
43
|
);
|
|
38
44
|
}
|
|
39
45
|
|
|
@@ -80,6 +86,8 @@ function createParserErrorModule(
|
|
|
80
86
|
message: LazyMessage,
|
|
81
87
|
public readonly depth: number,
|
|
82
88
|
public readonly position: number,
|
|
89
|
+
public readonly furthestReadPosition: number,
|
|
90
|
+
public readonly furthestPeekedPosition: number,
|
|
83
91
|
) {
|
|
84
92
|
super(message);
|
|
85
93
|
}
|
|
@@ -102,9 +110,11 @@ function createParserErrorModule(
|
|
|
102
110
|
message: LazyMessage,
|
|
103
111
|
depth: number,
|
|
104
112
|
position: number,
|
|
113
|
+
furthestReadPosition: number,
|
|
114
|
+
furthestPeekedPosition: number,
|
|
105
115
|
public readonly childErrors: ParserParsingFailedErrorInterface[],
|
|
106
116
|
) {
|
|
107
|
-
super(message, depth, position);
|
|
117
|
+
super(message, depth, position, furthestReadPosition, furthestPeekedPosition);
|
|
108
118
|
}
|
|
109
119
|
}
|
|
110
120
|
|
|
@@ -115,9 +125,11 @@ function createParserErrorModule(
|
|
|
115
125
|
message: LazyMessage,
|
|
116
126
|
depth: number,
|
|
117
127
|
position: number,
|
|
128
|
+
furthestReadPosition: number,
|
|
129
|
+
furthestPeekedPosition: number,
|
|
118
130
|
public readonly childErrors: ParserParsingFailedErrorInterface[],
|
|
119
131
|
) {
|
|
120
|
-
super(message, depth, position);
|
|
132
|
+
super(message, depth, position, furthestReadPosition, furthestPeekedPosition);
|
|
121
133
|
}
|
|
122
134
|
}
|
|
123
135
|
|
|
@@ -128,9 +140,11 @@ function createParserErrorModule(
|
|
|
128
140
|
message: LazyMessage,
|
|
129
141
|
depth: number,
|
|
130
142
|
position: number,
|
|
143
|
+
furthestReadPosition: number,
|
|
144
|
+
furthestPeekedPosition: number,
|
|
131
145
|
public readonly childErrors: ParserParsingFailedErrorInterface[],
|
|
132
146
|
) {
|
|
133
|
-
super(message, depth, position);
|
|
147
|
+
super(message, depth, position, furthestReadPosition, furthestPeekedPosition);
|
|
134
148
|
}
|
|
135
149
|
}
|
|
136
150
|
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
import * as fc from 'fast-check';
|
|
3
|
+
import { testProp } from '@fast-check/ava';
|
|
4
|
+
import { runParser, runParserWithRemainingInput } from './parser.js';
|
|
5
|
+
import { stringParserInputCompanion } from './parserInputCompanion.js';
|
|
6
|
+
import { createRegExpParser } from './regexpParser.js';
|
|
7
|
+
|
|
8
|
+
test('regexpParser matches digits', async t => {
|
|
9
|
+
const regexpParser = createRegExpParser(/\d+/);
|
|
10
|
+
|
|
11
|
+
const result = await runParser(
|
|
12
|
+
regexpParser,
|
|
13
|
+
'123',
|
|
14
|
+
stringParserInputCompanion,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
t.is(result[0], '123');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('regexpParser matches at start only', async t => {
|
|
21
|
+
const regexpParser = createRegExpParser(/\d+/);
|
|
22
|
+
|
|
23
|
+
const { output, remainingInput } = await runParserWithRemainingInput(
|
|
24
|
+
regexpParser,
|
|
25
|
+
'123abc',
|
|
26
|
+
stringParserInputCompanion,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
t.is(output[0], '123');
|
|
30
|
+
t.truthy(remainingInput);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('regexpParser fails when no match at start', async t => {
|
|
34
|
+
const regexpParser = createRegExpParser(/\d+/);
|
|
35
|
+
|
|
36
|
+
await t.throwsAsync(
|
|
37
|
+
runParser(
|
|
38
|
+
regexpParser,
|
|
39
|
+
'abc123',
|
|
40
|
+
stringParserInputCompanion,
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('regexpParser with capture groups', async t => {
|
|
46
|
+
const regexpParser = createRegExpParser(/(\d+)-(\d+)/);
|
|
47
|
+
|
|
48
|
+
const result = await runParser(
|
|
49
|
+
regexpParser,
|
|
50
|
+
'123-456',
|
|
51
|
+
stringParserInputCompanion,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
t.is(result[0], '123-456');
|
|
55
|
+
t.is(result[1], '123');
|
|
56
|
+
t.is(result[2], '456');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('regexpParser greedy matching', async t => {
|
|
60
|
+
const regexpParser = createRegExpParser(/a+/);
|
|
61
|
+
|
|
62
|
+
const { output } = await runParserWithRemainingInput(
|
|
63
|
+
regexpParser,
|
|
64
|
+
'aaab',
|
|
65
|
+
stringParserInputCompanion,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
t.is(output[0], 'aaa');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('regexpParser with anchored regexp', async t => {
|
|
72
|
+
const regexpParser = createRegExpParser(/^hello/);
|
|
73
|
+
|
|
74
|
+
const { output } = await runParserWithRemainingInput(
|
|
75
|
+
regexpParser,
|
|
76
|
+
'hello world',
|
|
77
|
+
stringParserInputCompanion,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
t.is(output[0], 'hello');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
testProp.serial(
|
|
84
|
+
'regexpParser matches word characters',
|
|
85
|
+
[
|
|
86
|
+
fc.tuple(
|
|
87
|
+
fc.stringMatching(/^\w+$/),
|
|
88
|
+
fc.stringMatching(/^\W*$/),
|
|
89
|
+
),
|
|
90
|
+
],
|
|
91
|
+
async (t, [ word, nonWord ]) => {
|
|
92
|
+
const regexpParser = createRegExpParser(/\w+/);
|
|
93
|
+
|
|
94
|
+
const { output, position } = await runParserWithRemainingInput(
|
|
95
|
+
regexpParser,
|
|
96
|
+
word + nonWord,
|
|
97
|
+
stringParserInputCompanion,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
t.is(output[0], word);
|
|
101
|
+
t.is(position, word.length);
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
verbose: true,
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Tests for zero-width/optional patterns at end of input
|
|
109
|
+
|
|
110
|
+
test('regexpParser with star quantifier on empty input', async t => {
|
|
111
|
+
const regexpParser = createRegExpParser(/a*/);
|
|
112
|
+
|
|
113
|
+
const result = await runParser(
|
|
114
|
+
regexpParser,
|
|
115
|
+
'',
|
|
116
|
+
stringParserInputCompanion,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
t.is(result[0], '');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('regexpParser with optional whitespace on empty input', async t => {
|
|
123
|
+
const regexpParser = createRegExpParser(/[ \t]*/);
|
|
124
|
+
|
|
125
|
+
const result = await runParser(
|
|
126
|
+
regexpParser,
|
|
127
|
+
'',
|
|
128
|
+
stringParserInputCompanion,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
t.is(result[0], '');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('regexpParser with star quantifier at end of input (no match)', async t => {
|
|
135
|
+
const regexpParser = createRegExpParser(/a*/);
|
|
136
|
+
|
|
137
|
+
const { output } = await runParserWithRemainingInput(
|
|
138
|
+
regexpParser,
|
|
139
|
+
'bbb',
|
|
140
|
+
stringParserInputCompanion,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
t.is(output[0], '');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('regexpParser with optional group on empty input', async t => {
|
|
147
|
+
const regexpParser = createRegExpParser(/(?:foo)?/);
|
|
148
|
+
|
|
149
|
+
const result = await runParser(
|
|
150
|
+
regexpParser,
|
|
151
|
+
'',
|
|
152
|
+
stringParserInputCompanion,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
t.is(result[0], '');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Tests for negative lookahead
|
|
159
|
+
|
|
160
|
+
test('regexpParser with negative lookahead should not match when followed by same char', async t => {
|
|
161
|
+
// This regex should NOT match anything in '||' - the | is followed by another |
|
|
162
|
+
const regexpParser = createRegExpParser(/\|(?!\|)/);
|
|
163
|
+
|
|
164
|
+
await t.throwsAsync(
|
|
165
|
+
runParser(
|
|
166
|
+
regexpParser,
|
|
167
|
+
'||',
|
|
168
|
+
stringParserInputCompanion,
|
|
169
|
+
),
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('regexpParser with negative lookahead should match single char', async t => {
|
|
174
|
+
// This regex should match single '|' when followed by something else
|
|
175
|
+
const regexpParser = createRegExpParser(/\|(?!\|)/);
|
|
176
|
+
|
|
177
|
+
const { output, position, remainingInput } = await runParserWithRemainingInput(
|
|
178
|
+
regexpParser,
|
|
179
|
+
'| ',
|
|
180
|
+
stringParserInputCompanion,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
t.is(output[0], '|');
|
|
184
|
+
t.is(position, 1); // Consumed 1 character
|
|
185
|
+
t.truthy(remainingInput); // There's remaining input (the space)
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('regexpParser should match exact 6-character input', async t => {
|
|
189
|
+
const regexpParser = createRegExpParser(/abcdef/);
|
|
190
|
+
|
|
191
|
+
const result = await runParser(
|
|
192
|
+
regexpParser,
|
|
193
|
+
'abcdef',
|
|
194
|
+
stringParserInputCompanion,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
t.is(result[0], 'abcdef');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('regexpParser should match quoted string of length 6', async t => {
|
|
201
|
+
const regexpParser = createRegExpParser(/"[^"]*"/);
|
|
202
|
+
|
|
203
|
+
const result = await runParser(
|
|
204
|
+
regexpParser,
|
|
205
|
+
'"abcd"', // 6 characters total
|
|
206
|
+
stringParserInputCompanion,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
t.is(result[0], '"abcd"');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Property-based tests for fixed-length patterns
|
|
213
|
+
|
|
214
|
+
testProp(
|
|
215
|
+
'regexpParser should match exact n-character input for any length',
|
|
216
|
+
[fc.integer({ min: 1, max: 20 })],
|
|
217
|
+
async (t, length) => {
|
|
218
|
+
const input = 'a'.repeat(length);
|
|
219
|
+
const regex = new RegExp(`a{${length}}`);
|
|
220
|
+
const regexpParser = createRegExpParser(regex);
|
|
221
|
+
|
|
222
|
+
const result = await runParser(
|
|
223
|
+
regexpParser,
|
|
224
|
+
input,
|
|
225
|
+
stringParserInputCompanion,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
t.is(result[0], input);
|
|
229
|
+
},
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
testProp(
|
|
233
|
+
'regexpParser should match quoted strings of any length',
|
|
234
|
+
[fc.integer({ min: 0, max: 20 })],
|
|
235
|
+
async (t, contentLength) => {
|
|
236
|
+
const content = 'x'.repeat(contentLength);
|
|
237
|
+
const input = `"${content}"`;
|
|
238
|
+
const regexpParser = createRegExpParser(/"[^"]*"/);
|
|
239
|
+
|
|
240
|
+
const result = await runParser(
|
|
241
|
+
regexpParser,
|
|
242
|
+
input,
|
|
243
|
+
stringParserInputCompanion,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
t.is(result[0], input);
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
testProp(
|
|
251
|
+
'regexpParser greedy patterns should match any length input',
|
|
252
|
+
[fc.stringMatching(/^[a-z]+$/).filter(s => s.length > 0 && s.length <= 20)],
|
|
253
|
+
async (t, input) => {
|
|
254
|
+
const regexpParser = createRegExpParser(/[a-z]+/);
|
|
255
|
+
|
|
256
|
+
const result = await runParser(
|
|
257
|
+
regexpParser,
|
|
258
|
+
input,
|
|
259
|
+
stringParserInputCompanion,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
t.is(result[0], input);
|
|
263
|
+
},
|
|
264
|
+
);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { type Parser, setParserName } from './parser.js';
|
|
2
|
+
|
|
3
|
+
export const createRegExpParser = (
|
|
4
|
+
regexp: RegExp,
|
|
5
|
+
): Parser<RegExpExecArray, string, string> => {
|
|
6
|
+
const regexpParser: Parser<RegExpExecArray, string, string> = async parserContext => {
|
|
7
|
+
let start = 0;
|
|
8
|
+
let window = 1;
|
|
9
|
+
let lastMatch: RegExpExecArray | undefined;
|
|
10
|
+
let reachedEndOfInput = false;
|
|
11
|
+
let inputLength: number | undefined;
|
|
12
|
+
|
|
13
|
+
while (true) {
|
|
14
|
+
const sequence = await parserContext.peekSequence(start, start + window);
|
|
15
|
+
|
|
16
|
+
if (sequence === undefined) {
|
|
17
|
+
if (!reachedEndOfInput) {
|
|
18
|
+
reachedEndOfInput = true;
|
|
19
|
+
|
|
20
|
+
// Binary search to find actual input length
|
|
21
|
+
// We know: length >= start (we've peeked successfully before)
|
|
22
|
+
// We know: length < start + window (current peek failed)
|
|
23
|
+
let lo = start;
|
|
24
|
+
let hi = start + window;
|
|
25
|
+
while (hi - lo > 1) {
|
|
26
|
+
const mid = (lo + hi) >> 1;
|
|
27
|
+
const probe = await parserContext.peekSequence(0, mid);
|
|
28
|
+
if (probe !== undefined) {
|
|
29
|
+
lo = mid;
|
|
30
|
+
} else {
|
|
31
|
+
hi = mid;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
inputLength = lo;
|
|
35
|
+
|
|
36
|
+
// Try matching against the full input
|
|
37
|
+
if (inputLength > 0) {
|
|
38
|
+
const fullInput = await parserContext.peekSequence(0, inputLength);
|
|
39
|
+
if (fullInput !== undefined) {
|
|
40
|
+
const match = regexp.exec(fullInput);
|
|
41
|
+
if (match !== null && match.index === 0) {
|
|
42
|
+
parserContext.skip(match[0].length);
|
|
43
|
+
return match;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
window = Math.floor(window / 2);
|
|
50
|
+
|
|
51
|
+
if (window === 0) {
|
|
52
|
+
// Get the full sequence we've accumulated to verify matches
|
|
53
|
+
const fullSequence = await parserContext.peekSequence(0, inputLength ?? start);
|
|
54
|
+
|
|
55
|
+
// Verify any previous match is still valid with full context
|
|
56
|
+
// For lookahead/lookbehind assertions, additional input might invalidate a match
|
|
57
|
+
if (fullSequence !== undefined) {
|
|
58
|
+
const verifyMatch = regexp.exec(fullSequence);
|
|
59
|
+
if (verifyMatch !== null && verifyMatch.index === 0) {
|
|
60
|
+
parserContext.skip(verifyMatch[0].length);
|
|
61
|
+
return verifyMatch;
|
|
62
|
+
}
|
|
63
|
+
} else if (lastMatch !== undefined) {
|
|
64
|
+
// No full sequence available but we have a previous match
|
|
65
|
+
parserContext.skip(lastMatch[0].length);
|
|
66
|
+
return lastMatch;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// No previous match - try matching against empty string for zero-width patterns (e.g., /a*/, /[ \t]*/)
|
|
70
|
+
const emptyMatch = regexp.exec('');
|
|
71
|
+
if (emptyMatch !== null && emptyMatch.index === 0) {
|
|
72
|
+
return emptyMatch;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return parserContext.invariant(false, 'Unexpected end of input without regex match');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const fullSequence = await parserContext.peekSequence(0, inputLength ?? start + window);
|
|
82
|
+
|
|
83
|
+
if (fullSequence === undefined) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const match = regexp.exec(fullSequence);
|
|
88
|
+
|
|
89
|
+
if (match === null || match.index !== 0) {
|
|
90
|
+
if (lastMatch !== undefined) {
|
|
91
|
+
// Verify lastMatch is still valid with current full context
|
|
92
|
+
// For lookahead/lookbehind assertions, a match on shorter input might be
|
|
93
|
+
// invalidated by additional input (e.g., /\|(?!\|)/ matches '|' but not '||')
|
|
94
|
+
const verifyMatch = regexp.exec(fullSequence);
|
|
95
|
+
if (verifyMatch !== null && verifyMatch.index === 0) {
|
|
96
|
+
parserContext.skip(verifyMatch[0].length);
|
|
97
|
+
return verifyMatch;
|
|
98
|
+
}
|
|
99
|
+
// lastMatch was invalidated by additional context
|
|
100
|
+
lastMatch = undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (reachedEndOfInput) {
|
|
104
|
+
parserContext.invariant(
|
|
105
|
+
false,
|
|
106
|
+
'Regex did not match at start of input',
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
start += window;
|
|
111
|
+
window *= 2;
|
|
112
|
+
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
lastMatch = match;
|
|
117
|
+
|
|
118
|
+
start += window;
|
|
119
|
+
window *= 2;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
setParserName(regexpParser, regexp.toString());
|
|
124
|
+
|
|
125
|
+
return regexpParser;
|
|
126
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type CodePointRange = {
|
|
2
|
+
start: number;
|
|
3
|
+
end: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type CharacterSet =
|
|
7
|
+
| { type: 'empty' }
|
|
8
|
+
| { type: 'node'; range: CodePointRange; left: CharacterSet; right: CharacterSet };
|
|
9
|
+
|
|
10
|
+
export type RepeatBounds = number | { min: number; max?: number } | { min?: number; max: number };
|
|
11
|
+
|
|
12
|
+
export type RegularExpression =
|
|
13
|
+
| { type: 'epsilon' }
|
|
14
|
+
| { type: 'literal'; charset: CharacterSet }
|
|
15
|
+
| { type: 'concat'; left: RegularExpression; right: RegularExpression }
|
|
16
|
+
| { type: 'union'; left: RegularExpression; right: RegularExpression }
|
|
17
|
+
| { type: 'star'; inner: RegularExpression }
|
|
18
|
+
| { type: 'plus'; inner: RegularExpression }
|
|
19
|
+
| { type: 'optional'; inner: RegularExpression }
|
|
20
|
+
| { type: 'repeat'; inner: RegularExpression; bounds: RepeatBounds }
|
|
21
|
+
| { type: 'capture-group'; inner: RegularExpression; name?: string }
|
|
22
|
+
| { type: 'lookahead'; isPositive: boolean; inner: RegularExpression; right: RegularExpression }
|
|
23
|
+
| { type: 'start-anchor'; left: RegularExpression; right: RegularExpression }
|
|
24
|
+
| { type: 'end-anchor'; left: RegularExpression; right: RegularExpression };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { testProp, fc } from '@fast-check/ava';
|
|
2
|
+
import { regularExpressionParser } from './regularExpressionParser.js';
|
|
3
|
+
|
|
4
|
+
const seed = process.env.SEED ? Number(process.env.SEED) : undefined;
|
|
5
|
+
|
|
6
|
+
// Import directly from file path to bypass package exports
|
|
7
|
+
// eslint-disable-next-line import/no-unresolved
|
|
8
|
+
import { parseRegExpString } from '../node_modules/@gruhn/regex-utils/dist/regex-parser.js';
|
|
9
|
+
import { runParser } from './parser.js';
|
|
10
|
+
import { stringParserInputCompanion } from './parserInputCompanion.js';
|
|
11
|
+
import { arbitrarilySlicedAsyncIterator } from './arbitrarilySlicedAsyncInterator.js';
|
|
12
|
+
import type { RegularExpression, CharacterSet } from './regularExpression.js';
|
|
13
|
+
|
|
14
|
+
// Normalize AST for comparison - removes hashes from CharSets and normalizes structure
|
|
15
|
+
function normalizeCharacterSet(charset: CharacterSet): CharacterSet {
|
|
16
|
+
if (charset.type === 'empty') {
|
|
17
|
+
return { type: 'empty' };
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
type: 'node',
|
|
21
|
+
range: { start: charset.range.start, end: charset.range.end },
|
|
22
|
+
left: normalizeCharacterSet(charset.left),
|
|
23
|
+
right: normalizeCharacterSet(charset.right),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeRegularExpression(ast: RegularExpression): RegularExpression {
|
|
28
|
+
switch (ast.type) {
|
|
29
|
+
case 'epsilon':
|
|
30
|
+
return { type: 'epsilon' };
|
|
31
|
+
case 'literal':
|
|
32
|
+
return { type: 'literal', charset: normalizeCharacterSet(ast.charset) };
|
|
33
|
+
case 'concat':
|
|
34
|
+
return { type: 'concat', left: normalizeRegularExpression(ast.left), right: normalizeRegularExpression(ast.right) };
|
|
35
|
+
case 'union':
|
|
36
|
+
return { type: 'union', left: normalizeRegularExpression(ast.left), right: normalizeRegularExpression(ast.right) };
|
|
37
|
+
case 'star':
|
|
38
|
+
return { type: 'star', inner: normalizeRegularExpression(ast.inner) };
|
|
39
|
+
case 'plus':
|
|
40
|
+
return { type: 'plus', inner: normalizeRegularExpression(ast.inner) };
|
|
41
|
+
case 'optional':
|
|
42
|
+
return { type: 'optional', inner: normalizeRegularExpression(ast.inner) };
|
|
43
|
+
case 'repeat':
|
|
44
|
+
return { type: 'repeat', inner: normalizeRegularExpression(ast.inner), bounds: ast.bounds };
|
|
45
|
+
case 'capture-group':
|
|
46
|
+
if (ast.name !== undefined) {
|
|
47
|
+
return { type: 'capture-group', inner: normalizeRegularExpression(ast.inner), name: ast.name };
|
|
48
|
+
}
|
|
49
|
+
return { type: 'capture-group', inner: normalizeRegularExpression(ast.inner) };
|
|
50
|
+
case 'lookahead':
|
|
51
|
+
return { type: 'lookahead', isPositive: ast.isPositive, inner: normalizeRegularExpression(ast.inner), right: normalizeRegularExpression(ast.right) };
|
|
52
|
+
case 'start-anchor':
|
|
53
|
+
return { type: 'start-anchor', left: normalizeRegularExpression(ast.left), right: normalizeRegularExpression(ast.right) };
|
|
54
|
+
case 'end-anchor':
|
|
55
|
+
return { type: 'end-anchor', left: normalizeRegularExpression(ast.left), right: normalizeRegularExpression(ast.right) };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Generate regex patterns that are likely to be supported
|
|
60
|
+
const supportedRegexArbitrary = fc.stringMatching(
|
|
61
|
+
/^([a-zA-Z0-9]|\\[dDwWsS.]|\[(\^)?([a-zA-Z0-9](-[a-zA-Z0-9])?|\\[dDwWsS])*\]|\.|\((\?[:=!])?[a-zA-Z0-9]*\)|[*+?]|\{[0-9]+(,[0-9]*)?\}|\||\^|\$)*$/,
|
|
62
|
+
).filter(s => {
|
|
63
|
+
// Filter out patterns that JavaScript doesn't support
|
|
64
|
+
try {
|
|
65
|
+
new RegExp(s);
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
// Filter out patterns that @gruhn/regex-utils doesn't support
|
|
70
|
+
try {
|
|
71
|
+
parseRegExpString(s);
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
// Filter out quantified lookaheads - @gruhn/regex-utils has a bug where it treats
|
|
76
|
+
// quantifiers after lookaheads as literals instead of quantifiers.
|
|
77
|
+
// See: https://github.com/gruhn/regex-utils/issues/13
|
|
78
|
+
// JavaScript allows (?=a){2} but @gruhn/regex-utils parses {2} as literal text.
|
|
79
|
+
if (/\(\?[=!][^)]*\)[*+?]|\(\?[=!][^)]*\)\{[0-9]/.test(s)) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
testProp(
|
|
86
|
+
'regularExpressionParser matches @gruhn/regex-utils',
|
|
87
|
+
[
|
|
88
|
+
arbitrarilySlicedAsyncIterator(supportedRegexArbitrary),
|
|
89
|
+
],
|
|
90
|
+
async (t, [regexStr, regexStringChunkIterator]) => {
|
|
91
|
+
const expected = normalizeRegularExpression(parseRegExpString(regexStr));
|
|
92
|
+
const actual = normalizeRegularExpression(await runParser(regularExpressionParser, regexStringChunkIterator, stringParserInputCompanion, {
|
|
93
|
+
errorJoinMode: 'none',
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
t.deepEqual(actual, expected);
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
verbose: true,
|
|
100
|
+
seed,
|
|
101
|
+
},
|
|
102
|
+
);
|