Multi-pass macro expansion

This commit is contained in:
naskya 2023-03-27 02:43:21 +09:00
parent fba94d0ad5
commit 68f92d8c27
No known key found for this signature in database
GPG Key ID: 164DFF24E2D40139
2 changed files with 40 additions and 24 deletions

View File

@ -125,9 +125,10 @@ export function parseKaTeXMacros(src: string): string {
return JSON.stringify(result); return JSON.stringify(result);
} }
export function expandKaTeXMacro(src: string, macrosAsJsonString: string): string { // returns [expanded text, whether something is expanded, how many times we can expand more]
const macros = JSON.parse(macrosAsJsonString); // the boolean value is used for multi-pass expansions (macros can expand to other macros)
function expandKaTeXMacroOnce(src: string, macros: { [name: string]: KaTeXMacro }, maxNumberOfExpansions: number)
: [string, boolean, number] {
const bracketKinds = 3; const bracketKinds = 3;
const openBracketId: { [bracket: string]: number } = {"(": 0, "{": 1, "[": 2}; const openBracketId: { [bracket: string]: number } = {"(": 0, "{": 1, "[": 2};
const closeBracketId: { [bracket: string]: number } = {")": 0, "}": 1, "]": 2}; const closeBracketId: { [bracket: string]: number } = {")": 0, "}": 1, "]": 2};
@ -186,20 +187,17 @@ export function expandKaTeXMacro(src: string, macrosAsJsonString: string): strin
return result; return result;
} }
let numberOfExpansions = 0;
const maxNumberOfExpansions = 200; // to prevent infinite expansion loop
// only expand src.slice(beginPos, endPos) // only expand src.slice(beginPos, endPos)
function expandKaTeXMacroImpl(beginPos: number, endPos: number): string { function expandKaTeXMacroImpl(beginPos: number, endPos: number): [string, boolean] {
if (endPos <= beginPos) if (endPos <= beginPos)
return ""; return ["", false];
const raw: string = src.slice(beginPos, endPos); const raw: string = src.slice(beginPos, endPos);
const fallback: string = raw; // returned for invalid inputs const fallback: string = raw; // returned for invalid inputs or too many expansions
if (maxNumberOfExpansions <= numberOfExpansions) if (maxNumberOfExpansions <= 0)
return fallback; return [fallback, false];
++numberOfExpansions; --maxNumberOfExpansions;
// search for a custom macro // search for a custom macro
let checkedPos = beginPos - 1; let checkedPos = beginPos - 1;
@ -219,11 +217,14 @@ export function expandKaTeXMacro(src: string, macrosAsJsonString: string): strin
// there is no macro to expand // there is no macro to expand
if (checkedPos === -1) if (checkedPos === -1)
return raw; return [raw, false];
// is it a custom macro? // is it a custom macro?
let nonAlphaPos = src.slice(checkedPos + 1).search(/[^A-Za-z]/) + checkedPos + 1; let nonAlphaPos = src.slice(checkedPos + 1).search(/[^A-Za-z]/) + checkedPos + 1;
if (nonAlphaPos === checkedPos)
nonAlphaPos = endPos;
let macroNameCandidate = src.slice(checkedPos + 1, nonAlphaPos); let macroNameCandidate = src.slice(checkedPos + 1, nonAlphaPos);
if (macros.hasOwnProperty(macroNameCandidate)) { if (macros.hasOwnProperty(macroNameCandidate)) {
// this is a custom macro without args // this is a custom macro without args
@ -241,7 +242,7 @@ export function expandKaTeXMacro(src: string, macrosAsJsonString: string): strin
} }
if (nextOpenBracketPos === endPos) if (nextOpenBracketPos === endPos)
return fallback; // there is no open bracket return [fallback, false]; // there is no open bracket
macroNameCandidate += src[nextOpenBracketPos]; macroNameCandidate += src[nextOpenBracketPos];
@ -263,18 +264,30 @@ export function expandKaTeXMacro(src: string, macrosAsJsonString: string): strin
// find the first open bracket after what we've searched // find the first open bracket after what we've searched
const nextOpenBracketPos = src.indexOf(openBracket, macroArgEndPos); const nextOpenBracketPos = src.indexOf(openBracket, macroArgEndPos);
if (nextOpenBracketPos === -1) if (nextOpenBracketPos === -1)
return fallback; // not enough arguments are provided return [fallback, false]; // not enough arguments are provided
if (!bracketMapping[nextOpenBracketPos]) if (!bracketMapping[nextOpenBracketPos])
return fallback; // found open bracket doesn't correspond to any close bracket return [fallback, false]; // found open bracket doesn't correspond to any close bracket
macroArgEndPos = bracketMapping[nextOpenBracketPos]; macroArgEndPos = bracketMapping[nextOpenBracketPos];
expandedArgs[i] = expandKaTeXMacroImpl(nextOpenBracketPos + 1, macroArgEndPos); expandedArgs[i] = expandKaTeXMacroImpl(nextOpenBracketPos + 1, macroArgEndPos)[0];
} }
return src.slice(beginPos, macroBackslashPos) return [src.slice(beginPos, macroBackslashPos)
+ expandSingleKaTeXMacro(expandedArgs, macroName) + expandSingleKaTeXMacro(expandedArgs, macroName)
+ expandKaTeXMacroImpl(macroArgEndPos + 1, endPos); + expandKaTeXMacroImpl(macroArgEndPos + 1, endPos)[0], true];
} }
return expandKaTeXMacroImpl(0, src.length); const [expandedText, expandedFlag]: [string, boolean] = expandKaTeXMacroImpl(0, src.length);
return [expandedText, expandedFlag, maxNumberOfExpansions];
}
export function expandKaTeXMacro(src: string, macrosAsJSONString: string, maxNumberOfExpansions: number): string {
const macros = JSON.parse(macrosAsJSONString);
let expandMore = true;
while (expandMore && (0 < maxNumberOfExpansions))
[src, expandMore, maxNumberOfExpansions] = expandKaTeXMacroOnce(src, macros, maxNumberOfExpansions);
return src;
} }

View File

@ -5,11 +5,14 @@ import { expandKaTeXMacro } from "@/scripts/katex-macro";
export function preprocess(text: string): string { export function preprocess(text: string): string {
if (defaultStore.state.enableCustomKaTeXMacro) { if (defaultStore.state.enableCustomKaTeXMacro) {
const parsedKaTeXMacro = localStorage.getItem("customKaTeXMacroParsed") ?? "{}"; const parsedKaTeXMacro = localStorage.getItem("customKaTeXMacroParsed") ?? "{}";
const maxNumberOfExpansions = 200; // to prevent infinite expansion loops
let nodes = mfm.parse(text); let nodes = mfm.parse(text);
for (let node of nodes) { for (let node of nodes) {
if (node["type"] === "mathInline" || node["type"] === "mathBlock") { if (node["type"] === "mathInline" || node["type"] === "mathBlock") {
node["props"]["formula"] = expandKaTeXMacro(node["props"]["formula"], parsedKaTeXMacro); node["props"]["formula"]
= expandKaTeXMacro(node["props"]["formula"], parsedKaTeXMacro, maxNumberOfExpansions);
} }
} }