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
fix: Support \let
via macros
option
#3738
Conversation
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
Codecov Report
@@ Coverage Diff @@
## main #3738 +/- ##
=======================================
Coverage 92.98% 92.98%
=======================================
Files 91 91
Lines 6770 6770
Branches 1574 1574
=======================================
Hits 6295 6295
Misses 437 437
Partials 38 38
Continue to review full report in Codecov by Sentry.
|
Would you be willing to consider an alternate approach? In the Temml library, I have exposed an additional function,
As you can see, the argument is a string containing macros written as they would be written in a document. The function returns a
As you can see, the function uses the existing parser to do the work. No need to reinvent a new wheel. If you look very closely, you can see that the call to After the parser has parsed the top-level expression, it checks if it is engaged in a preamble. If so, it extracts the macros and executes a quick return.
That’s about it. Temml does not have a |
@ronkok KaTeX already has the equivalent of const macros = {}
katex.renderToString(`...\let\foo=bar...`, {macros, globalGroup: true}) (The main motivation for It may be nice to add a helper for this, but at least it's possible. Probably we should document this approach at least. I still think this PR is an improvement over the current code, making the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the code changes look good. I've proposed some re-wording of the docs for macros.
docs/options.md
Outdated
- 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 `\global\let` (or via `\def` or `\newcommand` or `\let` when using `globalGroup`), which enables consecutive calls to KaTeX to share state. | ||
- Expansions can also be objects 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}}`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to propose an edited version of the macros
entry, with an eye to toward less experienced webpage administrators.
-
macros
:object
. A collection of custom macros.-
Each macro is a key-value pair in which the key is a new KaTeX function name and the value is the expansion of the macro. Example:
macros: {"\\R": "\\mathbb{R}"}
. -
To be more precise, 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 ofMacroExpander
as first argument and returns the expansion as a string.MacroExpander
is an internal API and subject to non-backwards compatible changes. Seesrc/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).
-
The
macros
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 usingglobalGroup
). This enables macro definitions to persist between calls to KaTeX. -
Expansions can also be objects matching an internal
MacroExpansion
specification, 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}}
.
-
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the comments, and sorry for taking so long to get to this. I just took a revision pass inspired by your feedback. Let me know if you have further comments, or I'll merge later today. (We can always edit further in another PR.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The macros entry looks much better. This is good to go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's good code. Let's go with it.
🎉 This PR is included in version 0.16.6 🎉 The release is available on: Your semantic-release bot 📦🚀 |
What is the previous behavior before this PR?
As described in #3737,
katex.renderToString("\\int", {macros: {"\\Oldint":{"tokens":[{"text":"\\int","noexpand":true}],"numArgs":0,"unexpandable":false},"\\int":{"tokens":[{"text":"\\limits"},{"text":"\\Oldint"}],"numArgs":0,"delimiters":[[]]}}})
results in an infinite loop.What is the new behavior after this PR?
Fixes #3737
The issue turned out to be how we handled the return value of
expandOnce
. We assumed that, if the return value isn't anArray
, it's aninstanceof Token
. This isn't necessary true with a user'smacros
object, and given that we don't currently exportToken
, it's pretty difficult to bypass.Given that we never actually use the array return values from
expandOnce
, I changed the return value forexpandOnce
to either anumber
(to indicate the number of expanded tokens, so you could still look up the tokens in the stack if you wanted to) orfalse
(to indicate no expansion happened). We can't use0
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
.