@@ -316,7 +316,8 @@ <h4>Supported syntax:</h4>
316
316
317
317
< h4 > Unsupported syntax:</ h4 >
318
318
< ul >
319
- < li > RegExp flags: < code > g</ code > , < code > i</ code > , < code > m</ code > , < code > s</ code > , < code > u</ code > , < code > y</ code > </ li >
319
+ < li > RegExp flags: < code > g</ code > , < code > i</ code > , < code > m</ code > , < code > s</ code > , < code > u</ code > , < code > y</ code >
320
+ </ li >
320
321
< li > Unicode property escapes: < code > \p{...}</ code > , < code > \P{...}</ code > </ li >
321
322
< li > Backreferences: < code > \1</ code > , < code > \2</ code > , ...</ li >
322
323
< li > Lookbehind assertions: < code > (?<=...)</ code > , < code > (?<!...)</ code > </ li >
@@ -339,8 +340,8 @@ <h4>Powered by:</h4>
339
340
CacheOverflowError
340
341
} from './dist/index.js' ;
341
342
342
- document . addEventListener ( 'DOMContentLoaded' , ( ) => {
343
-
343
+ document . addEventListener ( 'DOMContentLoaded' , ( ) => {
344
+
344
345
const regex1Input = document . getElementById ( 'regex1' ) ;
345
346
const regex2Input = document . getElementById ( 'regex2' ) ;
346
347
const checkEquivButton = document . getElementById ( 'check-equiv-button' ) ;
@@ -354,7 +355,15 @@ <h4>Powered by:</h4>
354
355
counterexamples1 . style . display = 'none' ;
355
356
counterexamples1 . innerHTML = '' ;
356
357
counterexamples2 . style . display = 'none' ;
357
- counterexamples2 . innerHTML = '' ;
358
+ counterexamples2 . innerHTML = '' ;
359
+ }
360
+
361
+ function assertMatchRegex ( regex , strings ) {
362
+ for ( const str of strings ) {
363
+ if ( ! regex . test ( str ) ) {
364
+ throw new Error ( 'logic error: generated example strings did not match input regex' ) ;
365
+ }
366
+ }
358
367
}
359
368
360
369
// clear results on new input:
@@ -386,7 +395,7 @@ <h4>Powered by:</h4>
386
395
// Compute differences between the two regex
387
396
const diffAB = RB ( regexA ) . without ( regexB ) ; // strings in A but not B
388
397
const diffBA = RB ( regexB ) . without ( regexA ) ; // strings in B but not A
389
-
398
+
390
399
const diffABEmpty = diffAB . isEmpty ( ) ;
391
400
const diffBAEmpty = diffBA . isEmpty ( ) ;
392
401
@@ -396,17 +405,21 @@ <h4>Powered by:</h4>
396
405
} else if ( diffABEmpty && ! diffBAEmpty ) {
397
406
// A is subset of B: no strings in A that aren't in B
398
407
const stringsMatchingBButNotA = Array . from ( diffBA . enumerate ( ) . take ( 5 ) ) ;
399
- const examples = stringsMatchingBButNotA . length > 0 ? { regex2Only : stringsMatchingBButNotA } : null ;
408
+ assertMatchRegex ( regexB , stringsMatchingBButNotA )
409
+ const examples = stringsMatchingBButNotA . length > 0 ? { regex2Only : stringsMatchingBButNotA } : null ;
400
410
showResultWithDiagram ( '📊 RegExp 1 matches a subset of RegExp 2. Every string matched by RegExp 1 is also matched by RegExp 2, but not vice versa.' , 'subset' , examples ) ;
401
411
} else if ( ! diffABEmpty && diffBAEmpty ) {
402
412
// B is subset of A: no strings in B that aren't in A
403
413
const stringsMatchingAButNotB = Array . from ( diffAB . enumerate ( ) . take ( 5 ) ) ;
404
- const examples = stringsMatchingAButNotB . length > 0 ? { regex1Only : stringsMatchingAButNotB } : null ;
414
+ assertMatchRegex ( regexA , stringsMatchingAButNotB )
415
+ const examples = stringsMatchingAButNotB . length > 0 ? { regex1Only : stringsMatchingAButNotB } : null ;
405
416
showResultWithDiagram ( '📊 RegExp 1 matches a superset of RegExp 2. Every string matched by RegExp 2 is also matched by RegExp 1, but not vice versa.' , 'superset' , examples ) ;
406
417
} else {
407
418
// Neither subset nor equivalent: both differences are non-empty
408
419
const stringsMatchingAButNotB = Array . from ( diffAB . enumerate ( ) . take ( 5 ) ) ;
420
+ assertMatchRegex ( regexA , stringsMatchingAButNotB )
409
421
const stringsMatchingBButNotA = Array . from ( diffBA . enumerate ( ) . take ( 5 ) ) ;
422
+ assertMatchRegex ( regexB , stringsMatchingBButNotA )
410
423
const examples = {
411
424
regex1Only : stringsMatchingAButNotB . length > 0 ? stringsMatchingAButNotB : null ,
412
425
regex2Only : stringsMatchingBButNotA . length > 0 ? stringsMatchingBButNotA : null
@@ -449,82 +462,72 @@ <h4>Powered by:</h4>
449
462
450
463
function showResultWithDiagram ( message , type , examples = null ) {
451
464
const resultDiv = document . getElementById ( 'result' ) ;
452
-
465
+
453
466
// Build the complete HTML structure
454
467
let html = message ;
455
-
468
+
456
469
// Add Venn diagram (only for subset/superset/not-equivalent cases)
457
470
if ( type !== 'equivalent' ) {
458
471
html += '<div class="venn-container"></div>' ;
459
472
}
460
-
473
+
461
474
// Add examples if provided
462
475
if ( examples ) {
463
476
html += '<p>For example, '
464
477
465
478
if ( examples . regex1Only && examples . regex1Only . length > 0 ) {
466
479
html += `RegExp 1 matches ` ;
467
- html += examples . regex1Only . map ( str => `<code>${ JSON . stringify ( str ) } </code>` ) . join ( ', ' ) ;
480
+ html += examples . regex1Only . map ( str => `<code>${ encode ( str ) } </code>` ) . join ( ', ' ) ;
468
481
html += ' but RegExp 2 does not. ' ;
469
482
}
470
-
483
+
471
484
if ( examples . regex2Only && examples . regex2Only . length > 0 ) {
472
485
html += `RegExp 2 matches ` ;
473
- html += examples . regex2Only . map ( str => `<code>${ JSON . stringify ( str ) } </code>` ) . join ( ', ' ) ;
486
+ html += examples . regex2Only . map ( str => `<code>${ encode ( str ) } </code>` ) . join ( ', ' ) ;
474
487
html += 'but RegExp 1 does not. ' ;
475
488
}
476
489
477
490
html += '</p>'
478
491
}
479
-
492
+
480
493
resultDiv . innerHTML = html ;
481
-
494
+
482
495
// Show/hide the appropriate hardcoded SVG
483
496
if ( type !== 'equivalent' ) {
484
497
const vennContainer = resultDiv . querySelector ( '.venn-container' ) ;
485
498
let targetDiagram ;
486
-
499
+
487
500
if ( type === 'subset' ) {
488
501
targetDiagram = document . querySelector ( '.venn-diagram-subset' ) ;
489
502
} else if ( type === 'superset' ) {
490
503
targetDiagram = document . querySelector ( '.venn-diagram-superset' ) ;
491
504
} else if ( type === 'not-equivalent' ) {
492
505
targetDiagram = document . querySelector ( '.venn-diagram-intersection' ) ;
493
506
}
494
-
507
+
495
508
if ( targetDiagram ) {
496
509
const clonedDiagram = targetDiagram . cloneNode ( true ) ;
497
510
clonedDiagram . style . display = 'block' ;
498
511
vennContainer . appendChild ( clonedDiagram ) ;
499
512
}
500
513
}
501
-
514
+
502
515
resultDiv . className = `result ${ type } ` ;
503
516
resultDiv . style . display = 'block' ;
504
517
}
505
518
506
- function showCounterexamples ( container , title , examples ) {
507
- let html = '<div class="counterexamples">' ;
508
- html += `<h4>${ title } </h4>` ;
509
- html += '<div class="examples-list">' ;
510
- examples . forEach ( str => {
511
- // const displayStr = str === '' ? '(empty string)' : str;
512
- html += `<code>${ JSON . stringify ( str ) } </code> ` ;
513
- } ) ;
514
- html += '</div></div>' ;
515
-
516
- container . innerHTML = html ;
517
- container . style . display = 'block' ;
518
- }
519
+ function encode ( text ) { // <code id="3">\u0000</code>
520
+ const explicitUnicode = JSON . stringify ( text ) // "<code id=\"3\">\\u0000</code>"
521
+ . slice ( 1 , - 1 ) // <code id=\"3\">\\u0000</code>
522
+ . replaceAll ( '\\"' , '"' ) // <code id="3">\\u0000</code>
519
523
520
- function escapeHtml ( text ) {
521
524
const div = document . createElement ( 'div' ) ;
522
- div . textContent = text ;
523
- return div . innerHTML ;
525
+ div . textContent = explicitUnicode ;
526
+ return div . innerHTML ; // <code id="3">\\u0000</code>
524
527
}
525
528
526
529
} )
527
530
</ script >
528
531
</ body >
529
532
530
- </ html >
533
+ </ html >
0 commit comments