Skip to content

Commit

Permalink
Scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
slevithan committed Nov 2, 2024
1 parent 89eb584 commit 3252eb3
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 120 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"build": "npm run bundle:global && npm run bundle:esm && npm run types",
"pretest": "npm run build",
"test": "jasmine",
"compare": "node spec/compare-oniguruma.js",
"onig:compare": "node scripts/onig-compare.js",
"onig:match": "node scripts/onig-match.js",
"prepare": "npm test"
},
"files": [
Expand Down
49 changes: 49 additions & 0 deletions scripts/onig-compare.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {r} from '../src/utils.js';
import {ansi, areMatchDetailsEqual, err, ok, onigurumaResult, transpiledRegExpResult} from './utils.js';

// Help with improving this script or moving it into Jasmine specs would be very welcome

compare([
[r`\x7F`, '\x7F'],
[r`\x80`, '\x80'],
[r`\x`, '\\x'],
[r`\p{`, '\\p{'],
[r`\O`, '\n'],
[r`\u{A0}`, '\u{A0}\\u{A0}']
]);

async function compare(tests) {
let numSame = 0;
let numDiff = 0;
for (let i = 0; i < tests.length; i++) {
const [pattern, str] = tests[i];
const lib = transpiledRegExpResult(pattern, str);
const onig = await onigurumaResult(pattern, str);
const searched = `/${pattern}/ with str "${esc(str)}" (len ${str.length})`;
if (areMatchDetailsEqual(lib, onig)) {
numSame++;
ok(i, `Results matched for ${searched}${lib.error ? ` ${ansi.yellow}(both errored)${ansi.reset}` : ''}`);
continue;
}
numDiff++;
if (lib.error) {
err(i, `Only lib errored for ${searched}`);
} else if (onig.error) {
err(i, `Only onig errored for ${searched}`);
} else if (lib.result !== onig.result) {
err(i, `Results differed for ${searched}: lib: ${lib.result && `"${esc(lib.result)}"`}, onig: ${onig.result && `"${esc(onig.result)}"`}`);
} else if (lib.index !== onig.index) {
err(i, `Match positions differed for ${searched}: lib: ${lib.index}, onig: ${onig.index}`);
}
}
numSame &&= `${ansi.green}${numSame}${ansi.reset}`;
numDiff &&= `${ansi.red}${numDiff}${ansi.reset}`;
console.log(`\nFinished: ${numSame} same, ${numDiff} different`);
}

function esc(str) {
return str.
replace(/\n/g, '\\n').
replace(/\r/g, '\\r').
replace(/\0/g, '\\0');
}
51 changes: 51 additions & 0 deletions scripts/onig-match.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {areMatchDetailsEqual, err, ok, onigurumaResult, transpiledRegExpResult} from "./utils.js";

exec(process.argv.slice(2));

// Basic Oniguruma console-based tester that also does a comparison with Oniguruma-to-ES results
async function exec([pattern, str]) {
if (!(typeof pattern === 'string' && typeof str === 'string')) {
err(null, 'pattern and str args expected');
return;
}

const libMatches = [];
let libMatch = transpiledRegExpResult(pattern, str, 0);
while (libMatch.result) {
libMatches.push(libMatch);
libMatch = transpiledRegExpResult(pattern, str, libMatch.index + libMatch.result.length);
}
const onigMatches = [];
let onigMatch = await onigurumaResult(pattern, str, 0);
while (onigMatch.result) {
onigMatches.push(onigMatch);
onigMatch = await onigurumaResult(pattern, str, onigMatch.index + onigMatch.result.length);
}

console.log('Pattern:', pattern);
console.log('String:', str);
if (onigMatch.error) {
err(null, `Oniguruma error: ${onigMatch.error.message}`);
} else {
console.log('Oniguruma results:', onigMatches);
}
if (!!libMatch.error !== !!onigMatch.error) {
err(null, `Oniguruma and library results differed (only ${libMatch.error ? 'library' : 'Oniguruma'} threw error)`);
} else if (libMatches.length !== onigMatches.length) {
err(null, `Oniguruma and library had different number of results (${onigMatches.length}, ${libMatches.length})`);
} else {
let hasDiff = false;
for (let i = 0; i < libMatches.length; i++) {
if (!areMatchDetailsEqual(libMatches[i], onigMatches[i])) {
hasDiff = true;
break;
}
}
if (hasDiff) {
err(null, 'Oniguruma and library results differed');
console.log('Library results:', libMatches);
} else {
ok(null, 'Oniguruma and library results matched');
}
}
}
127 changes: 127 additions & 0 deletions scripts/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import {toRegExp} from '../dist/index.mjs';
import {readFileSync} from 'node:fs';
// vscode-oniguruma 2.0.1 uses Oniguruma 6.9.8
import oniguruma from 'vscode-oniguruma';

const ansi = {
green: '\x1b[32m',
red: '\x1b[31m',
reset: '\x1b[0m',
yellow: '\x1b[33m',
};

function ok(i, msg) {
console.log(`${i ? ` ${i}. ` : ''}${ansi.green}${ansi.reset} ${msg}`);
}

function err(i, msg) {
console.log(`${i ? ` ${i}. ` : ''}${ansi.red}${msg}${ansi.reset}`);
}

/**
@typedef {{
result: string | null;
index: number | null;
error?: Error;
}} MatchDetails
*/
/**
@template [T=MatchDetails]
@typedef MatchDetailsFn
@type {{
(pattern: string, str: string, pos?: number): T;
}}
*/
/**
@param {RegExpExecArray | null | Error} match
@returns {MatchDetails}
*/
function getMatchDetails(match) {
if (!match) {
return {
result: null,
index: null,
}
}
if (match instanceof Error) {
return {
result: null,
index: null,
error: match,
};
}
return {
result: match[0],
index: match.index,
};
}

/**
@type {MatchDetailsFn<Promise<MatchDetails>>}
*/
const onigurumaResult = async (pattern, str, pos) => {
let result;
try {
result = await onigurumaExec(pattern, str, pos);
} catch (err) {
result = err;
}
return getMatchDetails(result);
};

/**
@type {MatchDetailsFn}
*/
const transpiledRegExpResult = (pattern, str, pos) => {
let result;
try {
const options = pos ? {global: true} : undefined;
const re = toRegExp(pattern, '', options);
if (pos) {
re.lastIndex = pos;
}
result = re.exec(str);
} catch (err) {
result = err;
}
return getMatchDetails(result);
};

async function onigurumaExec(pattern, str, pos = 0) {
await loadOniguruma();
// See https://github.com/microsoft/vscode-oniguruma/blob/main/main.d.ts
const re = new oniguruma.OnigScanner([pattern]);
const match = re.findNextMatchSync(str, pos);
if (!match) {
return null;
}
const m = match.captureIndices[0];
return {
'0': str.slice(m.start, m.end),
index: m.start,
};
}

async function loadOniguruma() {
const wasmPath = `${import.meta.dirname}/../node_modules/vscode-oniguruma/release/onig.wasm`;
const wasmBin = readFileSync(wasmPath).buffer;
await oniguruma.loadWASM(wasmBin);
}

/**
@param {MatchDetails} a
@param {MatchDetails} b
@returns {boolean}
*/
function areMatchDetailsEqual(a, b) {
return !(a.index !== b.index || a.result !== b.result || !!a.error !== !!b.error);
}

export {
ansi,
areMatchDetailsEqual,
err,
ok,
onigurumaResult,
transpiledRegExpResult,
};
119 changes: 0 additions & 119 deletions spec/compare-oniguruma.js

This file was deleted.

0 comments on commit 3252eb3

Please sign in to comment.