Skip to content

Commit

Permalink
Make ParseNode value payload and defineFunction handler functions t…
Browse files Browse the repository at this point in the history
…ype-safe (KaTeX#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.
  • Loading branch information
marcianx authored and kevinbarabash committed May 10, 2018
1 parent 3613885 commit 5a4aedd
Show file tree
Hide file tree
Showing 18 changed files with 370 additions and 87 deletions.
2 changes: 1 addition & 1 deletion katex.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 69,7 @@ const renderToString = function(
const generateParseTree = function(
expression: string,
options: SettingsOptions,
): ParseNode[] {
): ParseNode<*>[] {
const settings = new Settings(options);
return parseTree(expression, settings);
};
Expand Down
4 changes: 2 additions & 2 deletions src/ParseError.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 15,8 @@ class ParseError {
// Error position based on passed-in Token or ParseNode.

constructor(
message: string, // The error message
token?: Token | ParseNode, // An object providing position information
message: string, // The error message
token?: Token | ParseNode<*>, // An object providing position information
) {
let error = "KaTeX parse error: " message;
let start;
Expand Down
251 changes: 245 additions & 6 deletions src/ParseNode.js
Original file line number Diff line number Diff line change
@@ -1,6 1,9 @@
// @flow
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.
Expand All @@ -10,15 13,15 @@ import type {Mode} from "./types";
* For details on the corresponding properties see `Token` constructor.
* Providing such information can lead to better error reporting.
*/
export default class ParseNode {
type: *;
value: *;
export default class ParseNode<TYPE: NodeType> {
type: TYPE;
value: NodeValue<TYPE>;
mode: Mode;
loc: ?SourceLocation;

constructor(
type: string, // type of node, like e.g. "ordgroup"
value: mixed, // type-specific representation of the node
type: TYPE, // type of node, like e.g. "ordgroup"
value: NodeValue<TYPE>, // type-specific representation of the node
mode: Mode, // parse mode in action for this node, "math" or "text"
first?: {loc: ?SourceLocation}, // first token or node of the input for
// this node, will omit position information if unset
Expand All @@ -31,3 34,239 @@ export default class ParseNode {
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<*>,
|},
};
Loading

0 comments on commit 5a4aedd

Please sign in to comment.