mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-12 14:38:39 +00:00
Add support for \expandafter, \noexpand, \edef, \let, and \long (#2122)
* Add support for \expandafter * Add support for \noexpand * Add support for \edef * Update comments * Allow \long before macro definition * Update documentation * Update comments * Fix defPrefix * Add support for \let * Update documentation * Print error token * Update documentation * Check whether command is expandable * Add tests * Fix token order * Make noexpand a Token property * Throw error if control sequence is undefined when expanding * Rename expandableOnly to expandOnly * Make unexpandable macro property * Move \expandafter to macros.js * Add TODO * Fix merge conflict * Update a test case * Remove unused functions in MacroContextInterface * Update comments * Refactor code * Move \noexpand to macros * Update MacroExpander.js * Add a test case * Separate control sequence check to a function * Add support for \futurelet * Separate RHS getter to a function * Update documentation * Move expandOnly logic to expandOnce * Refactor code and update comments Co-authored-by: Kevin Barabash <kevinb@khanacademy.org>
This commit is contained in:
@@ -562,6 +562,42 @@ exports[`A font parser \\boldsymbol should inherit mbin/mrel from argument 1`] =
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`A macro expander \\let should consume one optional space after equals sign 1`] = `
|
||||
[
|
||||
{
|
||||
"type": "ordgroup",
|
||||
"body": [
|
||||
{
|
||||
"type": "font",
|
||||
"body": {
|
||||
"type": "ordgroup",
|
||||
"body": [
|
||||
{
|
||||
"type": "mathord",
|
||||
"loc": {
|
||||
"end": 39,
|
||||
"lexer": {
|
||||
"input": "\\\\def\\\\bold{\\\\bgroup\\\\bf\\\\let\\\\next= }\\\\bold{a}",
|
||||
"lastIndex": 40
|
||||
},
|
||||
"start": 38
|
||||
},
|
||||
"mode": "math",
|
||||
"text": "a"
|
||||
}
|
||||
],
|
||||
"mode": "math"
|
||||
},
|
||||
"font": "mathbf",
|
||||
"mode": "math"
|
||||
}
|
||||
],
|
||||
"loc": null,
|
||||
"mode": "math"
|
||||
}
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`A parse tree generator generates a tree 1`] = `
|
||||
[
|
||||
{
|
||||
|
@@ -2975,6 +2975,30 @@ describe("A macro expander", function() {
|
||||
}}));
|
||||
});
|
||||
|
||||
it("should delay expansion if preceded by \\expandafter", function() {
|
||||
expect`\expandafter\foo\bar`.toParseLike("x+y", new Settings({macros: {
|
||||
"\\foo": "#1+#2",
|
||||
"\\bar": "xy",
|
||||
}}));
|
||||
expect`\def\foo{x}\def\bar{\def\foo{y}}\expandafter\bar\foo`.toParseLike`x`;
|
||||
// \def is not expandable, i.e., \expandafter doesn't define the macro
|
||||
expect`\expandafter\foo\def\foo{x}`.not.toParse();
|
||||
});
|
||||
|
||||
it("should not expand if preceded by \\noexpand", function() {
|
||||
// \foo is not expanded and interpreted as if its meaning were \relax
|
||||
expect`\noexpand\foo y`.toParseLike("y",
|
||||
new Settings({macros: {"\\foo": "x"}}));
|
||||
// \noexpand is expandable, so the second \foo is not expanded
|
||||
expect`\expandafter\foo\noexpand\foo`.toParseLike("x",
|
||||
new Settings({macros: {"\\foo": "x"}}));
|
||||
// \frac is a macro and therefore expandable
|
||||
expect`\noexpand\frac xy`.toParseLike`xy`;
|
||||
// TODO(ylem): #2085
|
||||
// \def is not expandable, so is not affected by \noexpand
|
||||
// expect`\noexpand\def\foo{xy}\foo`.toParseLike`xy`;
|
||||
});
|
||||
|
||||
it("should allow for space macro argument (text version)", function() {
|
||||
expect`\text{\foo\bar}`.toParseLike(r`\text{( )}`, new Settings({macros: {
|
||||
"\\foo": "(#1)",
|
||||
@@ -3156,6 +3180,16 @@ describe("A macro expander", function() {
|
||||
//expect`\gdef{\foo}{}`.not.toParse();
|
||||
});
|
||||
|
||||
it("\\xdef should expand definition", function() {
|
||||
expect`\def\foo{a}\xdef\bar{\foo}\def\foo{}\bar`.toParseLike`a`;
|
||||
// \def\noexpand\foo{} expands into \def\foo{}
|
||||
expect`\def\foo{a}\xdef\bar{\def\noexpand\foo{}}\foo\bar\foo`.toParseLike`a`;
|
||||
// \foo\noexpand\foo expands into a\foo
|
||||
expect`\def\foo{a}\xdef\bar{\foo\noexpand\foo}\def\foo{b}\bar`.toParseLike`ab`;
|
||||
// \foo is not defined
|
||||
expect`\xdef\bar{\foo}`.not.toParse();
|
||||
});
|
||||
|
||||
it("\\def should be handled in Parser", () => {
|
||||
expect`\gdef\foo{1}`.toParse(new Settings({maxExpand: 0}));
|
||||
expect`2^\def\foo{1}2`.not.toParse();
|
||||
@@ -3177,13 +3211,23 @@ describe("A macro expander", function() {
|
||||
"\\x\\def\\x{5}\\x}\\x").toParseLike`1{2{34}35}3`;
|
||||
});
|
||||
|
||||
it("\\global needs to followed by \\def", () => {
|
||||
it("\\global needs to followed by macro prefixes, \\def or \\edef", () => {
|
||||
expect`\global\def\foo{}\foo`.toParseLike``;
|
||||
expect`\global\edef\foo{}\foo`.toParseLike``;
|
||||
expect`\def\DEF{\def}\global\DEF\foo{}\foo`.toParseLike``;
|
||||
expect`\global\global\def\foo{}\foo`.toParseLike``;
|
||||
expect`\global\long\def\foo{}\foo`.toParseLike``;
|
||||
expect`\global\foo`.not.toParse();
|
||||
expect`\global\bar x`.not.toParse();
|
||||
});
|
||||
|
||||
it("\\long needs to followed by macro prefixes, \\def or \\edef", () => {
|
||||
expect`\long\def\foo{}\foo`.toParseLike``;
|
||||
expect`\long\edef\foo{}\foo`.toParseLike``;
|
||||
expect`\long\global\def\foo{}\foo`.toParseLike``;
|
||||
expect`\long\foo`.not.toParse();
|
||||
});
|
||||
|
||||
it("Macro arguments do not generate groups", () => {
|
||||
expect("\\def\\x{1}\\x\\def\\foo#1{#1}\\foo{\\x\\def\\x{2}\\x}\\x")
|
||||
.toParseLike`1122`;
|
||||
@@ -3225,6 +3269,30 @@ describe("A macro expander", function() {
|
||||
expect(macros["\\foo"]).toBeTruthy();
|
||||
});
|
||||
|
||||
it("\\let copies the definition", () => {
|
||||
expect`\let\foo=\frac\def\frac{}\foo12`.toParseLike`\frac12`;
|
||||
expect`\def\foo{1}\let\bar\foo\def\foo{2}\bar`.toParseLike`1`;
|
||||
expect`\let\foo=\kern\edef\bar{\foo1em}\let\kern=\relax\bar`.toParseLike`\kern1em`;
|
||||
// \foo = { (left brace)
|
||||
expect`\let\foo{\frac\foo1}{2}`.toParseLike`\frac{1}{2}`;
|
||||
// \equals = = (equal sign)
|
||||
expect`\let\equals==a\equals b`.toParseLike`a=b`;
|
||||
// \foo should not be expandable and not affected by \noexpand or \edef
|
||||
expect`\let\foo=x\noexpand\foo`.toParseLike`x`;
|
||||
expect`\let\foo=x\edef\bar{\foo}\def\foo{y}\bar`.toParseLike`y`;
|
||||
});
|
||||
|
||||
it("\\let should consume one optional space after equals sign", () => {
|
||||
// https://tex.stackexchange.com/questions/141166/let-foo-bar-vs-let-foo-bar-let-with-equals-sign
|
||||
expect`\def\:{\let\space= }\: \text{\space}`.toParseLike`\text{ }`;
|
||||
const tree = getParsed`\def\bold{\bgroup\bf\let\next= }\bold{a}`;
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("\\futurelet should parse correctly", () => {
|
||||
expect`\futurelet\foo\frac1{2+\foo}`.toParseLike`\frac1{2+1}`;
|
||||
});
|
||||
|
||||
it("\\newcommand doesn't change settings.macros", () => {
|
||||
const macros = {};
|
||||
expect`\newcommand\foo{x^2}\foo+\foo`.toParse(new Settings({macros}));
|
||||
|
Reference in New Issue
Block a user