mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-08 04:28:41 +00:00
Move \global and \def to functions (#2138)
* Add parseNode type `internal` * Move \def and \newcommand to functions * Fix Flow error * Separate \global Move \newcommand back to macros for now * Rename assignment.js to def.js * Update test cases * Add comments * Update a test case
This commit is contained in:
committed by
Kevin Barabash
parent
0d8830af30
commit
b1eeeecf91
@@ -51,6 +51,11 @@ spread out over three different files [functions.js](src/functions.js),
|
||||
[buildHTML.js](src/buildHTML.js), [buildMathML.js](src/buildMathML.js) into a
|
||||
single file. The goal is to have all functions use this new system.
|
||||
|
||||
#### Macros
|
||||
|
||||
Macros should be added in [src/macros.js](src/macros.js) using `defineMacro`.
|
||||
They are expanded in the "gullet" (`MacroExpander`).
|
||||
|
||||
## Testing
|
||||
|
||||
Local testing can be done by running the webpack-dev-server using configuration
|
||||
|
@@ -655,6 +655,11 @@ const handleObject = (
|
||||
break;
|
||||
}
|
||||
|
||||
case "internal": {
|
||||
// internal nodes are never included in the parse tree
|
||||
break;
|
||||
}
|
||||
|
||||
case "html": {
|
||||
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||
break;
|
||||
|
@@ -184,6 +184,8 @@ export default class Parser {
|
||||
const atom = this.parseAtom(breakOnTokenText);
|
||||
if (!atom) {
|
||||
break;
|
||||
} else if (atom.type === "internal") {
|
||||
continue;
|
||||
}
|
||||
body.push(atom);
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ import "./functions/arrow";
|
||||
import "./functions/char";
|
||||
import "./functions/color";
|
||||
import "./functions/cr";
|
||||
import "./functions/def";
|
||||
import "./functions/delimsizing";
|
||||
import "./functions/enclose";
|
||||
import "./functions/environment";
|
||||
|
79
src/functions/def.js
Normal file
79
src/functions/def.js
Normal file
@@ -0,0 +1,79 @@
|
||||
//@flow
|
||||
import defineFunction from "../defineFunction";
|
||||
import ParseError from "../ParseError";
|
||||
import {assertNodeType} from "../parseNode";
|
||||
|
||||
const globalMap = {
|
||||
"\\def": "\\gdef",
|
||||
"\\gdef": "\\gdef",
|
||||
};
|
||||
|
||||
// Basic support for macro definitions:
|
||||
// \def\macro{expansion}
|
||||
// \def\macro#1{expansion}
|
||||
// \def\macro#1#2{expansion}
|
||||
// \def\macro#1#2#3#4#5#6#7#8#9{expansion}
|
||||
// Also the \gdef and \global\def equivalents
|
||||
defineFunction({
|
||||
type: "internal",
|
||||
names: ["\\global"],
|
||||
props: {
|
||||
numArgs: 0,
|
||||
allowedInText: true,
|
||||
},
|
||||
handler({parser}) {
|
||||
parser.consumeSpaces();
|
||||
const token = parser.fetch();
|
||||
if (globalMap[token.text]) {
|
||||
token.text = globalMap[token.text];
|
||||
return assertNodeType(parser.parseFunction(), "internal");
|
||||
}
|
||||
throw new ParseError(`Invalid token after \\global`, token);
|
||||
},
|
||||
});
|
||||
|
||||
defineFunction({
|
||||
type: "internal",
|
||||
names: ["\\def", "\\gdef"],
|
||||
props: {
|
||||
numArgs: 0,
|
||||
allowedInText: true,
|
||||
},
|
||||
handler({parser, funcName}) {
|
||||
let arg = parser.gullet.consumeArgs(1)[0];
|
||||
if (arg.length !== 1) {
|
||||
throw new ParseError("\\gdef's first argument must be a macro name");
|
||||
}
|
||||
const name = arg[0].text;
|
||||
// Count argument specifiers, and check they are in the order #1 #2 ...
|
||||
let numArgs = 0;
|
||||
arg = parser.gullet.consumeArgs(1)[0];
|
||||
while (arg.length === 1 && arg[0].text === "#") {
|
||||
arg = parser.gullet.consumeArgs(1)[0];
|
||||
if (arg.length !== 1) {
|
||||
throw new ParseError(
|
||||
`Invalid argument number length "${arg.length}"`);
|
||||
}
|
||||
if (!(/^[1-9]$/.test(arg[0].text))) {
|
||||
throw new ParseError(
|
||||
`Invalid argument number "${arg[0].text}"`);
|
||||
}
|
||||
numArgs++;
|
||||
if (parseInt(arg[0].text) !== numArgs) {
|
||||
throw new ParseError(
|
||||
`Argument number "${arg[0].text}" out of order`);
|
||||
}
|
||||
arg = parser.gullet.consumeArgs(1)[0];
|
||||
}
|
||||
// Final arg is the expansion of the macro
|
||||
parser.gullet.macros.set(name, {
|
||||
tokens: arg,
|
||||
numArgs,
|
||||
}, funcName === "\\gdef");
|
||||
|
||||
return {
|
||||
type: "internal",
|
||||
mode: parser.mode,
|
||||
};
|
||||
},
|
||||
});
|
@@ -199,59 +199,6 @@ defineMacro("\\char", function(context) {
|
||||
return `\\@char{${number}}`;
|
||||
});
|
||||
|
||||
// Basic support for macro definitions:
|
||||
// \def\macro{expansion}
|
||||
// \def\macro#1{expansion}
|
||||
// \def\macro#1#2{expansion}
|
||||
// \def\macro#1#2#3#4#5#6#7#8#9{expansion}
|
||||
// Also the \gdef and \global\def equivalents
|
||||
const def = (context, global: boolean) => {
|
||||
let arg = context.consumeArgs(1)[0];
|
||||
if (arg.length !== 1) {
|
||||
throw new ParseError("\\gdef's first argument must be a macro name");
|
||||
}
|
||||
const name = arg[0].text;
|
||||
// Count argument specifiers, and check they are in the order #1 #2 ...
|
||||
let numArgs = 0;
|
||||
arg = context.consumeArgs(1)[0];
|
||||
while (arg.length === 1 && arg[0].text === "#") {
|
||||
arg = context.consumeArgs(1)[0];
|
||||
if (arg.length !== 1) {
|
||||
throw new ParseError(`Invalid argument number length "${arg.length}"`);
|
||||
}
|
||||
if (!(/^[1-9]$/.test(arg[0].text))) {
|
||||
throw new ParseError(`Invalid argument number "${arg[0].text}"`);
|
||||
}
|
||||
numArgs++;
|
||||
if (parseInt(arg[0].text) !== numArgs) {
|
||||
throw new ParseError(`Argument number "${arg[0].text}" out of order`);
|
||||
}
|
||||
arg = context.consumeArgs(1)[0];
|
||||
}
|
||||
// Final arg is the expansion of the macro
|
||||
context.macros.set(name, {
|
||||
tokens: arg,
|
||||
numArgs,
|
||||
}, global);
|
||||
return '';
|
||||
};
|
||||
defineMacro("\\gdef", (context) => def(context, true));
|
||||
defineMacro("\\def", (context) => def(context, false));
|
||||
defineMacro("\\global", (context) => {
|
||||
const next = context.consumeArgs(1)[0];
|
||||
if (next.length !== 1) {
|
||||
throw new ParseError("Invalid command after \\global");
|
||||
}
|
||||
const command = next[0].text;
|
||||
// TODO: Should expand command
|
||||
if (command === "\\def") {
|
||||
// \global\def is equivalent to \gdef
|
||||
return def(context, true);
|
||||
} else {
|
||||
throw new ParseError(`Invalid command '${command}' after \\global`);
|
||||
}
|
||||
});
|
||||
|
||||
// \newcommand{\macro}[args]{definition}
|
||||
// \renewcommand{\macro}[args]{definition}
|
||||
// TODO: Optional arguments: \newcommand{\macro}[args][default]{definition}
|
||||
|
@@ -302,6 +302,11 @@ type ParseNodeTypes = {
|
||||
size?: Measurement,
|
||||
token: ?Token,
|
||||
|},
|
||||
"internal": {|
|
||||
type: "internal",
|
||||
mode: Mode,
|
||||
loc?: ?SourceLocation,
|
||||
|},
|
||||
"kern": {|
|
||||
type: "kern",
|
||||
mode: Mode,
|
||||
|
@@ -3156,6 +3156,11 @@ describe("A macro expander", function() {
|
||||
//expect`\gdef{\foo}{}`.not.toParse();
|
||||
});
|
||||
|
||||
it("\\def should be handled in Parser", () => {
|
||||
expect`\gdef\foo{1}`.toParse(new Settings({maxExpand: 0}));
|
||||
expect`2^\def\foo{1}2`.not.toParse();
|
||||
});
|
||||
|
||||
it("\\def works locally", () => {
|
||||
expect("\\def\\x{1}\\x{\\def\\x{2}\\x{\\def\\x{3}\\x}\\x}\\x")
|
||||
.toParseLike`1{2{3}2}1`;
|
||||
@@ -3174,8 +3179,7 @@ describe("A macro expander", function() {
|
||||
|
||||
it("\\global needs to followed by \\def", () => {
|
||||
expect`\global\def\foo{}\foo`.toParseLike``;
|
||||
// TODO: This doesn't work yet; \global needs to expand argument.
|
||||
//expect`\def\DEF{\def}\global\DEF\foo{}\foo`.toParseLike``;
|
||||
expect`\def\DEF{\def}\global\DEF\foo{}\foo`.toParseLike``;
|
||||
expect`\global\foo`.not.toParse();
|
||||
expect`\global\bar x`.not.toParse();
|
||||
});
|
||||
@@ -3563,8 +3567,7 @@ describe("The maxSize setting", function() {
|
||||
describe("The maxExpand setting", () => {
|
||||
it("should prevent expansion", () => {
|
||||
expect`\gdef\foo{1}\foo`.toParse();
|
||||
expect`\gdef\foo{1}\foo`.toParse(new Settings({maxExpand: 2}));
|
||||
expect`\gdef\foo{1}\foo`.not.toParse(new Settings({maxExpand: 1}));
|
||||
expect`\gdef\foo{1}\foo`.toParse(new Settings({maxExpand: 1}));
|
||||
expect`\gdef\foo{1}\foo`.not.toParse(new Settings({maxExpand: 0}));
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user