documents.js 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -5
- package/dist/diagnostics-C5-bG09J.d.cts +18 -0
- package/dist/diagnostics-C5-bG09J.d.ts +18 -0
- package/dist/index.cjs +10 -0
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/latex/diagnostics.cjs +21 -0
- package/dist/latex/diagnostics.d.cts +2 -0
- package/dist/latex/diagnostics.d.ts +2 -0
- package/dist/latex/diagnostics.js +19 -0
- package/dist/latex/lint.cjs +82 -0
- package/dist/latex/lint.d.cts +6 -0
- package/dist/latex/lint.d.ts +6 -0
- package/dist/latex/lint.js +81 -0
- package/dist/latex/lower.cjs +550 -0
- package/dist/latex/lower.d.cts +24 -0
- package/dist/latex/lower.d.ts +24 -0
- package/dist/latex/lower.js +548 -0
- package/dist/latex/rational.cjs +29 -0
- package/dist/latex/rational.d.cts +11 -0
- package/dist/latex/rational.d.ts +11 -0
- package/dist/latex/rational.js +27 -0
- package/dist/latex/symbols.cjs +128 -0
- package/dist/latex/symbols.d.cts +16 -0
- package/dist/latex/symbols.d.ts +16 -0
- package/dist/latex/symbols.js +124 -0
- package/dist/latex/temml.cjs +121 -0
- package/dist/latex/temml.d.cts +25 -0
- package/dist/latex/temml.d.ts +25 -0
- package/dist/latex/temml.js +93 -0
- package/dist/markdown/math.cjs +110 -0
- package/dist/markdown/math.d.cts +9 -0
- package/dist/markdown/math.d.ts +9 -0
- package/dist/markdown/math.js +109 -0
- package/dist/markdown/read.cjs +3 -2
- package/dist/markdown/read.d.cts +2 -1
- package/dist/markdown/read.d.ts +2 -1
- package/dist/markdown/read.js +3 -2
- package/dist/markdown/write.cjs +23 -4
- package/dist/markdown/write.js +23 -4
- package/dist/model/formula.cjs +1 -1
- package/dist/model/formula.js +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { reduceRational } from "./rational.js";
|
|
2
|
+
import { lowerLatex } from "./lower.js";
|
|
3
|
+
//#region src/latex/lint.ts
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function canonical(value) {
|
|
8
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
9
|
+
if (!isRecord(value)) return value;
|
|
10
|
+
if (value.kind === "num" && typeof value.numerator === "string" && typeof value.denominator === "string") return {
|
|
11
|
+
kind: "num",
|
|
12
|
+
...reduceRational(BigInt(value.numerator), BigInt(value.denominator))
|
|
13
|
+
};
|
|
14
|
+
const entries = Object.entries(value).map(([key, element]) => [key, canonical(element)]);
|
|
15
|
+
entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
16
|
+
return Object.fromEntries(entries);
|
|
17
|
+
}
|
|
18
|
+
function expressionsAgree(left, right) {
|
|
19
|
+
return JSON.stringify(canonical(left)) === JSON.stringify(canonical(right));
|
|
20
|
+
}
|
|
21
|
+
function isUnparsedRoot(expression) {
|
|
22
|
+
return expression.kind === "unparsed";
|
|
23
|
+
}
|
|
24
|
+
function lintFormula(formula, locate, symbolEntries, warnings) {
|
|
25
|
+
const presentation = formula.presentation;
|
|
26
|
+
const content = formula.content;
|
|
27
|
+
if (presentation === void 0 || content === void 0) return;
|
|
28
|
+
const provenance = formula.provenance === void 0 ? void 0 : [formula.provenance.source, ...formula.provenance.editTrail].join(" -> ");
|
|
29
|
+
const relowered = lowerLatex(presentation.latex, { symbolEntries });
|
|
30
|
+
const detail = `${locate}: ${presentation.latex}`;
|
|
31
|
+
if (relowered.diagnostics.some((diagnostic) => diagnostic.code === "latex/parse-error")) {
|
|
32
|
+
if (!isUnparsedRoot(content)) warnings.push({
|
|
33
|
+
code: "math/coherence-unparseable-presentation",
|
|
34
|
+
severity: "warning",
|
|
35
|
+
...provenance === void 0 ? {} : { provenance },
|
|
36
|
+
detail
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!expressionsAgree(relowered.expression, content)) warnings.push({
|
|
41
|
+
code: "math/coherence-divergence",
|
|
42
|
+
severity: "warning",
|
|
43
|
+
...provenance === void 0 ? {} : { provenance },
|
|
44
|
+
detail
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function collectBlockFormulas(blocks, locate, formulas) {
|
|
48
|
+
for (const [index, block] of blocks.entries()) {
|
|
49
|
+
if (block.kind === "embeddedObject" && block.objectKind === "formula" && block.document.kind === "formula") {
|
|
50
|
+
formulas.push({
|
|
51
|
+
formula: block.document.formula,
|
|
52
|
+
locate: `${locate}/blocks[${String(index)}]`
|
|
53
|
+
});
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (block.kind === "table") for (const [rowIndex, row] of block.rows.entries()) for (const [cellIndex, cell] of row.cells.entries()) collectBlockFormulas(cell.blocks, `${locate}/rows[${String(rowIndex)}].cells[${String(cellIndex)}]`, formulas);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function lintMathCoherence(pkg) {
|
|
60
|
+
const warnings = [];
|
|
61
|
+
const symbolEntries = pkg.content.symbolTable?.symbols;
|
|
62
|
+
const found = [];
|
|
63
|
+
const content = pkg.content;
|
|
64
|
+
if (content.kind === "formula") found.push({
|
|
65
|
+
formula: content.formula,
|
|
66
|
+
locate: "formula"
|
|
67
|
+
});
|
|
68
|
+
else if (content.kind === "wordprocessing") for (const [index, section] of content.sections.entries()) collectBlockFormulas(section.blocks, `sections[${String(index)}]`, found);
|
|
69
|
+
else if (content.kind === "presentation") for (const [slideIndex, slide] of content.slides.entries()) for (const [shapeIndex, shape] of slide.shapes.entries()) collectBlockFormulas(shape.blocks, `slides[${String(slideIndex)}].shapes[${String(shapeIndex)}]`, found);
|
|
70
|
+
else if (content.kind === "drawing") for (const [pageIndex, page] of content.pages.entries()) for (const [shapeIndex, shape] of page.shapes.entries()) collectBlockFormulas(shape.blocks, `pages[${String(pageIndex)}].shapes[${String(shapeIndex)}]`, found);
|
|
71
|
+
else if (content.kind === "spreadsheet") {
|
|
72
|
+
for (const [sheetIndex, sheet] of content.sheets.entries()) for (const [objectIndex, object] of (sheet.embeddedObjects ?? []).entries()) if (object.objectKind === "formula" && object.document.kind === "formula") found.push({
|
|
73
|
+
formula: object.document.formula,
|
|
74
|
+
locate: `sheets[${String(sheetIndex)}].embeddedObjects[${String(objectIndex)}]`
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
for (const { formula, locate } of found) lintFormula(formula, locate, symbolEntries, warnings);
|
|
78
|
+
return warnings;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { lintMathCoherence };
|
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_latex_temml = require("./temml.cjs");
|
|
3
|
+
const require_latex_symbols = require("./symbols.cjs");
|
|
4
|
+
const require_latex_rational = require("./rational.cjs");
|
|
5
|
+
//#region src/latex/lower.ts
|
|
6
|
+
const BINARY_ATOM_OPERATORS = {
|
|
7
|
+
"+": "math:add",
|
|
8
|
+
"-": "math:subtract",
|
|
9
|
+
"\\cdot": "math:multiply",
|
|
10
|
+
"\\times": "math:multiply",
|
|
11
|
+
"\\div": "math:divide"
|
|
12
|
+
};
|
|
13
|
+
const RELATION_ATOM_OPERATORS = {
|
|
14
|
+
"=": "math:eq",
|
|
15
|
+
"\\neq": "math:neq",
|
|
16
|
+
"\\ne": "math:neq",
|
|
17
|
+
"<": "math:lt",
|
|
18
|
+
"\\leq": "math:leq",
|
|
19
|
+
"\\le": "math:leq",
|
|
20
|
+
">": "math:gt",
|
|
21
|
+
"\\geq": "math:geq",
|
|
22
|
+
"\\ge": "math:geq"
|
|
23
|
+
};
|
|
24
|
+
const SUBTRACT_OPERATOR = "math:subtract";
|
|
25
|
+
const UNARY_MINUS_OPERATOR = "math:negate";
|
|
26
|
+
const NAMED_FUNCTION_OPERATORS = {
|
|
27
|
+
"\\sin": "math:sin",
|
|
28
|
+
"\\cos": "math:cos",
|
|
29
|
+
"\\tan": "math:tan",
|
|
30
|
+
"\\cot": "math:cot",
|
|
31
|
+
"\\sec": "math:sec",
|
|
32
|
+
"\\csc": "math:csc",
|
|
33
|
+
"\\arcsin": "math:arcsin",
|
|
34
|
+
"\\arccos": "math:arccos",
|
|
35
|
+
"\\arctan": "math:arctan",
|
|
36
|
+
"\\sinh": "math:sinh",
|
|
37
|
+
"\\cosh": "math:cosh",
|
|
38
|
+
"\\tanh": "math:tanh",
|
|
39
|
+
"\\exp": "math:exp",
|
|
40
|
+
"\\log": "math:log",
|
|
41
|
+
"\\ln": "math:ln"
|
|
42
|
+
};
|
|
43
|
+
const PRESENTATION_ONLY_TYPES = /* @__PURE__ */ new Set([
|
|
44
|
+
"spacing",
|
|
45
|
+
"rule",
|
|
46
|
+
"kern"
|
|
47
|
+
]);
|
|
48
|
+
const WRAPPER_TYPES = /* @__PURE__ */ new Set(["styling", "color"]);
|
|
49
|
+
function isRecord(value) {
|
|
50
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
function nodeText(node) {
|
|
53
|
+
return typeof node.text === "string" ? node.text : void 0;
|
|
54
|
+
}
|
|
55
|
+
function glyphOfNode(node) {
|
|
56
|
+
const text = nodeText(node);
|
|
57
|
+
return text === void 0 ? void 0 : require_latex_symbols.glyphOfSymbolText(text);
|
|
58
|
+
}
|
|
59
|
+
function numericText(text) {
|
|
60
|
+
return text.length === 1 && text >= "0" && text <= "9";
|
|
61
|
+
}
|
|
62
|
+
function diagnose(context, code, detail) {
|
|
63
|
+
context.diagnostics.push({
|
|
64
|
+
code,
|
|
65
|
+
...detail === void 0 ? {} : { detail }
|
|
66
|
+
});
|
|
67
|
+
context.sink?.({
|
|
68
|
+
code,
|
|
69
|
+
...detail === void 0 ? {} : { detail }
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function unparsed(latex) {
|
|
73
|
+
return {
|
|
74
|
+
kind: "unparsed",
|
|
75
|
+
latex
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function app(operator, args) {
|
|
79
|
+
return {
|
|
80
|
+
kind: "app",
|
|
81
|
+
operator,
|
|
82
|
+
args: [...args]
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function spanOfNodes(context, nodes) {
|
|
86
|
+
let start;
|
|
87
|
+
let end;
|
|
88
|
+
const visit = (node) => {
|
|
89
|
+
const loc = node.loc;
|
|
90
|
+
if (isRecord(loc) && typeof loc.start === "number" && typeof loc.end === "number") {
|
|
91
|
+
if (start === void 0 || loc.start < start) start = loc.start;
|
|
92
|
+
if (end === void 0 || loc.end > end) end = loc.end;
|
|
93
|
+
}
|
|
94
|
+
for (const value of Object.values(node)) if (require_latex_temml.isTemmlNode(value)) visit(value);
|
|
95
|
+
else if (Array.isArray(value)) {
|
|
96
|
+
for (const element of value) if (require_latex_temml.isTemmlNode(element)) visit(element);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
for (const node of nodes) visit(node);
|
|
100
|
+
return start === void 0 || end === void 0 ? "" : context.input.slice(start, end);
|
|
101
|
+
}
|
|
102
|
+
function nodesOfScript(script) {
|
|
103
|
+
if (!require_latex_temml.isTemmlNode(script)) return;
|
|
104
|
+
if (script.type === "ordgroup" || WRAPPER_TYPES.has(script.type)) {
|
|
105
|
+
const body = script.body;
|
|
106
|
+
return Array.isArray(body) && body.every(require_latex_temml.isTemmlNode) ? body : void 0;
|
|
107
|
+
}
|
|
108
|
+
return [script];
|
|
109
|
+
}
|
|
110
|
+
function lowerNodeList(nodes, context) {
|
|
111
|
+
const present = nodes.filter((node) => !PRESENTATION_ONLY_TYPES.has(node.type));
|
|
112
|
+
const segments = [];
|
|
113
|
+
const operators = [];
|
|
114
|
+
let current = [];
|
|
115
|
+
let unmappable;
|
|
116
|
+
for (const node of present) {
|
|
117
|
+
const operator = operatorOfNode(node);
|
|
118
|
+
if (operator !== void 0) {
|
|
119
|
+
segments.push(current);
|
|
120
|
+
operators.push(operator);
|
|
121
|
+
current = [];
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (isOperatorAtom(node)) {
|
|
125
|
+
unmappable = unmappable ?? nodeText(node) ?? node.type;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
current.push(node);
|
|
129
|
+
}
|
|
130
|
+
segments.push(current);
|
|
131
|
+
if (unmappable !== void 0) {
|
|
132
|
+
const detail = spanOfNodes(context, present) || unmappable;
|
|
133
|
+
diagnose(context, "latex/operator-unmapped", detail);
|
|
134
|
+
return unparsed(detail);
|
|
135
|
+
}
|
|
136
|
+
const [firstSegment = [], ...restSegments] = segments;
|
|
137
|
+
if (operators.length === 0) return lowerTerm(firstSegment, context);
|
|
138
|
+
const detail = spanOfNodes(context, present);
|
|
139
|
+
if (firstSegment.length === 0) {
|
|
140
|
+
const leading = operators[0];
|
|
141
|
+
const secondSegment = restSegments[0] ?? [];
|
|
142
|
+
if (leading !== SUBTRACT_OPERATOR || secondSegment.length === 0) {
|
|
143
|
+
diagnose(context, "latex/operator-placement-unparsed", detail);
|
|
144
|
+
return unparsed(detail);
|
|
145
|
+
}
|
|
146
|
+
return fold(app(UNARY_MINUS_OPERATOR, [lowerTerm(secondSegment, context)]), operators.slice(1), restSegments.slice(1), context, detail);
|
|
147
|
+
}
|
|
148
|
+
return fold(lowerTerm(firstSegment, context), operators, restSegments, context, detail);
|
|
149
|
+
}
|
|
150
|
+
function isOperatorAtom(node) {
|
|
151
|
+
return node.type === "atom" && (node.family === "bin" || node.family === "rel");
|
|
152
|
+
}
|
|
153
|
+
function operatorOfNode(node) {
|
|
154
|
+
if (node.type === "atom") {
|
|
155
|
+
const text = nodeText(node);
|
|
156
|
+
if (text === void 0) return;
|
|
157
|
+
return BINARY_ATOM_OPERATORS[text] ?? RELATION_ATOM_OPERATORS[text];
|
|
158
|
+
}
|
|
159
|
+
if (node.type === "textord" && nodeText(node) === "/") return "math:divide";
|
|
160
|
+
}
|
|
161
|
+
function fold(first, operators, segments, context, detail) {
|
|
162
|
+
let folded = first;
|
|
163
|
+
for (let index = 0; index < operators.length; index += 1) {
|
|
164
|
+
const operator = operators[index];
|
|
165
|
+
const segmentNodes = segments[index];
|
|
166
|
+
if (operator === void 0 || segmentNodes === void 0) throw new Error("operator and segment lists diverged while folding a lowered sequence");
|
|
167
|
+
if (segmentNodes.length === 0) {
|
|
168
|
+
diagnose(context, "latex/operator-placement-unparsed", detail);
|
|
169
|
+
return unparsed(detail);
|
|
170
|
+
}
|
|
171
|
+
folded = app(operator, [folded, lowerTerm(segmentNodes, context)]);
|
|
172
|
+
}
|
|
173
|
+
return folded;
|
|
174
|
+
}
|
|
175
|
+
function lowerTerm(nodes, context) {
|
|
176
|
+
return lowerTermItems(termItems(nodes), context);
|
|
177
|
+
}
|
|
178
|
+
function lowerTermItems(items, context) {
|
|
179
|
+
const first = items[0];
|
|
180
|
+
if (first === void 0) {
|
|
181
|
+
diagnose(context, "latex/construct-unparsed");
|
|
182
|
+
return unparsed("");
|
|
183
|
+
}
|
|
184
|
+
if (first.kind === "node") {
|
|
185
|
+
const binder = readBinder(first.node, context);
|
|
186
|
+
if (binder.status === "binder") {
|
|
187
|
+
const rest = items.slice(1);
|
|
188
|
+
const body = rest.length === 0 ? implicitArgument(context) : lowerTermItems(rest, {
|
|
189
|
+
...context,
|
|
190
|
+
binders: [binder.binder, ...context.binders]
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
kind: binder.kind,
|
|
194
|
+
binder: binder.binder,
|
|
195
|
+
lower: binder.lower,
|
|
196
|
+
upper: binder.upper,
|
|
197
|
+
body
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (binder.status === "degraded") return unparsed(binder.detail);
|
|
201
|
+
const namedFunction = namedFunctionOfOp(first.node);
|
|
202
|
+
if (namedFunction !== void 0) {
|
|
203
|
+
const rest = items.slice(1);
|
|
204
|
+
return app(namedFunction, [rest.length === 0 ? implicitArgument(context) : lowerTermItems(rest, context)]);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const lowered = lowerTermItem(first, context);
|
|
208
|
+
if (items.length > 1) {
|
|
209
|
+
const detail = spanOfNodes(context, nodesOfItems(items));
|
|
210
|
+
diagnose(context, "latex/juxtaposition-unparsed", detail);
|
|
211
|
+
return unparsed(detail);
|
|
212
|
+
}
|
|
213
|
+
return lowered;
|
|
214
|
+
}
|
|
215
|
+
function nodesOfItems(items) {
|
|
216
|
+
const nodes = [];
|
|
217
|
+
for (const item of items) if (item.kind === "node") nodes.push(item.node);
|
|
218
|
+
else nodes.push(...item.nodes);
|
|
219
|
+
return nodes;
|
|
220
|
+
}
|
|
221
|
+
function implicitArgument(context) {
|
|
222
|
+
diagnose(context, "latex/construct-unparsed");
|
|
223
|
+
return unparsed("");
|
|
224
|
+
}
|
|
225
|
+
function termItems(nodes) {
|
|
226
|
+
const items = [];
|
|
227
|
+
let digits = [];
|
|
228
|
+
let digitNodes = [];
|
|
229
|
+
const flushDigits = () => {
|
|
230
|
+
if (digits.length > 0) {
|
|
231
|
+
items.push({
|
|
232
|
+
kind: "number",
|
|
233
|
+
literal: digits.join(""),
|
|
234
|
+
nodes: digitNodes
|
|
235
|
+
});
|
|
236
|
+
digits = [];
|
|
237
|
+
digitNodes = [];
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
for (const node of nodes) {
|
|
241
|
+
const text = nodeText(node);
|
|
242
|
+
if (node.type === "textord" && text !== void 0 && (numericText(text) || text === ".")) {
|
|
243
|
+
digits.push(text);
|
|
244
|
+
digitNodes.push(node);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
flushDigits();
|
|
248
|
+
items.push({
|
|
249
|
+
kind: "node",
|
|
250
|
+
node
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
flushDigits();
|
|
254
|
+
return items;
|
|
255
|
+
}
|
|
256
|
+
function lowerTermItem(item, context) {
|
|
257
|
+
if (item.kind === "number") {
|
|
258
|
+
const rational = require_latex_rational.decimalToRational(item.literal);
|
|
259
|
+
if (rational === void 0) {
|
|
260
|
+
diagnose(context, "latex/construct-unparsed", item.literal);
|
|
261
|
+
return unparsed(item.literal);
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
kind: "num",
|
|
265
|
+
numerator: rational.numerator,
|
|
266
|
+
denominator: rational.denominator
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return lowerNode(item.node, context);
|
|
270
|
+
}
|
|
271
|
+
function lowerNode(node, context) {
|
|
272
|
+
switch (node.type) {
|
|
273
|
+
case "mathord": {
|
|
274
|
+
const glyph = glyphOfNode(node);
|
|
275
|
+
return glyph === void 0 ? degradeNode(node, context) : symbolExpression(glyph, context);
|
|
276
|
+
}
|
|
277
|
+
case "textord": {
|
|
278
|
+
const text = nodeText(node);
|
|
279
|
+
if (text === void 0) return degradeNode(node, context);
|
|
280
|
+
if (/^[0-9]$/.test(text)) return {
|
|
281
|
+
kind: "num",
|
|
282
|
+
numerator: text,
|
|
283
|
+
denominator: "1"
|
|
284
|
+
};
|
|
285
|
+
const glyph = require_latex_symbols.glyphOfSymbolText(text);
|
|
286
|
+
return glyph === void 0 ? degradeNode(node, context) : symbolExpression(glyph, context);
|
|
287
|
+
}
|
|
288
|
+
case "atom": return degradeNode(node, context);
|
|
289
|
+
case "genfrac": return lowerGenfrac(node, context);
|
|
290
|
+
case "sqrt": return lowerSqrt(node, context);
|
|
291
|
+
case "supsub": return lowerSupsub(node, context);
|
|
292
|
+
case "op": return degradeNode(node, context);
|
|
293
|
+
case "ordgroup":
|
|
294
|
+
case "styling":
|
|
295
|
+
case "color": {
|
|
296
|
+
const body = node.body;
|
|
297
|
+
if (!Array.isArray(body) || !body.every(require_latex_temml.isTemmlNode)) return degradeNode(node, context);
|
|
298
|
+
return lowerNodeList(body, context);
|
|
299
|
+
}
|
|
300
|
+
case "delimiter":
|
|
301
|
+
case "leftright": return lowerGrouping(node, context);
|
|
302
|
+
case "array": return lowerArray(node, context);
|
|
303
|
+
case "text": {
|
|
304
|
+
const detail = spanOfNodes(context, [node]);
|
|
305
|
+
diagnose(context, "latex/text-unparsed", detail);
|
|
306
|
+
return unparsed(detail);
|
|
307
|
+
}
|
|
308
|
+
default: return degradeNode(node, context);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function degradeNode(node, context) {
|
|
312
|
+
const span = spanOfNodes(context, [node]);
|
|
313
|
+
const detail = span !== "" ? span : nodeText(node) ?? node.type;
|
|
314
|
+
diagnose(context, "latex/construct-unparsed", detail);
|
|
315
|
+
return unparsed(detail);
|
|
316
|
+
}
|
|
317
|
+
function symbolExpression(glyph, context) {
|
|
318
|
+
if (context.binders.includes(glyph)) return {
|
|
319
|
+
kind: "sym",
|
|
320
|
+
id: glyph
|
|
321
|
+
};
|
|
322
|
+
return {
|
|
323
|
+
kind: "sym",
|
|
324
|
+
id: context.resolver.resolve(glyph)
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function lowerGenfrac(node, context) {
|
|
328
|
+
const hasBar = node.hasBarLine !== false;
|
|
329
|
+
const delimited = node.leftDelim !== null && node.leftDelim !== void 0 || node.rightDelim !== null && node.rightDelim !== void 0;
|
|
330
|
+
if (!hasBar || delimited || !require_latex_temml.isTemmlNode(node.numer) || !require_latex_temml.isTemmlNode(node.denom)) {
|
|
331
|
+
const detail = spanOfNodes(context, [node]);
|
|
332
|
+
diagnose(context, "latex/genfrac-unparsed", detail);
|
|
333
|
+
return unparsed(detail);
|
|
334
|
+
}
|
|
335
|
+
return app("math:divide", [lowerNode(node.numer, context), lowerNode(node.denom, context)]);
|
|
336
|
+
}
|
|
337
|
+
function lowerSqrt(node, context) {
|
|
338
|
+
const index = node.index === null ? void 0 : node.index;
|
|
339
|
+
if (!require_latex_temml.isTemmlNode(node.body) || index !== void 0 && !require_latex_temml.isTemmlNode(index)) return degradeNode(node, context);
|
|
340
|
+
const body = lowerNode(node.body, context);
|
|
341
|
+
if (index === void 0) return app("math:sqrt", [body]);
|
|
342
|
+
return app("math:pow", [body, app("math:divide", [{
|
|
343
|
+
kind: "num",
|
|
344
|
+
numerator: "1",
|
|
345
|
+
denominator: "1"
|
|
346
|
+
}, lowerNode(index, context)])]);
|
|
347
|
+
}
|
|
348
|
+
function lowerGrouping(node, context) {
|
|
349
|
+
const body = node.body;
|
|
350
|
+
if (!Array.isArray(body) || !body.every(require_latex_temml.isTemmlNode)) return degradeNode(node, context);
|
|
351
|
+
if (body.length === 1) {
|
|
352
|
+
const only = body[0];
|
|
353
|
+
if (only?.type === "array") return lowerArray(only, context);
|
|
354
|
+
}
|
|
355
|
+
return lowerNodeList(body, context);
|
|
356
|
+
}
|
|
357
|
+
function lowerArray(node, context) {
|
|
358
|
+
const separated = Array.isArray(node.cols) && node.cols.some((col) => !require_latex_temml.isTemmlNode(col) || col.type === "separator");
|
|
359
|
+
const body = node.body;
|
|
360
|
+
const envClasses = node.envClasses;
|
|
361
|
+
const layoutEnvironment = Array.isArray(envClasses) && envClasses.length > 0;
|
|
362
|
+
if (layoutEnvironment || separated || !Array.isArray(body)) {
|
|
363
|
+
const detail = spanOfNodes(context, [node]);
|
|
364
|
+
diagnose(context, layoutEnvironment ? "latex/array-environment-unparsed" : "latex/construct-unparsed", detail);
|
|
365
|
+
return unparsed(detail);
|
|
366
|
+
}
|
|
367
|
+
const rows = [];
|
|
368
|
+
for (const row of body) {
|
|
369
|
+
if (!Array.isArray(row) || !row.every(require_latex_temml.isTemmlNode)) return degradeNode(node, context);
|
|
370
|
+
rows.push(row.map((cell) => lowerNode(cell, context)));
|
|
371
|
+
}
|
|
372
|
+
if (rows.length > 0 && new Set(rows.map((row) => row.length)).size > 1) {
|
|
373
|
+
const detail = spanOfNodes(context, [node]);
|
|
374
|
+
diagnose(context, "latex/construct-unparsed", detail);
|
|
375
|
+
return unparsed(detail);
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
kind: "matrix",
|
|
379
|
+
rows
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function lowerSupsub(node, context) {
|
|
383
|
+
const binder = readBinder(node, context);
|
|
384
|
+
if (binder.status === "binder") return {
|
|
385
|
+
kind: binder.kind,
|
|
386
|
+
binder: binder.binder,
|
|
387
|
+
lower: binder.lower,
|
|
388
|
+
upper: binder.upper,
|
|
389
|
+
body: implicitArgument(context)
|
|
390
|
+
};
|
|
391
|
+
if (binder.status === "degraded") return unparsed(binder.detail);
|
|
392
|
+
const detail = spanOfNodes(context, [node]);
|
|
393
|
+
const base = require_latex_temml.isTemmlNode(node.base) ? node.base : void 0;
|
|
394
|
+
const sub = node.sub === void 0 ? void 0 : nodesOfScript(node.sub);
|
|
395
|
+
const sup = node.sup === void 0 ? void 0 : nodesOfScript(node.sup);
|
|
396
|
+
if (sub !== void 0) {
|
|
397
|
+
const subWritten = simpleScriptGlyph(sub);
|
|
398
|
+
const baseGlyph = base !== void 0 && (base.type === "mathord" || base.type === "textord") ? glyphOfNode(base) : void 0;
|
|
399
|
+
if (baseGlyph === void 0 || subWritten === void 0) {
|
|
400
|
+
diagnose(context, "latex/subscript-unparsed", detail);
|
|
401
|
+
return unparsed(detail);
|
|
402
|
+
}
|
|
403
|
+
const subscriptedGlyph = `${baseGlyph}_${subWritten}`;
|
|
404
|
+
if (sup === void 0) return symbolExpression(subscriptedGlyph, context);
|
|
405
|
+
const tripleGlyph = `${subscriptedGlyph}^${scriptWrittenForm(sup, context)}`;
|
|
406
|
+
if (context.resolver.isCurated(tripleGlyph)) return symbolExpression(tripleGlyph, context);
|
|
407
|
+
return app("math:pow", [symbolExpression(subscriptedGlyph, context), lowerNodeList(sup, context)]);
|
|
408
|
+
}
|
|
409
|
+
if (sup === void 0) return degradeNode(node, context);
|
|
410
|
+
const exponent = lowerNodeList(sup, context);
|
|
411
|
+
if (base === void 0) return degradeNode(node, context);
|
|
412
|
+
if (base.type !== "mathord" && base.type !== "textord") {
|
|
413
|
+
const loweredBase = lowerNode(base, context);
|
|
414
|
+
if (loweredBase.kind === "unparsed") {
|
|
415
|
+
diagnose(context, "latex/script-base-unparsed", detail);
|
|
416
|
+
return unparsed(detail);
|
|
417
|
+
}
|
|
418
|
+
return app("math:pow", [loweredBase, exponent]);
|
|
419
|
+
}
|
|
420
|
+
const baseGlyph = glyphOfNode(base);
|
|
421
|
+
if (baseGlyph === void 0) return degradeNode(node, context);
|
|
422
|
+
const scriptedGlyph = `${baseGlyph}^${scriptWrittenForm(sup, context)}`;
|
|
423
|
+
if (context.resolver.isCurated(scriptedGlyph)) return symbolExpression(scriptedGlyph, context);
|
|
424
|
+
return app("math:pow", [symbolExpression(baseGlyph, context), exponent]);
|
|
425
|
+
}
|
|
426
|
+
function scriptWrittenForm(nodes, context) {
|
|
427
|
+
const span = spanOfNodes(context, nodes);
|
|
428
|
+
if (span !== "") return span;
|
|
429
|
+
return nodes.map((node) => glyphOfNode(node) ?? nodeText(node) ?? "").join("");
|
|
430
|
+
}
|
|
431
|
+
function simpleScriptGlyph(nodes) {
|
|
432
|
+
const parts = [];
|
|
433
|
+
for (const node of nodes) {
|
|
434
|
+
const text = nodeText(node);
|
|
435
|
+
if (node.type === "mathord") {
|
|
436
|
+
const glyph = glyphOfNode(node);
|
|
437
|
+
if (glyph === void 0) return;
|
|
438
|
+
parts.push(glyph);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
if (node.type === "textord" && text !== void 0 && /^[0-9A-Za-z]$/.test(text)) {
|
|
442
|
+
parts.push(text);
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
return parts.length === 0 ? void 0 : parts.join("");
|
|
448
|
+
}
|
|
449
|
+
function readBinder(node, context) {
|
|
450
|
+
if (node.type !== "supsub") return { status: "not-a-binder" };
|
|
451
|
+
const base = require_latex_temml.isTemmlNode(node.base) ? node.base : void 0;
|
|
452
|
+
if (base?.type !== "op" || base?.symbol !== true || typeof base?.name !== "string") return { status: "not-a-binder" };
|
|
453
|
+
if (base.name !== "\\sum" && base.name !== "\\prod") return { status: "not-a-binder" };
|
|
454
|
+
const kind = base.name === "\\sum" ? "sum" : "prod";
|
|
455
|
+
const detail = spanOfNodes(context, [node]);
|
|
456
|
+
const sub = node.sub === void 0 ? void 0 : nodesOfScript(node.sub);
|
|
457
|
+
const sup = node.sup === void 0 ? void 0 : nodesOfScript(node.sup);
|
|
458
|
+
const upper = sup === void 0 ? implicitBound(context) : lowerNodeList(sup, context);
|
|
459
|
+
if ((sub?.length ?? 0) >= 3) {
|
|
460
|
+
const first = sub?.[0];
|
|
461
|
+
const relation = sub?.[1];
|
|
462
|
+
const rest = sub?.slice(2) ?? [];
|
|
463
|
+
const firstGlyph = first?.type === "mathord" ? glyphOfNode(first) : void 0;
|
|
464
|
+
if (firstGlyph !== void 0 && relation?.type === "atom" && nodeText(relation) === "=") return {
|
|
465
|
+
status: "binder",
|
|
466
|
+
kind,
|
|
467
|
+
binder: firstGlyph,
|
|
468
|
+
lower: lowerNodeList(rest, context),
|
|
469
|
+
upper
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
if ((sub?.length ?? 0) === 1 && sub !== void 0) {
|
|
473
|
+
const singleGlyph = simpleScriptGlyph(sub);
|
|
474
|
+
if (singleGlyph !== void 0) {
|
|
475
|
+
diagnose(context, "latex/binder-bound-implicit", detail);
|
|
476
|
+
return {
|
|
477
|
+
status: "binder",
|
|
478
|
+
kind,
|
|
479
|
+
binder: singleGlyph,
|
|
480
|
+
lower: unparsed(""),
|
|
481
|
+
upper
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
diagnose(context, "latex/binder-bound-unreadable", detail);
|
|
486
|
+
return {
|
|
487
|
+
status: "degraded",
|
|
488
|
+
detail
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function implicitBound(context) {
|
|
492
|
+
diagnose(context, "latex/binder-bound-implicit");
|
|
493
|
+
return unparsed("");
|
|
494
|
+
}
|
|
495
|
+
function namedFunctionOfOp(node) {
|
|
496
|
+
if (node.type !== "op" || node.symbol === true) return;
|
|
497
|
+
const name = typeof node.name === "string" ? node.name : void 0;
|
|
498
|
+
return name === void 0 ? void 0 : NAMED_FUNCTION_OPERATORS[name];
|
|
499
|
+
}
|
|
500
|
+
function lowerLatex(latex, options) {
|
|
501
|
+
return lowerParsed(latex, require_latex_temml.parseLatex(latex), options);
|
|
502
|
+
}
|
|
503
|
+
function lowerParsed(latex, parsed, options) {
|
|
504
|
+
if (latex.trim() === "") return {
|
|
505
|
+
expression: unparsed(""),
|
|
506
|
+
diagnostics: [],
|
|
507
|
+
mintedSymbols: []
|
|
508
|
+
};
|
|
509
|
+
const resolver = new require_latex_symbols.SymbolResolver(options?.symbolEntries ?? []);
|
|
510
|
+
const context = {
|
|
511
|
+
input: latex,
|
|
512
|
+
resolver,
|
|
513
|
+
binders: [],
|
|
514
|
+
diagnostics: [],
|
|
515
|
+
sink: options?.sink
|
|
516
|
+
};
|
|
517
|
+
if (parsed.status === "unparseable") {
|
|
518
|
+
diagnose(context, "latex/parse-error", parsed.message);
|
|
519
|
+
return {
|
|
520
|
+
expression: unparsed(latex),
|
|
521
|
+
diagnostics: context.diagnostics,
|
|
522
|
+
mintedSymbols: resolver.mintedEntries()
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
expression: lowerNodeList(parsed.nodes, context),
|
|
527
|
+
diagnostics: context.diagnostics,
|
|
528
|
+
mintedSymbols: resolver.mintedEntries()
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function latexToFormula(latex, options) {
|
|
532
|
+
const parsed = require_latex_temml.parseLatex(latex);
|
|
533
|
+
const lowering = lowerParsed(latex, parsed, options);
|
|
534
|
+
return {
|
|
535
|
+
formula: {
|
|
536
|
+
mathml: parsed.status === "parsed" ? [...parsed.mathml] : [],
|
|
537
|
+
presentation: { latex },
|
|
538
|
+
content: lowering.expression,
|
|
539
|
+
provenance: {
|
|
540
|
+
source: options?.source ?? "lowered:latex",
|
|
541
|
+
editTrail: []
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
diagnostics: lowering.diagnostics,
|
|
545
|
+
mintedSymbols: lowering.mintedSymbols
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
//#endregion
|
|
549
|
+
exports.latexToFormula = latexToFormula;
|
|
550
|
+
exports.lowerLatex = lowerLatex;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { i as LatexDiagnosticSink, n as LatexDiagnostic } from "../diagnostics-C5-bG09J.cjs";
|
|
2
|
+
import { ContentFormula, MathExpression, MathSymbolEntry } from "document-schema.js";
|
|
3
|
+
//#region src/latex/lower.d.ts
|
|
4
|
+
interface LowerLatexOptions {
|
|
5
|
+
readonly symbolEntries?: readonly MathSymbolEntry[];
|
|
6
|
+
readonly sink?: LatexDiagnosticSink;
|
|
7
|
+
}
|
|
8
|
+
interface LatexLoweringResult {
|
|
9
|
+
readonly expression: MathExpression;
|
|
10
|
+
readonly diagnostics: readonly LatexDiagnostic[];
|
|
11
|
+
readonly mintedSymbols: readonly MathSymbolEntry[];
|
|
12
|
+
}
|
|
13
|
+
declare function lowerLatex(latex: string, options?: LowerLatexOptions): LatexLoweringResult;
|
|
14
|
+
interface LatexFormulaOptions extends LowerLatexOptions {
|
|
15
|
+
readonly source?: string;
|
|
16
|
+
}
|
|
17
|
+
interface LatexFormulaResult {
|
|
18
|
+
readonly formula: ContentFormula;
|
|
19
|
+
readonly diagnostics: readonly LatexDiagnostic[];
|
|
20
|
+
readonly mintedSymbols: readonly MathSymbolEntry[];
|
|
21
|
+
}
|
|
22
|
+
declare function latexToFormula(latex: string, options?: LatexFormulaOptions): LatexFormulaResult;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { LatexFormulaOptions, LatexFormulaResult, LatexLoweringResult, LowerLatexOptions, latexToFormula, lowerLatex };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { i as LatexDiagnosticSink, n as LatexDiagnostic } from "../diagnostics-C5-bG09J.js";
|
|
2
|
+
import { ContentFormula, MathExpression, MathSymbolEntry } from "document-schema.js";
|
|
3
|
+
//#region src/latex/lower.d.ts
|
|
4
|
+
interface LowerLatexOptions {
|
|
5
|
+
readonly symbolEntries?: readonly MathSymbolEntry[];
|
|
6
|
+
readonly sink?: LatexDiagnosticSink;
|
|
7
|
+
}
|
|
8
|
+
interface LatexLoweringResult {
|
|
9
|
+
readonly expression: MathExpression;
|
|
10
|
+
readonly diagnostics: readonly LatexDiagnostic[];
|
|
11
|
+
readonly mintedSymbols: readonly MathSymbolEntry[];
|
|
12
|
+
}
|
|
13
|
+
declare function lowerLatex(latex: string, options?: LowerLatexOptions): LatexLoweringResult;
|
|
14
|
+
interface LatexFormulaOptions extends LowerLatexOptions {
|
|
15
|
+
readonly source?: string;
|
|
16
|
+
}
|
|
17
|
+
interface LatexFormulaResult {
|
|
18
|
+
readonly formula: ContentFormula;
|
|
19
|
+
readonly diagnostics: readonly LatexDiagnostic[];
|
|
20
|
+
readonly mintedSymbols: readonly MathSymbolEntry[];
|
|
21
|
+
}
|
|
22
|
+
declare function latexToFormula(latex: string, options?: LatexFormulaOptions): LatexFormulaResult;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { LatexFormulaOptions, LatexFormulaResult, LatexLoweringResult, LowerLatexOptions, latexToFormula, lowerLatex };
|