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

Break Up LaTeX Source on Section Macros #101

Merged
merged 43 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
3a85c98
added math mode macro report function and test file
renee-k May 25, 2024
0c1971a
updated report unsupported katex and expand command files
renee-k May 26, 2024
a140ea3
added more tests to be implemented
renee-k May 26, 2024
575141d
implemented expand macros tests and function
renee-k May 28, 2024
463f366
added files for breaking source on boundaries
renee-k May 29, 2024
26c09ac
added some test cases
renee-k May 29, 2024
2e5fdbd
updated code
renee-k May 29, 2024
2711765
formatted code
renee-k May 29, 2024
ac23334
Revert "added some test cases"
renee-k May 29, 2024
30bac19
Revert "updated code"
renee-k May 29, 2024
292de9f
fixed test file
renee-k May 29, 2024
a450ac1
addressed some katex related comments
renee-k May 30, 2024
bd2f4d5
added more tests
renee-k May 31, 2024
3f08772
started implementing break-on-boundaries
renee-k Jun 2, 2024
3e1a7e3
successfully splits on parts
renee-k Jun 4, 2024
76352c5
removed uneeded variable
renee-k Jun 4, 2024
d2bc84f
implemented for half the divisions
renee-k Jun 5, 2024
6f6221a
revised tests
renee-k Jun 5, 2024
b55e08c
revised some test descriptions
renee-k Jun 5, 2024
fa392d0
made tests more readable
renee-k Jun 5, 2024
49e25bc
fixed expand userdefined macros
renee-k Jun 5, 2024
577d1be
addressed more PR comments
renee-k Jun 8, 2024
7648bc7
moved files to pre-conversion-subs
renee-k Jun 9, 2024
bdd9581
added support for groups
renee-k Jun 11, 2024
422fd59
added more test cases
renee-k Jun 12, 2024
ffeb18f
Merge branch 'pretext' into break-section-pretext
renee-k Jun 12, 2024
34214da
Add pretext to tested branches
siefkenj Jun 12, 2024
9631ed4
moved files
renee-k Jun 12, 2024
8c4ed53
Merge branch 'main' into break-section-pretext
renee-k Jun 12, 2024
1f483a4
resolved some PR comments
renee-k Jun 15, 2024
8d4d570
added more test cases
renee-k Jun 17, 2024
ae7d000
restructured code
renee-k Jun 18, 2024
c5ed819
added warning messages
renee-k Jun 19, 2024
fc8511c
addressed some comments
renee-k Jun 26, 2024
9379b15
Merge branch 'pretext' into break-section-pretext
renee-k Jun 26, 2024
35627d8
used a custom macro matcher
renee-k Jun 26, 2024
7fba2d7
self-documented divisions
renee-k Jun 26, 2024
9f407ca
updated documentation
renee-k Jun 28, 2024
3d43101
fixed all test cases
renee-k Jul 4, 2024
1a586ec
made custom environment matcher
renee-k Jul 6, 2024
51ba97e
removed groups properly
renee-k Jul 9, 2024
5e388ae
imported VFileMessage
renee-k Jul 12, 2024
5b467b9
finished VFileMessage implementation
renee-k Jul 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions packages/unified-latex-to-pretext/libs/break-on-boundaries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { env, m } from "@unified-latex/unified-latex-builder";
import * as Ast from "@unified-latex/unified-latex-types";
import { getNamedArgsContent } from "@unified-latex/unified-latex-util-arguments";
import {
anyEnvironment,
anyMacro,
match,
} from "@unified-latex/unified-latex-util-match";
import { replaceNode } from "@unified-latex/unified-latex-util-replace";
import {
splitOnMacro,
unsplitOnMacro,
} from "@unified-latex/unified-latex-util-split";
import { visit } from "@unified-latex/unified-latex-util-visit";

/**
* Breaks up division macros into environments
*/
export function breakOnBoundaries(ast: Ast.Ast): void {
const divisions = [
renee-k marked this conversation as resolved.
Show resolved Hide resolved
"part",
"chapter",
"section",
"subsection",
"subsubsection",
"paragraph",
"subparagraph",
];

const new_boundaries = [
renee-k marked this conversation as resolved.
Show resolved Hide resolved
"_part",
"_chapter",
"_section",
"_subsection",
"_subsubsection",
"_paragraph",
"_subparagraph",
];

visit(ast, (node, info) => {
// needs to be an environment, root, or group node
renee-k marked this conversation as resolved.
Show resolved Hide resolved
if (
!(
anyEnvironment(node) ||
node.type === "root" ||
match.group(node)
) ||
info.context.hasMathModeAncestor
) {
return;
}

renee-k marked this conversation as resolved.
Show resolved Hide resolved
// if it's an environment, make sure it isn't a newly created one
else if (anyEnvironment(node) && new_boundaries.includes(node.env)) {
return;
}

// now break up the divisions, starting at part
node.content = breakUp(node.content, divisions, 0);
});

// remove all old division nodes
replaceNode(ast, (node) => {
renee-k marked this conversation as resolved.
Show resolved Hide resolved
if (anyMacro(node) && divisions.includes(node.content)) {
return null;
}
});
}

