mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-07 20:28:38 +00:00
Introduce "atom" parse node to coalesce various symbol nodes. (#1541)
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import functions from "./functions";
|
||||
import environments from "./environments";
|
||||
import MacroExpander from "./MacroExpander";
|
||||
import symbols, {extraLatin} from "./symbols";
|
||||
import symbols, {ATOMS, extraLatin} from "./symbols";
|
||||
import {validUnit} from "./units";
|
||||
import {supportedCodepoint} from "./unicodeScripts";
|
||||
import unicodeAccents from "./unicodeAccents";
|
||||
@@ -16,6 +16,7 @@ import Settings from "./Settings";
|
||||
import SourceLocation from "./SourceLocation";
|
||||
import {Token} from "./Token";
|
||||
import type {AnyParseNode, SymbolParseNode} from "./parseNode";
|
||||
import type {Atom, Group} from "./symbols";
|
||||
import type {Mode, ArgType, BreakToken} from "./types";
|
||||
import type {FunctionContext, FunctionSpec} from "./defineFunction";
|
||||
import type {EnvSpec} from "./defineEnvironment";
|
||||
@@ -1022,14 +1023,28 @@ export default class Parser {
|
||||
`Latin-1/Unicode text character "${text[0]}" used in ` +
|
||||
`math mode`, nucleus);
|
||||
}
|
||||
// TODO(#1492): Remove this override once this becomes an "atom" type.
|
||||
// $FlowFixMe
|
||||
const s: SymbolParseNode = {
|
||||
type: symbols[this.mode][text].group,
|
||||
mode: this.mode,
|
||||
loc: SourceLocation.range(nucleus),
|
||||
value: text,
|
||||
};
|
||||
const group: Group = symbols[this.mode][text].group;
|
||||
const loc = SourceLocation.range(nucleus);
|
||||
let s: SymbolParseNode;
|
||||
if (ATOMS.hasOwnProperty(group)) {
|
||||
// $FlowFixMe
|
||||
const family: Atom = group;
|
||||
s = {
|
||||
type: "atom",
|
||||
mode: this.mode,
|
||||
family,
|
||||
loc,
|
||||
value: text,
|
||||
};
|
||||
} else {
|
||||
// $FlowFixMe
|
||||
s = {
|
||||
type: group,
|
||||
mode: this.mode,
|
||||
loc,
|
||||
value: text,
|
||||
};
|
||||
}
|
||||
symbol = s;
|
||||
} else if (text.charCodeAt(0) >= 0x80) { // no symbol for e.g. ^
|
||||
if (this.settings.strict) {
|
||||
|
@@ -4,7 +4,7 @@ import buildCommon from "../buildCommon";
|
||||
import delimiter from "../delimiter";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import Style from "../Style";
|
||||
import {assertNodeType, checkNodeType} from "../parseNode";
|
||||
import {assertNodeType, assertAtomFamily, checkNodeType} from "../parseNode";
|
||||
|
||||
import * as html from "../buildHTML";
|
||||
import * as mml from "../buildMathML";
|
||||
@@ -370,17 +370,17 @@ defineFunction({
|
||||
// Look into the parse nodes to get the desired delimiters.
|
||||
let leftNode = checkNodeType(args[0], "ordgroup");
|
||||
if (leftNode) {
|
||||
leftNode = assertNodeType(leftNode.value[0], "open");
|
||||
leftNode = assertAtomFamily(leftNode.value[0], "open");
|
||||
} else {
|
||||
leftNode = assertNodeType(args[0], "open");
|
||||
leftNode = assertAtomFamily(args[0], "open");
|
||||
}
|
||||
const leftDelim = delimFromValue(leftNode.value);
|
||||
|
||||
let rightNode = checkNodeType(args[1], "ordgroup");
|
||||
if (rightNode) {
|
||||
rightNode = assertNodeType(rightNode.value[0], "close");
|
||||
rightNode = assertAtomFamily(rightNode.value[0], "close");
|
||||
} else {
|
||||
rightNode = assertNodeType(args[1], "close");
|
||||
rightNode = assertAtomFamily(args[1], "close");
|
||||
}
|
||||
const rightDelim = delimFromValue(rightNode.value);
|
||||
|
||||
|
@@ -45,15 +45,14 @@ defineFunction({
|
||||
mathmlBuilder,
|
||||
});
|
||||
|
||||
export const binrelClass = (arg: AnyParseNode) => {
|
||||
export const binrelClass = (arg: AnyParseNode): string => {
|
||||
// \binrel@ spacing varies with (bin|rel|ord) of the atom in the argument.
|
||||
// (by rendering separately and with {}s before and after, and measuring
|
||||
// the change in spacing). We'll do roughly the same by detecting the
|
||||
// atom type directly.
|
||||
const atomType = (arg.type === "ordgroup" &&
|
||||
arg.value.length ? arg.value[0].type : arg.type);
|
||||
if (/^(bin|rel)$/.test(atomType)) {
|
||||
return "m" + atomType;
|
||||
const atom = (arg.type === "ordgroup" && arg.value.length ? arg.value[0] : arg);
|
||||
if (atom.type === "atom" && (atom.family === "bin" || atom.family === "rel")) {
|
||||
return "m" + atom.family;
|
||||
} else {
|
||||
return "mord";
|
||||
}
|
||||
|
@@ -5,48 +5,26 @@ import mathMLTree from "../mathMLTree";
|
||||
|
||||
import * as mml from "../buildMathML";
|
||||
|
||||
import type Options from "../Options";
|
||||
import type {ParseNode} from "../parseNode";
|
||||
import type {Group} from "../symbols";
|
||||
|
||||
// Operator ParseNodes created in Parser.js from symbol Groups in src/symbols.js.
|
||||
|
||||
// NOTE: `NODETYPE` is constrained by `Group` instead of `NodeType`. This
|
||||
// guarantees that `group.value` is a string as required by buildCommon.mathsym.
|
||||
function defineOpFunction<NODETYPE: Group>(
|
||||
type: NODETYPE,
|
||||
mathmlNodePostProcessor?: (
|
||||
mathMLTree.MathNode,
|
||||
ParseNode<NODETYPE>,
|
||||
Options) => *,
|
||||
) {
|
||||
defineFunctionBuilders({
|
||||
type,
|
||||
htmlBuilder(group: ParseNode<NODETYPE>, options) {
|
||||
const groupValue: string = group.value;
|
||||
return buildCommon.mathsym(
|
||||
groupValue, group.mode, options, ["m" + type]);
|
||||
},
|
||||
mathmlBuilder(group: ParseNode<NODETYPE>, options) {
|
||||
const node = new mathMLTree.MathNode(
|
||||
"mo", [mml.makeText(group.value, group.mode)]);
|
||||
if (mathmlNodePostProcessor) {
|
||||
mathmlNodePostProcessor(node, group, options);
|
||||
defineFunctionBuilders({
|
||||
type: "atom",
|
||||
htmlBuilder(group, options) {
|
||||
return buildCommon.mathsym(
|
||||
group.value, group.mode, options, ["m" + group.family]);
|
||||
},
|
||||
mathmlBuilder(group, options) {
|
||||
const node = new mathMLTree.MathNode(
|
||||
"mo", [mml.makeText(group.value, group.mode)]);
|
||||
if (group.family === "bin") {
|
||||
const variant = mml.getVariant(group, options);
|
||||
if (variant === "bold-italic") {
|
||||
node.setAttribute("mathvariant", variant);
|
||||
}
|
||||
return node;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
defineOpFunction("bin", (mathNode, group: ParseNode<"bin">, options) => {
|
||||
const variant = mml.getVariant(group, options);
|
||||
if (variant === "bold-italic") {
|
||||
mathNode.setAttribute("mathvariant", variant);
|
||||
}
|
||||
} else if (group.family === "punct") {
|
||||
node.setAttribute("separator", "true");
|
||||
}
|
||||
return node;
|
||||
},
|
||||
});
|
||||
defineOpFunction("rel");
|
||||
defineOpFunction("open");
|
||||
defineOpFunction("close");
|
||||
defineOpFunction("inner");
|
||||
defineOpFunction("punct", mathNode => mathNode.setAttribute("separator", "true"));
|
||||
|
||||
|
106
src/parseNode.js
106
src/parseNode.js
@@ -1,7 +1,8 @@
|
||||
// @flow
|
||||
import {GROUPS} from "./symbols";
|
||||
import {NON_ATOMS} from "./symbols";
|
||||
import type SourceLocation from "./SourceLocation";
|
||||
import type {ArrayEnvNodeData} from "./environments/array";
|
||||
import type {Atom} from "./symbols";
|
||||
import type {Mode, StyleStr} from "./types";
|
||||
import type {Token} from "./Token";
|
||||
import type {Measurement} from "./units";
|
||||
@@ -18,15 +19,10 @@ export type LeftRightDelimType = {|
|
||||
|
||||
// ParseNode's corresponding to Symbol `Group`s in symbols.js.
|
||||
export type SymbolParseNode =
|
||||
ParseNode<"atom"> |
|
||||
ParseNode<"accent-token"> |
|
||||
ParseNode<"bin"> |
|
||||
ParseNode<"close"> |
|
||||
ParseNode<"inner"> |
|
||||
ParseNode<"mathord"> |
|
||||
ParseNode<"op-token"> |
|
||||
ParseNode<"open"> |
|
||||
ParseNode<"punct"> |
|
||||
ParseNode<"rel"> |
|
||||
ParseNode<"spacing"> |
|
||||
ParseNode<"textord">;
|
||||
|
||||
@@ -162,26 +158,9 @@ type ParseNodeTypes = {
|
||||
// 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": {|
|
||||
type: "accent-token",
|
||||
mode: Mode,
|
||||
loc?: ?SourceLocation,
|
||||
value: string,
|
||||
|},
|
||||
"bin": {|
|
||||
type: "bin",
|
||||
mode: Mode,
|
||||
loc?: ?SourceLocation,
|
||||
value: string,
|
||||
|},
|
||||
"close": {|
|
||||
type: "close",
|
||||
mode: Mode,
|
||||
loc?: ?SourceLocation,
|
||||
value: string,
|
||||
|},
|
||||
"inner": {|
|
||||
type: "inner",
|
||||
"atom": {|
|
||||
type: "atom",
|
||||
family: Atom,
|
||||
mode: Mode,
|
||||
loc?: ?SourceLocation,
|
||||
value: string,
|
||||
@@ -192,30 +171,6 @@ type ParseNodeTypes = {
|
||||
loc?: ?SourceLocation,
|
||||
value: string,
|
||||
|},
|
||||
"op-token": {|
|
||||
type: "op-token",
|
||||
mode: Mode,
|
||||
loc?: ?SourceLocation,
|
||||
value: string,
|
||||
|},
|
||||
"open": {|
|
||||
type: "open",
|
||||
mode: Mode,
|
||||
loc?: ?SourceLocation,
|
||||
value: string,
|
||||
|},
|
||||
"punct": {|
|
||||
type: "punct",
|
||||
mode: Mode,
|
||||
loc?: ?SourceLocation,
|
||||
value: string,
|
||||
|},
|
||||
"rel": {|
|
||||
type: "rel",
|
||||
mode: Mode,
|
||||
loc?: ?SourceLocation,
|
||||
value: string,
|
||||
|},
|
||||
"spacing": {|
|
||||
type: "spacing",
|
||||
mode: Mode,
|
||||
@@ -228,6 +183,19 @@ type ParseNodeTypes = {
|
||||
loc?: ?SourceLocation,
|
||||
value: string,
|
||||
|},
|
||||
// These "-token" types don't have corresponding HTML/MathML builders.
|
||||
"accent-token": {|
|
||||
type: "accent-token",
|
||||
mode: Mode,
|
||||
loc?: ?SourceLocation,
|
||||
value: string,
|
||||
|},
|
||||
"op-token": {|
|
||||
type: "op-token",
|
||||
mode: Mode,
|
||||
loc?: ?SourceLocation,
|
||||
value: string,
|
||||
|},
|
||||
// From functions.js and functions/*.js. See also "color", "op", "styling",
|
||||
// and "text" above.
|
||||
"accent": {|
|
||||
@@ -599,6 +567,40 @@ export function checkNodeType<NODETYPE: NodeType>(
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the node is of the given type and returns it with stricter
|
||||
* typing. Throws if the node's type does not match.
|
||||
*/
|
||||
export function assertAtomFamily(
|
||||
node: ?AnyParseNode,
|
||||
family: Atom,
|
||||
): ParseNode<"atom"> {
|
||||
const typedNode = checkAtomFamily(node, family);
|
||||
if (!typedNode) {
|
||||
throw new Error(
|
||||
`Expected node of type "atom" and family "${family}", but got ` +
|
||||
(node ?
|
||||
(node.type === "atom" ?
|
||||
`atom of family ${node.family}` :
|
||||
`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 checkAtomFamily(
|
||||
node: ?AnyParseNode,
|
||||
family: Atom,
|
||||
): ?ParseNode<"atom"> {
|
||||
return node && node.type === "atom" && node.family === family ?
|
||||
node :
|
||||
null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node more strictly typed iff it is of the given type. Otherwise,
|
||||
* returns null.
|
||||
@@ -618,7 +620,7 @@ export function assertSymbolNodeType(node: ?AnyParseNode): SymbolParseNode {
|
||||
* returns null.
|
||||
*/
|
||||
export function checkSymbolNodeType(node: ?AnyParseNode): ?SymbolParseNode {
|
||||
if (node && GROUPS.hasOwnProperty(node.type)) {
|
||||
if (node && (node.type === "atom" || NON_ATOMS.hasOwnProperty(node.type))) {
|
||||
// $FlowFixMe
|
||||
return node;
|
||||
}
|
||||
|
@@ -24,20 +24,25 @@ type Font = "main" | "ams";
|
||||
// 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.
|
||||
export const GROUPS = { // Set of all the groups.
|
||||
"accent-token": 1,
|
||||
export const ATOMS = {
|
||||
"bin": 1,
|
||||
"close": 1,
|
||||
"inner": 1,
|
||||
"mathord": 1,
|
||||
"op-token": 1,
|
||||
"open": 1,
|
||||
"punct": 1,
|
||||
"rel": 1,
|
||||
};
|
||||
export const NON_ATOMS = {
|
||||
"accent-token": 1,
|
||||
"mathord": 1,
|
||||
"op-token": 1,
|
||||
"spacing": 1,
|
||||
"textord": 1,
|
||||
};
|
||||
export type Group = $Keys<typeof GROUPS>;
|
||||
|
||||
export type Atom = $Keys<typeof ATOMS>;
|
||||
export type NonAtom = $Keys<typeof NON_ATOMS>
|
||||
export type Group = Atom | NonAtom;
|
||||
type CharInfoMap = {[string]: {font: Font, group: Group, replace: ?string}};
|
||||
|
||||
const symbols: {[Mode]: CharInfoMap} = {
|
||||
|
@@ -127,12 +127,7 @@ const isCharacterBox = function(group: AnyParseNode): boolean {
|
||||
// These are all they types of groups which hold single characters
|
||||
return baseElem.type === "mathord" ||
|
||||
baseElem.type === "textord" ||
|
||||
baseElem.type === "bin" ||
|
||||
baseElem.type === "rel" ||
|
||||
baseElem.type === "inner" ||
|
||||
baseElem.type === "open" ||
|
||||
baseElem.type === "close" ||
|
||||
baseElem.type === "punct";
|
||||
baseElem.type === "atom";
|
||||
};
|
||||
|
||||
export const assert = function<T>(value: ?T): T {
|
||||
|
@@ -71,7 +71,8 @@ describe("A bin parser", function() {
|
||||
|
||||
for (let i = 0; i < parse.length; i++) {
|
||||
const group = parse[i];
|
||||
expect(group.type).toEqual("bin");
|
||||
expect(group.type).toEqual("atom");
|
||||
expect(group.family).toEqual("bin");
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -96,7 +97,8 @@ describe("A rel parser", function() {
|
||||
if (group.type === "mclass") {
|
||||
expect(group.value.mclass).toEqual("mrel");
|
||||
} else {
|
||||
expect(group.type).toEqual("rel");
|
||||
expect(group.type).toEqual("atom");
|
||||
expect(group.family).toEqual("rel");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -114,7 +116,8 @@ describe("A punct parser", function() {
|
||||
|
||||
for (let i = 0; i < parse.length; i++) {
|
||||
const group = parse[i];
|
||||
expect(group.type).toEqual("punct");
|
||||
expect(group.type).toEqual("atom");
|
||||
expect(group.family).toEqual("punct");
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -131,7 +134,8 @@ describe("An open parser", function() {
|
||||
|
||||
for (let i = 0; i < parse.length; i++) {
|
||||
const group = parse[i];
|
||||
expect(group.type).toEqual("open");
|
||||
expect(group.type).toEqual("atom");
|
||||
expect(group.family).toEqual("open");
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -148,7 +152,8 @@ describe("A close parser", function() {
|
||||
|
||||
for (let i = 0; i < parse.length; i++) {
|
||||
const group = parse[i];
|
||||
expect(group.type).toEqual("close");
|
||||
expect(group.type).toEqual("atom");
|
||||
expect(group.family).toEqual("close");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user