mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-05 19:28:39 +00:00
Make ParseNode value
payload and defineFunction handler functions type-safe (#1276)
* Make ParseNode `value` payload type-safe. * Make defineFunction handlers aware of ParseNode data types. * Add `type` to all function definitions to help determine handler return type. * Added unit test for case caught only in screenshot test and fixed issue. * Rename some symbol `Group`s to avoid conflicts with `ParseNode` groups. Symbol `Group`s are also used as `ParseNode` types. However, `ParseNode`s of these types always contain a raw text token as opposed to any structured content. These `ParseNode`s are passed as arguments into function handlers to create more semantical `ParseNode`s with more structure. Before this change, "accent" and "op" were both symbol `Group`s and `ParseNode` types. With this change, these two types (the raw accent token `ParseNode`, and the structured semantical `ParseNode` are separated for better type-safety on the `ParseNode` payload). * stretchy: Remove FlowFixMe for a forced typecast that's no longer needed.
This commit is contained in:
committed by
Kevin Barabash
parent
3613885da1
commit
5a4aedd882
2
katex.js
2
katex.js
@@ -69,7 +69,7 @@ const renderToString = function(
|
|||||||
const generateParseTree = function(
|
const generateParseTree = function(
|
||||||
expression: string,
|
expression: string,
|
||||||
options: SettingsOptions,
|
options: SettingsOptions,
|
||||||
): ParseNode[] {
|
): ParseNode<*>[] {
|
||||||
const settings = new Settings(options);
|
const settings = new Settings(options);
|
||||||
return parseTree(expression, settings);
|
return parseTree(expression, settings);
|
||||||
};
|
};
|
||||||
|
@@ -15,8 +15,8 @@ class ParseError {
|
|||||||
// Error position based on passed-in Token or ParseNode.
|
// Error position based on passed-in Token or ParseNode.
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
message: string, // The error message
|
message: string, // The error message
|
||||||
token?: Token | ParseNode, // An object providing position information
|
token?: Token | ParseNode<*>, // An object providing position information
|
||||||
) {
|
) {
|
||||||
let error = "KaTeX parse error: " + message;
|
let error = "KaTeX parse error: " + message;
|
||||||
let start;
|
let start;
|
||||||
|
251
src/ParseNode.js
251
src/ParseNode.js
@@ -1,6 +1,9 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import SourceLocation from "./SourceLocation";
|
import SourceLocation from "./SourceLocation";
|
||||||
import type {Mode} from "./types";
|
import type {ArrayEnvNodeData} from "./environments/array.js";
|
||||||
|
import type {Mode, StyleStr} from "./types";
|
||||||
|
import type {Token} from "./Token.js";
|
||||||
|
import type {Measurement} from "./units.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The resulting parse tree nodes of the parse tree.
|
* The resulting parse tree nodes of the parse tree.
|
||||||
@@ -10,15 +13,15 @@ import type {Mode} from "./types";
|
|||||||
* For details on the corresponding properties see `Token` constructor.
|
* For details on the corresponding properties see `Token` constructor.
|
||||||
* Providing such information can lead to better error reporting.
|
* Providing such information can lead to better error reporting.
|
||||||
*/
|
*/
|
||||||
export default class ParseNode {
|
export default class ParseNode<TYPE: NodeType> {
|
||||||
type: *;
|
type: TYPE;
|
||||||
value: *;
|
value: NodeValue<TYPE>;
|
||||||
mode: Mode;
|
mode: Mode;
|
||||||
loc: ?SourceLocation;
|
loc: ?SourceLocation;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
type: string, // type of node, like e.g. "ordgroup"
|
type: TYPE, // type of node, like e.g. "ordgroup"
|
||||||
value: mixed, // type-specific representation of the node
|
value: NodeValue<TYPE>, // type-specific representation of the node
|
||||||
mode: Mode, // parse mode in action for this node, "math" or "text"
|
mode: Mode, // parse mode in action for this node, "math" or "text"
|
||||||
first?: {loc: ?SourceLocation}, // first token or node of the input for
|
first?: {loc: ?SourceLocation}, // first token or node of the input for
|
||||||
// this node, will omit position information if unset
|
// this node, will omit position information if unset
|
||||||
@@ -31,3 +34,239 @@ export default class ParseNode {
|
|||||||
this.loc = SourceLocation.range(first, last);
|
this.loc = SourceLocation.range(first, last);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type NodeType = $Keys<ParseNodeTypes>;
|
||||||
|
export type NodeValue<TYPE: NodeType> = $ElementType<ParseNodeTypes, TYPE>;
|
||||||
|
|
||||||
|
export type AccentStructType = {|
|
||||||
|
type: "accent",
|
||||||
|
label: string,
|
||||||
|
isStretchy: boolean,
|
||||||
|
isShifty: boolean,
|
||||||
|
base: ParseNode<*>,
|
||||||
|
|};
|
||||||
|
|
||||||
|
// Map from `type` field value to corresponding `value` type.
|
||||||
|
type ParseNodeTypes = {
|
||||||
|
"array": ArrayEnvNodeData,
|
||||||
|
"accent": AccentStructType,
|
||||||
|
"color": {|
|
||||||
|
type: "color",
|
||||||
|
color: string,
|
||||||
|
value: ParseNode<*>[],
|
||||||
|
|},
|
||||||
|
"leftright": {|
|
||||||
|
body: [{|
|
||||||
|
type: "array",
|
||||||
|
hskipBeforeAndAfter: boolean,
|
||||||
|
|} | ParseNode<*>],
|
||||||
|
left: string,
|
||||||
|
right: string,
|
||||||
|
|},
|
||||||
|
"op": {|
|
||||||
|
type: "op",
|
||||||
|
limits: boolean,
|
||||||
|
symbol: boolean,
|
||||||
|
alwaysHandleSupSub?: boolean,
|
||||||
|
body?: string,
|
||||||
|
value?: ParseNode<*>[],
|
||||||
|
|},
|
||||||
|
"ordgroup": ParseNode<*>[],
|
||||||
|
"size": {|
|
||||||
|
number: number,
|
||||||
|
unit: string,
|
||||||
|
|},
|
||||||
|
"styling": {|
|
||||||
|
type: "styling",
|
||||||
|
style: StyleStr,
|
||||||
|
value: ParseNode<*>[],
|
||||||
|
|},
|
||||||
|
"supsub": {|
|
||||||
|
base: ?ParseNode<*>,
|
||||||
|
sup?: ?ParseNode<*>,
|
||||||
|
sub?: ?ParseNode<*>,
|
||||||
|
|},
|
||||||
|
"text": {|
|
||||||
|
type: "text",
|
||||||
|
body: ParseNode<*>[],
|
||||||
|
font?: string,
|
||||||
|
|},
|
||||||
|
"textord": string,
|
||||||
|
"url": string,
|
||||||
|
"verb": {|
|
||||||
|
body: string,
|
||||||
|
star: boolean,
|
||||||
|
|},
|
||||||
|
// From symbol groups, constructed in Parser.js via `symbols` lookup.
|
||||||
|
// (Some of these have "-token" suffix to distinguish them from existing
|
||||||
|
// `ParseNode` types.)
|
||||||
|
"accent-token": string,
|
||||||
|
"bin": string,
|
||||||
|
"close": string,
|
||||||
|
"inner": string,
|
||||||
|
"mathord": string,
|
||||||
|
"op-token": string,
|
||||||
|
"open": string,
|
||||||
|
"punct": string,
|
||||||
|
"rel": string,
|
||||||
|
"spacing": string,
|
||||||
|
"textord": string,
|
||||||
|
// From functions.js and functions/*.js. See also "accent", "color", "op",
|
||||||
|
// "styling", and "text" above.
|
||||||
|
"accentUnder": {|
|
||||||
|
type: "accentUnder",
|
||||||
|
label: string,
|
||||||
|
base: ParseNode<*>,
|
||||||
|
|},
|
||||||
|
"cr": {|
|
||||||
|
type: "cr",
|
||||||
|
size: ?ParseNode<*>,
|
||||||
|
|},
|
||||||
|
"delimsizing": {|
|
||||||
|
type: "delimsizing",
|
||||||
|
size: 1 | 2 | 3 | 4,
|
||||||
|
mclass: "mopen" | "mclose" | "mrel" | "mord",
|
||||||
|
value: string,
|
||||||
|
|},
|
||||||
|
"enclose": {|
|
||||||
|
type: "enclose",
|
||||||
|
label: string,
|
||||||
|
backgroundColor?: ParseNode<*>,
|
||||||
|
borderColor?: ParseNode<*>,
|
||||||
|
body: ParseNode<*>,
|
||||||
|
|},
|
||||||
|
"environment": {|
|
||||||
|
type: "environment",
|
||||||
|
name: string,
|
||||||
|
nameGroup: ParseNode<*>,
|
||||||
|
|},
|
||||||
|
"font": {|
|
||||||
|
type: "font",
|
||||||
|
font: string,
|
||||||
|
body: ParseNode<*>,
|
||||||
|
|},
|
||||||
|
"genfrac": {|
|
||||||
|
type: "genfrac",
|
||||||
|
numer: ParseNode<*>,
|
||||||
|
denom: ParseNode<*>,
|
||||||
|
hasBarLine: boolean,
|
||||||
|
leftDelim: ?string,
|
||||||
|
rightDelim: ?string,
|
||||||
|
size: StyleStr | "auto",
|
||||||
|
|},
|
||||||
|
"horizBrace": {|
|
||||||
|
type: "horizBrace",
|
||||||
|
label: string,
|
||||||
|
isOver: boolean,
|
||||||
|
base: ParseNode<*>,
|
||||||
|
|},
|
||||||
|
"href": {|
|
||||||
|
type: "href",
|
||||||
|
href: string,
|
||||||
|
body: ParseNode<*>[],
|
||||||
|
|},
|
||||||
|
"infix": {|
|
||||||
|
type: "infix",
|
||||||
|
replaceWith: string,
|
||||||
|
token: ?Token,
|
||||||
|
|},
|
||||||
|
"kern": {|
|
||||||
|
type: "kern",
|
||||||
|
dimension: Measurement,
|
||||||
|
|},
|
||||||
|
"lap": {|
|
||||||
|
type: "lap",
|
||||||
|
alignment: string,
|
||||||
|
body: ParseNode<*>,
|
||||||
|
|},
|
||||||
|
"leftright": {|
|
||||||
|
type?: "leftright",
|
||||||
|
body?: ParseNode<*>[],
|
||||||
|
left: string,
|
||||||
|
right: string,
|
||||||
|
|} | {|
|
||||||
|
type: "leftright",
|
||||||
|
value: string,
|
||||||
|
|},
|
||||||
|
"mathchoice": {|
|
||||||
|
type: "mathchoice",
|
||||||
|
display: ParseNode<*>[],
|
||||||
|
text: ParseNode<*>[],
|
||||||
|
script: ParseNode<*>[],
|
||||||
|
scriptscript: ParseNode<*>[],
|
||||||
|
|},
|
||||||
|
"middle": {|
|
||||||
|
type: "middle",
|
||||||
|
value: string,
|
||||||
|
|},
|
||||||
|
"mclass": {|
|
||||||
|
type: "mclass",
|
||||||
|
mclass: string,
|
||||||
|
value: ParseNode<*>[],
|
||||||
|
|},
|
||||||
|
"mod": {|
|
||||||
|
type: "mod",
|
||||||
|
modType: string,
|
||||||
|
value: ?ParseNode<*>[],
|
||||||
|
|},
|
||||||
|
"operatorname": {|
|
||||||
|
type: "operatorname",
|
||||||
|
value: ParseNode<*>[],
|
||||||
|
|},
|
||||||
|
"overline": {|
|
||||||
|
type: "overline",
|
||||||
|
body: ParseNode<*>,
|
||||||
|
|},
|
||||||
|
"phantom": {|
|
||||||
|
type: "phantom",
|
||||||
|
value: ParseNode<*>[],
|
||||||
|
|},
|
||||||
|
"hphantom": {|
|
||||||
|
type: "hphantom",
|
||||||
|
body: ParseNode<*>,
|
||||||
|
value: ParseNode<*>[],
|
||||||
|
|},
|
||||||
|
"vphantom": {|
|
||||||
|
type: "vphantom",
|
||||||
|
body: ParseNode<*>,
|
||||||
|
value: ParseNode<*>[],
|
||||||
|
|},
|
||||||
|
"raisebox": {|
|
||||||
|
type: "raisebox",
|
||||||
|
dy: ParseNode<*>,
|
||||||
|
body: ParseNode<*>,
|
||||||
|
value: ParseNode<*>[],
|
||||||
|
|},
|
||||||
|
"rule": {|
|
||||||
|
type: "rule",
|
||||||
|
shift: ?Measurement,
|
||||||
|
width: ParseNode<*>,
|
||||||
|
height: ParseNode<*>,
|
||||||
|
|},
|
||||||
|
"sizing": {|
|
||||||
|
type: "sizing",
|
||||||
|
size: number,
|
||||||
|
value: ParseNode<*>[],
|
||||||
|
|},
|
||||||
|
"smash": {|
|
||||||
|
type: "smash",
|
||||||
|
body: ParseNode<*>,
|
||||||
|
smashHeight: boolean,
|
||||||
|
smashDepth: boolean,
|
||||||
|
|},
|
||||||
|
"sqrt": {|
|
||||||
|
type: "sqrt",
|
||||||
|
body: ParseNode<*>,
|
||||||
|
index: ?ParseNode<*>,
|
||||||
|
|},
|
||||||
|
"underline": {|
|
||||||
|
type: "underline",
|
||||||
|
body: ParseNode<*>,
|
||||||
|
|},
|
||||||
|
"xArrow": {|
|
||||||
|
type: "xArrow",
|
||||||
|
label: string,
|
||||||
|
body: ParseNode<*>,
|
||||||
|
below: ?ParseNode<*>,
|
||||||
|
|},
|
||||||
|
};
|
||||||
|
@@ -60,12 +60,12 @@ type ParsedFunc = {|
|
|||||||
|};
|
|};
|
||||||
type ParsedArg = {|
|
type ParsedArg = {|
|
||||||
type: "arg",
|
type: "arg",
|
||||||
result: ParseNode,
|
result: ParseNode<*>,
|
||||||
token: Token,
|
token: Token,
|
||||||
|};
|
|};
|
||||||
type ParsedFuncOrArg = ParsedFunc | ParsedArg;
|
type ParsedFuncOrArg = ParsedFunc | ParsedArg;
|
||||||
|
|
||||||
function newArgument(result: ParseNode, token: Token): ParsedArg {
|
function newArgument(result: ParseNode<*>, token: Token): ParsedArg {
|
||||||
return {type: "arg", result, token};
|
return {type: "arg", result, token};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ export default class Parser {
|
|||||||
/**
|
/**
|
||||||
* Main parsing function, which parses an entire input.
|
* Main parsing function, which parses an entire input.
|
||||||
*/
|
*/
|
||||||
parse(): ParseNode[] {
|
parse(): ParseNode<*>[] {
|
||||||
// Try to parse the input
|
// Try to parse the input
|
||||||
this.consume();
|
this.consume();
|
||||||
const parse = this.parseInput();
|
const parse = this.parseInput();
|
||||||
@@ -142,7 +142,7 @@ export default class Parser {
|
|||||||
/**
|
/**
|
||||||
* Parses an entire input tree.
|
* Parses an entire input tree.
|
||||||
*/
|
*/
|
||||||
parseInput(): ParseNode[] {
|
parseInput(): ParseNode<*>[] {
|
||||||
// Parse an expression
|
// Parse an expression
|
||||||
const expression = this.parseExpression(false);
|
const expression = this.parseExpression(false);
|
||||||
// If we succeeded, make sure there's an EOF at the end
|
// If we succeeded, make sure there's an EOF at the end
|
||||||
@@ -166,7 +166,7 @@ export default class Parser {
|
|||||||
parseExpression(
|
parseExpression(
|
||||||
breakOnInfix: boolean,
|
breakOnInfix: boolean,
|
||||||
breakOnTokenText?: BreakToken,
|
breakOnTokenText?: BreakToken,
|
||||||
): ParseNode[] {
|
): ParseNode<*>[] {
|
||||||
const body = [];
|
const body = [];
|
||||||
// Keep adding atoms to the body until we can't parse any more atoms (either
|
// Keep adding atoms to the body until we can't parse any more atoms (either
|
||||||
// we reached the end, a }, or a \right)
|
// we reached the end, a }, or a \right)
|
||||||
@@ -207,7 +207,7 @@ export default class Parser {
|
|||||||
* There can only be one infix operator per group. If there's more than one
|
* There can only be one infix operator per group. If there's more than one
|
||||||
* then the expression is ambiguous. This can be resolved by adding {}.
|
* then the expression is ambiguous. This can be resolved by adding {}.
|
||||||
*/
|
*/
|
||||||
handleInfixNodes(body: ParseNode[]): ParseNode[] {
|
handleInfixNodes(body: ParseNode<*>[]): ParseNode<*>[] {
|
||||||
let overIndex = -1;
|
let overIndex = -1;
|
||||||
let funcName;
|
let funcName;
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ export default class Parser {
|
|||||||
*/
|
*/
|
||||||
handleSupSubscript(
|
handleSupSubscript(
|
||||||
name: string, // For error reporting.
|
name: string, // For error reporting.
|
||||||
): ParseNode {
|
): ParseNode<*> {
|
||||||
const symbolToken = this.nextToken;
|
const symbolToken = this.nextToken;
|
||||||
const symbol = symbolToken.text;
|
const symbol = symbolToken.text;
|
||||||
this.consume();
|
this.consume();
|
||||||
@@ -296,7 +296,7 @@ export default class Parser {
|
|||||||
* Converts the textual input of an unsupported command into a text node
|
* Converts the textual input of an unsupported command into a text node
|
||||||
* contained within a color node whose color is determined by errorColor
|
* contained within a color node whose color is determined by errorColor
|
||||||
*/
|
*/
|
||||||
handleUnsupportedCmd(): ParseNode {
|
handleUnsupportedCmd(): ParseNode<*> {
|
||||||
const text = this.nextToken.text;
|
const text = this.nextToken.text;
|
||||||
const textordArray = [];
|
const textordArray = [];
|
||||||
|
|
||||||
@@ -328,7 +328,7 @@ export default class Parser {
|
|||||||
/**
|
/**
|
||||||
* Parses a group with optional super/subscripts.
|
* Parses a group with optional super/subscripts.
|
||||||
*/
|
*/
|
||||||
parseAtom(breakOnTokenText?: BreakToken): ?ParseNode {
|
parseAtom(breakOnTokenText?: BreakToken): ?ParseNode<*> {
|
||||||
// The body of an atom is an implicit group, so that things like
|
// The body of an atom is an implicit group, so that things like
|
||||||
// \left(x\right)^2 work correctly.
|
// \left(x\right)^2 work correctly.
|
||||||
const base = this.parseImplicitGroup(breakOnTokenText);
|
const base = this.parseImplicitGroup(breakOnTokenText);
|
||||||
@@ -402,6 +402,8 @@ export default class Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Base must be set if superscript or subscript are set per logic above,
|
||||||
|
// but need to check here for type check to pass.
|
||||||
if (superscript || subscript) {
|
if (superscript || subscript) {
|
||||||
// If we got either a superscript or subscript, create a supsub
|
// If we got either a superscript or subscript, create a supsub
|
||||||
return new ParseNode("supsub", {
|
return new ParseNode("supsub", {
|
||||||
@@ -423,7 +425,7 @@ export default class Parser {
|
|||||||
* implicit grouping after it until the end of the group. E.g.
|
* implicit grouping after it until the end of the group. E.g.
|
||||||
* small text {\Large large text} small text again
|
* small text {\Large large text} small text again
|
||||||
*/
|
*/
|
||||||
parseImplicitGroup(breakOnTokenText?: BreakToken): ?ParseNode {
|
parseImplicitGroup(breakOnTokenText?: BreakToken): ?ParseNode<*> {
|
||||||
const start = this.parseSymbol();
|
const start = this.parseSymbol();
|
||||||
|
|
||||||
if (start == null) {
|
if (start == null) {
|
||||||
@@ -477,7 +479,7 @@ export default class Parser {
|
|||||||
* Parses an entire function, including its base and all of its arguments.
|
* Parses an entire function, including its base and all of its arguments.
|
||||||
* It also handles the case where the parsed node is not a function.
|
* It also handles the case where the parsed node is not a function.
|
||||||
*/
|
*/
|
||||||
parseFunction(): ?ParseNode {
|
parseFunction(): ?ParseNode<*> {
|
||||||
const baseGroup = this.parseGroup();
|
const baseGroup = this.parseGroup();
|
||||||
return baseGroup ? this.parseGivenFunction(baseGroup) : null;
|
return baseGroup ? this.parseGivenFunction(baseGroup) : null;
|
||||||
}
|
}
|
||||||
@@ -489,7 +491,7 @@ export default class Parser {
|
|||||||
parseGivenFunction(
|
parseGivenFunction(
|
||||||
baseGroup: ParsedFuncOrArg,
|
baseGroup: ParsedFuncOrArg,
|
||||||
breakOnTokenText?: BreakToken,
|
breakOnTokenText?: BreakToken,
|
||||||
): ParseNode {
|
): ParseNode<*> {
|
||||||
if (baseGroup.type === "fn") {
|
if (baseGroup.type === "fn") {
|
||||||
const func = baseGroup.result;
|
const func = baseGroup.result;
|
||||||
const funcData = functions[func];
|
const funcData = functions[func];
|
||||||
@@ -530,8 +532,8 @@ export default class Parser {
|
|||||||
*/
|
*/
|
||||||
callFunction(
|
callFunction(
|
||||||
name: string,
|
name: string,
|
||||||
args: ParseNode[],
|
args: ParseNode<*>[],
|
||||||
optArgs: (?ParseNode)[],
|
optArgs: (?ParseNode<*>)[],
|
||||||
token?: Token,
|
token?: Token,
|
||||||
breakOnTokenText?: BreakToken,
|
breakOnTokenText?: BreakToken,
|
||||||
): * {
|
): * {
|
||||||
@@ -554,10 +556,10 @@ export default class Parser {
|
|||||||
*/
|
*/
|
||||||
parseArguments(
|
parseArguments(
|
||||||
func: string, // Should look like "\name" or "\begin{name}".
|
func: string, // Should look like "\name" or "\begin{name}".
|
||||||
funcData: FunctionSpec | EnvSpec,
|
funcData: FunctionSpec<*> | EnvSpec,
|
||||||
): {
|
): {
|
||||||
args: ParseNode[],
|
args: ParseNode<*>[],
|
||||||
optArgs: (?ParseNode)[],
|
optArgs: (?ParseNode<*>)[],
|
||||||
} {
|
} {
|
||||||
const totalArgs = funcData.numArgs + funcData.numOptionalArgs;
|
const totalArgs = funcData.numArgs + funcData.numOptionalArgs;
|
||||||
if (totalArgs === 0) {
|
if (totalArgs === 0) {
|
||||||
@@ -604,7 +606,7 @@ export default class Parser {
|
|||||||
"Expected group after '" + func + "'", nextToken);
|
"Expected group after '" + func + "'", nextToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let argNode: ParseNode;
|
let argNode: ParseNode<*>;
|
||||||
if (arg.type === "fn") {
|
if (arg.type === "fn") {
|
||||||
const argGreediness =
|
const argGreediness =
|
||||||
functions[arg.result].greediness;
|
functions[arg.result].greediness;
|
||||||
@@ -880,7 +882,7 @@ export default class Parser {
|
|||||||
* characters in its value. The representation is still ASCII source.
|
* characters in its value. The representation is still ASCII source.
|
||||||
* The group will be modified in place.
|
* The group will be modified in place.
|
||||||
*/
|
*/
|
||||||
formLigatures(group: ParseNode[]) {
|
formLigatures(group: ParseNode<*>[]) {
|
||||||
let n = group.length - 1;
|
let n = group.length - 1;
|
||||||
for (let i = 0; i < n; ++i) {
|
for (let i = 0; i < n; ++i) {
|
||||||
const a = group[i];
|
const a = group[i];
|
||||||
@@ -977,7 +979,7 @@ export default class Parser {
|
|||||||
// Transform combining characters into accents
|
// Transform combining characters into accents
|
||||||
if (match) {
|
if (match) {
|
||||||
for (let i = 0; i < match[0].length; i++) {
|
for (let i = 0; i < match[0].length; i++) {
|
||||||
const accent = match[0][i];
|
const accent: string = match[0][i];
|
||||||
if (!unicodeAccents[accent]) {
|
if (!unicodeAccents[accent]) {
|
||||||
throw new ParseError(`Unknown accent ' ${accent}'`, nucleus);
|
throw new ParseError(`Unknown accent ' ${accent}'`, nucleus);
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@ import {calculateSize} from "./units";
|
|||||||
|
|
||||||
import type Options from "./Options";
|
import type Options from "./Options";
|
||||||
import type ParseNode from "./ParseNode";
|
import type ParseNode from "./ParseNode";
|
||||||
|
import type {NodeType} from "./ParseNode";
|
||||||
import type {CharacterMetrics} from "./fontMetrics";
|
import type {CharacterMetrics} from "./fontMetrics";
|
||||||
import type {Mode} from "./types";
|
import type {Mode} from "./types";
|
||||||
import type {HtmlDomNode, DomSpan, SvgSpan, CssStyle} from "./domTree";
|
import type {HtmlDomNode, DomSpan, SvgSpan, CssStyle} from "./domTree";
|
||||||
@@ -138,7 +139,7 @@ const mathDefault = function(
|
|||||||
mode: Mode,
|
mode: Mode,
|
||||||
options: Options,
|
options: Options,
|
||||||
classes: string[],
|
classes: string[],
|
||||||
type: string, // TODO(#892): Use ParseNode type here.
|
type: NodeType,
|
||||||
): domTree.symbolNode {
|
): domTree.symbolNode {
|
||||||
if (type === "mathord") {
|
if (type === "mathord") {
|
||||||
const fontLookup = mathit(value, mode, options, classes);
|
const fontLookup = mathit(value, mode, options, classes);
|
||||||
@@ -223,9 +224,9 @@ const boldsymbol = function(
|
|||||||
* Makes either a mathord or textord in the correct font and color.
|
* Makes either a mathord or textord in the correct font and color.
|
||||||
*/
|
*/
|
||||||
const makeOrd = function(
|
const makeOrd = function(
|
||||||
group: ParseNode,
|
group: ParseNode<*>,
|
||||||
options: Options,
|
options: Options,
|
||||||
type: string, // TODO(#892): Use ParseNode type here.
|
type: NodeType,
|
||||||
): domTree.symbolNode {
|
): domTree.symbolNode {
|
||||||
const mode = group.mode;
|
const mode = group.mode;
|
||||||
const value = group.value;
|
const value = group.value;
|
||||||
@@ -591,9 +592,7 @@ const makeVList = function(params: VListParam, options: Options): DomSpan {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Converts verb group into body string, dealing with \verb* form
|
// Converts verb group into body string, dealing with \verb* form
|
||||||
const makeVerb = function(group: ParseNode, options: Options): string {
|
const makeVerb = function(group: ParseNode<"verb">, options: Options): string {
|
||||||
// TODO(#892): Make ParseNode type-safe and confirm `group.type` to guarantee
|
|
||||||
// that `group.value.body` is of type string.
|
|
||||||
let text = group.value.body;
|
let text = group.value.body;
|
||||||
if (group.value.star) {
|
if (group.value.star) {
|
||||||
text = text.replace(/ /g, '\u2423'); // Open Box
|
text = text.replace(/ /g, '\u2423'); // Open Box
|
||||||
|
@@ -17,7 +17,7 @@ const optionsFromSettings = function(settings: Settings) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const buildTree = function(
|
export const buildTree = function(
|
||||||
tree: ParseNode[],
|
tree: ParseNode<*>[],
|
||||||
expression: string,
|
expression: string,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
): DomSpan {
|
): DomSpan {
|
||||||
@@ -39,7 +39,7 @@ export const buildTree = function(
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const buildHTMLTree = function(
|
export const buildHTMLTree = function(
|
||||||
tree: ParseNode[],
|
tree: ParseNode<*>[],
|
||||||
expression: string,
|
expression: string,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
): DomSpan {
|
): DomSpan {
|
||||||
|
@@ -27,9 +27,9 @@ type EnvContext = {|
|
|||||||
*/
|
*/
|
||||||
type EnvHandler = (
|
type EnvHandler = (
|
||||||
context: EnvContext,
|
context: EnvContext,
|
||||||
args: ParseNode[],
|
args: ParseNode<*>[],
|
||||||
optArgs: (?ParseNode)[],
|
optArgs: (?ParseNode<*>)[],
|
||||||
) => ParseNode;
|
) => ParseNode<*>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - numArgs: (default 0) The number of arguments after the \begin{name} function.
|
* - numArgs: (default 0) The number of arguments after the \begin{name} function.
|
||||||
|
@@ -4,6 +4,7 @@ import {groupTypes as mathmlGroupTypes} from "./buildMathML";
|
|||||||
|
|
||||||
import type Parser from "./Parser";
|
import type Parser from "./Parser";
|
||||||
import type ParseNode from "./ParseNode";
|
import type ParseNode from "./ParseNode";
|
||||||
|
import type {NodeType, NodeValue} from "./ParseNode";
|
||||||
import type Options from "./Options";
|
import type Options from "./Options";
|
||||||
import type {ArgType, BreakToken, Mode} from "./types";
|
import type {ArgType, BreakToken, Mode} from "./types";
|
||||||
import type {Token} from "./Token";
|
import type {Token} from "./Token";
|
||||||
@@ -16,12 +17,11 @@ export type FunctionContext = {|
|
|||||||
breakOnTokenText?: BreakToken,
|
breakOnTokenText?: BreakToken,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
// TODO: Enumerate all allowed output types.
|
export type FunctionHandler<NODETYPE: NodeType> = (
|
||||||
export type FunctionHandler = (
|
|
||||||
context: FunctionContext,
|
context: FunctionContext,
|
||||||
args: ParseNode[],
|
args: ParseNode<*>[],
|
||||||
optArgs: (?ParseNode)[],
|
optArgs: (?ParseNode<*>)[],
|
||||||
) => *;
|
) => NodeValue<NODETYPE>;
|
||||||
|
|
||||||
export type FunctionPropSpec = {
|
export type FunctionPropSpec = {
|
||||||
// The number of arguments the function takes.
|
// The number of arguments the function takes.
|
||||||
@@ -80,9 +80,10 @@ export type FunctionPropSpec = {
|
|||||||
consumeMode?: ?Mode,
|
consumeMode?: ?Mode,
|
||||||
};
|
};
|
||||||
|
|
||||||
type FunctionDefSpec = {|
|
type FunctionDefSpec<NODETYPE: NodeType> = {|
|
||||||
// Unique string to differentiate parse nodes.
|
// Unique string to differentiate parse nodes.
|
||||||
type?: string,
|
// 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.
|
// 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.
|
// All functions named in such a list will share a single implementation.
|
||||||
@@ -92,23 +93,22 @@ type FunctionDefSpec = {|
|
|||||||
props: FunctionPropSpec,
|
props: FunctionPropSpec,
|
||||||
|
|
||||||
// The handler is called to handle these functions and their arguments.
|
// The handler is called to handle these functions and their arguments.
|
||||||
//
|
|
||||||
// The function should return an object with the following keys:
|
// The function should return an object with the following keys:
|
||||||
// - type: The type of element that this is. This is then used in
|
// - type: The type of element that this is. This is then used in
|
||||||
// buildHTML/buildMathML to determine which function
|
// buildHTML/buildMathML to determine which function
|
||||||
// should be called to build this node into a DOM node
|
// should be called to build this node into a DOM node
|
||||||
// Any other data can be added to the object, which will be passed
|
// Any other data can be added to the object, which will be passed
|
||||||
// in to the function in buildHTML/buildMathML as `group.value`.
|
// in to the function in buildHTML/buildMathML as `group.value`.
|
||||||
handler: ?FunctionHandler,
|
handler: ?FunctionHandler<NODETYPE>,
|
||||||
|
|
||||||
// This function returns an object representing the DOM structure to be
|
// This function returns an object representing the DOM structure to be
|
||||||
// created when rendering the defined LaTeX function.
|
// created when rendering the defined LaTeX function.
|
||||||
// TODO: Port buildHTML to flow and make the group and return types explicit.
|
// TODO: Change `group` to ParseNode<NODETYPE> and make return type explicit.
|
||||||
htmlBuilder?: (group: *, options: Options) => *,
|
htmlBuilder?: (group: *, options: Options) => *,
|
||||||
|
|
||||||
// This function returns an object representing the MathML structure to be
|
// This function returns an object representing the MathML structure to be
|
||||||
// created when rendering the defined LaTeX function.
|
// created when rendering the defined LaTeX function.
|
||||||
// TODO: Port buildMathML to flow and make the group and return types explicit.
|
// TODO: Change `group` to ParseNode<NODETYPE> and make return type explicit.
|
||||||
mathmlBuilder?: (group: *, options: Options) => *,
|
mathmlBuilder?: (group: *, options: Options) => *,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
@@ -119,7 +119,8 @@ type FunctionDefSpec = {|
|
|||||||
* 2. requires all arguments except argTypes.
|
* 2. requires all arguments except argTypes.
|
||||||
* It is generated by `defineFunction()` below.
|
* It is generated by `defineFunction()` below.
|
||||||
*/
|
*/
|
||||||
export type FunctionSpec = {|
|
export type FunctionSpec<NODETYPE: NodeType> = {|
|
||||||
|
type: NODETYPE, // Need to use the type to avoid error. See NOTES below.
|
||||||
numArgs: number,
|
numArgs: number,
|
||||||
argTypes?: ArgType[],
|
argTypes?: ArgType[],
|
||||||
greediness: number,
|
greediness: number,
|
||||||
@@ -128,8 +129,23 @@ export type FunctionSpec = {|
|
|||||||
numOptionalArgs: number,
|
numOptionalArgs: number,
|
||||||
infix: boolean,
|
infix: boolean,
|
||||||
consumeMode: ?Mode,
|
consumeMode: ?Mode,
|
||||||
|
|
||||||
|
// FLOW TYPE NOTES: Doing either one of the following two
|
||||||
|
//
|
||||||
|
// - removing the NOTETYPE 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.
|
// Must be specified unless it's handled directly in the parser.
|
||||||
handler: ?FunctionHandler,
|
handler: ?FunctionHandler<*>,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,18 +153,20 @@ export type FunctionSpec = {|
|
|||||||
* `functions.js` just exports this same dictionary again and makes it public.
|
* `functions.js` just exports this same dictionary again and makes it public.
|
||||||
* `Parser.js` requires this dictionary.
|
* `Parser.js` requires this dictionary.
|
||||||
*/
|
*/
|
||||||
export const _functions: {[string]: FunctionSpec} = {};
|
export const _functions: {[string]: FunctionSpec<*>} = {};
|
||||||
|
|
||||||
export default function defineFunction({
|
export default function defineFunction<NODETYPE: NodeType>({
|
||||||
type,
|
type,
|
||||||
|
nodeType,
|
||||||
names,
|
names,
|
||||||
props,
|
props,
|
||||||
handler,
|
handler,
|
||||||
htmlBuilder,
|
htmlBuilder,
|
||||||
mathmlBuilder,
|
mathmlBuilder,
|
||||||
}: FunctionDefSpec) {
|
}: FunctionDefSpec<NODETYPE>) {
|
||||||
// Set default values of functions
|
// Set default values of functions
|
||||||
const data = {
|
const data = {
|
||||||
|
type,
|
||||||
numArgs: props.numArgs,
|
numArgs: props.numArgs,
|
||||||
argTypes: props.argTypes,
|
argTypes: props.argTypes,
|
||||||
greediness: (props.greediness === undefined) ? 1 : props.greediness,
|
greediness: (props.greediness === undefined) ? 1 : props.greediness,
|
||||||
@@ -176,7 +194,7 @@ export default function defineFunction({
|
|||||||
|
|
||||||
// Since the corresponding buildHTML/buildMathML function expects a
|
// Since the corresponding buildHTML/buildMathML function expects a
|
||||||
// list of elements, we normalize for different kinds of arguments
|
// list of elements, we normalize for different kinds of arguments
|
||||||
export const ordargument = function(arg: ParseNode): ParseNode[] {
|
export const ordargument = function(arg: ParseNode<*>): ParseNode<*>[] {
|
||||||
if (arg.type === "ordgroup") {
|
if (arg.type === "ordgroup") {
|
||||||
return arg.value;
|
return arg.value;
|
||||||
} else {
|
} else {
|
||||||
|
@@ -21,7 +21,7 @@ type AlignSpec = { type: "separator", separator: string } | {
|
|||||||
pregap?: number,
|
pregap?: number,
|
||||||
postgap?: number,
|
postgap?: number,
|
||||||
};
|
};
|
||||||
type ArrayEnvNodeData = {
|
export type ArrayEnvNodeData = {
|
||||||
type: "array",
|
type: "array",
|
||||||
hskipBeforeAndAfter?: boolean,
|
hskipBeforeAndAfter?: boolean,
|
||||||
arraystretch?: number,
|
arraystretch?: number,
|
||||||
@@ -29,7 +29,7 @@ type ArrayEnvNodeData = {
|
|||||||
cols?: AlignSpec[],
|
cols?: AlignSpec[],
|
||||||
// These fields are always set, but not on struct construction
|
// These fields are always set, but not on struct construction
|
||||||
// initialization.
|
// initialization.
|
||||||
body?: ParseNode[][], // List of rows in the (2D) array.
|
body?: ParseNode<*>[][], // List of rows in the (2D) array.
|
||||||
rowGaps?: number[],
|
rowGaps?: number[],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ function parseArray(
|
|||||||
parser: Parser,
|
parser: Parser,
|
||||||
result: ArrayEnvNodeData,
|
result: ArrayEnvNodeData,
|
||||||
style: StyleStr,
|
style: StyleStr,
|
||||||
): ParseNode {
|
): ParseNode<*> {
|
||||||
let row = [];
|
let row = [];
|
||||||
const body = [row];
|
const body = [row];
|
||||||
const rowGaps = [];
|
const rowGaps = [];
|
||||||
@@ -52,6 +52,7 @@ function parseArray(
|
|||||||
cell = new ParseNode("ordgroup", cell, parser.mode);
|
cell = new ParseNode("ordgroup", cell, parser.mode);
|
||||||
if (style) {
|
if (style) {
|
||||||
cell = new ParseNode("styling", {
|
cell = new ParseNode("styling", {
|
||||||
|
type: "styling",
|
||||||
style: style,
|
style: style,
|
||||||
value: [cell],
|
value: [cell],
|
||||||
}, parser.mode);
|
}, parser.mode);
|
||||||
@@ -85,7 +86,7 @@ function parseArray(
|
|||||||
}
|
}
|
||||||
result.body = body;
|
result.body = body;
|
||||||
result.rowGaps = rowGaps;
|
result.rowGaps = rowGaps;
|
||||||
return new ParseNode(result.type, result, parser.mode);
|
return new ParseNode("array", result, parser.mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@ import {
|
|||||||
} from "./defineFunction";
|
} from "./defineFunction";
|
||||||
|
|
||||||
import type {FunctionPropSpec, FunctionHandler} from "./defineFunction";
|
import type {FunctionPropSpec, FunctionHandler} from "./defineFunction";
|
||||||
|
import type {NodeType} from "./ParseNode";
|
||||||
|
|
||||||
// WARNING: New functions should be added to src/functions and imported here.
|
// WARNING: New functions should be added to src/functions and imported here.
|
||||||
|
|
||||||
@@ -17,12 +18,15 @@ export default functions;
|
|||||||
|
|
||||||
// Define a convenience function that mimcs the old semantics of 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.
|
// to support existing code so that we can migrate it a little bit at a time.
|
||||||
const defineFunction = function(
|
const defineFunction = function<NODETYPE: NodeType>(
|
||||||
|
// Type of node data output by the function handler. This is required to aid
|
||||||
|
// type inference of the actual function output.
|
||||||
|
type: NODETYPE,
|
||||||
names: string[],
|
names: string[],
|
||||||
props: FunctionPropSpec,
|
props: FunctionPropSpec,
|
||||||
handler: ?FunctionHandler, // null only if handled in parser
|
handler: ?FunctionHandler<NODETYPE>, // null only if handled in parser
|
||||||
) {
|
) {
|
||||||
_defineFunction({names, props, handler});
|
_defineFunction({type, names, props, handler});
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO(kevinb): have functions return an object and call defineFunction with
|
// TODO(kevinb): have functions return an object and call defineFunction with
|
||||||
@@ -48,7 +52,7 @@ import "./functions/kern";
|
|||||||
import "./functions/phantom";
|
import "./functions/phantom";
|
||||||
|
|
||||||
// Math class commands except \mathop
|
// Math class commands except \mathop
|
||||||
defineFunction([
|
defineFunction("mclass", [
|
||||||
"\\mathord", "\\mathbin", "\\mathrel", "\\mathopen",
|
"\\mathord", "\\mathbin", "\\mathrel", "\\mathopen",
|
||||||
"\\mathclose", "\\mathpunct", "\\mathinner",
|
"\\mathclose", "\\mathpunct", "\\mathinner",
|
||||||
], {
|
], {
|
||||||
@@ -63,7 +67,7 @@ defineFunction([
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Build a relation or stacked op by placing one symbol on top of another
|
// Build a relation or stacked op by placing one symbol on top of another
|
||||||
defineFunction(["\\stackrel", "\\overset", "\\underset"], {
|
defineFunction("mclass", ["\\stackrel", "\\overset", "\\underset"], {
|
||||||
numArgs: 2,
|
numArgs: 2,
|
||||||
}, function(context, args) {
|
}, function(context, args) {
|
||||||
const mathAxisArg = args[1];
|
const mathAxisArg = args[1];
|
||||||
@@ -104,7 +108,7 @@ const singleCharIntegrals: {[string]: string} = {
|
|||||||
// displaystyle. These four groups cover the four possible choices.
|
// displaystyle. These four groups cover the four possible choices.
|
||||||
|
|
||||||
// No limits, not symbols
|
// No limits, not symbols
|
||||||
defineFunction([
|
defineFunction("op", [
|
||||||
"\\arcsin", "\\arccos", "\\arctan", "\\arctg", "\\arcctg",
|
"\\arcsin", "\\arccos", "\\arctan", "\\arctg", "\\arcctg",
|
||||||
"\\arg", "\\ch", "\\cos", "\\cosec", "\\cosh", "\\cot", "\\cotg",
|
"\\arg", "\\ch", "\\cos", "\\cosec", "\\cosh", "\\cot", "\\cotg",
|
||||||
"\\coth", "\\csc", "\\ctg", "\\cth", "\\deg", "\\dim", "\\exp",
|
"\\coth", "\\csc", "\\ctg", "\\cth", "\\deg", "\\dim", "\\exp",
|
||||||
@@ -122,7 +126,7 @@ defineFunction([
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Limits, not symbols
|
// Limits, not symbols
|
||||||
defineFunction([
|
defineFunction("op", [
|
||||||
"\\det", "\\gcd", "\\inf", "\\lim", "\\max", "\\min", "\\Pr", "\\sup",
|
"\\det", "\\gcd", "\\inf", "\\lim", "\\max", "\\min", "\\Pr", "\\sup",
|
||||||
], {
|
], {
|
||||||
numArgs: 0,
|
numArgs: 0,
|
||||||
@@ -136,7 +140,7 @@ defineFunction([
|
|||||||
});
|
});
|
||||||
|
|
||||||
// No limits, symbols
|
// No limits, symbols
|
||||||
defineFunction([
|
defineFunction("op", [
|
||||||
"\\int", "\\iint", "\\iiint", "\\oint", "\u222b", "\u222c",
|
"\\int", "\\iint", "\\iiint", "\\oint", "\u222b", "\u222c",
|
||||||
"\u222d", "\u222e",
|
"\u222d", "\u222e",
|
||||||
], {
|
], {
|
||||||
@@ -175,7 +179,7 @@ import "./functions/font";
|
|||||||
import "./functions/accent";
|
import "./functions/accent";
|
||||||
|
|
||||||
// Horizontal stretchy braces
|
// Horizontal stretchy braces
|
||||||
defineFunction([
|
defineFunction("horizBrace", [
|
||||||
"\\overbrace", "\\underbrace",
|
"\\overbrace", "\\underbrace",
|
||||||
], {
|
], {
|
||||||
numArgs: 1,
|
numArgs: 1,
|
||||||
@@ -193,7 +197,7 @@ defineFunction([
|
|||||||
import "./functions/accentunder";
|
import "./functions/accentunder";
|
||||||
|
|
||||||
// Stretchy arrows with an optional argument
|
// Stretchy arrows with an optional argument
|
||||||
defineFunction([
|
defineFunction("xArrow", [
|
||||||
"\\xleftarrow", "\\xrightarrow", "\\xLeftarrow", "\\xRightarrow",
|
"\\xleftarrow", "\\xrightarrow", "\\xLeftarrow", "\\xRightarrow",
|
||||||
"\\xleftrightarrow", "\\xLeftrightarrow", "\\xhookleftarrow",
|
"\\xleftrightarrow", "\\xLeftrightarrow", "\\xhookleftarrow",
|
||||||
"\\xhookrightarrow", "\\xmapsto", "\\xrightharpoondown",
|
"\\xhookrightarrow", "\\xmapsto", "\\xrightharpoondown",
|
||||||
@@ -219,7 +223,7 @@ defineFunction([
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Infix generalized fractions
|
// Infix generalized fractions
|
||||||
defineFunction(["\\over", "\\choose", "\\atop"], {
|
defineFunction("infix", ["\\over", "\\choose", "\\atop"], {
|
||||||
numArgs: 0,
|
numArgs: 0,
|
||||||
infix: true,
|
infix: true,
|
||||||
}, function(context) {
|
}, function(context) {
|
||||||
@@ -245,7 +249,7 @@ defineFunction(["\\over", "\\choose", "\\atop"], {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Row breaks for aligned data
|
// Row breaks for aligned data
|
||||||
defineFunction(["\\\\", "\\cr"], {
|
defineFunction("cr", ["\\\\", "\\cr"], {
|
||||||
numArgs: 0,
|
numArgs: 0,
|
||||||
numOptionalArgs: 1,
|
numOptionalArgs: 1,
|
||||||
argTypes: ["size"],
|
argTypes: ["size"],
|
||||||
@@ -258,7 +262,7 @@ defineFunction(["\\\\", "\\cr"], {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Environment delimiters
|
// Environment delimiters
|
||||||
defineFunction(["\\begin", "\\end"], {
|
defineFunction("environment", ["\\begin", "\\end"], {
|
||||||
numArgs: 1,
|
numArgs: 1,
|
||||||
argTypes: ["text"],
|
argTypes: ["text"],
|
||||||
}, function(context, args) {
|
}, function(context, args) {
|
||||||
@@ -278,7 +282,7 @@ defineFunction(["\\begin", "\\end"], {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Box manipulation
|
// Box manipulation
|
||||||
defineFunction(["\\raisebox"], {
|
defineFunction("raisebox", ["\\raisebox"], {
|
||||||
numArgs: 2,
|
numArgs: 2,
|
||||||
argTypes: ["size", "text"],
|
argTypes: ["size", "text"],
|
||||||
allowedInText: true,
|
allowedInText: true,
|
||||||
|
@@ -50,7 +50,10 @@ const delimiters = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Delimiter functions
|
// Delimiter functions
|
||||||
function checkDelimiter(delim: ParseNode, context: FunctionContext): ParseNode {
|
function checkDelimiter(
|
||||||
|
delim: ParseNode<*>,
|
||||||
|
context: FunctionContext,
|
||||||
|
): ParseNode<*> {
|
||||||
if (utils.contains(delimiters, delim.value)) {
|
if (utils.contains(delimiters, delim.value)) {
|
||||||
return delim;
|
return delim;
|
||||||
} else {
|
} else {
|
||||||
|
@@ -4,6 +4,7 @@ import ParseError from "../ParseError";
|
|||||||
|
|
||||||
// Switching from text mode back to math mode
|
// Switching from text mode back to math mode
|
||||||
defineFunction({
|
defineFunction({
|
||||||
|
type: "styling",
|
||||||
names: ["\\(", "$"],
|
names: ["\\(", "$"],
|
||||||
props: {
|
props: {
|
||||||
numArgs: 0,
|
numArgs: 0,
|
||||||
@@ -32,6 +33,7 @@ defineFunction({
|
|||||||
|
|
||||||
// Check for extra closing math delimiters
|
// Check for extra closing math delimiters
|
||||||
defineFunction({
|
defineFunction({
|
||||||
|
type: "text", // Doesn't matter what this is.
|
||||||
names: ["\\)", "\\]"],
|
names: ["\\)", "\\]"],
|
||||||
props: {
|
props: {
|
||||||
numArgs: 0,
|
numArgs: 0,
|
||||||
|
@@ -30,11 +30,15 @@ defineFunction({
|
|||||||
parser.consumeSpaces();
|
parser.consumeSpaces();
|
||||||
const body = parser.parseExpression(true, breakOnTokenText);
|
const body = parser.parseExpression(true, breakOnTokenText);
|
||||||
|
|
||||||
|
// TODO: Refactor to avoid duplicating styleMap in multiple places (e.g.
|
||||||
|
// here and in buildHTML and de-dupe the enumeration of all the styles).
|
||||||
|
// $FlowFixMe: The names above exactly match the styles.
|
||||||
|
const style: StyleStr = funcName.slice(1, funcName.length - 5);
|
||||||
return {
|
return {
|
||||||
type: "styling",
|
type: "styling",
|
||||||
// Figure out what style to use by pulling out the style from
|
// Figure out what style to use by pulling out the style from
|
||||||
// the function name
|
// the function name
|
||||||
style: funcName.slice(1, funcName.length - 5),
|
style,
|
||||||
value: body,
|
value: body,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@@ -12,7 +12,7 @@ import type Settings from "./Settings";
|
|||||||
/**
|
/**
|
||||||
* Parses an expression using a Parser, then returns the parsed result.
|
* Parses an expression using a Parser, then returns the parsed result.
|
||||||
*/
|
*/
|
||||||
const parseTree = function(toParse: string, settings: Settings): ParseNode[] {
|
const parseTree = function(toParse: string, settings: Settings): ParseNode<*>[] {
|
||||||
if (!(typeof toParse === 'string' || toParse instanceof String)) {
|
if (!(typeof toParse === 'string' || toParse instanceof String)) {
|
||||||
throw new TypeError('KaTeX can only parse string typed expression');
|
throw new TypeError('KaTeX can only parse string typed expression');
|
||||||
}
|
}
|
||||||
|
@@ -107,7 +107,7 @@ const mathMLnode = function(label: string): mathMLTree.MathNode {
|
|||||||
// corresponds to 0.522 em inside the document.
|
// corresponds to 0.522 em inside the document.
|
||||||
|
|
||||||
const katexImagesData: {
|
const katexImagesData: {
|
||||||
[string]: ([string[], number, number] | [string[], number, number, string])
|
[string]: ([string[], number, number] | [[string], number, number, string])
|
||||||
} = {
|
} = {
|
||||||
// path(s), minWidth, height, align
|
// path(s), minWidth, height, align
|
||||||
overrightarrow: [["rightarrow"], 0.888, 522, "xMaxYMin"],
|
overrightarrow: [["rightarrow"], 0.888, 522, "xMaxYMin"],
|
||||||
@@ -159,7 +159,7 @@ const katexImagesData: {
|
|||||||
"shortrightharpoonabovebar"], 1.75, 716],
|
"shortrightharpoonabovebar"], 1.75, 716],
|
||||||
};
|
};
|
||||||
|
|
||||||
const groupLength = function(arg: ParseNode): number {
|
const groupLength = function(arg: ParseNode<*>): number {
|
||||||
if (arg.type === "ordgroup") {
|
if (arg.type === "ordgroup") {
|
||||||
return arg.value.length;
|
return arg.value.length;
|
||||||
} else {
|
} else {
|
||||||
@@ -167,7 +167,10 @@ const groupLength = function(arg: ParseNode): number {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const svgSpan = function(group: ParseNode, options: Options): DomSpan | SvgSpan {
|
const svgSpan = function(
|
||||||
|
group: ParseNode<"accent">,
|
||||||
|
options: Options,
|
||||||
|
): DomSpan | SvgSpan {
|
||||||
// Create a span with inline SVG for the element.
|
// Create a span with inline SVG for the element.
|
||||||
function buildSvgSpan_(): {
|
function buildSvgSpan_(): {
|
||||||
span: DomSpan | SvgSpan,
|
span: DomSpan | SvgSpan,
|
||||||
@@ -219,13 +222,16 @@ const svgSpan = function(group: ParseNode, options: Options): DomSpan | SvgSpan
|
|||||||
} else {
|
} else {
|
||||||
const spans = [];
|
const spans = [];
|
||||||
|
|
||||||
const [paths, minWidth, viewBoxHeight, align1] = katexImagesData[label];
|
const data = katexImagesData[label];
|
||||||
|
const [paths, minWidth, viewBoxHeight] = data;
|
||||||
const height = viewBoxHeight / 1000;
|
const height = viewBoxHeight / 1000;
|
||||||
|
|
||||||
const numSvgChildren = paths.length;
|
const numSvgChildren = paths.length;
|
||||||
let widthClasses;
|
let widthClasses;
|
||||||
let aligns;
|
let aligns;
|
||||||
if (numSvgChildren === 1) {
|
if (numSvgChildren === 1) {
|
||||||
|
// $FlowFixMe: All these cases must be of the 4-tuple type.
|
||||||
|
const align1: string = data[3];
|
||||||
widthClasses = ["hide-tail"];
|
widthClasses = ["hide-tail"];
|
||||||
aligns = [align1];
|
aligns = [align1];
|
||||||
} else if (numSvgChildren === 2) {
|
} else if (numSvgChildren === 2) {
|
||||||
|
@@ -20,9 +20,13 @@
|
|||||||
import type {Mode} from "./types";
|
import type {Mode} from "./types";
|
||||||
|
|
||||||
type Font = "main" | "ams"
|
type Font = "main" | "ams"
|
||||||
|
// Some of these have a "-token" suffix since these are also used as `ParseNode`
|
||||||
|
// types for raw text tokens, and we want to avoid conflicts with higher-level
|
||||||
|
// `ParseNode` types. These `ParseNode`s are constructed within `Parser` by
|
||||||
|
// looking up the `symbols` map.
|
||||||
type Group =
|
type Group =
|
||||||
"accent" | "bin" | "close" | "inner" | "mathord" | "op" | "open" | "punct" |
|
"accent-token" | "bin" | "close" | "inner" | "mathord" |
|
||||||
"rel" | "spacing" | "textord";
|
"op-token" | "open" | "punct" | "rel" | "spacing" | "textord";
|
||||||
type CharInfoMap = {[string]: {font: Font, group: Group, replace: ?string}};
|
type CharInfoMap = {[string]: {font: Font, group: Group, replace: ?string}};
|
||||||
|
|
||||||
const symbols: {[Mode]: CharInfoMap} = {
|
const symbols: {[Mode]: CharInfoMap} = {
|
||||||
@@ -59,12 +63,12 @@ const main = "main";
|
|||||||
const ams = "ams";
|
const ams = "ams";
|
||||||
|
|
||||||
// groups:
|
// groups:
|
||||||
const accent = "accent";
|
const accent = "accent-token";
|
||||||
const bin = "bin";
|
const bin = "bin";
|
||||||
const close = "close";
|
const close = "close";
|
||||||
const inner = "inner";
|
const inner = "inner";
|
||||||
const mathord = "mathord";
|
const mathord = "mathord";
|
||||||
const op = "op";
|
const op = "op-token";
|
||||||
const open = "open";
|
const open = "open";
|
||||||
const punct = "punct";
|
const punct = "punct";
|
||||||
const rel = "rel";
|
const rel = "rel";
|
||||||
|
@@ -21,7 +21,7 @@ export type Mode = "math" | "text";
|
|||||||
export type ArgType = "color" | "size" | "url" | "original" | Mode;
|
export type ArgType = "color" | "size" | "url" | "original" | Mode;
|
||||||
|
|
||||||
// LaTeX display style.
|
// LaTeX display style.
|
||||||
export type StyleStr = "text" | "display";
|
export type StyleStr = "text" | "display" | "script" | "scriptscript";
|
||||||
|
|
||||||
// Allowable token text for "break" arguments in parser
|
// Allowable token text for "break" arguments in parser
|
||||||
export type BreakToken = "]" | "}" | "$" | "\\)";
|
export type BreakToken = "]" | "}" | "$" | "\\)";
|
||||||
|
@@ -407,6 +407,7 @@ describe("A subscript and superscript parser", function() {
|
|||||||
|
|
||||||
it("should not fail when there is no nucleus", function() {
|
it("should not fail when there is no nucleus", function() {
|
||||||
expect("^3").toParse();
|
expect("^3").toParse();
|
||||||
|
expect("^3+").toParse();
|
||||||
expect("_2").toParse();
|
expect("_2").toParse();
|
||||||
expect("^3_2").toParse();
|
expect("^3_2").toParse();
|
||||||
expect("_2^3").toParse();
|
expect("_2^3").toParse();
|
||||||
|
Reference in New Issue
Block a user