defineFunction.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. // @flow
  2. import {checkNodeType} from "./parseNode";
  3. import type Parser from "./Parser";
  4. import type {ParseNode, AnyParseNode, NodeType} from "./parseNode";
  5. import type Options from "./Options";
  6. import type {ArgType, BreakToken, Mode} from "./types";
  7. import type {HtmlDomNode} from "./domTree";
  8. import type {Token} from "./Token";
  9. import type {MathDomNode} from "./mathMLTree";
  10. /** Context provided to function handlers for error messages. */
  11. export type FunctionContext = {|
  12. funcName: string,
  13. parser: Parser,
  14. token?: Token,
  15. breakOnTokenText?: BreakToken,
  16. |};
  17. export type FunctionHandler<NODETYPE: NodeType> = (
  18. context: FunctionContext,
  19. args: AnyParseNode[],
  20. optArgs: (?AnyParseNode)[],
  21. ) => ParseNode<NODETYPE>;
  22. export type HtmlBuilder<NODETYPE> = (ParseNode<NODETYPE>, Options) => HtmlDomNode;
  23. export type MathMLBuilder<NODETYPE> = (
  24. group: ParseNode<NODETYPE>,
  25. options: Options,
  26. ) => MathDomNode;
  27. // More general version of `HtmlBuilder` for nodes (e.g. \sum, accent types)
  28. // whose presence impacts super/subscripting. In this case, ParseNode<"supsub">
  29. // delegates its HTML building to the HtmlBuilder corresponding to these nodes.
  30. export type HtmlBuilderSupSub<NODETYPE> =
  31. (ParseNode<"supsub"> | ParseNode<NODETYPE>, Options) => HtmlDomNode;
  32. export type FunctionPropSpec = {
  33. // The number of arguments the function takes.
  34. numArgs: number,
  35. // An array corresponding to each argument of the function, giving the
  36. // type of argument that should be parsed. Its length should be equal
  37. // to `numOptionalArgs + numArgs`, and types for optional arguments
  38. // should appear before types for mandatory arguments.
  39. argTypes?: ArgType[],
  40. // The greediness of the function to use ungrouped arguments.
  41. //
  42. // E.g. if you have an expression
  43. // \sqrt \frac 1 2
  44. // since \frac has greediness=2 vs \sqrt's greediness=1, \frac
  45. // will use the two arguments '1' and '2' as its two arguments,
  46. // then that whole function will be used as the argument to
  47. // \sqrt. On the other hand, the expressions
  48. // \frac \frac 1 2 3
  49. // and
  50. // \frac \sqrt 1 2
  51. // will fail because \frac and \frac have equal greediness
  52. // and \sqrt has a lower greediness than \frac respectively. To
  53. // make these parse, we would have to change them to:
  54. // \frac {\frac 1 2} 3
  55. // and
  56. // \frac {\sqrt 1} 2
  57. //
  58. // The default value is `1`
  59. greediness?: number,
  60. // Whether or not the function is allowed inside text mode
  61. // (default false)
  62. allowedInText?: boolean,
  63. // Whether or not the function is allowed inside text mode
  64. // (default true)
  65. allowedInMath?: boolean,
  66. // (optional) The number of optional arguments the function
  67. // should parse. If the optional arguments aren't found,
  68. // `null` will be passed to the handler in their place.
  69. // (default 0)
  70. numOptionalArgs?: number,
  71. // Must be true if the function is an infix operator.
  72. infix?: boolean,
  73. // Switch to the specified mode while consuming the command token.
  74. // This is useful for commands that switch between math and text mode,
  75. // for making sure that a switch happens early enough. Note that the
  76. // mode is switched immediately back to its original value after consuming
  77. // the command token, so that the argument parsing and/or function handler
  78. // can easily access the old mode while doing their own mode switching.
  79. consumeMode?: ?Mode,
  80. };
  81. type FunctionDefSpec<NODETYPE: NodeType> = {|
  82. // Unique string to differentiate parse nodes.
  83. // Also determines the type of the value returned by `handler`.
  84. type: NODETYPE,
  85. // The first argument to defineFunction is a single name or a list of names.
  86. // All functions named in such a list will share a single implementation.
  87. names: Array<string>,
  88. // Properties that control how the functions are parsed.
  89. props: FunctionPropSpec,
  90. // The handler is called to handle these functions and their arguments and
  91. // returns a `ParseNode`.
  92. handler: ?FunctionHandler<NODETYPE>,
  93. // This function returns an object representing the DOM structure to be
  94. // created when rendering the defined LaTeX function.
  95. // This should not modify the `ParseNode`.
  96. htmlBuilder?: HtmlBuilder<NODETYPE>,
  97. // This function returns an object representing the MathML structure to be
  98. // created when rendering the defined LaTeX function.
  99. // This should not modify the `ParseNode`.
  100. mathmlBuilder?: MathMLBuilder<NODETYPE>,
  101. |};
  102. /**
  103. * Final function spec for use at parse time.
  104. * This is almost identical to `FunctionPropSpec`, except it
  105. * 1. includes the function handler, and
  106. * 2. requires all arguments except argTypes.
  107. * It is generated by `defineFunction()` below.
  108. */
  109. export type FunctionSpec<NODETYPE: NodeType> = {|
  110. type: NODETYPE, // Need to use the type to avoid error. See NOTES below.
  111. numArgs: number,
  112. argTypes?: ArgType[],
  113. greediness: number,
  114. allowedInText: boolean,
  115. allowedInMath: boolean,
  116. numOptionalArgs: number,
  117. infix: boolean,
  118. consumeMode: ?Mode,
  119. // FLOW TYPE NOTES: Doing either one of the following two
  120. //
  121. // - removing the NODETYPE type parameter in FunctionSpec above;
  122. // - using ?FunctionHandler<NODETYPE> below;
  123. //
  124. // results in a confusing flow typing error:
  125. // "string literal `styling`. This type is incompatible with..."
  126. // pointing to the definition of `defineFunction` and finishing with
  127. // "some incompatible instantiation of `NODETYPE`"
  128. //
  129. // Having FunctionSpec<NODETYPE> above and FunctionHandler<*> below seems to
  130. // circumvent this error. This is not harmful for catching errors since
  131. // _functions is typed FunctionSpec<*> (it stores all TeX function specs).
  132. // Must be specified unless it's handled directly in the parser.
  133. handler: ?FunctionHandler<*>,
  134. |};
  135. /**
  136. * All registered functions.
  137. * `functions.js` just exports this same dictionary again and makes it public.
  138. * `Parser.js` requires this dictionary.
  139. */
  140. export const _functions: {[string]: FunctionSpec<*>} = {};
  141. /**
  142. * All HTML builders. Should be only used in the `define*` and the `build*ML`
  143. * functions.
  144. */
  145. export const _htmlGroupBuilders: {[string]: HtmlBuilder<*>} = {};
  146. /**
  147. * All MathML builders. Should be only used in the `define*` and the `build*ML`
  148. * functions.
  149. */
  150. export const _mathmlGroupBuilders: {[string]: MathMLBuilder<*>} = {};
  151. export default function defineFunction<NODETYPE: NodeType>({
  152. type,
  153. nodeType,
  154. names,
  155. props,
  156. handler,
  157. htmlBuilder,
  158. mathmlBuilder,
  159. }: FunctionDefSpec<NODETYPE>) {
  160. // Set default values of functions
  161. const data = {
  162. type,
  163. numArgs: props.numArgs,
  164. argTypes: props.argTypes,
  165. greediness: (props.greediness === undefined) ? 1 : props.greediness,
  166. allowedInText: !!props.allowedInText,
  167. allowedInMath: (props.allowedInMath === undefined)
  168. ? true
  169. : props.allowedInMath,
  170. numOptionalArgs: props.numOptionalArgs || 0,
  171. infix: !!props.infix,
  172. consumeMode: props.consumeMode,
  173. handler: handler,
  174. };
  175. for (let i = 0; i < names.length; ++i) {
  176. // TODO: The value type of _functions should be a type union of all
  177. // possible `FunctionSpec<>` possibilities instead of `FunctionSpec<*>`,
  178. // which is an existential type.
  179. // $FlowFixMe
  180. _functions[names[i]] = data;
  181. }
  182. if (type) {
  183. if (htmlBuilder) {
  184. _htmlGroupBuilders[type] = htmlBuilder;
  185. }
  186. if (mathmlBuilder) {
  187. _mathmlGroupBuilders[type] = mathmlBuilder;
  188. }
  189. }
  190. }
  191. /**
  192. * Use this to register only the HTML and MathML builders for a function (e.g.
  193. * if the function's ParseNode is generated in Parser.js rather than via a
  194. * stand-alone handler provided to `defineFunction`).
  195. */
  196. export function defineFunctionBuilders<NODETYPE: NodeType>({
  197. type, htmlBuilder, mathmlBuilder,
  198. }: {|
  199. type: NODETYPE,
  200. htmlBuilder?: HtmlBuilder<NODETYPE>,
  201. mathmlBuilder: MathMLBuilder<NODETYPE>,
  202. |}) {
  203. defineFunction({
  204. type,
  205. names: [],
  206. props: {numArgs: 0},
  207. handler() { throw new Error('Should never be called.'); },
  208. htmlBuilder,
  209. mathmlBuilder,
  210. });
  211. }
  212. // Since the corresponding buildHTML/buildMathML function expects a
  213. // list of elements, we normalize for different kinds of arguments
  214. export const ordargument = function(arg: AnyParseNode): AnyParseNode[] {
  215. const node = checkNodeType(arg, "ordgroup");
  216. return node ? node.body : [arg];
  217. };