Skip to content

Commit

Permalink
Demo: Auto-compare to all other targets/accuracies
Browse files Browse the repository at this point in the history
  • Loading branch information
slevithan committed Nov 8, 2024
1 parent 5fe21e8 commit 8a5a19d
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 49 deletions.
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,19 @@ The returned `flags` (as well as the `pattern`, of course) might be different th

If the only keys returned are `pattern` and `flags`, they can optionally be provided to JavaScript's `RegExp` constructor instead. Setting option `avoidSubclass` to `true` ensures that this is always the case, and any patterns that rely on `EmulatedRegExp`'s additional handling for emulation throw an error.

### `toOnigurumaAst`

Generates an Oniguruma AST from an Oniguruma pattern.

```ts
function toOnigurumaAst(
pattern: string,
options?: {
flags?: OnigurumaFlags;
}
): OnigurumaAst;
```

### `EmulatedRegExp`

Works the same as the native JavaScript `RegExp` constructor in all contexts, but can be provided results from `toDetails` to produce the same result as `toRegExp`.
Expand All @@ -135,19 +148,6 @@ class EmulatedRegExp extends RegExp {
};
```

### `toOnigurumaAst`

Generates an Oniguruma AST from an Oniguruma pattern.

```ts
function toOnigurumaAst(
pattern: string,
options?: {
flags?: OnigurumaFlags;
}
): OnigurumaAst;
```

## 🔩 Options

The following options are shared by functions [`toRegExp`](#toregexp) and [`toDetails`](#todetails).
Expand Down Expand Up @@ -620,7 +620,7 @@ Notice that nearly every feature below has at least subtle differences from Java
<td align="middle">✅</td>
<td align="middle">✅</td>
<td>
✔ Same as JS <code>^</code> <code>$</code> without flag <code>m</code><br>
✔ Same as JS <code>^</code> <code>$</code> without JS flag <code>m</code><br>
</td>
</tr>
<tr valign="top">
Expand Down
5 changes: 2 additions & 3 deletions demo/demo.css
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ pre, code, kbd, textarea {
background: repeating-linear-gradient(45deg, #edfff1, #edfff1 3px, #deffd5 3px, #deffd5 10px);
}

#info {
margin-top: -12px;
padding: 0.6em;
.info {
font-size: 0.9em;
}
126 changes: 98 additions & 28 deletions demo/demo.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
const ui = {
input: document.getElementById('input'),
output: document.getElementById('output'),
subclassInfo: document.getElementById('subclass-info'),
alternateInfo: document.getElementById('alternate-info'),
};
const state = {
flags: {
i: getValue('flag-i'),
Expand All @@ -15,44 +21,95 @@ const state = {
},
};

const inputEl = document.getElementById('input');
autoGrow(inputEl);
showOutput(inputEl);
autoGrow();
showTranspiled();

function autoGrow() {
ui.input.style.height = '0';
ui.input.style.height = (ui.input.scrollHeight + 5) + 'px';
}

function showOutput(el) {
const input = el.value;
const flags = `${state.flags.i ? 'i' : ''}${state.flags.m ? 'm' : ''}${state.flags.x ? 'x' : ''}`;
const outputEl = document.getElementById('output');
const infoEl = document.getElementById('info');
outputEl.classList.remove('error', 'subclass');
infoEl.classList.add('hidden');
const opts = {
function showTranspiled() {
ui.output.classList.remove('error', 'subclass');
ui.subclassInfo.classList.add('hidden');
const options = {
...state.opts,
flags,
flags: `${state.flags.i ? 'i' : ''}${state.flags.m ? 'm' : ''}${state.flags.x ? 'x' : ''}`,
maxRecursionDepth: state.opts.maxRecursionDepth === '' ? null : +state.opts.maxRecursionDepth,
};
let output = '';
const errorObj = {error: true};
let details;
let result = '';
try {
// Use `toDetails` but display output as if `toRegExp` was called. This avoids erroring when
// the selected `target` includes features that don't work in the user's browser
const details = OnigurumaToES.toDetails(input, opts);
// Use `toDetails` but display as if `toRegExp` was called. This avoids erroring when the
// selected `target` includes features that don't work in the user's browser
details = OnigurumaToES.toDetails(ui.input.value, options);
if (details.strategy) {
infoEl.classList.remove('hidden');
outputEl.classList.add('subclass');
output = getFormattedSubclass(details.pattern, details.flags, details.strategy);
result = getFormattedSubclass(details.pattern, details.flags, details.strategy);
ui.subclassInfo.classList.remove('hidden');
ui.output.classList.add('subclass');
} else {
output = `/${getRegExpLiteralPattern(details.pattern)}/${details.flags}`;
result = `/${getRegExpLiteralPattern(details.pattern)}/${details.flags}`;
}
} catch (err) {
outputEl.classList.add('error');
output = `Error: ${err.message}`;
details = errorObj;
result = `Error: ${err.message}`;
ui.output.classList.add('error');
}
ui.output.innerHTML = escapeHtml(result);

// ## Compare to all other accuracy/target combinations
const otherTargetAccuracyCombinations = ['ES2018', 'ES2024', 'ESNext'].flatMap(
t => ['loose', 'default', 'strict'].map(a => ({target: t, accuracy: a}))
).filter(c => c.target !== options.target || c.accuracy !== options.accuracy);
const differents = [];
// Collect the different results, including differences in error status
for (const other of otherTargetAccuracyCombinations) {
let otherDetails;
try {
otherDetails = OnigurumaToES.toDetails(ui.input.value, {...options, ...other});
} catch (err) {
otherDetails = errorObj;
} finally {
if (!areDetailsEqual(details, otherDetails)) {
differents.push({
...other,
error: !!otherDetails.error,
});
}
}
}
// Compose and display message about differences or lack thereof
if (differents.length) {
let str = '<p>🔀';
const withError = [];
const withDiff = [];
differents.forEach(d => (d.error ? withError : withDiff).push(d));
if (withError.length) {
str += ` Can't emulate for ${listDifferents(withError)}.`;
}
if (withDiff.length) {
str += ` Emulation uses different details for ${listDifferents(withDiff)}.`;
}
ui.alternateInfo.innerHTML = str;
} else {
ui.alternateInfo.innerHTML = `<p>🟰 Results are the same ${
details.error ? '' : '(apart from flag <code>u</code>/<code>v</code>) '
}with all other targets and accuracies.</p>`;
}
outputEl.innerHTML = escapeHtml(output);
}

