wip -- disambiguation works, tests passing

This commit is contained in:
evilchili
2026-07-05 14:09:27 -07:00
parent 9f03721f86
commit f27378dc04
3 changed files with 400 additions and 325 deletions
+149
View File
@@ -0,0 +1,149 @@
function disambiguateAsteriskRuns(line: string): string {
const SENTINEL = '\u200C';
const ASTERISK_RUN = /\*{1,3}/g;
const stack: ('*' | '**')[] = [];
let result = '';
let lastIndex = 0;
let match: RegExpExecArray | null;
console.log(`DISAMBIGUATE: '${line}'`);
while ((match = ASTERISK_RUN.exec(line)) !== null) {
result += line.slice(lastIndex, match.index);
const run = match[0];
console.log(`DISAMBIGUATE: run == '${run}', stack == ${stack}`);
if (run.length === 3 && stack.length === 2) {
const innerCloser = stack.pop()!;
const outerCloser = stack.pop()!;
result += innerCloser + SENTINEL + outerCloser;
} else if (run.length === 3 && stack.length === 0) {
if (match.index == 0) {
result += '***';
} else {
result += '*' + SENTINEL + '**';
stack.push('**');
stack.push('*');
}
} else if (run.length === 3) {
result += run;
} else if (stack.length > 0 && stack[stack.length - 1] === run) {
stack.pop();
result += run;
} else {
stack.push(run as '*' | '**');
result += run;
}
lastIndex = match.index + run.length;
}
result += line.slice(lastIndex);
console.log(`DISAMBIGUATE: '${result}'`);
return result;
}
function assert(condition, message) {
if (!condition) { throw new Error(message); }
}
function rewrite(line: string): string {
const SENTINEL = '|' //'\u200C';
const ASTERISK_RUN = /\*{1,3}/g;
const stack: ('*' | '**' | '***')[] = [];
let result = '';
let lastIndex = 0;
let match: RegExpExecArray | null;
const bold = '**';
const italic = '*';
let lastSequence = '';
while ((match = ASTERISK_RUN.exec(line)) !== null) {
result += line.slice(lastIndex, match.index);
const sequence = match[0];
if (sequence.length === 3) {
// split *** into opening ** and *
if (stack.length === 0) {
result += sequence;
stack.push('***');
// closing ***, close the stack
} else if (stack.length === 1 && stack[0] === sequence) {
result = result.replace('***', bold + SENTINEL + italic);
result += italic + SENTINEL + bold;
stack.pop();
} else if (stack.length === 2) {
const inner = stack.pop();
const outer = stack.pop();
result += inner + SENTINEL + outer;
} else if (stack.length === 1) {
console.warn(`Cannot parsed line '${line}': invalid sequence ${sequence} with stack ${stack}!`);
} else {
console.warn(`UNHANDLED '${sequence}', last sequence '${lastSequence}'`);
}
} else if (stack.length) {
if (stack[stack.length - 1] === sequence) {
result += stack.pop();
} else if (stack.length === 1 && stack[0] === '***') {
const opener = stack[0].substring(0, stack[0].length - sequence.length);
result = result.replace('***', opener + SENTINEL + sequence);
result += sequence;
stack[0] = opener as '*' | '**';
} else {
result += sequence;
stack.push(sequence as '*' | '**');
}
} else {
stack.push(sequence as '*' | '**');
result += sequence;
}
lastIndex = match.index + sequence.length;
}
result += line.slice(lastIndex);
return result;
}
function test_it() {
console.log(rewrite('*italic **bold***'));
let cases = [
{
'input': '***bold-italic***',
'expected': '**|*bold-italic*|**'
},
{
'input': '***bold** italic*',
'expected': '*|**bold** italic*',
},
{
'input': '***italic* bold**',
'expected': '**|*italic* bold**',
},
{
'input': '**bold, *italic***',
'expected': '**bold, *italic*|**'
},
{
'input': '*italic, **bold***',
'expected': '*italic, **bold**|*'
},
];
for (const testCase of cases) {
let result = rewrite(testCase.input);
assert(result == testCase.expected, `'${result}' (${result.length}) != '${testCase.expected}' (${testCase.expected.length})`);
}
};
test_it();
+7 -8
View File
@@ -125,11 +125,7 @@ async function resetEditor() {
*/
async function typeString(text) {
for (const character of text) {
if (character == ' ') {
await page.keyboard.press('Space');
} else {
await page.keyboard.insertText(character);
}
await page.keyboard.insertText(character);
await page.waitForTimeout(DELAY);
}
}
@@ -284,11 +280,12 @@ async function runTests() {
assert(markdown === '*italic*', `Expected "*italic*", got: "${markdown}"`);
});
await test('***bold-italic*** produces md-bold-italic span', async () => {
await test('***bold-italic*** produces md-bold anad md-italic spans', async () => {
await resetEditor();
await typeString('***both***');
const html = await getHTML();
assert(html.includes('md-bold-italic'), `Expected md-bold-italic span: ${html}`);
assert(html.includes('md-bold'), `Expected md-bold-italic span: ${html}`);
assert(html.includes('md-italic'), `Expected md-italic span: ${html}`);
const markdown = await getMarkdown();
assert(markdown === '***both***', `Expected "***both***", got: "${markdown}"`);
});
@@ -327,12 +324,14 @@ async function runTests() {
await typeString(testCase.markdown);
const markdown = await getMarkdown();
const html = await getHTML();
console.log(`MARKDOWN: ${markdown}`);
console.log(`HTML: ${html}`);
assert(
markdown === testCase.markdown,
`Round-trip failed.\nExpected: "${testCase.markdown}"\nGot: "${markdown}"`
);
const html = await getHTML();
const outerIndex = html.indexOf(testCase.outerClass);
const innerIndex = html.indexOf(testCase.innerClass);
assert(