@@ -316,7 +316,8 @@ <h4>Supported syntax:</h4>
316316
317317 < h4 > Unsupported syntax:</ h4 >
318318 < 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 >
320321 < li > Unicode property escapes: < code > \p{...}</ code > , < code > \P{...}</ code > </ li >
321322 < li > Backreferences: < code > \1</ code > , < code > \2</ code > , ...</ li >
322323 < li > Lookbehind assertions: < code > (?<=...)</ code > , < code > (?<!...)</ code > </ li >
@@ -339,8 +340,8 @@ <h4>Powered by:</h4>
339340 CacheOverflowError
340341 } from './dist/index.js' ;
341342
342- document . addEventListener ( 'DOMContentLoaded' , ( ) => {
343-
343+ document . addEventListener ( 'DOMContentLoaded' , ( ) => {
344+
344345 const regex1Input = document . getElementById ( 'regex1' ) ;
345346 const regex2Input = document . getElementById ( 'regex2' ) ;
346347 const checkEquivButton = document . getElementById ( 'check-equiv-button' ) ;
@@ -354,7 +355,15 @@ <h4>Powered by:</h4>
354355 counterexamples1 . style . display = 'none' ;
355356 counterexamples1 . innerHTML = '' ;
356357 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+ }
358367 }
359368
360369 // clear results on new input:
@@ -386,7 +395,7 @@ <h4>Powered by:</h4>
386395 // Compute differences between the two regex
387396 const diffAB = RB ( regexA ) . without ( regexB ) ; // strings in A but not B
388397 const diffBA = RB ( regexB ) . without ( regexA ) ; // strings in B but not A
389-
398+
390399 const diffABEmpty = diffAB . isEmpty ( ) ;
391400 const diffBAEmpty = diffBA . isEmpty ( ) ;
392401
@@ -396,17 +405,21 @@ <h4>Powered by:</h4>
396405 } else if ( diffABEmpty && ! diffBAEmpty ) {
397406 // A is subset of B: no strings in A that aren't in B
398407 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 ;
400410 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 ) ;
401411 } else if ( ! diffABEmpty && diffBAEmpty ) {
402412 // B is subset of A: no strings in B that aren't in A
403413 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 ;
405416 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 ) ;
406417 } else {
407418 // Neither subset nor equivalent: both differences are non-empty
408419 const stringsMatchingAButNotB = Array . from ( diffAB . enumerate ( ) . take ( 5 ) ) ;
420+ assertMatchRegex ( regexA , stringsMatchingAButNotB )
409421 const stringsMatchingBButNotA = Array . from ( diffBA . enumerate ( ) . take ( 5 ) ) ;
422+ assertMatchRegex ( regexB , stringsMatchingBButNotA )
410423 const examples = {
411424 regex1Only : stringsMatchingAButNotB . length > 0 ? stringsMatchingAButNotB : null ,
412425 regex2Only : stringsMatchingBButNotA . length > 0 ? stringsMatchingBButNotA : null
@@ -449,82 +462,72 @@ <h4>Powered by:</h4>
449462
450463 function showResultWithDiagram ( message , type , examples = null ) {
451464 const resultDiv = document . getElementById ( 'result' ) ;
452-
465+
453466 // Build the complete HTML structure
454467 let html = message ;
455-
468+
456469 // Add Venn diagram (only for subset/superset/not-equivalent cases)
457470 if ( type !== 'equivalent' ) {
458471 html += '<div class="venn-container"></div>' ;
459472 }
460-
473+
461474 // Add examples if provided
462475 if ( examples ) {
463476 html += '<p>For example, '
464477
465478 if ( examples . regex1Only && examples . regex1Only . length > 0 ) {
466479 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 ( ', ' ) ;
468481 html += ' but RegExp 2 does not. ' ;
469482 }
470-
483+
471484 if ( examples . regex2Only && examples . regex2Only . length > 0 ) {
472485 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 ( ', ' ) ;
474487 html += 'but RegExp 1 does not. ' ;
475488 }
476489
477490 html += '</p>'
478491 }
479-
492+
480493 resultDiv . innerHTML = html ;
481-
494+
482495 // Show/hide the appropriate hardcoded SVG
483496 if ( type !== 'equivalent' ) {
484497 const vennContainer = resultDiv . querySelector ( '.venn-container' ) ;
485498 let targetDiagram ;
486-
499+
487500 if ( type === 'subset' ) {
488501 targetDiagram = document . querySelector ( '.venn-diagram-subset' ) ;
489502 } else if ( type === 'superset' ) {
490503 targetDiagram = document . querySelector ( '.venn-diagram-superset' ) ;
491504 } else if ( type === 'not-equivalent' ) {
492505 targetDiagram = document . querySelector ( '.venn-diagram-intersection' ) ;
493506 }
494-
507+
495508 if ( targetDiagram ) {
496509 const clonedDiagram = targetDiagram . cloneNode ( true ) ;
497510 clonedDiagram . style . display = 'block' ;
498511 vennContainer . appendChild ( clonedDiagram ) ;
499512 }
500513 }
501-
514+
502515 resultDiv . className = `result ${ type } ` ;
503516 resultDiv . style . display = 'block' ;
504517 }
505518
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>
519523
520- function escapeHtml ( text ) {
521524 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>
524527 }
525528
526529 } )
527530 </ script >
528531</ body >
529532
530- </ html >
533+ </ html >
0 commit comments