152152 color : # 721c24 ;
153153 }
154154
155+ .result .subset {
156+ background : # cce5ff ;
157+ border : 1px solid # 99ccff ;
158+ color : # 003d7a ;
159+ }
160+
161+ .result .superset {
162+ background : # e5ccff ;
163+ border : 1px solid # cc99ff ;
164+ color : # 4d0080 ;
165+ }
166+
167+ .venn-container {
168+ display : flex;
169+ justify-content : center;
170+ margin : 20px 0 ;
171+ }
172+
173+ .venn-diagram {
174+ max-width : 300px ;
175+ width : 100% ;
176+ }
177+
178+ .result code {
179+ background : # e9ecef ;
180+ padding : 2px 6px ;
181+ border-radius : 3px ;
182+ font-family : 'Courier New' , monospace;
183+ margin : 2px ;
184+ display : inline-block;
185+ }
186+
155187 .result .error {
156188 background : # fff3cd ;
157189 border : 1px solid # ffeaa7 ;
@@ -211,7 +243,7 @@ <h1>RegExp Equivalence Checker</h1>
211243
212244 < div class ="input-container ">
213245 < div class ="input-group ">
214- < label for ="regex1 "> Regular Expression 1:</ label >
246+ < label for ="regex1 "> RegExp 1:</ label >
215247 < div class ="regex-input-wrapper ">
216248 < span class ="regex-slash "> /</ span >
217249 < input type ="text " id ="regex1 " placeholder ="^a{1,3}$ " />
@@ -221,7 +253,7 @@ <h1>RegExp Equivalence Checker</h1>
221253 </ div >
222254
223255 < div class ="input-group ">
224- < label for ="regex2 "> Regular Expression 2:</ label >
256+ < label for ="regex2 "> RegExp 2:</ label >
225257 < div class ="regex-input-wrapper ">
226258 < span class ="regex-slash "> /</ span >
227259 < input type ="text " id ="regex2 " placeholder ="^(a|aa|aaa)$ " />
@@ -235,13 +267,35 @@ <h1>RegExp Equivalence Checker</h1>
235267 < button id ="check-equiv-button "> Check Equivalence</ button >
236268 </ div >
237269
238- < div class ="result " id ="result "> </ div >
270+ < svg viewBox ="0 0 300 200 " class ="venn-diagram venn-diagram-superset " style ="display: none; ">
271+ < circle cx ="100 " cy ="100 " r ="60 " stroke ="#667eea " stroke-width ="2 " fill-opacity ="0.3 " fill ="#e9ecef "> </ circle >
272+ < circle cy ="100 " stroke ="#764ba2 " stroke-width ="2 " fill-opacity ="0.3 " fill ="#764ba2 " cx ="120 " r ="30 "> </ circle >
273+ < text x ="70 " y ="80 " font-family ="Arial, sans-serif " font-size ="14 " font-weight ="bold " fill ="#667eea "> 1</ text >
274+ < text font-family ="Arial, sans-serif " font-size ="14 " font-weight ="bold " fill ="#764ba2 " x ="115 " y ="105 "> 2</ text >
275+ </ svg >
276+
277+ < svg viewBox ="0 0 300 200 " class ="venn-diagram venn-diagram-subset " style ="display: none; ">
278+ < circle cx ="80 " cy ="100 " r ="30 " stroke ="#667eea " stroke-width ="2 " fill-opacity ="0.3 " fill ="#667eea "> </ circle >
279+ < circle cx ="100 " cy ="100 " stroke ="#764ba2 " stroke-width ="2 " fill-opacity ="0.3 " fill ="#e9ecef " r ="60 "> </ circle >
280+ < text x ="75 " y ="105 " font-family ="Arial, sans-serif " font-size ="14 " font-weight ="bold " fill ="#667eea "> 1</ text >
281+ < text font-family ="Arial, sans-serif " font-size ="14 " font-weight ="bold " fill ="#764ba2 " x ="120 " y ="80 "> 2</ text >
282+ </ svg >
283+
284+ < svg viewBox ="0 0 300 200 " class ="venn-diagram venn-diagram-intersection " style ="display: none; ">
285+ < circle cx ="80 " cy ="100 " r ="60 " stroke ="#667eea " stroke-width ="2 " fill-opacity ="0.3 " fill ="#667eea "> </ circle >
286+ < circle cx ="140 " cy ="100 " stroke ="#764ba2 " stroke-width ="2 " fill-opacity ="0.3 " fill ="#764ba2 " r ="60 "> </ circle >
287+ < text x ="50 " y ="105 " font-family ="Arial, sans-serif " font-size ="14 " font-weight ="bold " fill ="#667eea "> 1</ text >
288+ < text font-family ="Arial, sans-serif " font-size ="14 " font-weight ="bold " fill ="#764ba2 " x ="150 " y ="80 "> 2</ text >
289+ </ svg >
290+
291+ < div class ="result " id ="result ">
292+
293+ </ div >
239294
240295 < div class ="info ">
241296 < h4 > How to use:</ h4 >
242297 Enter two JavaScript regular expressions (without the surrounding slashes) and click "Check Equivalence".
243- The tool will determine if both expressions match exactly the same set of strings.
244- If they're not equivalent, it will show example strings that demonstrate the difference.
298+ The tool will determine the relationship between the two expressions: equivalent, subset, superset, or neither.
245299
246300 < h4 > Examples:</ h4 >
247301 < ul >
@@ -329,32 +383,35 @@ <h4>Powered by:</h4>
329383 const regexA = new RegExp ( pattern1 ) ;
330384 const regexB = new RegExp ( pattern2 ) ;
331385
332- // Then compute symmetric difference of the two regex:
333- const diffAB = RB ( regexA ) . without ( regexB ) ;
334- const diffBA = RB ( regexB ) . without ( regexA ) ;
335-
336- if ( diffAB . isEmpty ( ) && diffBA . isEmpty ( ) ) {
337- // Equivalent
338- showResult ( '✅ The regular expressions are equivalent!<br>Both expressions match exactly the same set of strings.' , 'equivalent' ) ;
386+ // Compute differences between the two regex
387+ const diffAB = RB ( regexA ) . without ( regexB ) ; // strings in A but not B
388+ const diffBA = RB ( regexB ) . without ( regexA ) ; // strings in B but not A
389+
390+ const diffABEmpty = diffAB . isEmpty ( ) ;
391+ const diffBAEmpty = diffBA . isEmpty ( ) ;
392+
393+ if ( diffABEmpty && diffBAEmpty ) {
394+ // Equivalent: both differences are empty
395+ showResultWithDiagram ( '✅ The regular expressions are equivalent! Both expressions match exactly the same strings.' , 'equivalent' ) ;
396+ } else if ( diffABEmpty && ! diffBAEmpty ) {
397+ // A is subset of B: no strings in A that aren't in B
398+ const stringsMatchingBButNotA = Array . from ( diffBA . enumerate ( ) . take ( 5 ) ) ;
399+ const examples = stringsMatchingBButNotA . length > 0 ? { regex2Only : stringsMatchingBButNotA } : null ;
400+ 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+ } else if ( ! diffABEmpty && diffBAEmpty ) {
402+ // B is subset of A: no strings in B that aren't in A
403+ const stringsMatchingAButNotB = Array . from ( diffAB . enumerate ( ) . take ( 5 ) ) ;
404+ const examples = stringsMatchingAButNotB . length > 0 ? { regex1Only : stringsMatchingAButNotB } : null ;
405+ 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 ) ;
339406 } else {
340- // Not equivalent - show counterexamples under respective text boxes
341- showResult ( '❌ The regular expressions are NOT equivalent.' , 'not-equivalent' ) ;
342-
343- // Show counterexamples under first regex (strings matching first but not second)
344- if ( ! diffAB . isEmpty ( ) ) {
345- const stringsMatchingAButNotB = Array . from ( diffAB . enumerate ( ) . take ( 5 ) ) ;
346- if ( stringsMatchingAButNotB . length > 0 ) {
347- showCounterexamples ( counterexamples1 , 'Matches only left regex:' , stringsMatchingAButNotB ) ;
348- }
349- }
350-
351- // Show counterexamples under second regex (strings matching second but not first)
352- if ( ! diffBA . isEmpty ( ) ) {
353- const stringsMatchingBButNotA = Array . from ( diffBA . enumerate ( ) . take ( 5 ) ) ;
354- if ( stringsMatchingBButNotA . length > 0 ) {
355- showCounterexamples ( counterexamples2 , 'Matches only right regex:' , stringsMatchingBButNotA ) ;
356- }
357- }
407+ // Neither subset nor equivalent: both differences are non-empty
408+ const stringsMatchingAButNotB = Array . from ( diffAB . enumerate ( ) . take ( 5 ) ) ;
409+ const stringsMatchingBButNotA = Array . from ( diffBA . enumerate ( ) . take ( 5 ) ) ;
410+ const examples = {
411+ regex1Only : stringsMatchingAButNotB . length > 0 ? stringsMatchingAButNotB : null ,
412+ regex2Only : stringsMatchingBButNotA . length > 0 ? stringsMatchingBButNotA : null
413+ } ;
414+ showResultWithDiagram ( '❌ The two RegExp are not equivalent. Neither matches a subset of the other.' , 'not-equivalent' , examples ) ;
358415 }
359416 } catch ( error ) {
360417 if ( error instanceof SyntaxError ) {
@@ -390,6 +447,62 @@ <h4>Powered by:</h4>
390447 resultDiv . style . display = 'block' ;
391448 }
392449
450+ function showResultWithDiagram ( message , type , examples = null ) {
451+ const resultDiv = document . getElementById ( 'result' ) ;
452+
453+ // Build the complete HTML structure
454+ let html = message ;
455+
456+ // Add Venn diagram (only for subset/superset/not-equivalent cases)
457+ if ( type !== 'equivalent' ) {
458+ html += '<div class="venn-container"></div>' ;
459+ }
460+
461+ // Add examples if provided
462+ if ( examples ) {
463+ html += '<p>For example, '
464+
465+ if ( examples . regex1Only && examples . regex1Only . length > 0 ) {
466+ html += `RegExp 1 matches ` ;
467+ html += examples . regex1Only . map ( str => `<code>${ JSON . stringify ( str ) } </code>` ) . join ( ', ' ) ;
468+ html += ' but RegExp 2 does not. ' ;
469+ }
470+
471+ if ( examples . regex2Only && examples . regex2Only . length > 0 ) {
472+ html += `RegExp 2 matches ` ;
473+ html += examples . regex2Only . map ( str => `<code>${ JSON . stringify ( str ) } </code>` ) . join ( ', ' ) ;
474+ html += 'but RegExp 1 does not. ' ;
475+ }
476+
477+ html += '</p>'
478+ }
479+
480+ resultDiv . innerHTML = html ;
481+
482+ // Show/hide the appropriate hardcoded SVG
483+ if ( type !== 'equivalent' ) {
484+ const vennContainer = resultDiv . querySelector ( '.venn-container' ) ;
485+ let targetDiagram ;
486+
487+ if ( type === 'subset' ) {
488+ targetDiagram = document . querySelector ( '.venn-diagram-subset' ) ;
489+ } else if ( type === 'superset' ) {
490+ targetDiagram = document . querySelector ( '.venn-diagram-superset' ) ;
491+ } else if ( type === 'not-equivalent' ) {
492+ targetDiagram = document . querySelector ( '.venn-diagram-intersection' ) ;
493+ }
494+
495+ if ( targetDiagram ) {
496+ const clonedDiagram = targetDiagram . cloneNode ( true ) ;
497+ clonedDiagram . style . display = 'block' ;
498+ vennContainer . appendChild ( clonedDiagram ) ;
499+ }
500+ }
501+
502+ resultDiv . className = `result ${ type } ` ;
503+ resultDiv . style . display = 'block' ;
504+ }
505+
393506 function showCounterexamples ( container , title , examples ) {
394507 let html = '<div class="counterexamples">' ;
395508 html += `<h4>${ title } </h4>` ;
@@ -409,6 +522,7 @@ <h4>Powered by:</h4>
409522 div . textContent = text ;
410523 return div . innerHTML ;
411524 }
525+
412526 } )
413527 </ script >
414528</ body >
0 commit comments