Skip to content

Commit d29d9dd

Browse files
committed
docs(equiv-checker): fix counterexample rendering
1 parent d2955b3 commit d29d9dd

File tree

1 file changed

+38
-35
lines changed

1 file changed

+38
-35
lines changed

equiv-checker.html

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -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>(?&lt;=...)</code>, <code>(?&lt;!...)</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; // &lt;code id="3"&gt;\\u0000&lt;/code&gt;
524527
}
525528

526529
})
527530
</script>
528531
</body>
529532

530-
</html>
533+
</html>

0 commit comments

Comments
 (0)