mathup
Version:
Easy MathML authoring tool with a quick to write syntax
184 lines (159 loc) • 4.13 kB
JavaScript
import { applyStyles } from "./styles.js";
import {
literalValue,
textLiteralAttrs,
textLiteralValue,
} from "./text-transforms.js";
/**
* @typedef {import("../../parser/index.js").Node} ASTNode
* @typedef {import("../../parser/index.js").UnaryOperation} UnaryOperation
* @typedef {import("../../parser/index.js").Literal} Literal
* @typedef {import("../../parser/index.js").SpaceLiteral} SpaceLiteral
*/
/**
* @template {ASTNode} Node
* @overload
* @param {Node} childNode
* @param {UnaryOperation} node
* @returns {Node}
*/
/**
* @template {ASTNode} Node
* @overload
* @param {Node[]} childNode
* @param {UnaryOperation} node
* @returns {Node[]}
*/
/**
* @template {ASTNode} Node
* @overload
* @param {Node[][]} childNode
* @param {UnaryOperation} node
* @returns {Node[][]}
*/
/**
* @template {ASTNode} Node
* @param {Node | Node[] | Node[][]} childNode
* @param {UnaryOperation} node
* @returns {Node | Node[] | Node[][]}
*/
function handleCommand(childNode, node) {
if (Array.isArray(childNode)) {
return childNode.map((grandChild) =>
handleCommand(/** @type {Node} */ (grandChild), node),
);
}
if (childNode.type === "TextLiteral") {
const { attrs = {}, transforms = [] } = node;
return {
...childNode,
attrs: textLiteralAttrs(attrs, transforms),
value: textLiteralValue(childNode.value, transforms),
};
}
if (
childNode.type === "IdentLiteral" &&
childNode.value.length === 1 &&
node.transforms &&
node.transforms.length === 1 &&
node.transforms[0] === "normal"
) {
// Disable auto-italic idents
return {
...childNode,
attrs: {
...(childNode.attrs ?? {}),
mathvariant: "normal",
},
};
}
if (
childNode.type === "IdentLiteral" ||
childNode.type === "NumberLiteral" ||
childNode.type === "OperatorLiteral"
) {
const { transforms = [] } = node;
return {
...childNode,
value: literalValue(childNode.value, transforms),
};
}
if (childNode.type === "MultiScripts") {
return {
...childNode,
base: handleCommand(childNode.base, node),
};
}
if ("items" in childNode) {
return {
...childNode,
items: childNode.items.map((grandChild) =>
handleCommand(/** @type {Node} */ (grandChild), node),
),
};
}
return childNode;
}
/** @type {import("../index.js").TransformFn<UnaryOperation>} */
export default function unaryOperation(node, transform) {
if (node.name === "command") {
return applyStyles(
node.styles,
transform(handleCommand(node.items[0], node)),
);
}
if (node.name === "fence") {
const { open, close } = node.attrs || {};
/** @type {(import("../index.js").Tag | null)[]} */
const childNodes = [
{ tag: "mo", attrs: { fence: true }, textContent: `${open}` },
node.items.length === 1
? transform(node.items[0])
: { tag: "mrow", childNodes: node.items.map(transform) },
{ tag: "mo", attrs: { fence: true }, textContent: `${close}` },
];
return {
tag: "mrow",
childNodes,
};
}
const tag = `m${node.name}`;
let [operand] = node.items;
if (operand.type === "Term" && operand.items.length === 1) {
// There is no need of a singleton term.
[operand] = operand.items;
}
if (node.accent) {
const attrs = node.attrs ? { ...node.attrs } : {};
if (node.name === "over") {
attrs.accent = true;
// Remove the dot from i and j to make way for the accent.
if (operand.type === "IdentLiteral") {
if (operand.value === "i") {
operand.value = "ı";
} else if (operand.value === "j") {
operand.value = "ȷ";
}
}
} else if (node.name === "under") {
attrs.accentunder = true;
}
const childNodes = [
transform(operand),
{
tag: "mo",
textContent: node.accent,
},
];
return {
tag,
childNodes,
attrs,
};
}
return {
tag,
childNodes: [transform(operand)],
attrs: node.attrs,
};
}