Replace ParseNode<*> with a more accurate AnyParseNode and fix flow errors. (#1387)

* Replace ParseNode<*> with a more accurate AnyParseNode and fix flow errors.

* Allow "array" environment type spec to use any all symbol type.

Before this commit, it was constrained to use "mathord" and "textord", but a
recent change in HEAD resulted in a "rel" node being used in the spec for, e.g.
\begin{array}{|l||c:r::}\end{array}

* Address reviewer comments: rename `lastRow` to `row` in array.js.
This commit is contained in:
Ashish Myles
2018-06-04 10:56:51 -04:00
committed by Kevin Barabash
parent 4492eedb68
commit 19d2aa63c3
20 changed files with 284 additions and 171 deletions

View File

@@ -18,7 +18,7 @@ import domTree from "./src/domTree";
import utils from "./src/utils"; import utils from "./src/utils";
import type {SettingsOptions} from "./src/Settings"; import type {SettingsOptions} from "./src/Settings";
import type ParseNode from "./src/ParseNode"; import type {AnyParseNode} from "./src/ParseNode";
import {defineSymbol} from './src/symbols'; import {defineSymbol} from './src/symbols';
import {defineMacro} from './src/macros'; import {defineMacro} from './src/macros';
@@ -70,7 +70,7 @@ const renderToString = function(
const generateParseTree = function( const generateParseTree = function(
expression: string, expression: string,
options: SettingsOptions, options: SettingsOptions,
): ParseNode<*>[] { ): AnyParseNode[] {
const settings = new Settings(options); const settings = new Settings(options);
return parseTree(expression, settings); return parseTree(expression, settings);
}; };

View File

@@ -1,7 +1,8 @@
// @flow // @flow
import ParseNode from "./ParseNode";
import {Token} from "./Token"; import {Token} from "./Token";
import type {AnyParseNode} from "./ParseNode";
/** /**
* This is the ParseError class, which is the main error thrown by KaTeX * This is the ParseError class, which is the main error thrown by KaTeX
* functions when something has gone wrong. This is used to distinguish internal * functions when something has gone wrong. This is used to distinguish internal
@@ -15,8 +16,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 | AnyParseNode, // An object providing position information
) { ) {
let error = "KaTeX parse error: " + message; let error = "KaTeX parse error: " + message;
let start; let start;

View File

@@ -1,9 +1,10 @@
// @flow // @flow
import SourceLocation from "./SourceLocation"; import SourceLocation from "./SourceLocation";
import type {ArrayEnvNodeData} from "./environments/array.js"; import {GROUPS} from "./symbols";
import type {ArrayEnvNodeData} from "./environments/array";
import type {Mode, StyleStr} from "./types"; import type {Mode, StyleStr} from "./types";
import type {Token} from "./Token.js"; import type {Token} from "./Token";
import type {Measurement} from "./units.js"; import type {Measurement} from "./units";
/** /**
* The resulting parse tree nodes of the parse tree. * The resulting parse tree nodes of the parse tree.
@@ -40,18 +41,83 @@ export type NodeValue<TYPE: NodeType> = $ElementType<ParseNodeTypes, TYPE>;
export type LeftRightDelimType = {| export type LeftRightDelimType = {|
type: "leftright", type: "leftright",
body: ParseNode<*>[], body: AnyParseNode[],
left: string, left: string,
right: string, right: string,
|}; |};
// ParseNode's corresponding to Symbol `Group`s in symbols.js.
export type SymbolParseNode =
ParseNode<"accent-token"> |
ParseNode<"bin"> |
ParseNode<"close"> |
ParseNode<"inner"> |
ParseNode<"mathord"> |
ParseNode<"op-token"> |
ParseNode<"open"> |
ParseNode<"punct"> |
ParseNode<"rel"> |
ParseNode<"spacing"> |
ParseNode<"textord">;
// Union of all possible `ParseNode<>` types.
// Unable to derive this directly from `ParseNodeTypes` due to
// https://github.com/facebook/flow/issues/6369.
// Cannot use `ParseNode<NodeType>` since `ParseNode` is not strictly co-variant
// w.r.t. its type parameter due to the way the value type is computed.
export type AnyParseNode =
SymbolParseNode |
ParseNode<"array"> |
ParseNode<"color"> |
ParseNode<"color-token"> |
ParseNode<"op"> |
ParseNode<"ordgroup"> |
ParseNode<"size"> |
ParseNode<"styling"> |
ParseNode<"supsub"> |
ParseNode<"tag"> |
ParseNode<"text"> |
ParseNode<"url"> |
ParseNode<"verb"> |
ParseNode<"accent"> |
ParseNode<"accentUnder"> |
ParseNode<"cr"> |
ParseNode<"delimsizing"> |
ParseNode<"enclose"> |
ParseNode<"environment"> |
ParseNode<"font"> |
ParseNode<"genfrac"> |
ParseNode<"horizBrace"> |
ParseNode<"href"> |
ParseNode<"infix"> |
ParseNode<"kern"> |
ParseNode<"lap"> |
ParseNode<"leftright"> |
ParseNode<"leftright-right"> |
ParseNode<"mathchoice"> |
ParseNode<"middle"> |
ParseNode<"mclass"> |
ParseNode<"mod"> |
ParseNode<"operatorname"> |
ParseNode<"overline"> |
ParseNode<"phantom"> |
ParseNode<"hphantom"> |
ParseNode<"vphantom"> |
ParseNode<"raisebox"> |
ParseNode<"rule"> |
ParseNode<"sizing"> |
ParseNode<"smash"> |
ParseNode<"sqrt"> |
ParseNode<"underline"> |
ParseNode<"xArrow">;
// Map from `type` field value to corresponding `value` type. // Map from `type` field value to corresponding `value` type.
export type ParseNodeTypes = { export type ParseNodeTypes = {
"array": ArrayEnvNodeData, "array": ArrayEnvNodeData,
"color": {| "color": {|
type: "color", type: "color",
color: string, color: string,
value: ParseNode<*>[], value: AnyParseNode[],
|}, |},
"color-token": string, "color-token": string,
// To avoid requiring run-time type assertions, this more carefully captures // To avoid requiring run-time type assertions, this more carefully captures
@@ -73,9 +139,9 @@ export type ParseNodeTypes = {
suppressBaseShift?: boolean, suppressBaseShift?: boolean,
symbol: false, // If 'symbol' is true, `body` *must* be set. symbol: false, // If 'symbol' is true, `body` *must* be set.
body?: void, body?: void,
value: ParseNode<*>[], value: AnyParseNode[],
|}, |},
"ordgroup": ParseNode<*>[], "ordgroup": AnyParseNode[],
"size": {| "size": {|
type: "size", type: "size",
value: Measurement, value: Measurement,
@@ -83,22 +149,22 @@ export type ParseNodeTypes = {
"styling": {| "styling": {|
type: "styling", type: "styling",
style: StyleStr, style: StyleStr,
value: ParseNode<*>[], value: AnyParseNode[],
|}, |},
"supsub": {| "supsub": {|
type: "supsub", type: "supsub",
base: ?ParseNode<*>, base: ?AnyParseNode,
sup?: ?ParseNode<*>, sup?: ?AnyParseNode,
sub?: ?ParseNode<*>, sub?: ?AnyParseNode,
|}, |},
"tag": {| "tag": {|
type: "tag", type: "tag",
body: ParseNode<*>[], body: AnyParseNode[],
tag: ParseNode<*>[], tag: AnyParseNode[],
|}, |},
"text": {| "text": {|
type: "text", type: "text",
body: ParseNode<*>[], body: AnyParseNode[],
font?: string, font?: string,
|}, |},
"url": {| "url": {|
@@ -131,14 +197,14 @@ export type ParseNodeTypes = {
label: string, label: string,
isStretchy?: boolean, isStretchy?: boolean,
isShifty?: boolean, isShifty?: boolean,
base: ParseNode<*>, base: AnyParseNode,
|}, |},
"accentUnder": {| "accentUnder": {|
type: "accentUnder", type: "accentUnder",
label: string, label: string,
isStretchy?: boolean, isStretchy?: boolean,
isShifty?: boolean, isShifty?: boolean,
base: ParseNode<*>, base: AnyParseNode,
|}, |},
"cr": {| "cr": {|
type: "cr", type: "cr",
@@ -157,23 +223,23 @@ export type ParseNodeTypes = {
label: string, label: string,
backgroundColor?: ParseNode<"color-token">, backgroundColor?: ParseNode<"color-token">,
borderColor?: ParseNode<"color-token">, borderColor?: ParseNode<"color-token">,
body: ParseNode<*>, body: AnyParseNode,
|}, |},
"environment": {| "environment": {|
type: "environment", type: "environment",
name: string, name: string,
nameGroup: ParseNode<*>, nameGroup: AnyParseNode,
|}, |},
"font": {| "font": {|
type: "font", type: "font",
font: string, font: string,
body: ParseNode<*>, body: AnyParseNode,
|}, |},
"genfrac": {| "genfrac": {|
type: "genfrac", type: "genfrac",
continued: boolean, continued: boolean,
numer: ParseNode<*>, numer: AnyParseNode,
denom: ParseNode<*>, denom: AnyParseNode,
hasBarLine: boolean, hasBarLine: boolean,
leftDelim: ?string, leftDelim: ?string,
rightDelim: ?string, rightDelim: ?string,
@@ -183,12 +249,12 @@ export type ParseNodeTypes = {
type: "horizBrace", type: "horizBrace",
label: string, label: string,
isOver: boolean, isOver: boolean,
base: ParseNode<*>, base: AnyParseNode,
|}, |},
"href": {| "href": {|
type: "href", type: "href",
href: string, href: string,
body: ParseNode<*>[], body: AnyParseNode[],
|}, |},
"infix": {| "infix": {|
type: "infix", type: "infix",
@@ -202,7 +268,7 @@ export type ParseNodeTypes = {
"lap": {| "lap": {|
type: "lap", type: "lap",
alignment: string, alignment: string,
body: ParseNode<*>, body: AnyParseNode,
|}, |},
"leftright": LeftRightDelimType, "leftright": LeftRightDelimType,
"leftright-right": {| "leftright-right": {|
@@ -211,10 +277,10 @@ export type ParseNodeTypes = {
|}, |},
"mathchoice": {| "mathchoice": {|
type: "mathchoice", type: "mathchoice",
display: ParseNode<*>[], display: AnyParseNode[],
text: ParseNode<*>[], text: AnyParseNode[],
script: ParseNode<*>[], script: AnyParseNode[],
scriptscript: ParseNode<*>[], scriptscript: AnyParseNode[],
|}, |},
"middle": {| "middle": {|
type: "middle", type: "middle",
@@ -223,40 +289,40 @@ export type ParseNodeTypes = {
"mclass": {| "mclass": {|
type: "mclass", type: "mclass",
mclass: string, mclass: string,
value: ParseNode<*>[], value: AnyParseNode[],
|}, |},
"mod": {| "mod": {|
type: "mod", type: "mod",
modType: string, modType: string,
value: ?ParseNode<*>[], value: ?AnyParseNode[],
|}, |},
"operatorname": {| "operatorname": {|
type: "operatorname", type: "operatorname",
value: ParseNode<*>[], value: AnyParseNode[],
|}, |},
"overline": {| "overline": {|
type: "overline", type: "overline",
body: ParseNode<*>, body: AnyParseNode,
|}, |},
"phantom": {| "phantom": {|
type: "phantom", type: "phantom",
value: ParseNode<*>[], value: AnyParseNode[],
|}, |},
"hphantom": {| "hphantom": {|
type: "hphantom", type: "hphantom",
body: ParseNode<*>, body: AnyParseNode,
value: ParseNode<*>[], value: AnyParseNode[],
|}, |},
"vphantom": {| "vphantom": {|
type: "vphantom", type: "vphantom",
body: ParseNode<*>, body: AnyParseNode,
value: ParseNode<*>[], value: AnyParseNode[],
|}, |},
"raisebox": {| "raisebox": {|
type: "raisebox", type: "raisebox",
dy: ParseNode<"size">, dy: ParseNode<"size">,
body: ParseNode<*>, body: AnyParseNode,
value: ParseNode<*>[], value: AnyParseNode[],
|}, |},
"rule": {| "rule": {|
type: "rule", type: "rule",
@@ -267,28 +333,28 @@ export type ParseNodeTypes = {
"sizing": {| "sizing": {|
type: "sizing", type: "sizing",
size: number, size: number,
value: ParseNode<*>[], value: AnyParseNode[],
|}, |},
"smash": {| "smash": {|
type: "smash", type: "smash",
body: ParseNode<*>, body: AnyParseNode,
smashHeight: boolean, smashHeight: boolean,
smashDepth: boolean, smashDepth: boolean,
|}, |},
"sqrt": {| "sqrt": {|
type: "sqrt", type: "sqrt",
body: ParseNode<*>, body: AnyParseNode,
index: ?ParseNode<*>, index: ?AnyParseNode,
|}, |},
"underline": {| "underline": {|
type: "underline", type: "underline",
body: ParseNode<*>, body: AnyParseNode,
|}, |},
"xArrow": {| "xArrow": {|
type: "xArrow", type: "xArrow",
label: string, label: string,
body: ParseNode<*>, body: AnyParseNode,
below: ?ParseNode<*>, below: ?AnyParseNode,
|}, |},
}; };
@@ -297,8 +363,7 @@ export type ParseNodeTypes = {
* typing. Throws if the node's type does not match. * typing. Throws if the node's type does not match.
*/ */
export function assertNodeType<NODETYPE: NodeType>( export function assertNodeType<NODETYPE: NodeType>(
// The union allows either ParseNode<*> or the union of two specific nodes. node: ?AnyParseNode,
node: ?ParseNode<*> | ParseNode<*>,
type: NODETYPE, type: NODETYPE,
): ParseNode<NODETYPE> { ): ParseNode<NODETYPE> {
const typedNode = checkNodeType(node, type); const typedNode = checkNodeType(node, type);
@@ -315,11 +380,38 @@ export function assertNodeType<NODETYPE: NodeType>(
* returns null. * returns null.
*/ */
export function checkNodeType<NODETYPE: NodeType>( export function checkNodeType<NODETYPE: NodeType>(
// The union allows either ParseNode<*> or the union of two specific nodes. node: ?AnyParseNode,
node: ?ParseNode<*> | ParseNode<*>,
type: NODETYPE, type: NODETYPE,
): ?ParseNode<NODETYPE> { ): ?ParseNode<NODETYPE> {
return node && node.type === type ? if (node && node.type === type) {
(node: ParseNode<NODETYPE>) : // $FlowFixMe: Inference not sophisticated enough to figure this out.
null; return node;
}
return null;
}
/**
* Returns the node more strictly typed iff it is of the given type. Otherwise,
* returns null.
*/
export function assertSymbolNodeType(node: ?AnyParseNode): SymbolParseNode {
const typedNode = checkSymbolNodeType(node);
if (!typedNode) {
throw new Error(
`Expected node of symbol group type, but got ` +
(node ? `node of type ${node.type}` : String(node)));
}
return typedNode;
}
/**
* Returns the node more strictly typed iff it is of the given type. Otherwise,
* returns null.
*/
export function checkSymbolNodeType(node: ?AnyParseNode): ?SymbolParseNode {
if (node && GROUPS.hasOwnProperty(node.type)) {
// $FlowFixMe
return node;
}
return null;
} }

View File

@@ -9,12 +9,12 @@ import {validUnit} from "./units";
import {supportedCodepoint} from "./unicodeScripts"; import {supportedCodepoint} from "./unicodeScripts";
import unicodeAccents from "./unicodeAccents"; import unicodeAccents from "./unicodeAccents";
import unicodeSymbols from "./unicodeSymbols"; import unicodeSymbols from "./unicodeSymbols";
import ParseNode, {assertNodeType} from "./ParseNode"; import ParseNode, {assertNodeType, checkNodeType} from "./ParseNode";
import ParseError from "./ParseError"; import ParseError from "./ParseError";
import {combiningDiacriticalMarksEndRegex} from "./Lexer.js"; import {combiningDiacriticalMarksEndRegex} from "./Lexer.js";
import Settings from "./Settings"; import Settings from "./Settings";
import {Token} from "./Token"; import {Token} from "./Token";
import type {AnyParseNode} from "./ParseNode";
import type {Mode, ArgType, BreakToken} from "./types"; import type {Mode, ArgType, BreakToken} from "./types";
import type {FunctionContext, FunctionSpec} from "./defineFunction"; import type {FunctionContext, FunctionSpec} from "./defineFunction";
import type {EnvSpec} from "./defineEnvironment"; import type {EnvSpec} from "./defineEnvironment";
@@ -60,12 +60,12 @@ type ParsedFunc = {|
|}; |};
type ParsedArg = {| type ParsedArg = {|
type: "arg", type: "arg",
result: ParseNode<*>, result: AnyParseNode,
token: Token, token: Token,
|}; |};
type ParsedFuncOrArg = ParsedFunc | ParsedArg; type ParsedFuncOrArg = ParsedFunc | ParsedArg;
function newArgument(result: ParseNode<*>, token: Token): ParsedArg { function newArgument(result: AnyParseNode, token: Token): ParsedArg {
return {type: "arg", result, token}; return {type: "arg", result, token};
} }
@@ -127,7 +127,7 @@ export default class Parser {
/** /**
* Main parsing function, which parses an entire input. * Main parsing function, which parses an entire input.
*/ */
parse(): ParseNode<*>[] { parse(): AnyParseNode[] {
// Create a group namespace for the math expression. // Create a group namespace for the math expression.
// (LaTeX creates a new group for every $...$, $$...$$, \[...\].) // (LaTeX creates a new group for every $...$, $$...$$, \[...\].)
this.gullet.beginGroup(); this.gullet.beginGroup();
@@ -167,7 +167,7 @@ export default class Parser {
parseExpression( parseExpression(
breakOnInfix: boolean, breakOnInfix: boolean,
breakOnTokenText?: BreakToken, breakOnTokenText?: BreakToken,
): ParseNode<*>[] { ): AnyParseNode[] {
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)
@@ -208,13 +208,13 @@ 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: AnyParseNode[]): AnyParseNode[] {
let overIndex = -1; let overIndex = -1;
let funcName; let funcName;
for (let i = 0; i < body.length; i++) { for (let i = 0; i < body.length; i++) {
const node = body[i]; const node = checkNodeType(body[i], "infix");
if (node.type === "infix") { if (node) {
if (overIndex !== -1) { if (overIndex !== -1) {
throw new ParseError( throw new ParseError(
"only one infix operator per group", "only one infix operator per group",
@@ -259,7 +259,7 @@ export default class Parser {
*/ */
handleSupSubscript( handleSupSubscript(
name: string, // For error reporting. name: string, // For error reporting.
): ParseNode<*> { ): AnyParseNode {
const symbolToken = this.nextToken; const symbolToken = this.nextToken;
const symbol = symbolToken.text; const symbol = symbolToken.text;
this.consume(); this.consume();
@@ -297,7 +297,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(): AnyParseNode {
const text = this.nextToken.text; const text = this.nextToken.text;
const textordArray = []; const textordArray = [];
@@ -329,7 +329,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): ?AnyParseNode {
// 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);
@@ -352,14 +352,15 @@ export default class Parser {
if (lex.text === "\\limits" || lex.text === "\\nolimits") { if (lex.text === "\\limits" || lex.text === "\\nolimits") {
// We got a limit control // We got a limit control
if (!base || base.type !== "op") { const opNode = checkNodeType(base, "op");
if (opNode) {
const limits = lex.text === "\\limits";
opNode.value.limits = limits;
opNode.value.alwaysHandleSupSub = true;
} else {
throw new ParseError( throw new ParseError(
"Limit controls must follow a math operator", "Limit controls must follow a math operator",
lex); lex);
} else {
const limits = lex.text === "\\limits";
base.value.limits = limits;
base.value.alwaysHandleSupSub = true;
} }
this.consume(); this.consume();
} else if (lex.text === "^") { } else if (lex.text === "^") {
@@ -427,7 +428,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): ?AnyParseNode {
const start = this.parseSymbol(); const start = this.parseSymbol();
if (start == null) { if (start == null) {
@@ -463,10 +464,12 @@ export default class Parser {
const result = env.handler(context, args, optArgs); const result = env.handler(context, args, optArgs);
this.expect("\\end", false); this.expect("\\end", false);
const endNameToken = this.nextToken; const endNameToken = this.nextToken;
const end = assertNodeType(this.parseFunction(), "environment"); let end = this.parseFunction();
if (!end) { if (!end) {
throw new ParseError("failed to parse function after \\end"); throw new ParseError("failed to parse function after \\end");
} else if (end.value.name !== envName) { }
end = assertNodeType(end, "environment");
if (end.value.name !== envName) {
throw new ParseError( throw new ParseError(
"Mismatch: \\begin{" + envName + "} matched " + "Mismatch: \\begin{" + envName + "} matched " +
"by \\end{" + end.value.name + "}", "by \\end{" + end.value.name + "}",
@@ -483,7 +486,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(): ?AnyParseNode {
const baseGroup = this.parseGroup(); const baseGroup = this.parseGroup();
return baseGroup ? this.parseGivenFunction(baseGroup) : null; return baseGroup ? this.parseGivenFunction(baseGroup) : null;
} }
@@ -495,7 +498,7 @@ export default class Parser {
parseGivenFunction( parseGivenFunction(
baseGroup: ParsedFuncOrArg, baseGroup: ParsedFuncOrArg,
breakOnTokenText?: BreakToken, breakOnTokenText?: BreakToken,
): ParseNode<*> { ): AnyParseNode {
if (baseGroup.type === "fn") { if (baseGroup.type === "fn") {
const func = baseGroup.result; const func = baseGroup.result;
const funcData = functions[func]; const funcData = functions[func];
@@ -535,11 +538,11 @@ export default class Parser {
*/ */
callFunction( callFunction(
name: string, name: string,
args: ParseNode<*>[], args: AnyParseNode[],
optArgs: (?ParseNode<*>)[], optArgs: (?AnyParseNode)[],
token?: Token, token?: Token,
breakOnTokenText?: BreakToken, breakOnTokenText?: BreakToken,
): ParseNode<*> { ): AnyParseNode {
const context: FunctionContext = { const context: FunctionContext = {
funcName: name, funcName: name,
parser: this, parser: this,
@@ -561,8 +564,8 @@ export default class Parser {
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: AnyParseNode[],
optArgs: (?ParseNode<*>)[], optArgs: (?AnyParseNode)[],
} { } {
const totalArgs = funcData.numArgs + funcData.numOptionalArgs; const totalArgs = funcData.numArgs + funcData.numOptionalArgs;
if (totalArgs === 0) { if (totalArgs === 0) {
@@ -609,7 +612,7 @@ export default class Parser {
"Expected group after '" + func + "'", nextToken); "Expected group after '" + func + "'", nextToken);
} }
} }
let argNode: ParseNode<*>; let argNode: AnyParseNode;
if (arg.type === "fn") { if (arg.type === "fn") {
const argGreediness = const argGreediness =
functions[arg.result].greediness; functions[arg.result].greediness;
@@ -895,7 +898,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: AnyParseNode[]) {
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];

View File

@@ -7,13 +7,13 @@
import utils from "./utils"; import utils from "./utils";
import ParseError from "./ParseError.js"; import ParseError from "./ParseError.js";
import ParseNode from "./ParseNode";
import {Token} from "./Token"; import {Token} from "./Token";
import type {AnyParseNode} from "./ParseNode";
import type {MacroMap} from "./macros"; import type {MacroMap} from "./macros";
export type StrictFunction = export type StrictFunction =
(errorCode: string, errorMsg: string, token?: Token | ParseNode<*>) => (errorCode: string, errorMsg: string, token?: Token | AnyParseNode) =>
?(boolean | string); ?(boolean | string);
export type SettingsOptions = { export type SettingsOptions = {
@@ -65,7 +65,7 @@ class Settings {
* Can safely not be called if `this.strict` is false in JavaScript. * Can safely not be called if `this.strict` is false in JavaScript.
*/ */
reportNonstrict(errorCode: string, errorMsg: string, reportNonstrict(errorCode: string, errorMsg: string,
token?: Token | ParseNode<*>) { token?: Token | AnyParseNode) {
let strict = this.strict; let strict = this.strict;
if (typeof strict === "function") { if (typeof strict === "function") {
// Allow return value of strict function to be boolean or string // Allow return value of strict function to be boolean or string
@@ -98,7 +98,7 @@ class Settings {
* This is for the second category of `errorCode`s listed in the README. * This is for the second category of `errorCode`s listed in the README.
*/ */
useStrictBehavior(errorCode: string, errorMsg: string, useStrictBehavior(errorCode: string, errorMsg: string,
token?: Token | ParseNode<*>) { token?: Token | AnyParseNode) {
let strict = this.strict; let strict = this.strict;
if (typeof strict === "function") { if (typeof strict === "function") {
// Allow return value of strict function to be boolean or string // Allow return value of strict function to be boolean or string

View File

@@ -6,7 +6,7 @@ import Options from "./Options";
import Settings from "./Settings"; import Settings from "./Settings";
import Style from "./Style"; import Style from "./Style";
import type ParseNode from "./ParseNode"; import type {AnyParseNode} from "./ParseNode";
import type {DomSpan} from "./domTree"; import type {DomSpan} from "./domTree";
const optionsFromSettings = function(settings: Settings) { const optionsFromSettings = function(settings: Settings) {
@@ -17,7 +17,7 @@ const optionsFromSettings = function(settings: Settings) {
}; };
export const buildTree = function( export const buildTree = function(
tree: ParseNode<*>[], tree: AnyParseNode[],
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: AnyParseNode[],
expression: string, expression: string,
settings: Settings, settings: Settings,
): DomSpan { ): DomSpan {

View File

@@ -1,14 +1,13 @@
// @flow // @flow
import {_htmlGroupBuilders, _mathmlGroupBuilders} from "./defineFunction"; import {_htmlGroupBuilders, _mathmlGroupBuilders} from "./defineFunction";
import Options from "./Options";
import ParseNode from "./ParseNode"; import ParseNode from "./ParseNode";
import type Parser from "./Parser"; import type Parser from "./Parser";
import type {AnyParseNode} from "./ParseNode";
import type {ArgType, Mode} from "./types"; import type {ArgType, Mode} from "./types";
import type {HtmlDomNode} from "./domTree";
import type {NodeType} from "./ParseNode"; import type {NodeType} from "./ParseNode";
import type {MathNode} from "./mathMLTree"; import type {HtmlBuilder, MathMLBuilder} from "./defineFunction";
/** /**
* The context contains the following properties: * The context contains the following properties:
@@ -29,8 +28,8 @@ type EnvContext = {|
*/ */
type EnvHandler<NODETYPE: NodeType> = ( type EnvHandler<NODETYPE: NodeType> = (
context: EnvContext, context: EnvContext,
args: ParseNode<*>[], args: AnyParseNode[],
optArgs: (?ParseNode<*>)[], optArgs: (?AnyParseNode)[],
) => ParseNode<NODETYPE>; ) => ParseNode<NODETYPE>;
/** /**
@@ -85,11 +84,11 @@ type EnvDefSpec<NODETYPE: 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.
htmlBuilder: (group: ParseNode<NODETYPE>, options: Options) => HtmlDomNode, htmlBuilder: HtmlBuilder<NODETYPE>,
// 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.
mathmlBuilder: (group: ParseNode<NODETYPE>, options: Options) => MathNode, mathmlBuilder: MathMLBuilder<NODETYPE>,
|}; |};
export default function defineEnvironment<NODETYPE: NodeType>({ export default function defineEnvironment<NODETYPE: NodeType>({

View File

@@ -3,7 +3,7 @@ import {checkNodeType} from "./ParseNode";
import domTree from "./domTree"; import domTree from "./domTree";
import type Parser from "./Parser"; import type Parser from "./Parser";
import type ParseNode, {NodeType} from "./ParseNode"; import type ParseNode, {AnyParseNode, NodeType} 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 {HtmlDomNode} from "./domTree"; import type {HtmlDomNode} from "./domTree";
@@ -20,8 +20,8 @@ export type FunctionContext = {|
export type FunctionHandler<NODETYPE: NodeType> = ( export type FunctionHandler<NODETYPE: NodeType> = (
context: FunctionContext, context: FunctionContext,
args: ParseNode<*>[], args: AnyParseNode[],
optArgs: (?ParseNode<*>)[], optArgs: (?AnyParseNode)[],
) => ParseNode<NODETYPE>; ) => ParseNode<NODETYPE>;
export type HtmlBuilder<NODETYPE> = (ParseNode<NODETYPE>, Options) => HtmlDomNode; export type HtmlBuilder<NODETYPE> = (ParseNode<NODETYPE>, Options) => HtmlDomNode;
@@ -245,7 +245,7 @@ export function defineFunctionBuilders<NODETYPE: NodeType>({
// 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: AnyParseNode): AnyParseNode[] {
const node = checkNodeType(arg, "ordgroup"); const node = checkNodeType(arg, "ordgroup");
return node ? node.value : [arg]; return node ? node.value : [arg];
}; };

View File

@@ -4,7 +4,8 @@ import defineEnvironment from "../defineEnvironment";
import mathMLTree from "../mathMLTree"; import mathMLTree from "../mathMLTree";
import ParseError from "../ParseError"; import ParseError from "../ParseError";
import ParseNode from "../ParseNode"; import ParseNode from "../ParseNode";
import {assertNodeType} from "../ParseNode"; import {assertNodeType, assertSymbolNodeType} from "../ParseNode";
import {checkNodeType, checkSymbolNodeType} from "../ParseNode";
import {calculateSize} from "../units"; import {calculateSize} from "../units";
import utils from "../utils"; import utils from "../utils";
@@ -12,7 +13,9 @@ import * as html from "../buildHTML";
import * as mml from "../buildMathML"; import * as mml from "../buildMathML";
import type Parser from "../Parser"; import type Parser from "../Parser";
import type {AnyParseNode} from "../ParseNode";
import type {StyleStr} from "../types"; import type {StyleStr} from "../types";
import type {HtmlBuilder, MathMLBuilder} from "../defineFunction";
// Data stored in the ParseNode associated with the environment. // Data stored in the ParseNode associated with the environment.
type AlignSpec = { type: "separator", separator: string } | { type AlignSpec = { type: "separator", separator: string } | {
@@ -28,8 +31,8 @@ export type ArrayEnvNodeData = {|
arraystretch: number, arraystretch: number,
addJot?: boolean, addJot?: boolean,
cols?: AlignSpec[], cols?: AlignSpec[],
body: ParseNode<*>[][], // List of rows in the (2D) array. body: AnyParseNode[][], // List of rows in the (2D) array.
rowGaps: (?ParseNode<*>)[], rowGaps: (?ParseNode<"size">)[],
numHLinesBeforeRow: number[], numHLinesBeforeRow: number[],
|}; |};
// Same as above but with some fields not yet filled. // Same as above but with some fields not yet filled.
@@ -40,8 +43,8 @@ type ArrayEnvNodeDataIncomplete = {|
addJot?: boolean, addJot?: boolean,
cols?: AlignSpec[], cols?: AlignSpec[],
// Before these fields are filled. // Before these fields are filled.
body?: ParseNode<*>[][], body?: AnyParseNode[][],
rowGaps?: (?ParseNode<*>)[], rowGaps?: (?ParseNode<"size">)[],
numHLinesBeforeRow?: number[], numHLinesBeforeRow?: number[],
|}; |};
@@ -110,10 +113,8 @@ function parseArray(
} else if (next === "\\end") { } else if (next === "\\end") {
// Arrays terminate newlines with `\crcr` which consumes a `\cr` if // Arrays terminate newlines with `\crcr` which consumes a `\cr` if
// the last line is empty. // the last line is empty.
const lastRow = body[body.length - 1]; // NOTE: Currently, `cell` is the last item added into `row`.
if (body.length > 1 if (row.length === 1 && cell.value.value[0].value.length === 0) {
&& lastRow.length === 1
&& lastRow[0].value.value[0].value.length === 0) {
body.pop(); body.pop();
} }
break; break;
@@ -161,8 +162,7 @@ type Outrow = {
pos: number, pos: number,
}; };
const htmlBuilder = function(group, options) { const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
const groupValue = assertNodeType(group, "array").value;
let r; let r;
let c; let c;
const nr = group.value.body.length; const nr = group.value.body.length;
@@ -180,7 +180,7 @@ const htmlBuilder = function(group, options) {
// Default \jot from ltmath.dtx // Default \jot from ltmath.dtx
// TODO(edemaine): allow overriding \jot via \setlength (#687) // TODO(edemaine): allow overriding \jot via \setlength (#687)
const jot = 3 * pt; const jot = 3 * pt;
const arrayskip = groupValue.arraystretch * baselineskip; const arrayskip = group.value.arraystretch * baselineskip;
const arstrutHeight = 0.7 * arrayskip; // \strutbox in ltfsstrc.dtx and const arstrutHeight = 0.7 * arrayskip; // \strutbox in ltfsstrc.dtx and
const arstrutDepth = 0.3 * arrayskip; // \@arstrutbox in lttab.dtx const arstrutDepth = 0.3 * arrayskip; // \@arstrutbox in lttab.dtx
@@ -194,8 +194,8 @@ const htmlBuilder = function(group, options) {
hlinePos.push(totalHeight); hlinePos.push(totalHeight);
} }
for (r = 0; r < groupValue.body.length; ++r) { for (r = 0; r < group.value.body.length; ++r) {
const inrow = groupValue.body[r]; const inrow = group.value.body[r];
let height = arstrutHeight; // \@array adds an \@arstrut let height = arstrutHeight; // \@array adds an \@arstrut
let depth = arstrutDepth; // to each tow (via the template) let depth = arstrutDepth; // to each tow (via the template)
@@ -215,7 +215,7 @@ const htmlBuilder = function(group, options) {
outrow[c] = elt; outrow[c] = elt;
} }
const rowGap = groupValue.rowGaps[r]; const rowGap = group.value.rowGaps[r];
let gap = 0; let gap = 0;
if (rowGap) { if (rowGap) {
gap = calculateSize(rowGap.value.value, options); gap = calculateSize(rowGap.value.value, options);
@@ -230,7 +230,7 @@ const htmlBuilder = function(group, options) {
// In AMS multiline environments such as aligned and gathered, rows // In AMS multiline environments such as aligned and gathered, rows
// correspond to lines that have additional \jot added to the // correspond to lines that have additional \jot added to the
// \baselineskip via \openup. // \baselineskip via \openup.
if (groupValue.addJot) { if (group.value.addJot) {
depth += jot; depth += jot;
} }
@@ -251,7 +251,7 @@ const htmlBuilder = function(group, options) {
} }
const offset = totalHeight / 2 + options.fontMetrics().axisHeight; const offset = totalHeight / 2 + options.fontMetrics().axisHeight;
const colDescriptions = groupValue.cols || []; const colDescriptions = group.value.cols || [];
const cols = []; const cols = [];
let colSep; let colSep;
let colDescrNum; let colDescrNum;
@@ -307,7 +307,7 @@ const htmlBuilder = function(group, options) {
} }
let sepwidth; let sepwidth;
if (c > 0 || groupValue.hskipBeforeAndAfter) { if (c > 0 || group.value.hskipBeforeAndAfter) {
sepwidth = utils.deflt(colDescr.pregap, arraycolsep); sepwidth = utils.deflt(colDescr.pregap, arraycolsep);
if (sepwidth !== 0) { if (sepwidth !== 0) {
colSep = buildCommon.makeSpan(["arraycolsep"], []); colSep = buildCommon.makeSpan(["arraycolsep"], []);
@@ -338,7 +338,7 @@ const htmlBuilder = function(group, options) {
[col]); [col]);
cols.push(col); cols.push(col);
if (c < nc - 1 || groupValue.hskipBeforeAndAfter) { if (c < nc - 1 || group.value.hskipBeforeAndAfter) {
sepwidth = utils.deflt(colDescr.postgap, arraycolsep); sepwidth = utils.deflt(colDescr.postgap, arraycolsep);
if (sepwidth !== 0) { if (sepwidth !== 0) {
colSep = buildCommon.makeSpan(["arraycolsep"], []); colSep = buildCommon.makeSpan(["arraycolsep"], []);
@@ -366,10 +366,9 @@ const htmlBuilder = function(group, options) {
return buildCommon.makeSpan(["mord"], [body], options); return buildCommon.makeSpan(["mord"], [body], options);
}; };
const mathmlBuilder = function(group, options) { const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
const groupValue = assertNodeType(group, "array").value;
return new mathMLTree.MathNode( return new mathMLTree.MathNode(
"mtable", groupValue.body.map(function(row) { "mtable", group.value.body.map(function(row) {
return new mathMLTree.MathNode( return new mathMLTree.MathNode(
"mtr", row.map(function(cell) { "mtr", row.map(function(cell) {
return new mathMLTree.MathNode( return new mathMLTree.MathNode(
@@ -400,10 +399,12 @@ const alignedHandler = function(context, args) {
let numMaths; let numMaths;
let numCols = 0; let numCols = 0;
const emptyGroup = new ParseNode("ordgroup", [], context.mode); const emptyGroup = new ParseNode("ordgroup", [], context.mode);
if (args[0] && args[0].value) { const ordgroup = checkNodeType(args[0], "ordgroup");
if (ordgroup) {
let arg0 = ""; let arg0 = "";
for (let i = 0; i < args[0].value.length; i++) { for (let i = 0; i < ordgroup.value.length; i++) {
arg0 += args[0].value[i].value; const textord = assertNodeType(ordgroup.value[i], "textord");
arg0 += textord.value;
} }
numMaths = Number(arg0); numMaths = Number(arg0);
numCols = numMaths * 2; numCols = numMaths * 2;
@@ -412,7 +413,8 @@ const alignedHandler = function(context, args) {
res.value.body.forEach(function(row) { res.value.body.forEach(function(row) {
for (let i = 1; i < row.length; i += 2) { for (let i = 1; i < row.length; i += 2) {
// Modify ordgroup node within styling node // Modify ordgroup node within styling node
const ordgroup = row[i].value.value[0]; const styling = assertNodeType(row[i], "styling");
const ordgroup = assertNodeType(styling.value.value[0], "ordgroup");
ordgroup.value.unshift(emptyGroup); ordgroup.value.unshift(emptyGroup);
} }
if (!isAligned) { // Case 1 if (!isAligned) { // Case 1
@@ -459,10 +461,16 @@ defineEnvironment({
props: { props: {
numArgs: 1, numArgs: 1,
}, },
handler: function(context, args) { handler(context, args) {
let colalign = args[0]; // Since no types are specified above, the two possibilities are
colalign = colalign.value.map ? colalign.value : [colalign]; // - The argument is wrapped in {} or [], in which case Parser's
const cols = colalign.map(function(node) { // parseGroup() returns an "ordgroup" wrapping some symbol node.
// - The argument is a bare symbol node.
const symNode = checkSymbolNodeType(args[0]);
const colalign: AnyParseNode[] =
symNode ? [args[0]] : assertNodeType(args[0], "ordgroup").value;
const cols = colalign.map(function(nde) {
const node = assertSymbolNodeType(nde);
const ca = node.value; const ca = node.value;
if ("lcr".indexOf(ca) !== -1) { if ("lcr".indexOf(ca) !== -1) {
return { return {
@@ -480,9 +488,7 @@ defineEnvironment({
separator: ":", separator: ":",
}; };
} }
throw new ParseError( throw new ParseError("Unknown column alignment: " + ca, nde);
"Unknown column alignment: " + node.value,
node);
}); });
let res = { let res = {
type: "array", type: "array",

View File

@@ -9,13 +9,14 @@ import ParseNode, {assertNodeType, checkNodeType} from "../ParseNode";
import * as html from "../buildHTML"; import * as html from "../buildHTML";
import * as mml from "../buildMathML"; import * as mml from "../buildMathML";
import type {AnyParseNode} from "../ParseNode";
import type {HtmlBuilderSupSub, MathMLBuilder} from "../defineFunction"; import type {HtmlBuilderSupSub, MathMLBuilder} from "../defineFunction";
// NOTE: Unlike most `htmlBuilder`s, this one handles not only "accent", but // NOTE: Unlike most `htmlBuilder`s, this one handles not only "accent", but
// also "supsub" since an accent can affect super/subscripting. // also "supsub" since an accent can affect super/subscripting.
export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => { export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => {
// Accents are handled in the TeXbook pg. 443, rule 12. // Accents are handled in the TeXbook pg. 443, rule 12.
let base: ParseNode<*>; let base: AnyParseNode;
let group: ParseNode<"accent">; let group: ParseNode<"accent">;
const supSub = checkNodeType(grp, "supsub"); const supSub = checkNodeType(grp, "supsub");

View File

@@ -2,7 +2,6 @@
import defineFunction, {ordargument} from "../defineFunction"; import defineFunction, {ordargument} from "../defineFunction";
import buildCommon from "../buildCommon"; import buildCommon from "../buildCommon";
import mathMLTree from "../mathMLTree"; import mathMLTree from "../mathMLTree";
import ParseError from "../ParseError";
import ParseNode, {assertNodeType} from "../ParseNode"; import ParseNode, {assertNodeType} from "../ParseNode";
import * as html from "../buildHTML"; import * as html from "../buildHTML";
@@ -99,10 +98,7 @@ defineFunction({
argTypes: ["color"], argTypes: ["color"],
}, },
handler({parser, breakOnTokenText}, args) { handler({parser, breakOnTokenText}, args) {
const color = args[0]; const color = assertNodeType(args[0], "color-token");
if (!color) {
throw new ParseError("\\color not followed by color");
}
// If we see a styling function, parse out the implicit body // If we see a styling function, parse out the implicit body
const body = parser.parseExpression(true, breakOnTokenText); const body = parser.parseExpression(true, breakOnTokenText);

View File

@@ -5,11 +5,12 @@ import delimiter from "../delimiter";
import mathMLTree from "../mathMLTree"; import mathMLTree from "../mathMLTree";
import ParseError from "../ParseError"; import ParseError from "../ParseError";
import utils from "../utils"; import utils from "../utils";
import ParseNode, {assertNodeType} from "../ParseNode"; import ParseNode, {assertNodeType, checkSymbolNodeType} from "../ParseNode";
import * as html from "../buildHTML"; import * as html from "../buildHTML";
import * as mml from "../buildMathML"; import * as mml from "../buildMathML";
import type {AnyParseNode, SymbolParseNode} from "../ParseNode";
import type {LeftRightDelimType} from "../ParseNode"; import type {LeftRightDelimType} from "../ParseNode";
import type {FunctionContext} from "../defineFunction"; import type {FunctionContext} from "../defineFunction";
@@ -52,14 +53,15 @@ const delimiters = [
// Delimiter functions // Delimiter functions
function checkDelimiter( function checkDelimiter(
delim: ParseNode<*>, delim: AnyParseNode,
context: FunctionContext, context: FunctionContext,
): ParseNode<*> { ): SymbolParseNode {
if (utils.contains(delimiters, delim.value)) { const symDelim = checkSymbolNodeType(delim);
return delim; if (symDelim && utils.contains(delimiters, symDelim.value)) {
return symDelim;
} else { } else {
throw new ParseError( throw new ParseError(
"Invalid delimiter: '" + delim.value + "' after '" + "Invalid delimiter: '" + String(delim.value) + "' after '" +
context.funcName + "'", delim); context.funcName + "'", delim);
} }
} }

View File

@@ -1,7 +1,7 @@
// @flow // @flow
import defineFunction from "../defineFunction"; import defineFunction from "../defineFunction";
import ParseError from "../ParseError"; import ParseError from "../ParseError";
import ParseNode from "../ParseNode"; import ParseNode, {assertNodeType} from "../ParseNode";
// Environment delimiters. HTML/MathML rendering is defined in the corresponding // Environment delimiters. HTML/MathML rendering is defined in the corresponding
// defineEnvironment definitions. // defineEnvironment definitions.
@@ -19,7 +19,7 @@ defineFunction({
} }
let name = ""; let name = "";
for (let i = 0; i < nameGroup.value.length; ++i) { for (let i = 0; i < nameGroup.value.length; ++i) {
name += nameGroup.value[i].value; name += assertNodeType(nameGroup.value[i], "textord").value;
} }
return new ParseNode("environment", { return new ParseNode("environment", {
type: "environment", type: "environment",

View File

@@ -19,7 +19,7 @@ defineFunction({
const height = assertNodeType(args[1], "size"); const height = assertNodeType(args[1], "size");
return new ParseNode("rule", { return new ParseNode("rule", {
type: "rule", type: "rule",
shift: shift && shift.value, shift: shift && assertNodeType(shift, "size").value.value,
width: width.value.value, width: width.value.value,
height: height.value.value, height: height.value.value,
}, parser.mode); }, parser.mode);

View File

@@ -3,7 +3,7 @@
import defineFunction from "../defineFunction"; import defineFunction from "../defineFunction";
import buildCommon from "../buildCommon"; import buildCommon from "../buildCommon";
import mathMLTree from "../mathMLTree"; import mathMLTree from "../mathMLTree";
import ParseNode from "../ParseNode"; import ParseNode, {assertNodeType} from "../ParseNode";
import * as html from "../buildHTML"; import * as html from "../buildHTML";
import * as mml from "../buildMathML"; import * as mml from "../buildMathML";
@@ -19,7 +19,7 @@ defineFunction({
handler: ({parser}, args, optArgs) => { handler: ({parser}, args, optArgs) => {
let smashHeight = false; let smashHeight = false;
let smashDepth = false; let smashDepth = false;
const tbArg = optArgs[0]; const tbArg = optArgs[0] && assertNodeType(optArgs[0], "ordgroup");
if (tbArg) { if (tbArg) {
// Optional [tb] argument is engaged. // Optional [tb] argument is engaged.
// ref: amsmath: \renewcommand{\smash}[1][tb]{% // ref: amsmath: \renewcommand{\smash}[1][tb]{%

View File

@@ -5,6 +5,7 @@ import domTree from "../domTree";
import mathMLTree from "../mathMLTree"; import mathMLTree from "../mathMLTree";
import utils from "../utils"; import utils from "../utils";
import Style from "../Style"; import Style from "../Style";
import {checkNodeType} from "../ParseNode";
import * as html from "../buildHTML"; import * as html from "../buildHTML";
import * as mml from "../buildMathML"; import * as mml from "../buildMathML";
@@ -184,13 +185,13 @@ defineFunctionBuilders({
let isBrace = false; let isBrace = false;
let isOver; let isOver;
let isSup; let isSup;
if (group.value.base) {
if (group.value.base.value.type === "horizBrace") { const horizBrace = checkNodeType(group.value.base, "horizBrace");
isSup = (group.value.sup ? true : false); if (horizBrace) {
if (isSup === group.value.base.value.isOver) { isSup = !!group.value.sup;
isBrace = true; if (isSup === horizBrace.value.isOver) {
isOver = group.value.base.value.isOver; isBrace = true;
} isOver = horizBrace.value.isOver;
} }
} }

View File

@@ -9,11 +9,12 @@ import ParseError from "./ParseError";
import ParseNode from "./ParseNode"; import ParseNode from "./ParseNode";
import type Settings from "./Settings"; import type Settings from "./Settings";
import type {AnyParseNode} from "./ParseNode";
/** /**
* 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): AnyParseNode[] {
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');
} }

View File

@@ -11,7 +11,7 @@ import mathMLTree from "./mathMLTree";
import utils from "./utils"; import utils from "./utils";
import type Options from "./Options"; import type Options from "./Options";
import type ParseNode from "./ParseNode"; import type ParseNode, {AnyParseNode} from "./ParseNode";
import type {DomSpan, SvgSpan} from "./domTree"; import type {DomSpan, SvgSpan} from "./domTree";
const stretchyCodePoint: {[string]: string} = { const stretchyCodePoint: {[string]: string} = {
@@ -159,7 +159,7 @@ const katexImagesData: {
"shortrightharpoonabovebar"], 1.75, 716], "shortrightharpoonabovebar"], 1.75, 716],
}; };
const groupLength = function(arg: ParseNode<*>): number { const groupLength = function(arg: AnyParseNode): number {
if (arg.type === "ordgroup") { if (arg.type === "ordgroup") {
return arg.value.length; return arg.value.length;
} else { } else {

View File

@@ -24,9 +24,20 @@ type Font = "main" | "ams"
// types for raw text tokens, and we want to avoid conflicts with higher-level // types for raw text tokens, and we want to avoid conflicts with higher-level
// `ParseNode` types. These `ParseNode`s are constructed within `Parser` by // `ParseNode` types. These `ParseNode`s are constructed within `Parser` by
// looking up the `symbols` map. // looking up the `symbols` map.
export type Group = export const GROUPS = { // Set of all the groups.
"accent-token" | "bin" | "close" | "inner" | "mathord" | "accent-token": 1,
"op-token" | "open" | "punct" | "rel" | "spacing" | "textord"; "bin": 1,
"close": 1,
"inner": 1,
"mathord": 1,
"op-token": 1,
"open": 1,
"punct": 1,
"rel": 1,
"spacing": 1,
"textord": 1,
};
export type Group = $Keys<typeof GROUPS>;
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} = {

View File

@@ -4,7 +4,7 @@
* files. * files.
*/ */
import type ParseNode from "./ParseNode"; import type {AnyParseNode} from "./ParseNode";
/** /**
* Provide an `indexOf` function which works in IE8, but defers to native if * Provide an `indexOf` function which works in IE8, but defers to native if
@@ -96,7 +96,7 @@ function clearNode(node: Node) {
* cases, this will just be the group itself, but when ordgroups and colors have * cases, this will just be the group itself, but when ordgroups and colors have
* a single element, we want to pull that out. * a single element, we want to pull that out.
*/ */
const getBaseElem = function(group: ParseNode<*>): ParseNode<*> { const getBaseElem = function(group: AnyParseNode): AnyParseNode {
if (group.type === "ordgroup") { if (group.type === "ordgroup") {
if (group.value.length === 1) { if (group.value.length === 1) {
return getBaseElem(group.value[0]); return getBaseElem(group.value[0]);
@@ -121,7 +121,7 @@ const getBaseElem = function(group: ParseNode<*>): ParseNode<*> {
* with a single character in them. To decide if something is a character box, * with a single character in them. To decide if something is a character box,
* we find its innermost group, and see if it is a single character. * we find its innermost group, and see if it is a single character.
*/ */
const isCharacterBox = function(group: ParseNode<*>): boolean { const isCharacterBox = function(group: AnyParseNode): boolean {
const baseElem = getBaseElem(group); const baseElem = getBaseElem(group);
// These are all they types of groups which hold single characters // These are all they types of groups which hold single characters