function autoGrow(el) {
el.style.height = '0';
el.style.height = (el.scrollHeight + 5) + 'px';
function areDetailsEqual(a, b) {
if (a.error && b.error) {
return true;
}
if (a.error !== b.error) {
return false;
}
return a.pattern === b.pattern &&
a.flags.replace(/[vu]/, '') === b.flags.replace(/[vu]/, '') &&
a.strategy?.name === b.strategy?.name &&
a.strategy?.subpattern === b.strategy?.subpattern;
}

function escapeHtml(str) {
Expand All @@ -76,12 +133,25 @@ function getValue(id) {
return el.type === 'checkbox' ? el.checked : el.value;
}

function listDifferents(arr) {
const target = {};
for (const a of arr) {
target[a.target] ?? (target[a.target] = []);
target[a.target].push(a.accuracy);
}
return Object.keys(target).map(t => {
return `target <code>'${t}'</code> with ${
target[t].length > 1 ? 'accuracies' : 'accuracy'
} <code>'${target[t].join("'</code>/<code>'")}'</code>`;
}).join(', ');
}

function setFlag(flag, value) {
state.flags[flag] = value;
showOutput(inputEl);
showTranspiled();
}

function setOption(option, value) {
state.opts[option] = value;
showOutput(inputEl);
showTranspiled();
}
7 changes: 4 additions & 3 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ <h1>
<p>Use this page to test the output of <a href="https://github.com/slevithan/oniguruma-to-es">Oniguruma-To-ES</a>, an Oniguruma to JavaScript regex transpiler. See <a href="https://github.com/slevithan/oniguruma-to-es#-supported-features">Readme: Supported features</a>.</p>

<h2>Try it</h2>
<p><textarea id="input" spellcheck="false" oninput="autoGrow(this); showOutput(this)"></textarea></p>
<p><textarea id="input" spellcheck="false" oninput="autoGrow(); showTranspiled()"></textarea></p>
<p>
<label><code>flags</code></label>
<label>
Expand Down Expand Up @@ -111,8 +111,9 @@ <h2>Try it</h2>
</section>
</details>
<pre id="output"></pre>
<div id="info" class="hidden"><p>✅ A <code>RegExp</code> subclass instance with a custom execution strategy is returned for this pattern. It remains a native JavaScript regex and works the same as <code>RegExp</code> in all contexts.</p></div>
<p>See <a href="https://github.com/slevithan/oniguruma-to-es#-options">Readme: Options</a> for more detailed explanations of each option. The output shows the result of calling <code>toRegExp</code>. Functions <code>toDetails</code> and <code>toOnigurumaAst</code> can also be run from the console on this page. Pretty-print ASTs by passing them to <code>printAst</code>.</li>
<div id="subclass-info" class="hidden info"><p>✅ A <code>RegExp</code> subclass instance with a custom execution strategy is used for this pattern. It remains a native JavaScript regex.</p></div>
<div id="alternate-info" class="info"><p></p></div>
<p>See <a href="https://github.com/slevithan/oniguruma-to-es#-options">Readme: Options</a> for more detailed explanations of each option. The output shows the result of calling <code>toRegExp</code>. Functions <code>toDetails</code> and <code>toOnigurumaAst</code> can also be run from the console on this page. Pretty-print ASTs by passing them to <code>printAst</code>.</p>
</main>

<!-- Hack to make it work on GitHub Pages where `dist/` isn't checked in; use the latest release's copy -->
Expand Down
2 changes: 1 addition & 1 deletion spec/match-backreference.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ describe('Backreference', () => {
target,
})).toThrow();
});
// Matches same case as group with other `accuracy` values
// Matches only the same case as the reffed case-sensitive group with other `accuracy` values
['default', 'loose'].forEach(accuracy => {
expect('aa').toExactlyMatch({
pattern: r`(a)(?i)\1`,
Expand Down

0 comments on commit 8a5a19d

Please sign in to comment.