function breakUp(
renee-k marked this conversation as resolved.
Show resolved Hide resolved
content: Ast.Node[],
divisions: string[],
index: number
): Ast.Node[] {
// broke up all divisions
if (index > 6) {
renee-k marked this conversation as resolved.
Show resolved Hide resolved
return content;
}

const splits = splitOnMacro(content, divisions[index]);

// go through each segment to recursively break
for (let i = 0; i < splits.segments.length; i++) {
splits.segments[i] = breakUp(splits.segments[i], divisions, index + 1);
}

createEnvironments(splits, "_" + divisions[index]);

index++; // go to next division

// rebuild this part of the ast
return unsplitOnMacro(splits);
}

function createEnvironments(
splits: { segments: Ast.Node[][]; macros: Ast.Macro[] },
newEnviron: string
): void {
// loop through segments (skipping first segment)
for (let i = 1; i < splits.segments.length; i++) {
// create the title
const title = getNamedArgsContent(splits.macros[i - 1])["title"];

// add title to the front of the segment
if (title) {
splits.segments[i].unshift(
m("title", (title[0] as Ast.Macro).content)
);
}

// wrap segment around a new environment
splits.segments[i] = [env(newEnviron, splits.segments[i])];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as Ast from "@unified-latex/unified-latex-types";
renee-k marked this conversation as resolved.
Show resolved Hide resolved
import {
expandMacrosExcludingDefinitions,
listNewcommands,
} from "@unified-latex/unified-latex-util-macros";
import { attachMacroArgs } from "@unified-latex/unified-latex-util-arguments";
import { anyMacro } 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 {
const newcommands = listNewcommands(ast);

// get a set of all macros to be expanded
const macrosToExpand = new Set(newcommands.map((command) => command.name));

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, macrosToExpand)) {
break;
}

// attach the arguments to each macro before processing it
attachMacroArgs(ast, macroInfo);
expandMacrosExcludingDefinitions(ast, newcommands);
}
}

function needToExpand(ast: Ast.Ast, macros: Set<string>): boolean {
let needExpand = false;

visit(ast, (node) => {
if (anyMacro(node) && macros.has(node.content)) {
needExpand = true;
EXIT;
}
});

return needExpand;
}
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 "./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,94 @@
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 { breakOnBoundaries } from "../libs/break-on-boundaries";

// 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:break-on-boundaries", () => {
let value: string;

it("can break on parts", () => {
value = String.raw`\part{Foo}Hi, this is a part\part{Bar}This is another part`;

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

breakOnBoundaries(ast);

expect(printRaw(ast)).toEqual(
String.raw`\begin{_part}\title{Foo}Hi, this is a part\end{_part}\begin{_part}\title{Bar}This is another part\end{_part}`
);
});

it("can break on a combination of divisions", () => {
value = String.raw`\part{part1}\section{Section1}Hi, this is a section\chapter{chap1}This is a chapter\section{Subsection2}`;

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

breakOnBoundaries(ast);

expect(printRaw(ast)).toEqual(
renee-k marked this conversation as resolved.
Show resolved Hide resolved
String.raw`\begin{_part}\title{part1}` +
String.raw`\begin{_section}\title{Section1}Hi, this is a section\end{_section}` +
String.raw`\begin{_chapter}\title{chap1}This is a chapter` +
String.raw`\begin{_section}\title{Subsection2}\end{_section}\end{_chapter}\end{_part}`
);
});

it("can break on divisions wrapped around by a document environment", () => {
value = String.raw`\begin{document}\section{name}Hi, this is a subsection\subsubsection{title}description.\end{document}`;
renee-k marked this conversation as resolved.
Show resolved Hide resolved

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

breakOnBoundaries(ast);

expect(printRaw(ast)).toEqual(
String.raw`\begin{document}\begin{_section}\title{name}Hi, this is a subsection` +
String.raw`\begin{_subsubsection}\title{title}description.\end{_subsubsection}` +
String.raw`\end{_section}\end{document}`
);
});

it("can break on divisions wrapped around by different environments", () => {
value =
String.raw`\begin{center}\part{name}Hi, this is a part\begin{environ}` +
String.raw`\subparagraph{title}description.\end{environ}\end{center}`;

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

breakOnBoundaries(ast);

expect(printRaw(ast)).toEqual(
String.raw`\begin{center}\begin{_part}\title{name}Hi, this is a part` +
String.raw`\begin{environ}\begin{_subparagraph}\title{title}description.` +
String.raw`\end{_subparagraph}\end{environ}\end{_part}\end{center}`
);
});

it("can break on divisions in a group", () => {
value =
String.raw`\begin{document}\chapter{Chap}` +
String.raw`{\paragraph{Intro}Introduction.\begin{center}\subparagraph{Conclusion}Conclusion.\end{center}}` +
String.raw`Chapter finished.\end{document}`;

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

breakOnBoundaries(ast);

expect(printRaw(ast)).toEqual(
String.raw`\begin{document}\begin{_chapter}\title{Chap}{\begin{_paragraph}\title{Intro}Introduction.` +
renee-k marked this conversation as resolved.
Show resolved Hide resolved
String.raw`\begin{center}\begin{_subparagraph}\title{Conclusion}Conclusion.\end{_subparagraph}` +
String.raw`\end{center}\end{_paragraph}}Chapter finished.\end{_chapter}\end{document}`
);
});
});
siefkenj marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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/pre-conversion-subs/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`;

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` +
String.raw`\renewcommand{\foo}{\bar}` +
String.raw`\foo`;

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

expandUserDefinedMacros(ast);

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

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

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

expandUserDefinedMacros(ast);

expect(printRaw(ast)).toEqual(String.raw`\providecommand{\bar}{\b} \b`);
});
});
Loading