forked from KaTeX/KaTeX
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdefineFunction.js
223 lines (195 loc) · 7.6 KB
/
defineFunction.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// @flow
import type Parser from "./Parser";
import type {ParseNode, AnyParseNode, NodeType, UnsupportedCmdParseNode}
from "./parseNode";
import type Options from "./Options";
import type {ArgType, BreakToken} from "./types";
import type {HtmlDomNode} from "./domTree";
import type {Token} from "./Token";
import type {MathDomNode} from "./mathMLTree";
/** Context provided to function handlers for error messages. */
export type FunctionContext = {|
funcName: string,
parser: Parser,
token?: Token,
breakOnTokenText?: BreakToken,
|};
export type FunctionHandler<NODETYPE: NodeType> = (
context: FunctionContext,
args: AnyParseNode[],
optArgs: (?AnyParseNode)[],
) => UnsupportedCmdParseNode | ParseNode<NODETYPE>;
// Note: reverse the order of the return type union will cause a flow error.
// See https://github.com/facebook/flow/issues/3663.
export type HtmlBuilder<NODETYPE> = (ParseNode<NODETYPE>, Options) => HtmlDomNode;
export type MathMLBuilder<NODETYPE> = (
group: ParseNode<NODETYPE>,
options: Options,
) => MathDomNode;
// More general version of `HtmlBuilder` for nodes (e.g. \sum, accent types)
// whose presence impacts super/subscripting. In this case, ParseNode<"supsub">
// delegates its HTML building to the HtmlBuilder corresponding to these nodes.
export type HtmlBuilderSupSub<NODETYPE> =
(ParseNode<"supsub"> | ParseNode<NODETYPE>, Options) => HtmlDomNode;
export type FunctionPropSpec = {
// 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 `numOptionalArgs + numArgs`, and types for optional arguments
// should appear before types for mandatory arguments.
argTypes?: ArgType[],
// 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)
allowedInText?: boolean,
// Whether or not the function is allowed inside text mode
// (default true)
allowedInMath?: 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,
// Whether or not the function is a TeX primitive.
primitive?: boolean,
};
type FunctionDefSpec<NODETYPE: NodeType> = {|
// Unique string to differentiate parse nodes.
// Also determines the type of the value returned by `handler`.
type: NODETYPE,
// 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: Array<string>,
// Properties that control how the functions are parsed.
props: FunctionPropSpec,
// The handler is called to handle these functions and their arguments and
// returns a `ParseNode`.
handler: ?FunctionHandler<NODETYPE>,
// This function returns an object representing the DOM structure to be
// created when rendering the defined LaTeX function.
// This should not modify the `ParseNode`.
htmlBuilder?: HtmlBuilder<NODETYPE>,
// This function returns an object representing the MathML structure to be
// created when rendering the defined LaTeX function.
// This should not modify the `ParseNode`.
mathmlBuilder?: MathMLBuilder<NODETYPE>,
|};
/**
* Final function spec for use at parse time.
* This is almost identical to `FunctionPropSpec`, except it
* 1. includes the function handler, and
* 2. requires all arguments except argTypes.
* It is generated by `defineFunction()` below.
*/
export type FunctionSpec<NODETYPE: NodeType> = {|
type: NODETYPE, // Need to use the type to avoid error. See NOTES below.
numArgs: number,
argTypes?: ArgType[],
allowedInArgument: boolean,
allowedInText: boolean,
allowedInMath: boolean,
numOptionalArgs: number,
infix: boolean,
primitive: boolean,
// FLOW TYPE NOTES: Doing either one of the following two
//
// - removing the NODETYPE type parameter in FunctionSpec above;
// - using ?FunctionHandler<NODETYPE> below;
//
// results in a confusing flow typing error:
// "string literal `styling`. This type is incompatible with..."
// pointing to the definition of `defineFunction` and finishing with
// "some incompatible instantiation of `NODETYPE`"
//
// Having FunctionSpec<NODETYPE> above and FunctionHandler<*> below seems to
// circumvent this error. This is not harmful for catching errors since
// _functions is typed FunctionSpec<*> (it stores all TeX function specs).
// Must be specified unless it's handled directly in the parser.
handler: ?FunctionHandler<*>,
|};
/**
* All registered functions.
* `functions.js` just exports this same dictionary again and makes it public.
* `Parser.js` requires this dictionary.
*/
export const _functions: {[string]: FunctionSpec<*>} = {};
/**
* All HTML builders. Should be only used in the `define*` and the `build*ML`
* functions.
*/
export const _htmlGroupBuilders: {[string]: HtmlBuilder<*>} = {};
/**
* All MathML builders. Should be only used in the `define*` and the `build*ML`
* functions.
*/
export const _mathmlGroupBuilders: {[string]: MathMLBuilder<*>} = {};
export default function defineFunction<NODETYPE: NodeType>({
type,
names,
props,
handler,
htmlBuilder,
mathmlBuilder,
}: FunctionDefSpec<NODETYPE>) {
// Set default values of functions
const data = {
type,
numArgs: props.numArgs,
argTypes: props.argTypes,
allowedInArgument: !!props.allowedInArgument,
allowedInText: !!props.allowedInText,
allowedInMath: (props.allowedInMath === undefined)
? true
: props.allowedInMath,
numOptionalArgs: props.numOptionalArgs || 0,
infix: !!props.infix,
primitive: !!props.primitive,
handler: handler,
};
for (let i = 0; i < names.length; ++i) {
_functions[names[i]] = data;
}
if (type) {
if (htmlBuilder) {
_htmlGroupBuilders[type] = htmlBuilder;
}
if (mathmlBuilder) {
_mathmlGroupBuilders[type] = mathmlBuilder;
}
}
}
/**
* Use this to register only the HTML and MathML builders for a function (e.g.
* if the function's ParseNode is generated in Parser.js rather than via a
* stand-alone handler provided to `defineFunction`).
*/
export function defineFunctionBuilders<NODETYPE: NodeType>({
type, htmlBuilder, mathmlBuilder,
}: {|
type: NODETYPE,
htmlBuilder?: HtmlBuilder<NODETYPE>,
mathmlBuilder: MathMLBuilder<NODETYPE>,
|}) {
defineFunction({
type,
names: [],
props: {numArgs: 0},
handler() { throw new Error('Should never be called.'); },
htmlBuilder,
mathmlBuilder,
});
}
export const normalizeArgument = function(arg: AnyParseNode): AnyParseNode {
return arg.type === "ordgroup" && arg.body.length === 1 ? arg.body[0] : arg;
};
// Since the corresponding buildHTML/buildMathML function expects a
// list of elements, we normalize for different kinds of arguments
export const ordargument = function(arg: AnyParseNode): AnyParseNode[] {
return arg.type === "ordgroup" ? arg.body : [arg];
};