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:
ylemkimon
2019-12-05 10:07:20 +09:00
committed by Kevin Barabash
parent 0d8830af30
commit b1eeeecf91
8 changed files with 104 additions and 57 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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
View 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,
};
},
});

View File

@@ -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}

View File

@@ -302,6 +302,11 @@ type ParseNodeTypes = {
size?: Measurement,
token: ?Token,
|},
"internal": {|
type: "internal",
mode: Mode,
loc?: ?SourceLocation,
|},
"kern": {|
type: "kern",
mode: Mode,

View File

@@ -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}));
});