ribbit/test/disambiguate.ts

150 lines
4.5 KiB
TypeScript

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();