mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-06 19:58:40 +00:00
fix: Support \let
via macros
option (#3738)
* fix: Support `\let` via `macros` option Issue #3737 turned out to be how we handled the return value of `expandOnce`. We assumed that, if the return value isn't an `Array`, it's an `instanceof Token`. This isn't necessary true with a user's `macros` object, and given that we don't currently export `Token`, it's pretty difficult to bypass. Given that we never actually use the array return values from `expandOnce`, I changed the return value for `expandOnce` to either a `number` (to indicate the number of expanded tokens, so you could still look up the tokens in the stack if you wanted to) or `false` (to indicate no expansion happened). We can't use `0` for the latter because an actual expansion might result in zero tokens. The resulting code is arguably cleaner. I also documented that `macros` can have object expansions, and specified how to simulate `\let`. Fixes #3737 * Revise macros documentation according to comments
This commit is contained in:
@@ -15,7 +15,14 @@ You can provide an object of options as the last argument to [`katex.render` and
|
||||
- `fleqn`: `boolean`. If `true`, display math renders flush left with a `2em` left margin, like `\documentclass[fleqn]` in LaTeX with the `amsmath` package.
|
||||
- `throwOnError`: `boolean`. If `true` (the default), KaTeX will throw a `ParseError` when it encounters an unsupported command or invalid LaTeX. If `false`, KaTeX will render unsupported commands as text, and render invalid LaTeX as its source code with hover text giving the error, in the color given by `errorColor`.
|
||||
- `errorColor`: `string`. A color string given in the format `"#XXX"` or `"#XXXXXX"`. This option determines the color that unsupported commands and invalid LaTeX are rendered in when `throwOnError` is set to `false`. (default: `#cc0000`)
|
||||
- `macros`: `object`. A collection of custom macros. Each macro is a property with a name like `\name` (written `"\\name"` in JavaScript) which maps to a string that describes the expansion of the macro, or a function that accepts an instance of `MacroExpander` as first argument and returns the expansion as a string. `MacroExpander` is an internal API and subject to non-backwards compatible changes. See [`src/defineMacro.js`](https://github.com/KaTeX/KaTeX/blob/main/src/defineMacro.js) for its usage. Single-character keys can also be included in which case the character will be redefined as the given macro (similar to TeX active characters). *This object will be modified* if the LaTeX code defines its own macros via `\gdef` (or via `\def` or `\newcommand` when using `globalGroup`), which enables consecutive calls to KaTeX to share state.
|
||||
- `macros`: `object`. A collection of custom macros.
|
||||
- Each macro is a key-value pair where the key is a new command name and the value is the expansion of the macro.
|
||||
- Example: `macros: {"\\R": "\\mathbb{R}"}`
|
||||
- More precisely, each property of `macros` can have a name that starts with a backslash like `"\\foo"` (defining command `\foo`) or is a single character like `"α"` (defining the equivalent of a TeX active character), and a value that is one of the following:
|
||||
- A string with the LaTeX expansion of the macro (which will be recursively expanded when used). The string can invoke (required) arguments via `#1`, `#2`, etc.
|
||||
- A function that accepts an instance of `MacroExpander` as first argument and returns the expansion as a string. `MacroExpander` is an internal API and subject to non-backwards compatible changes. See [`src/defineMacro.js`](https://github.com/KaTeX/KaTeX/blob/main/src/defineMacro.js) for its usage.
|
||||
- An expansion object matching [an internal `MacroExpansion` specification](https://github.com/KaTeX/KaTeX/blob/main/src/defineMacro.js), which is what results from global `\def` or `\let`. For example, you can simulate the effect of `\let\realint=\int` via `{"\\realint": {tokens: [{text: "\\int", noexpand: true}], numArgs: 0}}`.
|
||||
- *This object will be modified* if the LaTeX code defines its own macros via `\gdef` or `\global\let` (or via `\def` or `\newcommand` or `\let` when using `globalGroup`). This enables consecutive calls to KaTeX to share state (in particular, user macro definitions) if you pass in the same `macros` object each time.
|
||||
- `minRuleThickness`: `number`. Specifies a minimum thickness, in ems, for fraction lines, `\sqrt` top lines, `{array}` vertical lines, `\hline`, `\hdashline`, `\underline`, `\overline`, and the borders of `\fbox`, `\boxed`, and `\fcolorbox`. The usual value for these items is `0.04`, so for `minRuleThickness` to be effective it should probably take a value slightly above `0.04`, say `0.05` or `0.06`. Negative values will be ignored.
|
||||
- `colorIsTextColor`: `boolean`. In early versions of both KaTeX (<0.8.0) and MathJax, the `\color` function expected the content to be a function argument, as in `\color{blue}{hello}`. In current KaTeX, `\color` is a switch, as in `\color{blue} hello`. This matches LaTeX behavior. If you want the old `\color` behavior, set option `colorIsTextColor` to true.
|
||||
- `maxSize`: `number`. All user-specified sizes, e.g. in `\rule{500em}{500em}`, will be capped to `maxSize` ems. If set to `Infinity` (the default), users can make elements and spaces arbitrarily large.
|
||||
|
@@ -249,22 +249,22 @@ export default class MacroExpander implements MacroContextInterface {
|
||||
* Expand the next token only once if possible.
|
||||
*
|
||||
* If the token is expanded, the resulting tokens will be pushed onto
|
||||
* the stack in reverse order and will be returned as an array,
|
||||
* also in reverse order.
|
||||
* the stack in reverse order, and the number of such tokens will be
|
||||
* returned. This number might be zero or positive.
|
||||
*
|
||||
* If not, the next token will be returned without removing it
|
||||
* from the stack. This case can be detected by a `Token` return value
|
||||
* instead of an `Array` return value.
|
||||
* If not, the return value is `false`, and the next token remains at the
|
||||
* top of the stack.
|
||||
*
|
||||
* In either case, the next token will be on the top of the stack,
|
||||
* or the stack will be empty.
|
||||
* or the stack will be empty (in case of empty expansion
|
||||
* and no other tokens).
|
||||
*
|
||||
* Used to implement `expandAfterFuture` and `expandNextToken`.
|
||||
*
|
||||
* If expandableOnly, only expandable tokens are expanded and
|
||||
* an undefined control sequence results in an error.
|
||||
*/
|
||||
expandOnce(expandableOnly?: boolean): Token | Token[] {
|
||||
expandOnce(expandableOnly?: boolean): number | boolean {
|
||||
const topToken = this.popToken();
|
||||
const name = topToken.text;
|
||||
const expansion = !topToken.noexpand ? this._getExpansion(name) : null;
|
||||
@@ -274,7 +274,7 @@ export default class MacroExpander implements MacroContextInterface {
|
||||
throw new ParseError("Undefined control sequence: " + name);
|
||||
}
|
||||
this.pushToken(topToken);
|
||||
return topToken;
|
||||
return false;
|
||||
}
|
||||
this.expansionCount++;
|
||||
if (this.expansionCount > this.settings.maxExpand) {
|
||||
@@ -310,7 +310,7 @@ export default class MacroExpander implements MacroContextInterface {
|
||||
}
|
||||
// Concatenate expansion onto top of stack.
|
||||
this.pushTokens(tokens);
|
||||
return tokens;
|
||||
return tokens.length;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,15 +329,14 @@ export default class MacroExpander implements MacroContextInterface {
|
||||
*/
|
||||
expandNextToken(): Token {
|
||||
for (;;) {
|
||||
const expanded = this.expandOnce();
|
||||
// expandOnce returns Token if and only if it's fully expanded.
|
||||
if (expanded instanceof Token) {
|
||||
if (this.expandOnce() === false) { // fully expanded
|
||||
const token = this.stack.pop();
|
||||
// the token after \noexpand is interpreted as if its meaning
|
||||
// were ‘\relax’
|
||||
if (expanded.treatAsRelax) {
|
||||
expanded.text = "\\relax";
|
||||
if (token.treatAsRelax) {
|
||||
token.text = "\\relax";
|
||||
}
|
||||
return this.stack.pop(); // === expanded
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,15 +364,15 @@ export default class MacroExpander implements MacroContextInterface {
|
||||
const oldStackLength = this.stack.length;
|
||||
this.pushTokens(tokens);
|
||||
while (this.stack.length > oldStackLength) {
|
||||
const expanded = this.expandOnce(true); // expand only expandable tokens
|
||||
// expandOnce returns Token if and only if it's fully expanded.
|
||||
if (expanded instanceof Token) {
|
||||
if (expanded.treatAsRelax) {
|
||||
// Expand only expandable tokens
|
||||
if (this.expandOnce(true) === false) { // fully expanded
|
||||
const token = this.stack.pop();
|
||||
if (token.treatAsRelax) {
|
||||
// the expansion of \noexpand is the token itself
|
||||
expanded.noexpand = false;
|
||||
expanded.treatAsRelax = false;
|
||||
token.noexpand = false;
|
||||
token.treatAsRelax = false;
|
||||
}
|
||||
output.push(this.stack.pop());
|
||||
output.push(token);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
|
@@ -35,7 +35,7 @@ export interface MacroContextInterface {
|
||||
/**
|
||||
* Expand the next token only once if possible.
|
||||
*/
|
||||
expandOnce(expandableOnly?: boolean): Token | Token[];
|
||||
expandOnce(expandableOnly?: boolean): number | boolean;
|
||||
|
||||
/**
|
||||
* Expand the next token only once (if possible), and return the resulting
|
||||
|
@@ -3499,6 +3499,20 @@ describe("A macro expander", function() {
|
||||
expect`\futurelet\foo\frac1{2+\foo}`.toParseLike`\frac1{2+1}`;
|
||||
});
|
||||
|
||||
it("macros argument can simulate \\let", () => {
|
||||
expect("\\int").toParseLike("\\int\\limits", {macros: {
|
||||
"\\Oldint": {
|
||||
tokens: [{text: "\\int", noexpand: true}],
|
||||
numArgs: 0,
|
||||
unexpandable: true,
|
||||
},
|
||||
"\\int": {
|
||||
tokens: [{text: "\\limits"}, {text: "\\Oldint"}],
|
||||
numArgs: 0,
|
||||
},
|
||||
}});
|
||||
});
|
||||
|
||||
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