fix: remove local macros upon parse error (#3114)

Close all groups after parse, in particular in case of parse error,
completing `Namespace`'s simulation of local definitions.

Fixes #3122

Co-authored-by: ylemkimon <y@ylem.kim>
This commit is contained in:
Erik Demaine
2021-08-28 18:59:24 -04:00
committed by GitHub
parent ff1734f7c4
commit a6f29e3612
4 changed files with 39 additions and 8 deletions

View File

@@ -74,6 +74,14 @@ export default class MacroExpander implements MacroContextInterface {
this.macros.endGroup();
}
/**
* Ends all currently nested groups (if any), restoring values before the
* groups began. Useful in case of an error in the middle of parsing.
*/
endGroups() {
this.macros.endGroups();
}
/**
* Returns the topmost token on the stack, without expanding it.
* Similar in behavior to TeX's `\futurelet`.

View File

@@ -57,6 +57,16 @@ export default class Namespace<Value> {
}
}
/**
* Ends all currently nested groups (if any), restoring values before the
* groups began. Useful in case of an error in the middle of parsing.
*/
endGroups() {
while (this.undefStack.length > 0) {
this.endGroup();
}
}
/**
* Detect whether `name` has a definition. Equivalent to
* `get(name) != null`.

View File

@@ -130,6 +130,7 @@ export default class Parser {
this.gullet.macros.set("\\color", "\\textcolor");
}
try {
// Try to parse the input
const parse = this.parseExpression(false);
@@ -140,7 +141,13 @@ export default class Parser {
if (!this.settings.globalGroup) {
this.gullet.endGroup();
}
return parse;
// Close any leftover groups in case of a parse error.
} finally {
this.gullet.endGroups();
}
}
static endOfExpression: string[] = ["}", "\\endgroup", "\\end", "\\right", "&"];

View File

@@ -3440,6 +3440,12 @@ describe("A macro expander", function() {
expect(macros["\\foo"]).toBeFalsy();
});
it("\\def doesn't change settings.macros on error", () => {
const macros = {};
expect`\def\foo{c^}\foo`.not.toParse(new Settings({macros}));
expect(macros["\\foo"]).toBeFalsy();
});
it("\\def changes settings.macros with globalGroup", () => {
const macros = {};
expect`\gdef\foo{1}`.toParse(new Settings({macros, globalGroup: true}));