mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-05 03:08:40 +00:00
update defineFunction to accept a single arg, introduce flow to do some typechecking
This commit is contained in:
committed by
Kevin Barabash
parent
a99c7c9e0f
commit
d8116bdc64
2
.babelrc
2
.babelrc
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"presets": ["es2015"],
|
||||
"presets": ["es2015", "flow"],
|
||||
"plugins": [
|
||||
"transform-runtime",
|
||||
"transform-class-properties"
|
||||
|
11
.flowconfig
Normal file
11
.flowconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
[ignore]
|
||||
<PROJECT_ROOT>/build
|
||||
<PROJECT_ROOT>/node_modules/match-at
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
|
||||
[lints]
|
||||
|
||||
[options]
|
@@ -21,12 +21,14 @@
|
||||
"babel-plugin-transform-class-properties": "^6.23.0",
|
||||
"babel-plugin-transform-runtime": "^6.15.0",
|
||||
"babel-preset-es2015": "^6.18.0",
|
||||
"babel-preset-flow": "^6.23.0",
|
||||
"babel-register": "^6.24.0",
|
||||
"babelify": "^7.3.0",
|
||||
"browserify": "^13.3.0",
|
||||
"clean-css": "^3.4.23",
|
||||
"eslint": "^3.13.0",
|
||||
"express": "^4.14.0",
|
||||
"flow-bin": "^0.53.1",
|
||||
"glob": "^7.1.1",
|
||||
"jest": "^20.0.4",
|
||||
"jest-serializer-html": "^4.0.0",
|
||||
|
@@ -1,87 +1,108 @@
|
||||
// @flow
|
||||
import functions from "./functions";
|
||||
import {groupTypes as htmlGroupTypes} from "./buildHTML";
|
||||
import {groupTypes as mathmlGroupTypes} from "./buildMathML";
|
||||
|
||||
/* This file contains a list of functions that we parse, identified by
|
||||
* the calls to defineFunction.
|
||||
*
|
||||
* The first argument to defineFunction is a single name or a list of names.
|
||||
* All functions named in such a list will share a single implementation.
|
||||
*
|
||||
* Each declared function can have associated properties, which
|
||||
* include the following:
|
||||
*
|
||||
* - numArgs: The number of arguments the function takes.
|
||||
* If this is the only property, it can be passed as a number
|
||||
* instead of an element of a properties object.
|
||||
* - argTypes: (optional) An array corresponding to each argument of the
|
||||
* function, giving the type of argument that should be parsed. Its
|
||||
* length should be equal to `numArgs + numOptionalArgs`. Valid
|
||||
* types:
|
||||
* - "size": A size-like thing, such as "1em" or "5ex"
|
||||
* - "color": An html color, like "#abc" or "blue"
|
||||
* - "original": The same type as the environment that the
|
||||
* function being parsed is in (e.g. used for the
|
||||
* bodies of functions like \textcolor where the
|
||||
* first argument is special and the second
|
||||
* argument is parsed normally)
|
||||
* Other possible types (probably shouldn't be used)
|
||||
* - "text": Text-like (e.g. \text)
|
||||
* - "math": Normal math
|
||||
* If undefined, this will be treated as an appropriate length
|
||||
* array of "original" strings
|
||||
* - greediness: (optional) 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`
|
||||
* - allowedInText: (optional) Whether or not the function is allowed inside
|
||||
* text mode (default false)
|
||||
* - numOptionalArgs: (optional) The number of optional arguments the function
|
||||
* should parse. If the optional arguments aren't found,
|
||||
* `null` will be passed to the handler in their place.
|
||||
* (default 0)
|
||||
* - infix: (optional) Must be true if the function is an infix operator.
|
||||
*
|
||||
* The last argument is that implementation, the handler for the function(s).
|
||||
* It is called to handle these functions and their arguments.
|
||||
* It receives two arguments:
|
||||
* - context contains information and references provided by the parser
|
||||
* - args is an array of arguments obtained from TeX input
|
||||
* The context contains the following properties:
|
||||
* - funcName: the text (i.e. name) of the function, including \
|
||||
* - parser: the parser object
|
||||
* - lexer: the lexer object
|
||||
* - positions: the positions in the overall string of the function
|
||||
* and the arguments.
|
||||
* The latter three should only be used to produce error messages.
|
||||
*
|
||||
* The function should return an object with the following keys:
|
||||
* - type: The type of element that this is. This is then used in
|
||||
* buildHTML/buildMathML to determine which function
|
||||
* should be called to build this node into a DOM node
|
||||
* Any other data can be added to the object, which will be passed
|
||||
* in to the function in buildHTML/buildMathML as `group.value`.
|
||||
*/
|
||||
// TODO(kevinb) use flow to define a proper type for Options
|
||||
type Options = any;
|
||||
|
||||
export default function defineFunction(
|
||||
names, props, handler, type, htmlBuilder, mathmlBuilder
|
||||
) {
|
||||
type FunctionSpec<T> = {
|
||||
// Unique string to differentiate parse nodes.
|
||||
type: string,
|
||||
|
||||
// The first argument to defineFunction is a single name or a list of names.
|
||||
// All functions named in such a list will share a single implementation.
|
||||
names: string | Array<string>,
|
||||
|
||||
// Properties that control how the functions are parsed.
|
||||
props: {
|
||||
// The number of arguments the function takes.
|
||||
numArgs?: number,
|
||||
|
||||
// An array corresponding to each argument of the function, giving the
|
||||
// type of argument that should be parsed. Its length should be equal
|
||||
// to `numArgs + numOptionalArgs`. Valid types:
|
||||
// - "size": A size-like thing, such as "1em" or "5ex"
|
||||
// - "color": An html color, like "#abc" or "blue"
|
||||
// - "original": The same type as the environment that the
|
||||
// function being parsed is in (e.g. used for the
|
||||
// bodies of functions like \textcolor where the
|
||||
// first argument is special and the second
|
||||
// argument is parsed normally)
|
||||
argTypes?: "color" | "size" | "original",
|
||||
|
||||
// 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 or not the function is allowed inside text mode
|
||||
// (default false)
|
||||
allowedInText?: boolean,
|
||||
|
||||
// (optional) The number of optional arguments the function
|
||||
// should parse. If the optional arguments aren't found,
|
||||
// `null` will be passed to the handler in their place.
|
||||
// (default 0)
|
||||
numOptionalArgs?: number,
|
||||
|
||||
// Must be true if the function is an infix operator.
|
||||
infix?: boolean,
|
||||
},
|
||||
|
||||
// The handler is called to handle these functions and their arguments.
|
||||
// It receives two arguments:
|
||||
// - context contains information and references provided by the parser
|
||||
// - args is an array of arguments obtained from TeX input
|
||||
// The context contains the following properties:
|
||||
// - funcName: the text (i.e. name) of the function, including \
|
||||
// - parser: the parser object
|
||||
// - lexer: the lexer object
|
||||
// - positions: the positions in the overall string of the function
|
||||
// and the arguments.
|
||||
// The latter three should only be used to produce error messages.
|
||||
//
|
||||
// The function should return an object with the following keys:
|
||||
// - type: The type of element that this is. This is then used in
|
||||
// buildHTML/buildMathML to determine which function
|
||||
// should be called to build this node into a DOM node
|
||||
// Any other data can be added to the object, which will be passed
|
||||
// in to the function in buildHTML/buildMathML as `group.value`.
|
||||
handler: (context: any, args: any) => T,
|
||||
|
||||
// This function returns an object representing the DOM structure to be
|
||||
// created when rendering the defined LaTeX function.
|
||||
htmlBuilder: (group: T, options: Options) => any,
|
||||
|
||||
// This function returns an object representing the MathML structure to be
|
||||
// created when rendering the defined LaTeX function.
|
||||
mathmlBuilder: (group: T, options: Options) => any,
|
||||
}
|
||||
|
||||
export default function defineFunction({
|
||||
type,
|
||||
names,
|
||||
props,
|
||||
handler,
|
||||
htmlBuilder,
|
||||
mathmlBuilder,
|
||||
}: FunctionSpec<*>) {
|
||||
if (typeof names === "string") {
|
||||
names = [names];
|
||||
}
|
||||
@@ -114,7 +135,7 @@ export default function defineFunction(
|
||||
|
||||
// Since the corresponding buildHTML/buildMathML function expects a
|
||||
// list of elements, we normalize for different kinds of arguments
|
||||
export const ordargument = function(arg) {
|
||||
export const ordargument = function(arg: any) {
|
||||
if (arg.type === "ordgroup") {
|
||||
return arg.value;
|
||||
} else {
|
||||
|
@@ -1,8 +1,13 @@
|
||||
import utils from "./utils";
|
||||
import ParseError from "./ParseError";
|
||||
import ParseNode from "./ParseNode";
|
||||
import {default as _defineFunction, ordargument} from "./defineFunction";
|
||||
|
||||
import defineFunction, {ordargument} from "./defineFunction";
|
||||
// Define a convenience function that mimcs the old semantics of defineFunction
|
||||
// to support existing code so that we can migrate it a little bit at a time.
|
||||
const defineFunction = function(names, props, handler) {
|
||||
_defineFunction({names, props, handler});
|
||||
};
|
||||
|
||||
// A normal square root
|
||||
defineFunction("\\sqrt", {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// @flow
|
||||
import buildCommon, {makeSpan} from "../buildCommon";
|
||||
import defineFunction from "../defineFunction";
|
||||
import delimiter from "../delimiter";
|
||||
@@ -54,17 +55,18 @@ const checkDelimiter = function(delim, context) {
|
||||
}
|
||||
};
|
||||
|
||||
defineFunction(
|
||||
[
|
||||
defineFunction({
|
||||
type: "delimsizing",
|
||||
names: [
|
||||
"\\bigl", "\\Bigl", "\\biggl", "\\Biggl",
|
||||
"\\bigr", "\\Bigr", "\\biggr", "\\Biggr",
|
||||
"\\bigm", "\\Bigm", "\\biggm", "\\Biggm",
|
||||
"\\big", "\\Big", "\\bigg", "\\Bigg",
|
||||
],
|
||||
{
|
||||
props: {
|
||||
numArgs: 1,
|
||||
},
|
||||
function(context, args) {
|
||||
handler: (context, args) => {
|
||||
const delim = checkDelimiter(args[0], context);
|
||||
|
||||
return {
|
||||
@@ -74,8 +76,7 @@ defineFunction(
|
||||
value: delim.value,
|
||||
};
|
||||
},
|
||||
"delimsizing",
|
||||
function(group, options) {
|
||||
htmlBuilder: (group, options) => {
|
||||
const delim = group.value.value;
|
||||
|
||||
if (delim === ".") {
|
||||
@@ -89,7 +90,7 @@ defineFunction(
|
||||
delim, group.value.size, options, group.mode,
|
||||
[group.value.mclass]);
|
||||
},
|
||||
function(group) {
|
||||
mathmlBuilder: (group) => {
|
||||
const children = [];
|
||||
|
||||
if (group.value.value !== ".") {
|
||||
@@ -111,14 +112,17 @@ defineFunction(
|
||||
|
||||
return node;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
defineFunction(
|
||||
[
|
||||
defineFunction({
|
||||
type: "leftright",
|
||||
names: [
|
||||
"\\left", "\\right",
|
||||
], {
|
||||
],
|
||||
props: {
|
||||
numArgs: 1,
|
||||
}, function(context, args) {
|
||||
},
|
||||
handler: (context, args) => {
|
||||
const delim = checkDelimiter(args[0], context);
|
||||
|
||||
// \left and \right are caught somewhere in Parser.js, which is
|
||||
@@ -128,8 +132,7 @@ defineFunction(
|
||||
value: delim.value,
|
||||
};
|
||||
},
|
||||
"leftright",
|
||||
function(group, options) {
|
||||
htmlBuilder: (group, options) => {
|
||||
// Build the inner expression
|
||||
const inner = html.buildExpression(group.value.body, options, true);
|
||||
|
||||
@@ -199,7 +202,7 @@ defineFunction(
|
||||
|
||||
return makeSpan(["minner"], inner, options);
|
||||
},
|
||||
function(group, options) {
|
||||
mathmlBuilder: (group, options) => {
|
||||
const inner = mml.buildExpression(group.value.body, options);
|
||||
|
||||
if (group.value.left !== ".") {
|
||||
@@ -224,13 +227,15 @@ defineFunction(
|
||||
|
||||
return outerNode;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
defineFunction(
|
||||
"\\middle",
|
||||
{
|
||||
defineFunction({
|
||||
type: "middle",
|
||||
names: "\\middle",
|
||||
props: {
|
||||
numArgs: 1,
|
||||
}, function(context, args) {
|
||||
},
|
||||
handler: (context, args) => {
|
||||
const delim = checkDelimiter(args[0], context);
|
||||
if (!context.parser.leftrightDepth) {
|
||||
throw new ParseError("\\middle without preceding \\left", delim);
|
||||
@@ -241,8 +246,7 @@ defineFunction(
|
||||
value: delim.value,
|
||||
};
|
||||
},
|
||||
"middle",
|
||||
function(group, options) {
|
||||
htmlBuilder: (group, options) => {
|
||||
let middleDelim;
|
||||
if (group.value.value === ".") {
|
||||
middleDelim = html.makeNullDelimiter(options, []);
|
||||
@@ -254,10 +258,10 @@ defineFunction(
|
||||
}
|
||||
return middleDelim;
|
||||
},
|
||||
function(group, options) {
|
||||
mathmlBuilder: (group, options) => {
|
||||
const middleNode = new mathMLTree.MathNode(
|
||||
"mo", [mml.makeText(group.value.middle, group.mode)]);
|
||||
middleNode.setAttribute("fence", "true");
|
||||
return middleNode;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// @flow
|
||||
import defineFunction, {ordargument} from "../defineFunction";
|
||||
import buildCommon from "../buildCommon";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
@@ -5,20 +6,20 @@ import mathMLTree from "../mathMLTree";
|
||||
import * as html from "../buildHTML";
|
||||
import * as mml from "../buildMathML";
|
||||
|
||||
defineFunction(
|
||||
"\\phantom",
|
||||
{
|
||||
defineFunction({
|
||||
type: "phantom",
|
||||
names: "\\phantom",
|
||||
props: {
|
||||
numArgs: 1,
|
||||
},
|
||||
(context, args) => {
|
||||
handler: (context, args) => {
|
||||
const body = args[0];
|
||||
return {
|
||||
type: "phantom",
|
||||
value: ordargument(body),
|
||||
};
|
||||
},
|
||||
"phantom",
|
||||
(group, options) => {
|
||||
htmlBuilder: (group, options) => {
|
||||
const elements = html.buildExpression(
|
||||
group.value.value,
|
||||
options.withPhantom(),
|
||||
@@ -29,18 +30,19 @@ defineFunction(
|
||||
// See "color" for more details.
|
||||
return new buildCommon.makeFragment(elements);
|
||||
},
|
||||
(group, options) => {
|
||||
mathmlBuilder: (group, options) => {
|
||||
const inner = mml.buildExpression(group.value.value, options);
|
||||
return new mathMLTree.MathNode("mphantom", inner);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
defineFunction(
|
||||
"\\hphantom",
|
||||
{
|
||||
defineFunction({
|
||||
type: "hphantom",
|
||||
names: "\\hphantom",
|
||||
props: {
|
||||
numArgs: 1,
|
||||
},
|
||||
(context, args) => {
|
||||
handler: (context, args) => {
|
||||
const body = args[0];
|
||||
return {
|
||||
type: "hphantom",
|
||||
@@ -48,8 +50,7 @@ defineFunction(
|
||||
body: body,
|
||||
};
|
||||
},
|
||||
"hphantom",
|
||||
(group, options) => {
|
||||
htmlBuilder: (group, options) => {
|
||||
let node = buildCommon.makeSpan(
|
||||
[], [html.buildGroup(group.value.body, options.withPhantom())]);
|
||||
node.height = 0;
|
||||
@@ -68,20 +69,21 @@ defineFunction(
|
||||
|
||||
return node;
|
||||
},
|
||||
(group, options) => {
|
||||
mathmlBuilder: (group, options) => {
|
||||
const inner = mml.buildExpression(group.value.value, options);
|
||||
const node = new mathMLTree.MathNode("mphantom", inner);
|
||||
node.setAttribute("height", "0px");
|
||||
return node;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
defineFunction(
|
||||
"\\vphantom",
|
||||
{
|
||||
defineFunction({
|
||||
type: "vphantom",
|
||||
names: "\\vphantom",
|
||||
props: {
|
||||
numArgs: 1,
|
||||
},
|
||||
(context, args) => {
|
||||
handler: (context, args) => {
|
||||
const body = args[0];
|
||||
return {
|
||||
type: "vphantom",
|
||||
@@ -89,8 +91,7 @@ defineFunction(
|
||||
body: body,
|
||||
};
|
||||
},
|
||||
"vphantom",
|
||||
(group, options) => {
|
||||
htmlBuilder: (group, options) => {
|
||||
const inner = buildCommon.makeSpan(
|
||||
["inner"],
|
||||
[html.buildGroup(group.value.body, options.withPhantom())]);
|
||||
@@ -98,11 +99,10 @@ defineFunction(
|
||||
return buildCommon.makeSpan(
|
||||
["mord", "rlap"], [inner, fix], options);
|
||||
},
|
||||
(group, options) => {
|
||||
mathmlBuilder: (group, options) => {
|
||||
const inner = mml.buildExpression(group.value.value, options);
|
||||
const node = new mathMLTree.MathNode("mphantom", inner);
|
||||
node.setAttribute("width", "0px");
|
||||
return node;
|
||||
},
|
||||
);
|
||||
|
||||
});
|
||||
|
Reference in New Issue
Block a user