Multi-pass macro expansion
This commit is contained in:
parent
fba94d0ad5
commit
68f92d8c27
|
@ -125,9 +125,10 @@ export function parseKaTeXMacros(src: string): string {
|
|||
return JSON.stringify(result);
|
||||
}
|
||||
|
||||
export function expandKaTeXMacro(src: string, macrosAsJsonString: string): string {
|
||||
const macros = JSON.parse(macrosAsJsonString);
|
||||
|
||||
// returns [expanded text, whether something is expanded, how many times we can expand more]
|
||||
// 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 openBracketId: { [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;
|
||||
}
|
||||
|
||||
let numberOfExpansions = 0;
|
||||
const maxNumberOfExpansions = 200; // to prevent infinite expansion loop
|
||||
|
||||
// only expand src.slice(beginPos, endPos)
|
||||
function expandKaTeXMacroImpl(beginPos: number, endPos: number): string {
|
||||
function expandKaTeXMacroImpl(beginPos: number, endPos: number): [string, boolean] {
|
||||
if (endPos <= beginPos)
|
||||
return "";
|
||||
return ["", false];
|
||||
|
||||
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)
|
||||
return fallback;
|
||||
++numberOfExpansions;
|
||||
if (maxNumberOfExpansions <= 0)
|
||||
return [fallback, false];
|
||||
--maxNumberOfExpansions;
|
||||
|
||||
// search for a custom macro
|
||||
let checkedPos = beginPos - 1;
|
||||
|
@ -219,11 +217,14 @@ export function expandKaTeXMacro(src: string, macrosAsJsonString: string): strin
|
|||
|
||||
// there is no macro to expand
|
||||
if (checkedPos === -1)
|
||||
return raw;
|
||||
return [raw, false];
|
||||
|
||||
// is it a custom macro?
|
||||
let nonAlphaPos = src.slice(checkedPos + 1).search(/[^A-Za-z]/) + checkedPos + 1;
|
||||
|
||||
if (nonAlphaPos === checkedPos)
|
||||
nonAlphaPos = endPos;
|
||||
|
||||
let macroNameCandidate = src.slice(checkedPos + 1, nonAlphaPos);
|
||||
if (macros.hasOwnProperty(macroNameCandidate)) {
|
||||
// this is a custom macro without args
|
||||
|
@ -241,7 +242,7 @@ export function expandKaTeXMacro(src: string, macrosAsJsonString: string): strin
|
|||
}
|
||||
|
||||
if (nextOpenBracketPos === endPos)
|
||||
return fallback; // there is no open bracket
|
||||
return [fallback, false]; // there is no open bracket
|
||||
|
||||
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
|
||||
const nextOpenBracketPos = src.indexOf(openBracket, macroArgEndPos);
|
||||
if (nextOpenBracketPos === -1)
|
||||
return fallback; // not enough arguments are provided
|
||||
return [fallback, false]; // not enough arguments are provided
|
||||
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];
|
||||
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)
|
||||
+ 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;
|
||||
}
|
||||
|
|
|
@ -5,11 +5,14 @@ import { expandKaTeXMacro } from "@/scripts/katex-macro";
|
|||
export function preprocess(text: string): string {
|
||||
if (defaultStore.state.enableCustomKaTeXMacro) {
|
||||
const parsedKaTeXMacro = localStorage.getItem("customKaTeXMacroParsed") ?? "{}";
|
||||
const maxNumberOfExpansions = 200; // to prevent infinite expansion loops
|
||||
|
||||
let nodes = mfm.parse(text);
|
||||
|
||||
for (let node of nodes) {
|
||||
if (node["type"] === "mathInline" || node["type"] === "mathBlock") {
|
||||
node["props"]["formula"] = expandKaTeXMacro(node["props"]["formula"], parsedKaTeXMacro);
|
||||
node["props"]["formula"]
|
||||
= expandKaTeXMacro(node["props"]["formula"], parsedKaTeXMacro, maxNumberOfExpansions);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue