docsui-cli 0.0.59

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.
Files changed (49) hide show
  1. package/README.md +279 -0
  2. package/dist/commands/add.d.ts +5 -0
  3. package/dist/commands/add.js +254 -0
  4. package/dist/commands/doctor.d.ts +5 -0
  5. package/dist/commands/doctor.js +250 -0
  6. package/dist/commands/init.d.ts +5 -0
  7. package/dist/commands/init.js +548 -0
  8. package/dist/commands/list.d.ts +5 -0
  9. package/dist/commands/list.js +84 -0
  10. package/dist/commands/mcp.d.ts +3 -0
  11. package/dist/commands/mcp.js +1562 -0
  12. package/dist/commands/new.d.ts +5 -0
  13. package/dist/commands/new.js +113 -0
  14. package/dist/commands/remove.d.ts +5 -0
  15. package/dist/commands/remove.js +134 -0
  16. package/dist/commands/save.d.ts +5 -0
  17. package/dist/commands/save.js +60 -0
  18. package/dist/commands/update.d.ts +5 -0
  19. package/dist/commands/update.js +247 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +88 -0
  22. package/dist/latex-to-primitives.d.ts +59 -0
  23. package/dist/latex-to-primitives.js +1019 -0
  24. package/dist/lib/component-registry.d.ts +33 -0
  25. package/dist/lib/component-registry.js +843 -0
  26. package/dist/lib/css-tokens.d.ts +8 -0
  27. package/dist/lib/css-tokens.js +294 -0
  28. package/dist/metadata.d.ts +1 -0
  29. package/dist/metadata.js +4 -0
  30. package/dist/symbol-map.d.ts +30 -0
  31. package/dist/symbol-map.js +1607 -0
  32. package/dist/utils/detect-structure.d.ts +16 -0
  33. package/dist/utils/detect-structure.js +58 -0
  34. package/dist/utils/fetch-component.d.ts +13 -0
  35. package/dist/utils/fetch-component.js +81 -0
  36. package/dist/utils/get-config.d.ts +14 -0
  37. package/dist/utils/get-config.js +19 -0
  38. package/dist/utils/install-deps.d.ts +3 -0
  39. package/dist/utils/install-deps.js +23 -0
  40. package/dist/utils/save-mdx-page.d.ts +35 -0
  41. package/dist/utils/save-mdx-page.js +44 -0
  42. package/dist/utils/scan-mdx.d.ts +20 -0
  43. package/dist/utils/scan-mdx.js +106 -0
  44. package/dist/utils/telemetry.d.ts +3 -0
  45. package/dist/utils/telemetry.js +42 -0
  46. package/dist/utils/write-component.d.ts +7 -0
  47. package/dist/utils/write-component.js +25 -0
  48. package/dist/webview-bundle.js +3478 -0
  49. package/package.json +94 -0
