You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
223 lines
7.6 KiB
223 lines
7.6 KiB
// @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]; |
|
};
|
|
|