Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

report unsupported macros by katex and expand user-define commands #99

Merged
merged 16 commits into from
Jun 12, 2024
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as Ast from "@unified-latex/unified-latex-types";
import {
expandMacrosExcludingDefinitions,
listNewcommands, newcommandMacroToName
} from "@unified-latex/unified-latex-util-macros";
import { attachMacroArgs } from "@unified-latex/unified-latex-util-arguments";
import { anyMacro, match } from "@unified-latex/unified-latex-util-match";
import { EXIT, visit } from "@unified-latex/unified-latex-util-visit";

type NewCommandSpec = ReturnType<typeof listNewcommands>[number];

/**
* Expands user-defined macros
*/
export function ExpandUserDefinedMacros(ast: Ast.Ast): void {
renee-k marked this conversation as resolved.
Show resolved Hide resolved
const newcommands = listNewcommands(ast);

// get a list of all macros to be expanded
const macros_to_expand = getToBeExpandedMacros(newcommands);
renee-k marked this conversation as resolved.
Show resolved Hide resolved

const macroInfo = Object.fromEntries(
newcommands.map((m) => [m.name, { signature: m.signature }])
);

// recursively expand at most 100 times
for (let i = 0; i < 100; i++) {
// check if any macros still need expanding
if (needToExpand(ast, macros_to_expand)) {
ExpandUseDefinedMacrosOnce(ast, newcommands, macroInfo);
}
renee-k marked this conversation as resolved.
Show resolved Hide resolved
}
}

function getToBeExpandedMacros(newcommands: NewCommandSpec[]): string[] {
renee-k marked this conversation as resolved.
Show resolved Hide resolved
const macros = [];

// loop through each new command
for (const command of newcommands) {
macros.push(command.name)
}

return macros
}

function needToExpand(ast: Ast.Ast, macros: string[]): boolean {
let needExpand = false;

visit(ast, (node) => {
if (anyMacro(node) && macros.includes(node.content)) {
renee-k marked this conversation as resolved.
Show resolved Hide resolved
needExpand = true;
EXIT;
}
})

return needExpand;
}

function ExpandUseDefinedMacrosOnce(
renee-k marked this conversation as resolved.
Show resolved Hide resolved
ast: Ast.Ast,
newcommands: NewCommandSpec[],
macroInfo: {
[k: string]: {
signature: string;
};
}
): void {
// attach the arguments to each macro before processing it
attachMacroArgs(ast, macroInfo);
expandMacrosExcludingDefinitions(ast, newcommands);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as Ast from "@unified-latex/unified-latex-types";
import { anyMacro, match } from "@unified-latex/unified-latex-util-match";
import { visit } from "@unified-latex/unified-latex-util-visit";
import { KATEX_SUPPORT } from "./pre-conversion-subs/katex-subs";

/**
* Return list of macros unsupported by Katex
*/
export function reportMacrosUnsupportedByKatex(ast: Ast.Ast): string[] {
const unsupported: string[] = [];

// match a macro supported by Katex
const isSupported = match.createMacroMatcher(KATEX_SUPPORT.macros);

// visit all nodes
visit(ast, (node, info) => {
// macro in math mode
if (anyMacro(node) && info.context.hasMathModeAncestor) {
// check if not supported by katex
if (!isSupported(node)) {
unsupported.push((node as Ast.Macro).content);
}
}
});

return unsupported;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { describe, it, expect } from "vitest";
import util from "util";
import { getParser } from "@unified-latex/unified-latex-util-parse";
import { printRaw } from "@unified-latex/unified-latex-util-print-raw";
import { ExpandUserDefinedMacros } from "@unified-latex/unified-latex-to-pretext/libs/expand-user-defined-macros";

// Make console.log pretty-print by default
const origLog = console.log;
console.log = (...args) => {
origLog(...args.map((x) => util.inspect(x, false, 10, true)));
};

describe("unified-latex-to-pretext:expand-user-deifned-macros", () => {
let value: string;

it("can expand newcommand", () => {
value = String.raw`\newcommand{\foo}{\bar} \foo`;

const parser = getParser();
const ast = parser.parse(value);

ExpandUserDefinedMacros(ast);

expect(printRaw(ast)).toEqual(String.raw`\newcommand{\foo}{\bar} \bar`);
});

it("can expand renewcommand", () => {
value = String.raw`\renewcommand{\O}{\mathcal{O}} \O`; // not subbing at all

const parser = getParser();
const ast = parser.parse(value);

ExpandUserDefinedMacros(ast);

expect(printRaw(ast)).toEqual(
String.raw`\renewcommand{\O}{\mathcal{O}} \mathcal{O}`
);
});

it("can recursively expand multiple user-defined commands", () => {
value =
String.raw`\newcommand{\join}{\vee}` +
String.raw`\join` +
String.raw`\renewcommand{\vee}{\foo}` +
String.raw`\vee`;

const parser = getParser();
const ast = parser.parse(value);

ExpandUserDefinedMacros(ast);

expect(printRaw(ast)).toEqual(
String.raw`\newcommand{\join}{\vee}` +
String.raw`\foo` +
String.raw`\renewcommand{\vee}{\foo}` +
String.raw`\foo`
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, it, expect } from "vitest";
import util from "util";
import { getParser } from "@unified-latex/unified-latex-util-parse";
import { reportMacrosUnsupportedByKatex } from "@unified-latex/unified-latex-to-pretext/libs/report-unsupported-macro-katex";

// Make console.log pretty-print by default
const origLog = console.log;
console.log = (...args) => {
origLog(...args.map((x) => util.inspect(x, false, 10, true)));
};

describe("unified-latex-to-pretext:report-unsupported-macro-katex", () => {
let value: string;

it("can reported unsupported macros in mathmode", () => {
value = String.raw`$\mathbb{R} \fakemacro{X}$`;

const parser = getParser();
const ast = parser.parse(value);

expect(reportMacrosUnsupportedByKatex(ast)).toEqual(["fakemacro"]);
});

it("can report no unsupported macros in mathmode", () => {
value = String.raw`$\mathbb{R} \frac{1}{2} \cup$`;

const parser = getParser();
const ast = parser.parse(value);

expect(reportMacrosUnsupportedByKatex(ast)).toEqual([]);
});

it("can report no unsupported macros outside of math mode", () => {
renee-k marked this conversation as resolved.
Show resolved Hide resolved
value = String.raw`\fakemacro`;

const parser = getParser();
const ast = parser.parse(value);

expect(reportMacrosUnsupportedByKatex(ast)).toEqual([]);
});

it("can report unsupported macros in text mode with a math anscestor", () => {
renee-k marked this conversation as resolved.
Show resolved Hide resolved
value = String.raw`$\frac{1}{\text{ hi \unsupported}}$`;

const parser = getParser();
const ast = parser.parse(value);

expect(reportMacrosUnsupportedByKatex(ast)).toEqual(["unsupported"]);
});
});
renee-k marked this conversation as resolved.
Show resolved Hide resolved