@@ -26,6 +26,9 @@ let counter = 0;
26
26
* @prop {string } [peg$startRuleFunction] In the augmented code only, use this
27
27
* function as the start rule rather than the default. This gives access
28
28
* to functions that are NOT valid start rules for internal testing.
29
+ * @prop {{[ruleName: string]: number} } [peg$failAfter] Number of times for each of the
30
+ * given rules to succeed before they fail. Only applies in the augmented
31
+ * code.
29
32
*/
30
33
31
34
/**
@@ -41,6 +44,9 @@ let counter = 0;
41
44
* @prop {number } [peg$maxFailPos = 0] Expected peg$maxFailPos.
42
45
* @prop {string } [invalid = "\uffff"] What to append to validInput to make it
43
46
* invalid, so that library mode will return a prefix match.
47
+ * @prop {boolean } [only] If any test has this set to true, only run the
48
+ * tests with this set to true.
49
+ * @prop {boolean } [skip] If true, skip this test.
44
50
* @prop {import('peggy').ParserOptions & ExtraParserOptions } [options = {}]
45
51
* Extra options to pass to parse(), overriding whatever else this library
46
52
* would have otherwise used.
@@ -63,28 +69,49 @@ let counter = 0;
63
69
* @param {TestCounts } counts
64
70
*/
65
71
function checkParserStarts ( grammar , starts , modified , counts ) {
72
+ let count = 0 ;
73
+ const M = modified ? "M_" : "U_" ;
74
+ const only = starts . some ( s => s . only ) ;
66
75
for ( const start of starts ) {
76
+ let source = `${ M } valid_${ count ++ } ` ;
67
77
const startRule = start . startRule || undefined ; // NOT `??`
68
78
const peg$maxFailPos = start . peg$maxFailPos ?? undefined ;
69
- const options = start . options ?? { } ;
79
+ const options = start . options ?? Object . create ( null ) ;
70
80
const invalid = start . invalid ?? INVALID ;
71
- if ( ! modified && options . peg$startRuleFunction ) {
81
+ if ( start . skip || ( only && ! start . only ) ) {
72
82
continue ;
73
83
}
84
+ if ( ! modified ) {
85
+ if ( options . peg$startRuleFunction || options . peg$failAfter ) {
86
+ continue ;
87
+ }
88
+ }
74
89
if ( typeof start . validInput === "string" ) {
75
90
// Note: validResult might be specified as undefined.
76
91
const expected = Object . prototype . hasOwnProperty . call ( start , "validResult" )
77
92
? start . validResult
78
93
: start . validInput ;
79
94
80
- let res = grammar . parse ( start . validInput , { startRule, ...options } ) ;
95
+ // eslint-disable-next-line no-useless-assignment
96
+ let res = undefined ;
97
+ try {
98
+ res = grammar . parse ( start . validInput , {
99
+ startRule,
100
+ grammarSource : source ,
101
+ ...options ,
102
+ } ) ;
103
+ } catch ( er ) {
104
+ const e = /** @type {import('peggy').parser.SyntaxError } */ ( er ) ;
105
+ e . message = e . format ( [ { source, text : start . validInput } ] ) ;
106
+ throw e ;
107
+ }
81
108
if ( typeof expected === "string" ) {
82
- equal ( res , expected ) ;
109
+ equal ( res , expected , ` ${ source } (eq): " ${ start . validInput } "` ) ;
83
110
} else if ( typeof expected === "function" ) {
84
111
// @ts -expect-error Can't figure this out
85
112
res = expected ( res ) ;
86
113
} else {
87
- deepEqual ( res , expected ) ;
114
+ deepEqual ( res , expected , ` ${ source } (dp_eq): " ${ start . validInput } "` ) ;
88
115
}
89
116
90
117
const expectedLib = {
@@ -96,37 +123,51 @@ function checkParserStarts(grammar, starts, modified, counts) {
96
123
if ( peg$maxFailPos === undefined ) {
97
124
delete expectedLib . peg$maxFailPos ;
98
125
}
126
+ source = `${ M } valid_library_${ count } ` ;
127
+ // No exception in library mode
99
128
let lib = grammar . parse ( start . validInput , {
100
129
peg$library : true ,
101
130
startRule,
131
+ grammarSource : source ,
102
132
...options ,
103
133
} ) ;
134
+ if ( lib . peg$result === lib . peg$FAILED ) {
135
+ throw new Error ( `Failed in library context: ${ source } ` ) ;
136
+ }
137
+
138
+ // @ts -expect-error This is for testing.
104
139
delete lib . peg$maxFailExpected ;
105
140
if ( peg$maxFailPos === undefined ) {
141
+ // @ts -expect-error This is for testing.
106
142
delete lib . peg$maxFailPos ;
107
143
}
108
144
109
145
if ( typeof expected === "function" ) {
110
146
// @ts -ignore
111
147
lib . peg$result = expected ( lib . peg$result ) ;
112
148
}
113
- deepEqual ( lib , expectedLib ) ;
149
+ deepEqual ( lib , expectedLib , ` ${ source } (dp_eq): " ${ start . validInput } "` ) ;
114
150
115
151
if ( invalid ) {
152
+ source = `${ M } valid+_${ count } ` ;
116
153
lib = grammar . parse ( start . validInput + invalid , {
117
154
peg$library : true ,
118
155
startRule,
156
+ grammarSource : source ,
119
157
...options ,
120
158
} ) ;
159
+
160
+ // @ts -expect-error This is for testing.
121
161
delete lib . peg$maxFailExpected ;
122
162
if ( peg$maxFailPos === undefined ) {
163
+ // @ts -expect-error This is for testing.
123
164
delete lib . peg$maxFailPos ;
124
165
}
125
166
if ( typeof expected === "function" ) {
126
167
// @ts -ignore
127
168
lib . peg$result = expected ( lib . peg$result ) ;
128
169
}
129
- deepEqual ( lib , expectedLib ) ;
170
+ deepEqual ( lib , expectedLib , ` ${ source } (dp_eq): " ${ start . validInput } "` ) ;
130
171
131
172
throws ( ( ) => grammar . parse ( start . validInput + invalid , {
132
173
startRule,
@@ -143,9 +184,11 @@ function checkParserStarts(grammar, starts, modified, counts) {
143
184
startRule,
144
185
...options ,
145
186
} ) ;
146
- fail ( "Cannot reach here " ) ;
187
+ fail ( "Expected error but none received " ) ;
147
188
} catch ( er ) {
148
- ok ( er instanceof grammar . SyntaxError ) ;
189
+ if ( ! ( er instanceof grammar . SyntaxError ) ) {
190
+ throw er ;
191
+ }
149
192
equal ( typeof er . format , "function" ) ;
150
193
let fmt = er . format ( [ { source : "test" , text : start . invalidInput } ] ) ;
151
194
equal ( typeof fmt , "string" ) ;
@@ -186,7 +229,7 @@ function checkParserStarts(grammar, starts, modified, counts) {
186
229
* @typedef {object } TestPeggyOptions
187
230
* @prop {boolean } [noDelete] Do not delete the generated file.
188
231
* @prop {boolean } [noMap] Do not add a sourcemap to the generated file.
189
- * Defaults to true if peggy $debugger is set on any start, otherwise false.
232
+ * Defaults to true if peg $debugger is set on any start, otherwise false.
190
233
* @prop {boolean } [noGenerate] Do not generate a file, only run tests on the
191
234
* original.
192
235
* @prop {boolean } [noOriginal] Do not run tests on the original code, only
@@ -274,13 +317,31 @@ export async function testPeggy(grammarUrl, starts, opts) {
274
317
if ( / ^ \s * p e g \$ r e s u l t = p e g \$ s t a r t R u l e F u n c t i o n \( \) ; / . test ( line ) ) {
275
318
src . add ( `\
276
319
//#region Inserted by @peggyjs/coverage
320
+ let replacements = Object.create(null);
277
321
(() => {
278
322
if (options.peg$debugger) {
279
323
debugger;
280
324
}
281
325
if (options.peg$startRuleFunction) {
282
326
peg$startRuleFunction = eval(options.peg$startRuleFunction); // Ew.
283
327
}
328
+ if (options.peg$failAfter) {
329
+ for (const [name, count] of Object.entries(options.peg$failAfter)) {
330
+ const fn = eval(name); // Ew, again.
331
+ replacements[name] = fn;
332
+ if (typeof fn !== 'function') {
333
+ throw new Error('Unknown function for peg$failAfter: ' + name);
334
+ }
335
+ let remains = count;
336
+ const replace = name + \` = (...args) => {
337
+ if (remains-- > 0) {
338
+ return fn.call(this, ...args)
339
+ }
340
+ return peg$FAILED;
341
+ }\`;
342
+ eval(replace); // We've already called eval twice. What's one more?
343
+ }
344
+ }
284
345
285
346
text();
286
347
offset();
@@ -346,8 +407,18 @@ export async function testPeggy(grammarUrl, starts, opts) {
346
407
//#endregion
347
408
348
409
` ) ;
410
+ src . add ( new SourceNode ( lineNum ++ , 0 , grammarPath , line ) ) ;
411
+ src . add ( `
412
+ //#region Inserted by @peggyjs/coverage
413
+ for (const [name, orig] of Object.entries(replacements)) {
414
+ eval(name + '= orig');
415
+ }
416
+ replacements = Object.create(null);
417
+ //#endregion
418
+ ` ) ;
419
+ } else {
420
+ src . add ( new SourceNode ( lineNum ++ , 0 , grammarPath , line ) ) ;
349
421
}
350
- src . add ( new SourceNode ( lineNum ++ , 0 , grammarPath , line ) ) ;
351
422
}
352
423
353
424
const withMap = src . toStringWithSourceMap ( ) ;
@@ -362,7 +433,6 @@ export async function testPeggy(grammarUrl, starts, opts) {
362
433
${ start } sourceMappingURL=data:application/json;charset=utf-8;base64,${ map }
363
434
` ;
364
435
}
365
-
366
436
await fs . writeFile ( modifiedPath , code ) ;
367
437
try {
368
438
const agrammar = /** @type {Parser } */ (
0 commit comments