Skip to content

Commit

Permalink
fix: remove local macros upon parse error (#3114)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
edemaine and ylemkimon committed Aug 28, 2021
1 parent ff1734f commit a6f29e3
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 8 deletions.
8 changes: 8 additions & 0 deletions src/MacroExpander.js
Expand Up @@ -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`.
Expand Down
10 changes: 10 additions & 0 deletions src/Namespace.js
Expand Up @@ -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`.
Expand Down
23 changes: 15 additions & 8 deletions src/Parser.js
Expand Up @@ -130,17 +130,24 @@ export default class Parser {
this.gullet.macros.set("\\color", "\\textcolor");
}

// Try to parse the input
const parse = this.parseExpression(false);
try {
// Try to parse the input
const parse = this.parseExpression(false);

// If we succeeded, make sure there's an EOF at the end
this.expect("EOF");
// If we succeeded, make sure there's an EOF at the end
this.expect("EOF");

// End the group namespace for the expression
if (!this.settings.globalGroup) {
this.gullet.endGroup();
// End the group namespace for the expression
if (!this.settings.globalGroup) {
this.gullet.endGroup();
}

return parse;

// Close any leftover groups in case of a parse error.
} finally {
this.gullet.endGroups();
}
return parse;
}

static endOfExpression: string[] = ["}", "\\endgroup", "\\end", "\\right", "&"];
Expand Down
6 changes: 6 additions & 0 deletions test/katex-spec.js
Expand Up @@ -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}));
Expand Down

0 comments on commit a6f29e3

Please sign in to comment.