// @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]; | |
}; | |