@@ -0,0 +1,1019 @@
1
+ function tokenize(src) {
2
+ const tokens = [];
3
+ let i = 0;
4
+ while (i < src.length) {
5
+ const ch = src[i];
6
+ if (ch === "\\") {
7
+ i++;
8
+ if (i >= src.length) break;
9
+ if (/[a-zA-Z]/.test(src[i])) {
10
+ let cmd = "";
11
+ while (i < src.length && /[a-zA-Z*]/.test(src[i])) cmd += src[i++];
12
+ tokens.push({ t: "CMD", v: cmd });
13
+ } else {
14
+ tokens.push({ t: "CMD", v: src[i++] });
15
+ }
16
+ } else if (ch === "{") {
17
+ tokens.push({ t: "LBRACE" });
18
+ i++;
19
+ } else if (ch === "}") {
20
+ tokens.push({ t: "RBRACE" });
21
+ i++;
22
+ } else if (ch === "[") {
23
+ tokens.push({ t: "LBRACKET" });
24
+ i++;
25
+ } else if (ch === "]") {
26
+ tokens.push({ t: "RBRACKET" });
27
+ i++;
28
+ } else if (ch === "^") {
29
+ tokens.push({ t: "CARET" });
30
+ i++;
31
+ } else if (ch === "_") {
32
+ tokens.push({ t: "UNDER" });
33
+ i++;
34
+ } else if (ch === "(") {
35
+ tokens.push({ t: "LPAREN" });
36
+ i++;
37
+ } else if (ch === ")") {
38
+ tokens.push({ t: "RPAREN" });
39
+ i++;
40
+ } else if (ch === "&") {
41
+ tokens.push({ t: "AMP" });
42
+ i++;
43
+ } else if (/\s/.test(ch)) {
44
+ i++;
45
+ } else {
46
+ tokens.push({ t: "CHAR", v: ch });
47
+ i++;
48
+ }
49
+ }
50
+ tokens.push({ t: "EOF" });
51
+ return tokens;
52
+ }
53
+ function isAllText(nodes) {
54
+ return nodes.every((n) => n.k === "text");
55
+ }
56
+ function textOf(nodes) {
57
+ return nodes.map((n) => n.k === "text" ? n.v : "").join("");
58
+ }
59
+ function emitProp(nodes) {
60
+ if (nodes.length === 0) return '""';
61
+ if (isAllText(nodes)) return `"${textOf(nodes)}"`;
62
+ if (nodes.length === 1 && nodes[0].k === "comp" && !nodes[0].children) {
63
+ const n = nodes[0];
64
+ const props = emitPropsStr(n.props);
65
+ return `{<${n.name}${props} />}`;
66
+ }
67
+ return `{<span>${emitNodes(nodes)}</span>}`;
68
+ }
69
+ function emitPropsStr(props) {
70
+ if (!props) return "";
71
+ return Object.entries(props).map(([k, v]) => ` ${k}=${emitProp(v)}`).join("");
72
+ }
73
+ function emitNode(n) {
74
+ if (n.k === "text") return n.v;
75
+ if (n.k === "seq") return emitNodes(n.nodes);
76
+ if (n.k === "comp") {
77
+ const props = emitPropsStr(n.props);
78
+ if (!n.children) return `<${n.name}${props} />`;
79
+ return `<${n.name}${props}>${emitNodes(n.children)}</${n.name}>`;
80
+ }
81
+ return "";
82
+ }
83
+ function emitNodes(nodes) {
84
+ return nodes.map(emitNode).join("");
85
+ }
86
+ class Parser {
87
+ constructor(tokens) {
88
+ this.tokens = tokens;
89
+ this.pos = 0;
90
+ }
91
+ peek() {
92
+ return this.tokens[this.pos];
93
+ }
94
+ eat() {
95
+ return this.tokens[this.pos++];
96
+ }
97
+ eatIf(t) {
98
+ if (this.peek().t === t) {
99
+ this.pos++;
100
+ return true;
101
+ }
102
+ return false;
103
+ }
104
+ // Parse a group {...} — returns the nodes inside
105
+ parseGroup() {
106
+ if (this.peek().t !== "LBRACE") return [this.parsePrimary()];
107
+ this.eat();
108
+ const nodes = this.parseSeq(["RBRACE"]);
109
+ this.eatIf("RBRACE");
110
+ return nodes;
111
+ }
112
+ // Parse an optional group [...] — returns nodes or null
113
+ parseOptGroup() {
114
+ if (this.peek().t !== "LBRACKET") return null;
115
+ this.eat();
116
+ const nodes = this.parseSeq(["RBRACKET"]);
117
+ this.eatIf("RBRACKET");
118
+ return nodes;
119
+ }
120
+ // Parse a sequence of nodes until one of the stop tokens
121
+ parseSeq(stop = ["EOF"]) {
122
+ const nodes = [];
123
+ while (!stop.includes(this.peek().t)) {
124
+ if (this.peek().t === "EOF") break;
125
+ const n = this.parseAtom();
126
+ if (n) nodes.push(n);
127
+ }
128
+ return nodes;
129
+ }
130
+ // Parse a single atom possibly followed by ^ and/or _
131
+ parseAtom() {
132
+ const base = this.parsePrimary();
133
+ if (!base) return null;
134
+ return this.parseScripts(base);
135
+ }
136
+ // Attach ^ and _ to a base node
137
+ parseScripts(base) {
138
+ const node = base;
139
+ let sup = null;
140
+ let sub = null;
141
+ for (let i = 0; i < 2; i++) {
142
+ if (this.peek().t === "CARET" && !sup) {
143
+ this.eat();
144
+ sup = this.parseGroup();
145
+ } else if (this.peek().t === "UNDER" && !sub) {
146
+ this.eat();
147
+ sub = this.parseGroup();
148
+ } else break;
149
+ }
150
+ if (sup && sub) {
151
+ const withSub = {
152
+ k: "comp",
153
+ name: "Sub",
154
+ props: { sub },
155
+ children: [node]
156
+ };
157
+ return {
158
+ k: "comp",
159
+ name: "Pow",
160
+ props: { exp: sup },
161
+ children: [withSub]
162
+ };
163
+ }
164
+ if (sup)
165
+ return { k: "comp", name: "Pow", props: { exp: sup }, children: [node] };
166
+ if (sub)
167
+ return { k: "comp", name: "Sub", props: { sub }, children: [node] };
168
+ return node;
169
+ }
170
+ // Parse the primary token (no scripts yet)
171
+ parsePrimary() {
172
+ const tok = this.peek();
173
+ if (tok.t === "LBRACE") {
174
+ const children = this.parseGroup();
175
+ return { k: "seq", nodes: children };
176
+ }
177
+ if (tok.t === "LPAREN") {
178
+ this.eat();
179
+ const children = this.parseSeq(["RPAREN"]);
180
+ this.eatIf("RPAREN");
181
+ return { k: "comp", name: "Paren", children };
182
+ }
183
+ if (tok.t === "CHAR") {
184
+ this.eat();
185
+ const map = {
186
+ "+": "+",
187
+ "-": "\u2212",
188
+ "=": " = ",
189
+ ",": ", ",
190
+ ".": ".",
191
+ "/": "/",
192
+ ":": ":",
193
+ ";": " ",
194
+ "!": "!",
195
+ "|": "|",
196
+ "<": "<",
197
+ ">": ">"
198
+ };
199
+ return { k: "text", v: map[tok.v] ?? tok.v };
200
+ }
201
+ if (tok.t === "CMD") {
202
+ this.eat();
203
+ return this.parseCommand(tok.v);
204
+ }
205
+ this.eat();
206
+ return { k: "text", v: "" };
207
+ }
208
+ parseCommand(cmd) {
209
+ switch (cmd) {
210
+ // ── Structural ────────────────────────────────────────────────────────
211
+ case "frac": {
212
+ const num = this.parseGroup();
213
+ const den = this.parseGroup();
214
+ const numStr = textOf(num);
215
+ const denStr = textOf(den);
216
+ if ((numStr.startsWith("\u2202") || isPartial(num)) && (denStr.startsWith("\u2202") || isPartial(den))) {
217
+ const ofNodes = extractAfterPartial(num);
218
+ const byNodes = extractAfterPartial(den);
219
+ return {
220
+ k: "comp",
221
+ name: "PDeriv",
222
+ props: {
223
+ of: ofNodes.length ? ofNodes : [{ k: "text", v: "f" }],
224
+ by: byNodes.length ? byNodes : [{ k: "text", v: "x" }]
225
+ }
226
+ };
227
+ }
228
+ if (/^d[a-zA-Z]$/.test(numStr) && /^d[a-zA-Z]$/.test(denStr)) {
229
+ return {
230
+ k: "comp",
231
+ name: "Deriv",
232
+ props: {
233
+ of: [{ k: "text", v: numStr[1] }],
234
+ by: [{ k: "text", v: denStr[1] }]
235
+ }
236
+ };
237
+ }
238
+ return { k: "comp", name: "Frac", props: { num, den } };
239
+ }
240
+ case "sqrt": {
241
+ const optN = this.parseOptGroup();
242
+ const body = this.parseGroup();
243
+ const props = {};
244
+ if (optN) props["n"] = optN;
245
+ return {
246
+ k: "comp",
247
+ name: "Sqrt",
248
+ props: Object.keys(props).length ? props : void 0,
249
+ children: body
250
+ };
251
+ }
252
+ // ── Calculus ──────────────────────────────────────────────────────────
253
+ case "int":
254
+ case "intop": {
255
+ const { from, to } = this.parseSubSup();
256
+ const body = this.parseSeq(["RBRACE", "EOF"]);
257
+ return {
258
+ k: "comp",
259
+ name: "Integral",
260
+ props: buildBounds(from, to),
261
+ children: body
262
+ };
263
+ }
264
+ case "oint": {
265
+ const { from, to } = this.parseSubSup();
266
+ const body = this.parseSeq(["RBRACE", "EOF"]);
267
+ return {
268
+ k: "comp",
269
+ name: "ContourIntegral",
270
+ props: buildBounds(from, to),
271
+ children: body
272
+ };
273
+ }
274
+ case "iint":
275
+ return { k: "comp", name: "DoubleInt" };
276
+ case "iiint":
277
+ return { k: "comp", name: "TripleInt" };
278
+ case "sum": {
279
+ const { from, to } = this.parseSubSup();
280
+ const body = this.parseSeq(["RBRACE", "EOF"]);
281
+ return {
282
+ k: "comp",
283
+ name: "Sum",
284
+ props: buildBounds(from, to),
285
+ children: body
286
+ };
287
+ }
288
+ case "prod": {
289
+ const { from, to } = this.parseSubSup();
290
+ const body = this.parseSeq(["RBRACE", "EOF"]);
291
+ return {
292
+ k: "comp",
293
+ name: "Prod",
294
+ props: buildBounds(from, to),
295
+ children: body
296
+ };
297
+ }
298
+ case "lim": {
299
+ const subNodes = this.parseUnder();
300
+ const body = this.parseSeq(["RBRACE", "EOF"]);
301
+ const props = subNodes ? { sub: subNodes } : void 0;
302
+ return { k: "comp", name: "Lim", props, children: body };
303
+ }
304
+ case "partial":
305
+ return { k: "comp", name: "PartialDiff" };
306
+ case "nabla":
307
+ return { k: "comp", name: "Nabla" };
308
+ // Limits
309
+ case "limsup": {
310
+ const subNodes = this.parseUnder();
311
+ return {
312
+ k: "comp",
313
+ name: "Limsup",
314
+ props: subNodes ? { sub: subNodes } : void 0
315
+ };
316
+ }
317
+ case "liminf": {
318
+ const subNodes = this.parseUnder();
319
+ return {
320
+ k: "comp",
321
+ name: "Liminf",
322
+ props: subNodes ? { sub: subNodes } : void 0
323
+ };
324
+ }
325
+ // ── Accents ───────────────────────────────────────────────────────────
326
+ case "vec":
327
+ return { k: "comp", name: "Vec", children: this.parseGroup() };
328
+ case "hat":
329
+ return { k: "comp", name: "Hat", children: this.parseGroup() };
330
+ case "bar":
331
+ case "overline":
332
+ return { k: "comp", name: "Bar", children: this.parseGroup() };
333
+ case "tilde":
334
+ case "widetilde":
335
+ return { k: "comp", name: "Tilde", children: this.parseGroup() };
336
+ case "dot":
337
+ return { k: "comp", name: "DotAccent", children: this.parseGroup() };
338
+ case "ddot":
339
+ return { k: "comp", name: "DDot", children: this.parseGroup() };
340
+ case "overbrace":
341
+ return { k: "comp", name: "Overbrace", children: this.parseGroup() };
342
+ case "underbrace":
343
+ return { k: "comp", name: "Underbrace", children: this.parseGroup() };
344
+ case "widehat":
345
+ return { k: "comp", name: "Hat", children: this.parseGroup() };
346
+ case "underline":
347
+ return { k: "comp", name: "Bar", children: this.parseGroup() };
348
+ // closest approximation
349
+ // ── Left/Right delimiters ──────────────────────────────────────────────
350
+ case "left": {
351
+ const delim = this.peek();
352
+ this.eat();
353
+ let compName = "Paren";
354
+ if (delim.t === "LBRACKET")
355
+ compName = "group";
356
+ else if (delim.t === "CMD" && delim.v === "langle")
357
+ compName = "AngleBracket";
358
+ else if (delim.t === "CMD" && delim.v === "lfloor") compName = "Floor";
359
+ else if (delim.t === "CMD" && delim.v === "lceil") compName = "Ceil";
360
+ else if (delim.t === "CMD" && delim.v === "lVert") compName = "Norm";
361
+ else if (delim.t === "CMD" && delim.v === "vert") compName = "Abs";
362
+ const children = this.parseUntilRight();
363
+ if (compName === "group") {
364
+ return {
365
+ k: "seq",
366
+ nodes: [{ k: "text", v: "[" }, ...children, { k: "text", v: "]" }]
367
+ };
368
+ }
369
+ return { k: "comp", name: compName, children };
370
+ }
371
+ case "right": {
372
+ this.eat();
373
+ return { k: "text", v: "" };
374
+ }
375
+ // consumed by left
376
+ // ── Brackets standalone ───────────────────────────────────────────────
377
+ case "langle": {
378
+ const children = this.parseSeq(["CMD"]);
379
+ return { k: "comp", name: "AngleBracket", children };
380
+ }
381
+ case "rangle":
382
+ return { k: "text", v: "" };
383
+ case "lfloor": {
384
+ const children = this.parseSeq(["CMD"]);
385
+ return { k: "comp", name: "Floor", children };
386
+ }
387
+ case "rfloor":
388
+ return { k: "text", v: "" };
389
+ case "lceil": {
390
+ const children = this.parseSeq(["CMD"]);
391
+ return { k: "comp", name: "Ceil", children };
392
+ }
393
+ case "rceil":
394
+ return { k: "text", v: "" };
395
+ // ── Text ──────────────────────────────────────────────────────────────
396
+ case "text":
397
+ case "textrm":
398
+ case "textit":
399
+ case "textbf":
400
+ case "mathrm":
401
+ case "mathit":
402
+ case "mathbf": {
403
+ const g = this.parseGroup();
404
+ return { k: "seq", nodes: g };
405
+ }
406
+ // ── Mathbb / Mathcal ──────────────────────────────────────────────────
407
+ case "mathbb": {
408
+ const g = this.parseGroup();
409
+ const letter = textOf(g).trim().toUpperCase();
410
+ const bbMap = {
411
+ N: "NN",
412
+ Z: "ZZ",
413
+ Q: "QQ",
414
+ R: "RR",
415
+ C: "CC",
416
+ P: "PP",
417
+ F: "FF",
418
+ E: "EulerE"
419
+ };
420
+ return { k: "comp", name: bbMap[letter] ?? `BB${letter}` };
421
+ }
422
+ case "mathcal": {
423
+ const g = this.parseGroup();
424
+ const letter = textOf(g).trim();
425
+ return { k: "comp", name: `Script${letter.toUpperCase()}` };
426
+ }
427
+ case "mathscr": {
428
+ const g = this.parseGroup();
429
+ const letter = textOf(g).trim();
430
+ return { k: "comp", name: `Script${letter.toUpperCase()}` };
431
+ }
432
+ case "ell":
433
+ return { k: "comp", name: "ScriptEll" };
434
+ // ── Greek letters ─────────────────────────────────────────────────────
435
+ case "alpha":
436
+ return { k: "comp", name: "Alpha" };
437
+ case "beta":
438
+ return { k: "comp", name: "Beta" };
439
+ case "gamma":
440
+ return { k: "comp", name: "Gamma" };
441
+ case "delta":
442
+ return { k: "comp", name: "GDelta" };
443
+ case "epsilon":
444
+ return { k: "comp", name: "Epsilon" };
445
+ case "varepsilon":
446
+ return { k: "comp", name: "Varepsilon" };
447
+ case "zeta":
448
+ return { k: "comp", name: "Zeta" };
449
+ case "eta":
450
+ return { k: "comp", name: "Eta" };
451
+ case "theta":
452
+ return { k: "comp", name: "Theta" };
453
+ case "vartheta":
454
+ return { k: "comp", name: "Vartheta" };
455
+ case "iota":
456
+ return { k: "comp", name: "Iota" };
457
+ case "kappa":
458
+ return { k: "comp", name: "Kappa" };
459
+ case "lambda":
460
+ return { k: "comp", name: "Lambda" };
461
+ case "mu":
462
+ return { k: "comp", name: "Mu" };
463
+ case "nu":
464
+ return { k: "comp", name: "Nu" };
465
+ case "xi":
466
+ return { k: "comp", name: "Xi" };
467
+ case "pi":
468
+ return { k: "comp", name: "PiSym" };
469
+ case "varpi":
470
+ return { k: "comp", name: "Varpi" };
471
+ case "rho":
472
+ return { k: "comp", name: "Rho" };
473
+ case "varrho":
474
+ return { k: "comp", name: "Varrho" };
475
+ case "sigma":
476
+ return { k: "comp", name: "SigmaSym" };
477
+ case "varsigma":
478
+ return { k: "comp", name: "Varsigma" };
479
+ case "tau":
480
+ return { k: "comp", name: "Tau" };
481
+ case "upsilon":
482
+ return { k: "comp", name: "Upsilon" };
483
+ case "phi":
484
+ return { k: "comp", name: "Phi" };
485
+ case "varphi":
486
+ return { k: "comp", name: "Varphi" };
487
+ case "chi":
488
+ return { k: "comp", name: "Chi" };
489
+ case "psi":
490
+ return { k: "comp", name: "Psi" };
491
+ case "omega":
492
+ return { k: "comp", name: "Omega" };
493
+ // uppercase
494
+ case "Gamma":
495
+ return { k: "comp", name: "GammaU" };
496
+ case "Delta":
497
+ return { k: "comp", name: "DeltaU" };
498
+ case "Theta":
499
+ return { k: "comp", name: "ThetaU" };
500
+ case "Lambda":
501
+ return { k: "comp", name: "LambdaU" };
502
+ case "Xi":
503
+ return { k: "comp", name: "XiU" };
504
+ case "Pi":
505
+ return { k: "comp", name: "PiU" };
506
+ case "Sigma":
507
+ return { k: "comp", name: "SigmaU" };
508
+ case "Phi":
509
+ return { k: "comp", name: "PhiU" };
510
+ case "Psi":
511
+ return { k: "comp", name: "PsiU" };
512
+ case "Omega":
513
+ return { k: "comp", name: "OmegaU" };
514
+ // ── Symbols ───────────────────────────────────────────────────────────
515
+ case "infty":
516
+ case "infinity":
517
+ return { k: "comp", name: "Inf" };
518
+ case "pm":
519
+ return { k: "comp", name: "PlusMinus" };
520
+ case "mp":
521
+ return { k: "comp", name: "MinusPlus" };
522
+ case "times":
523
+ return { k: "comp", name: "Times" };
524
+ case "div":
525
+ return { k: "comp", name: "Division" };
526
+ case "cdot":
527
+ return { k: "comp", name: "Dot" };
528
+ // Relations
529
+ case "leq":
530
+ case "le":
531
+ return { k: "comp", name: "Leq" };
532
+ case "geq":
533
+ case "ge":
534
+ return { k: "comp", name: "Geq" };
535
+ case "neq":
536
+ case "ne":
537
+ return { k: "comp", name: "Neq" };
538
+ case "approx":
539
+ return { k: "comp", name: "Approx" };
540
+ case "equiv":
541
+ return { k: "comp", name: "Equiv" };
542
+ case "cong":
543
+ return { k: "comp", name: "Cong" };
544
+ case "sim":
545
+ return { k: "comp", name: "Sim" };
546
+ case "ll":
547
+ return { k: "comp", name: "Ll" };
548
+ case "gg":
549
+ return { k: "comp", name: "Gg" };
550
+ case "propto":
551
+ return { k: "comp", name: "Propto" };
552
+ case "doteq":
553
+ return { k: "comp", name: "Approaches" };
554
+ case "triangleq":
555
+ return { k: "comp", name: "DefinedAs" };
556
+ // Sets
557
+ case "in":
558
+ return { k: "comp", name: "In" };
559
+ case "notin":
560
+ return { k: "comp", name: "NotIn" };
561
+ case "subset":
562
+ return { k: "comp", name: "Subset" };
563
+ case "subseteq":
564
+ return { k: "comp", name: "SubsetEq" };
565
+ case "subsetneq":
566
+ return { k: "comp", name: "ProperSubset" };
567
+ case "supset":
568
+ return { k: "comp", name: "Supset" };
569
+ case "supseteq":
570
+ return { k: "comp", name: "SupsetEq" };
571
+ case "cup":
572
+ return { k: "comp", name: "Union" };
573
+ case "cap":
574
+ return { k: "comp", name: "Intersect" };
575
+ case "bigcup":
576
+ return { k: "comp", name: "BigUnion" };
577
+ case "bigcap":
578
+ return { k: "comp", name: "BigIntersect" };
579
+ case "emptyset":
580
+ case "varnothing":
581
+ return { k: "comp", name: "Empty" };
582
+ case "setminus":
583
+ return { k: "comp", name: "SetMinus" };
584
+ // Logic
585
+ case "land":
586
+ case "wedge":
587
+ return { k: "comp", name: "And" };
588
+ case "lor":
589
+ case "vee":
590
+ return { k: "comp", name: "Or" };
591
+ case "lnot":
592
+ case "neg":
593
+ return { k: "comp", name: "Not" };
594
+ case "forall":
595
+ return { k: "comp", name: "ForAll" };
596
+ case "exists":
597
+ return { k: "comp", name: "Exists" };
598
+ case "nexists":
599
+ return { k: "comp", name: "NotExists" };
600
+ case "therefore":
601
+ return { k: "comp", name: "Therefore" };
602
+ case "because":
603
+ return { k: "comp", name: "Because" };
604
+ case "vdash":
605
+ return { k: "comp", name: "Turnstile" };
606
+ case "models":
607
+ return { k: "comp", name: "Models" };
608
+ case "top":
609
+ return { k: "comp", name: "Top" };
610
+ case "bot":
611
+ return { k: "comp", name: "Bot" };
612
+ case "oplus":
613
+ return { k: "comp", name: "DirectSum" };
614
+ case "otimes":
615
+ return { k: "comp", name: "OTimes" };
616
+ case "odot":
617
+ return { k: "comp", name: "Hadamard" };
618
+ // Arrows
619
+ case "to":
620
+ case "rightarrow":
621
+ return { k: "comp", name: "Arrow" };
622
+ case "leftarrow":
623
+ case "gets":
624
+ return { k: "comp", name: "LeftArrow" };
625
+ case "leftrightarrow":
626
+ return { k: "comp", name: "LeftRightArrow" };
627
+ case "Rightarrow":
628
+ case "implies":
629
+ return { k: "comp", name: "DoubleRightArrow" };
630
+ case "Leftarrow":
631
+ return { k: "comp", name: "DoubleLeftArrow" };
632
+ case "Leftrightarrow":
633
+ case "iff":
634
+ return { k: "comp", name: "DoubleLeftRightArrow" };
635
+ case "longrightarrow":
636
+ return { k: "comp", name: "LongRightArrow" };
637
+ case "longleftarrow":
638
+ return { k: "comp", name: "LongLeftArrow" };
639
+ case "longleftrightarrow":
640
+ return { k: "comp", name: "LongLeftRightArrow" };
641
+ case "mapsto":
642
+ return { k: "comp", name: "MapsTo" };
643
+ case "longmapsto":
644
+ return { k: "comp", name: "LongMapsTo" };
645
+ case "nearrow":
646
+ return { k: "comp", name: "NearArrow" };
647
+ case "searrow":
648
+ return { k: "comp", name: "SeArrow" };
649
+ case "uparrow":
650
+ return { k: "comp", name: "UpArrow" };
651
+ case "downarrow":
652
+ return { k: "comp", name: "DownArrow" };
653
+ case "updownarrow":
654
+ return { k: "comp", name: "UpDownArrow" };
655
+ case "rightharpoonup":
656
+ return { k: "comp", name: "RightHarpoonUp" };
657
+ case "leftharpoonup":
658
+ return { k: "comp", name: "LeftHarpoonUp" };
659
+ case "rightleftharpoons":
660
+ return { k: "comp", name: "EquilibriumArrow" };
661
+ case "hookleftarrow":
662
+ return { k: "comp", name: "HookLeftArrow" };
663
+ case "hookrightarrow":
664
+ return { k: "comp", name: "HookRightArrow" };
665
+ // Linear algebra
666
+ case "det":
667
+ return { k: "comp", name: "Det", children: this.parseMaybeArg() };
668
+ case "ker":
669
+ return { k: "comp", name: "Ker", children: this.parseMaybeArg() };
670
+ case "dim":
671
+ return { k: "comp", name: "Dim", children: this.parseMaybeArg() };
672
+ case "rank":
673
+ return { k: "comp", name: "Rank", children: this.parseMaybeArg() };
674
+ case "tr":
675
+ case "trace":
676
+ return { k: "comp", name: "Trace", children: this.parseMaybeArg() };
677
+ // Operator names
678
+ case "operatorname": {
679
+ const g = this.parseGroup();
680
+ const name = textOf(g).trim();
681
+ const opMap = {
682
+ span: "SpanOp",
683
+ rank: "Rank",
684
+ ker: "Ker",
685
+ dim: "Dim",
686
+ null: "NullOp",
687
+ img: "Img",
688
+ tr: "Trace",
689
+ det: "Det",
690
+ Re: "Re",
691
+ Im: "Im",
692
+ Res: "Res",
693
+ sgn: "Sgn",
694
+ arg: "Arg",
695
+ max: "Max",
696
+ min: "Min",
697
+ sup: "Sup",
698
+ inf: "Inf2",
699
+ gcd: "Gcd",
700
+ lcm: "Lcm",
701
+ ord: "Ord"
702
+ };
703
+ return {
704
+ k: "comp",
705
+ name: opMap[name] ?? name,
706
+ children: this.parseMaybeArg()
707
+ };
708
+ }
709
+ // Functions (trig etc.)
710
+ case "sin":
711
+ return { k: "comp", name: "Sin", children: this.parseMaybeArg() };
712
+ case "cos":
713
+ return { k: "comp", name: "Cos", children: this.parseMaybeArg() };
714
+ case "tan":
715
+ return { k: "comp", name: "Tan", children: this.parseMaybeArg() };
716
+ case "cot":
717
+ return { k: "comp", name: "Cot", children: this.parseMaybeArg() };
718
+ case "sec":
719
+ return { k: "comp", name: "Sec", children: this.parseMaybeArg() };
720
+ case "csc":
721
+ return { k: "comp", name: "Csc", children: this.parseMaybeArg() };
722
+ case "arcsin":
723
+ return { k: "comp", name: "ArcSin", children: this.parseMaybeArg() };
724
+ case "arccos":
725
+ return { k: "comp", name: "ArcCos", children: this.parseMaybeArg() };
726
+ case "arctan":
727
+ return { k: "comp", name: "ArcTan", children: this.parseMaybeArg() };
728
+ case "sinh":
729
+ return { k: "comp", name: "Sinh", children: this.parseMaybeArg() };
730
+ case "cosh":
731
+ return { k: "comp", name: "Cosh", children: this.parseMaybeArg() };
732
+ case "tanh":
733
+ return { k: "comp", name: "Tanh", children: this.parseMaybeArg() };
734
+ case "log":
735
+ return { k: "comp", name: "Log", children: this.parseMaybeArg() };
736
+ case "ln":
737
+ return { k: "comp", name: "Ln", children: this.parseMaybeArg() };
738
+ case "exp":
739
+ return { k: "comp", name: "Exp", children: this.parseMaybeArg() };
740
+ // Combinatorics
741
+ case "binom": {
742
+ const n = this.parseGroup();
743
+ const k = this.parseGroup();
744
+ return { k: "comp", name: "Choose", props: { n, k } };
745
+ }
746
+ case "pmod": {
747
+ const m = this.parseGroup();
748
+ return { k: "comp", name: "Mod", children: m };
749
+ }
750
+ // Probability / stats
751
+ case "Pr":
752
+ return { k: "comp", name: "Prob", children: this.parseMaybeArg() };
753
+ // Dots
754
+ case "cdots":
755
+ case "hdots":
756
+ return { k: "comp", name: "CDots" };
757
+ case "vdots":
758
+ return { k: "comp", name: "VDots" };
759
+ case "ddots":
760
+ return { k: "comp", name: "DDots" };
761
+ case "ldots":
762
+ case "dots":
763
+ return { k: "comp", name: "LDots" };
764
+ // Spacing — just emit a thin space
765
+ case ",":
766
+ case ";":
767
+ case ":":
768
+ case "!":
769
+ case " ":
770
+ case "quad":
771
+ case "qquad":
772
+ return { k: "text", v: " " };
773
+ // Misc
774
+ case "prime":
775
+ return { k: "comp", name: "Prime" };
776
+ case "circ":
777
+ return { k: "comp", name: "Compose" };
778
+ case "ast":
779
+ return { k: "comp", name: "Star" };
780
+ case "star":
781
+ return { k: "comp", name: "Star" };
782
+ case "dagger":
783
+ return { k: "comp", name: "Dagger" };
784
+ case "ddagger":
785
+ return { k: "comp", name: "DoubleDagger" };
786
+ case "aleph":
787
+ return { k: "comp", name: "Aleph" };
788
+ case "perp":
789
+ return { k: "comp", name: "Perpendicular" };
790
+ case "parallel":
791
+ return { k: "comp", name: "Parallel" };
792
+ case "angle":
793
+ return { k: "comp", name: "Angle" };
794
+ case "triangle":
795
+ return { k: "comp", name: "Triangle" };
796
+ case "square":
797
+ return { k: "comp", name: "Square" };
798
+ case "diamond":
799
+ return { k: "comp", name: "Diamond" };
800
+ case "not": {
801
+ const next = this.peek();
802
+ if (next.t === "CMD") {
803
+ this.eat();
804
+ const notMap = {
805
+ in: "NotIn",
806
+ subset: "NotSubset",
807
+ sim: "NotSim",
808
+ cong: "NotCong",
809
+ "=": "Neq",
810
+ equiv: "Neq"
811
+ };
812
+ return { k: "comp", name: notMap[next.v] ?? "Neq" };
813
+ }
814
+ return { k: "comp", name: "Not" };
815
+ }
816
+ case "qed":
817
+ case "blacksquare":
818
+ return { k: "comp", name: "QED" };
819
+ default:
820
+ return { k: "text", v: `\\${cmd}` };
821
+ }
822
+ }
823
+ // Parse _ immediately (without CARET) — for \lim_{...}
824
+ parseUnder() {
825
+ if (this.peek().t !== "UNDER") return null;
826
+ this.eat();
827
+ return this.parseGroup();
828
+ }
829
+ // Parse _lower ^upper (or ^upper _lower) — for \int, \sum
830
+ parseSubSup() {
831
+ let from = null;
832
+ let to = null;
833
+ for (let i = 0; i < 2; i++) {
834
+ if (this.peek().t === "UNDER" && !from) {
835
+ this.eat();
836
+ from = this.parseGroup();
837
+ } else if (this.peek().t === "CARET" && !to) {
838
+ this.eat();
839
+ to = this.parseGroup();
840
+ } else break;
841
+ }
842
+ return { from, to };
843
+ }
844
+ // Parse content until \right
845
+ parseUntilRight() {
846
+ const nodes = [];
847
+ while (true) {
848
+ const tok = this.peek();
849
+ if (tok.t === "EOF") break;
850
+ if (tok.t === "CMD" && tok.v === "right") {
851
+ this.eat();
852
+ this.eat();
853
+ break;
854
+ }
855
+ const n = this.parseAtom();
856
+ if (n) nodes.push(n);
857
+ }
858
+ return nodes;
859
+ }
860
+ // Optionally parse a following group (for function args like \sin{x})
861
+ parseMaybeArg() {
862
+ if (this.peek().t === "LBRACE") return this.parseGroup();
863
+ return void 0;
864
+ }
865
+ }
866
+ function buildBounds(from, to) {
867
+ const props = {};
868
+ if (from) props["from"] = from;
869
+ if (to) props["to"] = to;
870
+ return Object.keys(props).length ? props : void 0;
871
+ }
872
+ function isPartial(nodes) {
873
+ return nodes.some(
874
+ (n) => n.k === "comp" && n.name === "PartialDiff" || n.k === "text" && n.v === "\u2202"
875
+ );
876
+ }
877
+ function extractAfterPartial(nodes) {
878
+ const idx = nodes.findIndex(
879
+ (n) => n.k === "comp" && n.name === "PartialDiff" || n.k === "text" && n.v === "\u2202"
880
+ );
881
+ if (idx === -1) return nodes;
882
+ return nodes.slice(idx + 1).filter((n) => !(n.k === "text" && n.v === " "));
883
+ }
884
+ function latexToPrimitives(latex, block = false) {
885
+ try {
886
+ const tokens = tokenize(latex.trim());
887
+ const parser = new Parser(tokens);
888
+ const nodes = parser.parseSeq(["EOF"]);
889
+ const jsx = emitNodes(nodes).trim();
890
+ if (block) return `<Equation>
891
+ ${jsx}
892
+ </Equation>`;
893
+ return jsx;
894
+ } catch {
895
+ return `\`${latex}\``;
896
+ }
897
+ }
898
+ function convertMarkdownMath(mdx) {
899
+ let result = mdx.replace(
900
+ /\$\$([^$]+)\$\$/gs,
901
+ (_, latex) => latexToPrimitives(latex.trim(), true)
902
+ );
903
+ result = result.replace(
904
+ /(?<!\$)\$(?!\$)([^$\n]+)\$(?!\$)/g,
905
+ (_, latex) => latexToPrimitives(latex.trim(), false)
906
+ );
907
+ return result;
908
+ }
909
+ function hasLatex(content) {
910
+ return /\$/.test(content) || /\\(?:frac|int|sum|prod|sqrt|alpha|beta|gamma|infty)\b/.test(content);
911
+ }
912
+ function convertInlineMath(expr) {
913
+ let s = expr.trim();
914
+ s = s.replace(/⇒/g, "<DoubleRightArrow />");
915
+ s = s.replace(/→/g, "<Arrow />");
916
+ s = s.replace(/±/g, "<PlusMinus />");
917
+ s = s.replace(/∓/g, "<MinusPlus />");
918
+ s = s.replace(/×/g, "<Times />");
919
+ s = s.replace(/÷/g, "<Division />");
920
+ s = s.replace(/≤/g, "<Leq />");
921
+ s = s.replace(/≥/g, "<Geq />");
922
+ s = s.replace(/≠/g, "<Neq />");
923
+ s = s.replace(/≈/g, "<Approx />");
924
+ s = s.replace(/∞/g, "<Inf />");
925
+ s = s.replace(/∈/g, "<In />");
926
+ s = s.replace(/∀/g, "<ForAll />");
927
+ s = s.replace(/∃/g, "<Exists />");
928
+ s = s.replace(/∴/g, "<Therefore />");
929
+ s = s.replace(/∵/g, "<Because />");
930
+ s = s.replace(/√/g, () => "<Sqrt>");
931
+ s = s.replace(
932
+ /(\([^)]+\)|[0-9]+[a-zA-Z]*[0-9]*|[a-zA-Z][0-9a-zA-Z]*)\/(\([^)]+\)|[0-9]+[a-zA-Z]*[0-9]*|[a-zA-Z][0-9a-zA-Z]*)/g,
933
+ (_, num, den) => `<Frac num="${num}" den="${den}" />`
934
+ );
935
+ s = s.replace(
936
+ /([a-zA-Z0-9]+)\^([0-9]+|[a-zA-Z])/g,
937
+ (_, base, exp) => `<Pow exp="${exp}">${base}</Pow>`
938
+ );
939
+ return s;
940
+ }
941
+ function extractReason(step) {
942
+ const match = step.match(/^([\s\S]*?)\s*\(([^)]+)\)\s*$/);
943
+ if (match) return { expr: match[1].trim(), reason: match[2].trim() };
944
+ return { expr: step.trim(), reason: null };
945
+ }
946
+ function isFinalAnswer(step) {
947
+ const s = step.trim();
948
+ return /^[a-zA-Z]\s*=\s*/.test(s) && s.split("=").length === 2 && s.length < 40;
949
+ }
950
+ function parseSolutionText(text) {
951
+ const lines = text.split(/\n/).map((l) => l.trim()).filter(Boolean);
952
+ const solutionIdx = lines.findIndex(
953
+ (l) => /^solution[:\s]/i.test(l) || l.toLowerCase() === "solution"
954
+ );
955
+ const titleLine = solutionIdx > 0 ? lines.slice(0, solutionIdx).join(" ") : lines[0] ?? "Solve";
956
+ const title = `Solve: ${titleLine.replace(/^(solve\s*[::]?\s*)/i, "").trim()}`;
957
+ const stepLines = solutionIdx >= 0 ? lines.slice(solutionIdx + 1) : lines.slice(1);
958
+ const rawSteps = [];
959
+ for (const line of stepLines) {
960
+ if (/^[⇒=]>/.test(line) || line.startsWith("\u21D2") || line.startsWith("=>")) {
961
+ rawSteps.push(line.replace(/^[⇒=>\s]+/, "").trim());
962
+ } else if (line.includes("\u21D2")) {
963
+ const parts = line.split(/\s*⇒\s*/);
964
+ rawSteps.push(...parts.filter(Boolean).map((p) => p.trim()));
965
+ } else if (rawSteps.length > 0) {
966
+ rawSteps[rawSteps.length - 1] += " " + line;
967
+ } else {
968
+ rawSteps.push(line);
969
+ }
970
+ }
971
+ const validSteps = rawSteps.filter((s) => s.length > 0);
972
+ const steps = [];
973
+ let answer = "";
974
+ for (let i = 0; i < validSteps.length; i++) {
975
+ const { expr, reason } = extractReason(validSteps[i]);
976
+ const isLast = i === validSteps.length - 1;
977
+ if (isLast && isFinalAnswer(expr)) {
978
+ answer = expr;
979
+ } else {
980
+ const highlight = reason ? /cross.?multipl|key|main|therefore|hence|substitut/i.test(reason) : false;
981
+ steps.push({ expr, reason, highlight });
982
+ }
983
+ }
984
+ if (!answer && steps.length > 0) {
985
+ const last = steps.pop();
986
+ answer = last.expr;
987
+ }
988
+ return { title, steps, answer };
989
+ }
990
+ function solutionToMdx(parsed, convertMath = true) {
991
+ const cvt = convertMath ? convertInlineMath : (s) => s;
992
+ const stepLines = parsed.steps.map((s) => {
993
+ const reason = s.reason ? ` reason="${s.reason}"` : "";
994
+ const highlight = s.highlight ? " highlight" : "";
995
+ return ` <SolutionStep${reason}${highlight}>
996
+ ${cvt(s.expr)}
997
+ </SolutionStep>`;
998
+ });
999
+ const answerLine = ` <SolutionAnswer>
1000
+ ${cvt(parsed.answer)}
1001
+ </SolutionAnswer>`;
1002
+ return [
1003
+ `<Solution title="${parsed.title}">`,
1004
+ ...stepLines,
1005
+ answerLine,
1006
+ `</Solution>`
1007
+ ].join("\n");
1008
+ }
1009
+ function parseSolution(text) {
1010
+ return solutionToMdx(parseSolutionText(text));
1011
+ }
1012
+ export {
1013
+ convertMarkdownMath,
1014
+ hasLatex,
1015
+ latexToPrimitives,
1016
+ parseSolution,
1017
+ parseSolutionText,
1018
+ solutionToMdx
1019
+ };