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 33 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
4 changes: 2 additions & 2 deletions .github/workflows/on-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Node.js CI

on:
push:
branches: ["main"]
branches: ["main", "pretext"]
pull_request:
branches: ["main"]
branches: ["main", "pretext"]

jobs:
build:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { env, arg } 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 { printRaw } from "@unified-latex/unified-latex-util-print-raw";
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";

// all divisions [division macro, environment]
renee-k marked this conversation as resolved.
Show resolved Hide resolved
const divisions: [string, string][] = [
["part", "_part"],
["chapter", "_chapter"],
["section", "_section"],
["subsection", "_subsection"],
["subsubsection", "_subsubsection"],
["paragraph", "_paragraph"],
["subparagraph", "_subparagraph"],
];

/**
* Breaks up division macros into environments. Returns a list of warning messages
* for any groups that were removed.
*/
export function breakOnBoundaries(ast: Ast.Ast): string[] {
renee-k marked this conversation as resolved.
Show resolved Hide resolved
// messages for any groups removed
const messages: string[] = [];

visit(ast, (node, info) => {
// needs to be an environment, root, or group node
if (
!(
anyEnvironment(node) ||
node.type === "root" ||
match.group(node)
) ||
// skip math mode
info.context.hasMathModeAncestor
) {
return;
}
// if it's an environment, make sure it isn't a newly created one
else if (
anyEnvironment(node) &&
divisions.map((x) => x[1]).includes(node.env)
) {
return;
}

// if it's a group, push a warning message
if (match.group(node)) {
messages.push(
`Warning: hoisted out of a group, which might break the LaTeX code. { group: ${printRaw(
node
)} }`
);
}

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

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

return messages;
}

/**
* Recursively breaks up the ast at the division macros.
renee-k marked this conversation as resolved.
Show resolved Hide resolved
*/
function breakUp(
content: Ast.Node[],
divisions: [string, string][],
renee-k marked this conversation as resolved.
Show resolved Hide resolved
depth: number
): Ast.Node[] {
// broke up all divisions
if (depth > 6) {
return content;
}

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

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

createEnvironments(splits, divisions[depth][1]);

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

/**
* Create the new environments that replace the division macros
*/
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++) {
// get the title
const title = getNamedArgsContent(splits.macros[i - 1])["title"];
const titleArg: Ast.Argument[] = [];

// create title argument
if (title) {
titleArg.push(arg(title, { braces: "[]" }));
}

// wrap segment with a new environment
splits.segments[i] = [env(newEnviron, splits.segments[i], titleArg)];
}
}
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;
}
149 changes: 149 additions & 0 deletions packages/unified-latex-to-pretext/tests/break-on-boundaries.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
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/pre-conversion-subs/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);

expect(breakOnBoundaries(ast)).toEqual([]);

expect(printRaw(ast)).toEqual(
String.raw`\begin{_part}[Foo]Hi, this is a part\end{_part}\begin{_part}[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);

expect(breakOnBoundaries(ast)).toEqual([]);

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

it("can break on divisions wrapped around by a document environment", () => {
value = String.raw`\begin{document}\section{Baz}Hi, this is a subsection\subsubsection{Foo}description.\end{document}`;

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

expect(breakOnBoundaries(ast)).toEqual([]);

expect(printRaw(ast)).toEqual(
String.raw`\begin{document}\begin{_section}[Baz]Hi, this is a subsection` +
String.raw`\begin{_subsubsection}[Foo]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);

expect(breakOnBoundaries(ast)).toEqual([]);

expect(printRaw(ast)).toEqual(
String.raw`\begin{center}\begin{_part}[name]Hi, this is a part` +
String.raw`\begin{environ}\begin{_subparagraph}[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);

expect(breakOnBoundaries(ast)).toEqual([
String.raw`Warning: hoisted out of a group, which might break the LaTeX code. ` +
String.raw`{ group: {\paragraph{Intro}Introduction.\begin{center}\subparagraph{Conclusion}Conclusion.\end{center}} }`,
]);

expect(printRaw(ast)).toEqual(
String.raw`\begin{document}\begin{_chapter}[Chap]\begin{_paragraph}[Intro]Introduction.` +
String.raw`\begin{center}\begin{_subparagraph}[Conclusion]Conclusion.\end{_subparagraph}` +
String.raw`\end{center}\end{_paragraph}Chapter finished.\end{_chapter}\end{document}`
);
});

it("can break on divisions in nested groups", () => {
value =
String.raw`\part{part1}{\subsection{Intro}description.` +
String.raw`\subsubsection{body}more text.{\subparagraph{Conclusion}Conclusion.}}`;

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

expect(breakOnBoundaries(ast)).toEqual([
String.raw`Warning: hoisted out of a group, which might break the LaTeX code. ` +
String.raw`{ group: {\subsection{Intro}description.\subsubsection{body}more text.{\subparagraph{Conclusion}Conclusion.}} }`,
String.raw`Warning: hoisted out of a group, which might break the LaTeX code. ` +
String.raw`{ group: {\subparagraph{Conclusion}Conclusion.} }`,
]);

expect(printRaw(ast)).toEqual(
String.raw`\begin{_part}[part1]\begin{_subsection}[Intro]description.` +
String.raw`\begin{_subsubsection}[body]more text.\begin{_subparagraph}[Conclusion]Conclusion.` +
String.raw`\end{_subparagraph}\end{_subsubsection}\end{_subsection}\end{_part}`
);
});

it("can break on divisions with latex in their titles", () => {
renee-k marked this conversation as resolved.
Show resolved Hide resolved
value = String.raw`\chapter{$x = \frac{1}{2}$}Chapter 1\subsection{\"name\_1\" \(\mathbb{R}\)}This is subsection`;

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

expect(breakOnBoundaries(ast)).toEqual([]);

expect(printRaw(ast)).toEqual(
String.raw`\begin{_chapter}[$x = \frac{1}{2}$]Chapter 1` +
String.raw`\begin{_subsection}[\"name\_1\" \(\mathbb{R}\)]This is subsection` +
String.raw`\end{_subsection}\end{_chapter}`
);
});

it("can break on divisions and trim whitespace around division beginnings and endings", () => {
value = String.raw` \subsubsection{first}subsection 1 \paragraph{body}This is paragraph `;

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

expect(breakOnBoundaries(ast)).toEqual([]);

expect(printRaw(ast)).toEqual(
String.raw`\begin{_subsubsection}[first]subsection 1` +
String.raw`\begin{_paragraph}[body]This is paragraph` +
String.raw`\end{_paragraph}\end{_subsubsection}`
);
});
});
siefkenj marked this conversation as resolved.
Show resolved Hide resolved
Loading