mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-07 20:28:38 +00:00
feat(function): add allowedInArgument
instead of greediness
property (#2134)
* Remove `greediness` property Use boolean `grouped` property instead, which is more consistent with LaTeX. BREAKING CHANGE: \cfrac, \color, \textcolor, \colorbox, \fcolorbox are no longer grouped. * Rename grouped to allowedInArgument * Reenable tests * Update documentation * Fix typo Co-authored-by: Kevin Barabash <kevinb@khanacademy.org>
This commit is contained in:
@@ -9,8 +9,8 @@ some gaps between KaTeX and LaTeX and therefore there may be breaking changes.
|
||||
## Macro arguments
|
||||
Tokens will not be expanded while parsing a macro argument. For example, `\frac\foo\foo`,
|
||||
where the `\foo` is defined as `12`, will be parsed as `\frac{12}{12}`, not
|
||||
`\frac{1}{2}12`. <!--To expand the argument before parsing, `\expandafter` can
|
||||
be used` like `\expandafter\frac\foo\foo`.-->
|
||||
`\frac{1}{2}12`. To expand the argument before parsing, `\expandafter` can
|
||||
be used like `\expandafter\frac\foo\foo`.
|
||||
|
||||
## `\def`
|
||||
`\def` no longer accepts a control sequence enclosed in braces. For example,
|
||||
@@ -22,3 +22,9 @@ It also no longer accepts replacement text not enclosed in braces. For example,
|
||||
## `\newline` and `\cr`
|
||||
`\newline` and `\cr` no longer takes an optional size argument. To specify vertical
|
||||
spacing, `\\` should be used.
|
||||
|
||||
## `\cfrac`, `\color`, `\textcolor`, `\colorbox`, `\fcolorbox`
|
||||
They are no longer allowed as an argument to primitive commands, such as `\sqrt`
|
||||
(without the optional argument) and super/subscript. For example,
|
||||
`\sqrt\textcolor{red}{x}` no longer works and should be changed to
|
||||
`\sqrt{\textcolor{red}{x}}`.
|
||||
|
@@ -247,9 +247,6 @@ export default class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
// The greediness of a superscript or subscript
|
||||
static SUPSUB_GREEDINESS = 1;
|
||||
|
||||
/**
|
||||
* Handle a subscript or superscript with nice errors.
|
||||
*/
|
||||
@@ -260,7 +257,7 @@ export default class Parser {
|
||||
const symbol = symbolToken.text;
|
||||
this.consume();
|
||||
this.consumeSpaces(); // ignore spaces before sup/subscript argument
|
||||
const group = this.parseGroup(name, Parser.SUPSUB_GREEDINESS);
|
||||
const group = this.parseGroup(name);
|
||||
|
||||
if (!group) {
|
||||
throw new ParseError(
|
||||
@@ -305,7 +302,7 @@ export default class Parser {
|
||||
parseAtom(breakOnTokenText?: BreakToken): ?AnyParseNode {
|
||||
// The body of an atom is an implicit group, so that things like
|
||||
// \left(x\right)^2 work correctly.
|
||||
const base = this.parseGroup("atom", null, breakOnTokenText);
|
||||
const base = this.parseGroup("atom", breakOnTokenText);
|
||||
|
||||
// In text mode, we don't have superscripts or subscripts
|
||||
if (this.mode === "text") {
|
||||
@@ -402,8 +399,7 @@ export default class Parser {
|
||||
*/
|
||||
parseFunction(
|
||||
breakOnTokenText?: BreakToken,
|
||||
name?: string, // For error reporting.
|
||||
greediness?: ?number,
|
||||
name?: string, // For determining its context
|
||||
): ?AnyParseNode {
|
||||
const token = this.fetch();
|
||||
const func = token.text;
|
||||
@@ -413,7 +409,7 @@ export default class Parser {
|
||||
}
|
||||
this.consume(); // consume command token
|
||||
|
||||
if (greediness != null && funcData.greediness <= greediness) {
|
||||
if (name && name !== "atom" && !funcData.allowedInArgument) {
|
||||
throw new ParseError(
|
||||
"Got function '" + func + "' with no arguments" +
|
||||
(name ? " as " + name : ""), token);
|
||||
@@ -468,7 +464,6 @@ export default class Parser {
|
||||
return {args: [], optArgs: []};
|
||||
}
|
||||
|
||||
const baseGreediness = funcData.greediness;
|
||||
const args = [];
|
||||
const optArgs = [];
|
||||
|
||||
@@ -483,7 +478,7 @@ export default class Parser {
|
||||
}
|
||||
|
||||
const arg = this.parseGroupOfType(`argument to '${func}'`,
|
||||
argType, isOptional, baseGreediness);
|
||||
argType, isOptional);
|
||||
if (isOptional) {
|
||||
optArgs.push(arg);
|
||||
} else if (arg != null) {
|
||||
@@ -503,7 +498,6 @@ export default class Parser {
|
||||
name: string,
|
||||
type: ?ArgType,
|
||||
optional: boolean,
|
||||
greediness: ?number,
|
||||
): ?AnyParseNode {
|
||||
switch (type) {
|
||||
case "color":
|
||||
@@ -538,7 +532,7 @@ export default class Parser {
|
||||
if (optional) {
|
||||
throw new ParseError("A primitive argument cannot be optional");
|
||||
}
|
||||
const group = this.parseGroup(name, greediness);
|
||||
const group = this.parseGroup(name);
|
||||
if (group == null) {
|
||||
throw new ParseError("Expected group as " + name, this.fetch());
|
||||
}
|
||||
@@ -744,7 +738,6 @@ export default class Parser {
|
||||
*/
|
||||
parseGroup(
|
||||
name: string, // For error reporting.
|
||||
greediness?: ?number,
|
||||
breakOnTokenText?: BreakToken,
|
||||
): ?AnyParseNode {
|
||||
const firstToken = this.fetch();
|
||||
@@ -776,7 +769,7 @@ export default class Parser {
|
||||
} else {
|
||||
// If there exists a function with this name, parse the function.
|
||||
// Otherwise, just return a nucleus
|
||||
result = this.parseFunction(breakOnTokenText, name, greediness) ||
|
||||
result = this.parseFunction(breakOnTokenText, name) ||
|
||||
this.parseSymbol();
|
||||
if (result == null && text[0] === "\\" &&
|
||||
!implicitCommands.hasOwnProperty(text)) {
|
||||
|
@@ -52,7 +52,6 @@ export type EnvSpec<NODETYPE: NodeType> = {|
|
||||
type: NODETYPE, // Need to use the type to avoid error. See NOTES below.
|
||||
numArgs: number,
|
||||
argTypes?: ArgType[],
|
||||
greediness: number,
|
||||
allowedInText: boolean,
|
||||
numOptionalArgs: number,
|
||||
handler: EnvHandler,
|
||||
@@ -99,7 +98,6 @@ export default function defineEnvironment<NODETYPE: NodeType>({
|
||||
const data = {
|
||||
type,
|
||||
numArgs: props.numArgs || 0,
|
||||
greediness: 1,
|
||||
allowedInText: false,
|
||||
numOptionalArgs: 0,
|
||||
handler,
|
||||
|
@@ -46,26 +46,10 @@ export type FunctionPropSpec = {
|
||||
// should appear before types for mandatory arguments.
|
||||
argTypes?: ArgType[],
|
||||
|
||||
// The greediness of the function to use ungrouped arguments.
|
||||
//
|
||||
// E.g. if you have an expression
|
||||
// \sqrt \frac 1 2
|
||||
// since \frac has greediness=2 vs \sqrt's greediness=1, \frac
|
||||
// will use the two arguments '1' and '2' as its two arguments,
|
||||
// then that whole function will be used as the argument to
|
||||
// \sqrt. On the other hand, the expressions
|
||||
// \frac \frac 1 2 3
|
||||
// and
|
||||
// \frac \sqrt 1 2
|
||||
// will fail because \frac and \frac have equal greediness
|
||||
// and \sqrt has a lower greediness than \frac respectively. To
|
||||
// make these parse, we would have to change them to:
|
||||
// \frac {\frac 1 2} 3
|
||||
// and
|
||||
// \frac {\sqrt 1} 2
|
||||
//
|
||||
// The default value is `1`
|
||||
greediness?: number,
|
||||
// Whether it expands to a single token or a braced group of tokens.
|
||||
// If it's grouped, it can be used as an argument to primitive commands,
|
||||
// such as \sqrt (without the optional argument) and super/subscript.
|
||||
allowedInArgument?: boolean,
|
||||
|
||||
// Whether or not the function is allowed inside text mode
|
||||
// (default false)
|
||||
@@ -126,7 +110,7 @@ export type FunctionSpec<NODETYPE: NodeType> = {|
|
||||
type: NODETYPE, // Need to use the type to avoid error. See NOTES below.
|
||||
numArgs: number,
|
||||
argTypes?: ArgType[],
|
||||
greediness: number,
|
||||
allowedInArgument: boolean,
|
||||
allowedInText: boolean,
|
||||
allowedInMath: boolean,
|
||||
numOptionalArgs: number,
|
||||
@@ -183,7 +167,7 @@ export default function defineFunction<NODETYPE: NodeType>({
|
||||
type,
|
||||
numArgs: props.numArgs,
|
||||
argTypes: props.argTypes,
|
||||
greediness: (props.greediness === undefined) ? 1 : props.greediness,
|
||||
allowedInArgument: !!props.allowedInArgument,
|
||||
allowedInText: !!props.allowedInText,
|
||||
allowedInMath: (props.allowedInMath === undefined)
|
||||
? true
|
||||
|
@@ -38,7 +38,6 @@ defineFunction({
|
||||
props: {
|
||||
numArgs: 2,
|
||||
allowedInText: true,
|
||||
greediness: 3,
|
||||
argTypes: ["color", "original"],
|
||||
},
|
||||
handler({parser}, args) {
|
||||
@@ -61,7 +60,6 @@ defineFunction({
|
||||
props: {
|
||||
numArgs: 1,
|
||||
allowedInText: true,
|
||||
greediness: 3,
|
||||
argTypes: ["color"],
|
||||
},
|
||||
handler({parser, breakOnTokenText}, args) {
|
||||
|
@@ -225,7 +225,6 @@ defineFunction({
|
||||
props: {
|
||||
numArgs: 2,
|
||||
allowedInText: true,
|
||||
greediness: 3,
|
||||
argTypes: ["color", "text"],
|
||||
},
|
||||
handler({parser, funcName}, args, optArgs) {
|
||||
@@ -249,7 +248,6 @@ defineFunction({
|
||||
props: {
|
||||
numArgs: 3,
|
||||
allowedInText: true,
|
||||
greediness: 3,
|
||||
argTypes: ["color", "color", "text"],
|
||||
},
|
||||
handler({parser, funcName}, args, optArgs) {
|
||||
|
@@ -44,7 +44,7 @@ defineFunction({
|
||||
],
|
||||
props: {
|
||||
numArgs: 1,
|
||||
greediness: 2,
|
||||
allowedInArgument: true,
|
||||
},
|
||||
handler: ({parser, funcName}, args) => {
|
||||
const body = normalizeArgument(args[0]);
|
||||
@@ -68,7 +68,6 @@ defineFunction({
|
||||
names: ["\\boldsymbol", "\\bm"],
|
||||
props: {
|
||||
numArgs: 1,
|
||||
greediness: 2,
|
||||
},
|
||||
handler: ({parser}, args) => {
|
||||
const body = args[0];
|
||||
|
@@ -241,14 +241,14 @@ const mathmlBuilder = (group, options) => {
|
||||
defineFunction({
|
||||
type: "genfrac",
|
||||
names: [
|
||||
"\\cfrac", "\\dfrac", "\\frac", "\\tfrac",
|
||||
"\\dfrac", "\\frac", "\\tfrac",
|
||||
"\\dbinom", "\\binom", "\\tbinom",
|
||||
"\\\\atopfrac", // can’t be entered directly
|
||||
"\\\\bracefrac", "\\\\brackfrac", // ditto
|
||||
],
|
||||
props: {
|
||||
numArgs: 2,
|
||||
greediness: 2,
|
||||
allowedInArgument: true,
|
||||
},
|
||||
handler: ({parser, funcName}, args) => {
|
||||
const numer = args[0];
|
||||
@@ -259,7 +259,6 @@ defineFunction({
|
||||
let size = "auto";
|
||||
|
||||
switch (funcName) {
|
||||
case "\\cfrac":
|
||||
case "\\dfrac":
|
||||
case "\\frac":
|
||||
case "\\tfrac":
|
||||
@@ -290,7 +289,6 @@ defineFunction({
|
||||
}
|
||||
|
||||
switch (funcName) {
|
||||
case "\\cfrac":
|
||||
case "\\dfrac":
|
||||
case "\\dbinom":
|
||||
size = "display";
|
||||
@@ -304,7 +302,7 @@ defineFunction({
|
||||
return {
|
||||
type: "genfrac",
|
||||
mode: parser.mode,
|
||||
continued: funcName === "\\cfrac",
|
||||
continued: false,
|
||||
numer,
|
||||
denom,
|
||||
hasBarLine,
|
||||
@@ -319,6 +317,31 @@ defineFunction({
|
||||
mathmlBuilder,
|
||||
});
|
||||
|
||||
defineFunction({
|
||||
type: "genfrac",
|
||||
names: ["\\cfrac"],
|
||||
props: {
|
||||
numArgs: 2,
|
||||
},
|
||||
handler: ({parser, funcName}, args) => {
|
||||
const numer = args[0];
|
||||
const denom = args[1];
|
||||
|
||||
return {
|
||||
type: "genfrac",
|
||||
mode: parser.mode,
|
||||
continued: true,
|
||||
numer,
|
||||
denom,
|
||||
hasBarLine: true,
|
||||
leftDelim: null,
|
||||
rightDelim: null,
|
||||
size: "display",
|
||||
barSize: null,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Infix generalized fractions -- these are not rendered directly, but replaced
|
||||
// immediately by one of the variants above.
|
||||
defineFunction({
|
||||
@@ -374,7 +397,7 @@ defineFunction({
|
||||
names: ["\\genfrac"],
|
||||
props: {
|
||||
numArgs: 6,
|
||||
greediness: 6,
|
||||
allowedInArgument: true,
|
||||
argTypes: ["math", "math", "size", "text", "math", "math"],
|
||||
},
|
||||
handler({parser}, args) {
|
||||
|
@@ -48,7 +48,7 @@ defineFunction({
|
||||
props: {
|
||||
numArgs: 1,
|
||||
argTypes: ["text"],
|
||||
greediness: 2,
|
||||
allowedInArgument: true,
|
||||
allowedInText: true,
|
||||
},
|
||||
handler({parser, funcName}, args) {
|
||||
|
@@ -849,13 +849,6 @@ describe("A color parser", function() {
|
||||
expect(newColorExpression).toParse();
|
||||
});
|
||||
|
||||
it("should have correct greediness", function() {
|
||||
expect`\textcolor{red}a`.toParse();
|
||||
expect`\textcolor{red}{\text{a}}`.toParse();
|
||||
expect`\textcolor{red}\text{a}`.not.toParse();
|
||||
expect`\textcolor{red}\frac12`.not.toParse();
|
||||
});
|
||||
|
||||
it("should use one-argument \\color by default", function() {
|
||||
expect(oldColorExpression).toParseLike`\textcolor{#fA6}{xy}`;
|
||||
});
|
||||
@@ -1619,7 +1612,7 @@ describe("A font parser", function() {
|
||||
expect(bf.body.body[2].text).toEqual("c");
|
||||
});
|
||||
|
||||
it("should have the correct greediness", function() {
|
||||
it("should be allowed in the argument", function() {
|
||||
expect`e^\mathbf{x}`.toParse();
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user