150 lines
4.5 KiB
TypeScript
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();
|