type TokenIdentifier = { rx: RegExp; parse: (s: string) => Token; search?: (s: string, start: number, end: number) => { start: number; end: number; text: string; lastIndex: number; }; }; export const TokenIdentifiers = new Map< string, TokenIdentifier >(); // TokenIdentifiers.set("p", { // rx: /\n{2,}((?:.|\n)*?)\n{2,}/g, // parse(s) { // const [_, content] = s.match(new RegExp(this.rx, ""))!; // return { // // content, // content, // raw: s, // metadata: {}, // type: "p", // uuid: crypto.randomUUID(), // }; // }, // }); const rendersContentOnly = true; const rendersChildrenOnly = true; TokenIdentifiers.set("grid", { search(s, start, end) { const rx = /(?>/g, parse(s) { const [_, title, content] = s.match(new RegExp(this.rx, ""))!; return { // content, content, raw: s, metadata: { title }, type: "popover", uuid: crypto.randomUUID(), rendersContentOnly, }; }, }); TokenIdentifiers.set("accordion", { rx: /\[accordion(\s.*?)?]\n+((?:.|\n)*?)\n+\[\/accordion\]/g, parse(s) { const [_, title, content] = s.match(new RegExp(this.rx, ""))!; return { // content, content, raw: s, metadata: { title }, type: "accordion", uuid: crypto.randomUUID(), }; }, }); TokenIdentifiers.set("p", { // rx: /(?<=\n)\n?([\s\S]*?)\n(?=\n)/g, rx: /(?<=\n\n)([\s\S]*?)(?=\n\n)/g, parse(s) { // const [_, content] = s.match(new RegExp(this.rx, ""))!; return { // content, content: s, raw: s, metadata: {}, type: "p", uuid: crypto.randomUUID(), }; }, }); // const p = TokenIdentifiers.get("p"); // TokenIdentifiers.clear(); // p && TokenIdentifiers.set("p", p); function findMatchingClosedParenthesis( str: string, openRegex: RegExp, closedRegex: RegExp, ): number | null { let openings = 0; let closings = 0; openRegex = new RegExp(openRegex, "g"); closedRegex = new RegExp(closedRegex, "g"); let lastOpeningSuccessIndex = 0; let lastClosingSuccessIndex = 0; do { const openingMatch = openRegex.exec(str); const closingMatch = closedRegex.exec(str); if ((openingMatch && !closingMatch)) { throw Error("Things have gone horribly wrong"); } // if ((!openingMatch && closingMatch) || (!openingMatch && !closingMatch)) break; if ( openingMatch && closingMatch && openingMatch.index < closingMatch.index ) { openings++; lastOpeningSuccessIndex = openingMatch.index + openingMatch[0].length; closedRegex.lastIndex = lastClosingSuccessIndex; } else if ( (!openingMatch && closingMatch) || (openingMatch && closingMatch && openingMatch.index > closingMatch.index) ) { closings++; lastClosingSuccessIndex = closingMatch.index + closingMatch[0].length; openRegex.lastIndex = lastOpeningSuccessIndex; } else { return closingMatch?.index ?? null; } } while (openings > closings); return closedRegex.lastIndex; } interface SearchResult { start: number; end: number; text: string; lastIndex: number; } function search( s: string, start: number, end: number, openRx: RegExp, closeRx: RegExp, ): SearchResult { const oldEnd = end; const newEnd = findMatchingClosedParenthesis( s, openRx, closeRx, ); if (newEnd === null) throw Error("BAD BAD BAD BAD"); end = newEnd + start; return { start, end, text: s.substring(0, newEnd), lastIndex: oldEnd === end ? end : start + s.match(openRx)![0].length, }; }