import { match } from "@unified-latex/unified-latex-util-match"; import { visit, EXIT, SKIP } from "@unified-latex/unified-latex-util-visit"; function findRegionInArray(tree, start, end) { const ret = []; let currRegion = { start: void 0, end: tree.length }; for (let i = 0; i < tree.length; i++) { const node = tree[i]; if (start(node)) { currRegion.start = i; } if (end(node)) { currRegion.end = i + 1; ret.push(currRegion); currRegion = { start: void 0, end: tree.length }; } } if (currRegion.start != null) { ret.push(currRegion); } return ret; } function refineRegions(regions) { const _regions = [...regions]; _regions.sort((a, b) => a.start - b.start); const cutPointsSet = new Set(_regions.flatMap((r) => [r.start, r.end])); const cutPoints = Array.from(cutPointsSet); cutPoints.sort((a, b) => a - b); const retRegions = []; const retRegionsContainedIn = []; let seekIndex = 0; for (let i = 0; i < cutPoints.length - 1; i++) { const start = cutPoints[i]; const end = cutPoints[i + 1]; const region = { start, end }; const regionContainedIn = /* @__PURE__ */ new Set(); let encounteredEndPastStart = false; for (let j = seekIndex; j < _regions.length; j++) { const superRegion = _regions[j]; if (superRegion.end >= region.start) { encounteredEndPastStart = true; } if (!encounteredEndPastStart && superRegion.end < region.start) { seekIndex = j + 1; continue; } if (superRegion.start > end) { break; } if (superRegion.start <= region.start && superRegion.end >= region.end) { encounteredEndPastStart = true; regionContainedIn.add(superRegion); } } if (regionContainedIn.size > 0) { retRegions.push(region); retRegionsContainedIn.push(regionContainedIn); } } return { regions: retRegions, regionsContainedIn: retRegionsContainedIn }; } function splitByRegions(array, regionsRecord) { const ret = []; const indices = [0, array.length]; const reverseMap = {}; for (const [key, records] of Object.entries(regionsRecord)) { indices.push( ...records.flatMap((r) => { reverseMap["" + [r.start, r.end]] = key; return [r.start, r.end]; }) ); } indices.sort((a, b) => a - b); for (let i = 0; i < indices.length - 1; i++) { const start = indices[i]; const end = indices[i + 1]; if (start === end) { continue; } const regionKey = reverseMap["" + [start, end]]; ret.push([regionKey || null, array.slice(start, end)]); } return ret; } function escapeRegExp(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function buildWordRegex(allowedSet) { const regexpStr = `^(${["\\p{L}"].concat(Array.from(allowedSet).map(escapeRegExp)).join("|")})*`; return new RegExp(regexpStr, "u"); } function hasReparsableMacroNamesInArray(tree, allowedTokens) { for (let i = 0; i < tree.length; i++) { const macro = tree[i]; const string = tree[i + 1]; if (match.anyMacro(macro) && match.anyString(string)) { if (allowedTokens.has( macro.content.charAt(macro.content.length - 1) ) || allowedTokens.has(string.content.charAt(0))) { return true; } } } return false; } function hasReparsableMacroNames(tree, allowedTokens) { if (typeof allowedTokens === "string") { allowedTokens = new Set(allowedTokens.split("")); } const _allowedTokens = allowedTokens; for (const v of _allowedTokens) { if (v.length > 1) { throw new Error( `Only single characters are allowed as \`allowedTokens\` when reparsing macro names, not \`${v}\`.` ); } } let ret = false; visit( tree, (nodes) => { if (hasReparsableMacroNamesInArray(nodes, _allowedTokens)) { ret = true; return EXIT; } }, { includeArrays: true, test: Array.isArray } ); return ret; } function reparseMacroNamesInArray(tree, allowedTokens) { var _a, _b, _c; const regex = buildWordRegex(allowedTokens); let i = 0; while (i < tree.length) { const macro = tree[i]; const string = tree[i + 1]; if (match.anyMacro(macro) && // The _^ macros in math mode should not be extended no-matter what; // So we check to make sure that the macro we're dealing with has the default escape token. (macro.escapeToken == null || macro.escapeToken === "\\") && match.anyString(string) && // There are two options. Either the macro ends with the special character, // e.g. `\@foo` or the special character starts the next string, e.g. `\foo@`. (allowedTokens.has( macro.content.charAt(macro.content.length - 1) ) || allowedTokens.has(string.content.charAt(0)))) { const match2 = string.content.match(regex); const takeable = match2 ? match2[0] : ""; if (takeable.length > 0) { if (takeable.length === string.content.length) { macro.content += string.content; tree.splice(i + 1, 1); if (macro.position && ((_a = string.position) == null ? void 0 : _a.end)) { macro.position.end = string.position.end; } } else { macro.content += takeable; string.content = string.content.slice(takeable.length); if ((_b = macro.position) == null ? void 0 : _b.end) { macro.position.end.offset += takeable.length; macro.position.end.column += takeable.length; } if ((_c = string.position) == null ? void 0 : _c.start) { string.position.start.offset += takeable.length; string.position.start.column += takeable.length; } } } else { i++; } } else { ++i; } } } function reparseMacroNames(tree, allowedTokens) { if (typeof allowedTokens === "string") { allowedTokens = new Set(allowedTokens.split("")); } const _allowedTokens = allowedTokens; for (const v of _allowedTokens) { if (v.length > 1) { throw new Error( `Only single characters are allowed as \`allowedTokens\` when reparsing macro names, not \`${v}\`.` ); } } visit( tree, (nodes) => { reparseMacroNamesInArray(nodes, _allowedTokens); }, { includeArrays: true, test: Array.isArray } ); } const expl3Find = { start: match.createMacroMatcher(["ExplSyntaxOn"]), end: match.createMacroMatcher(["ExplSyntaxOff"]) }; const atLetterFind = { start: match.createMacroMatcher(["makeatletter"]), end: match.createMacroMatcher(["makeatother"]) }; function findExpl3AndAtLetterRegionsInArray(tree) { const expl3 = findRegionInArray(tree, expl3Find.start, expl3Find.end); const atLetter = findRegionInArray( tree, atLetterFind.start, atLetterFind.end ); const regionMap = new Map([ ...expl3.map((x) => [x, "expl"]), ...atLetter.map((x) => [x, "atLetter"]) ]); const all = refineRegions([...expl3, ...atLetter]); const ret = { explOnly: [], atLetterOnly: [], both: [] }; for (let i = 0; i < all.regions.length; i++) { const region = all.regions[i]; const containedIn = all.regionsContainedIn[i]; if (containedIn.size === 2) { ret.both.push(region); continue; } for (const v of containedIn.values()) { if (regionMap.get(v) === "expl") { ret.explOnly.push(region); } if (regionMap.get(v) === "atLetter") { ret.atLetterOnly.push(region); } } } ret.explOnly = ret.explOnly.filter((r) => r.end - r.start > 1); ret.atLetterOnly = ret.atLetterOnly.filter((r) => r.end - r.start > 1); ret.both = ret.both.filter((r) => r.end - r.start > 1); return ret; } const atLetterSet = /* @__PURE__ */ new Set(["@"]); const explSet = /* @__PURE__ */ new Set(["_", ":"]); const bothSet = /* @__PURE__ */ new Set(["_", ":", "@"]); function reparseExpl3AndAtLetterRegions(tree) { visit( tree, { leave: (nodes) => { const regions = findExpl3AndAtLetterRegionsInArray(nodes); const totalNumRegions = regions.both.length + regions.atLetterOnly.length + regions.explOnly.length; if (totalNumRegions === 0) { return; } const splits = splitByRegions(nodes, regions); const processed = []; for (const [key, slice] of splits) { switch (key) { case null: processed.push(...slice); continue; case "atLetterOnly": reparseMacroNames(slice, atLetterSet); processed.push(...slice); continue; case "explOnly": reparseMacroNames(slice, explSet); processed.push(...slice); continue; case "both": reparseMacroNames(slice, bothSet); processed.push(...slice); continue; default: throw new Error( `Unexpected case when splitting ${key}` ); } } nodes.length = 0; nodes.push(...processed); return SKIP; } }, { includeArrays: true, test: Array.isArray } ); } export { findExpl3AndAtLetterRegionsInArray, findRegionInArray, hasReparsableMacroNames, hasReparsableMacroNamesInArray, reparseExpl3AndAtLetterRegions, reparseMacroNames, reparseMacroNamesInArray }; //# sourceMappingURL=index.js.map