@sciexpr/parser 0.1.0

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/dist/index.cjs ADDED
@@ -0,0 +1,562 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ChemicalParser: () => ChemicalParser,
24
+ LatexParser: () => LatexParser,
25
+ LatexTokenizer: () => LatexTokenizer,
26
+ ParserRegistry: () => ParserRegistry,
27
+ TokenType: () => TokenType
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/types.ts
32
+ var TokenType = /* @__PURE__ */ ((TokenType2) => {
33
+ TokenType2["Command"] = "command";
34
+ TokenType2["OpenBrace"] = "openBrace";
35
+ TokenType2["CloseBrace"] = "closeBrace";
36
+ TokenType2["OpenBracket"] = "openBracket";
37
+ TokenType2["CloseBracket"] = "closeBracket";
38
+ TokenType2["OpenParen"] = "openParen";
39
+ TokenType2["CloseParen"] = "closeParen";
40
+ TokenType2["Superscript"] = "superscript";
41
+ TokenType2["Subscript"] = "subscript";
42
+ TokenType2["Whitespace"] = "whitespace";
43
+ TokenType2["Text"] = "text";
44
+ TokenType2["Number"] = "number";
45
+ TokenType2["Ampersand"] = "ampersand";
46
+ TokenType2["Newline"] = "newline";
47
+ TokenType2["Dollar"] = "dollar";
48
+ TokenType2["DoubleDollar"] = "doubleDollar";
49
+ TokenType2["EOF"] = "eof";
50
+ return TokenType2;
51
+ })(TokenType || {});
52
+
53
+ // src/tokenizer.ts
54
+ var LatexTokenizer = class {
55
+ input;
56
+ pos = 0;
57
+ constructor(input) {
58
+ this.input = input;
59
+ }
60
+ /** 获取所有 token */
61
+ tokenize() {
62
+ const tokens = [];
63
+ while (this.pos < this.input.length) {
64
+ const token = this.nextToken();
65
+ if (token) tokens.push(token);
66
+ }
67
+ tokens.push({
68
+ type: "eof" /* EOF */,
69
+ value: "",
70
+ position: this.pos,
71
+ length: 0
72
+ });
73
+ return tokens;
74
+ }
75
+ /** 获取下一个 token */
76
+ nextToken() {
77
+ const ch = this.input[this.pos];
78
+ if (ch === void 0) return null;
79
+ const start = this.pos;
80
+ if (ch === " " || ch === " ") {
81
+ this.pos++;
82
+ while (this.pos < this.input.length && (this.input[this.pos] === " " || this.input[this.pos] === " ")) {
83
+ this.pos++;
84
+ }
85
+ return { type: "whitespace" /* Whitespace */, value: this.input.slice(start, this.pos), position: start, length: this.pos - start };
86
+ }
87
+ if (ch === "$" && this.input[this.pos + 1] === "$") {
88
+ this.pos += 2;
89
+ return { type: "doubleDollar" /* DoubleDollar */, value: "$$", position: start, length: 2 };
90
+ }
91
+ if (ch === "$") {
92
+ this.pos++;
93
+ return { type: "dollar" /* Dollar */, value: "$", position: start, length: 1 };
94
+ }
95
+ if (ch === "\\") {
96
+ this.pos++;
97
+ if (this.pos < this.input.length && /[a-zA-Z]/.test(this.input[this.pos])) {
98
+ while (this.pos < this.input.length && /[a-zA-Z*]/.test(this.input[this.pos])) {
99
+ this.pos++;
100
+ }
101
+ return { type: "command" /* Command */, value: this.input.slice(start, this.pos), position: start, length: this.pos - start };
102
+ }
103
+ this.pos++;
104
+ return { type: "command" /* Command */, value: this.input.slice(start, this.pos), position: start, length: this.pos - start };
105
+ }
106
+ if (ch === "{") {
107
+ this.pos++;
108
+ return { type: "openBrace" /* OpenBrace */, value: "{", position: start, length: 1 };
109
+ }
110
+ if (ch === "}") {
111
+ this.pos++;
112
+ return { type: "closeBrace" /* CloseBrace */, value: "}", position: start, length: 1 };
113
+ }
114
+ if (ch === "[") {
115
+ this.pos++;
116
+ return { type: "openBracket" /* OpenBracket */, value: "[", position: start, length: 1 };
117
+ }
118
+ if (ch === "]") {
119
+ this.pos++;
120
+ return { type: "closeBracket" /* CloseBracket */, value: "]", position: start, length: 1 };
121
+ }
122
+ if (ch === "(") {
123
+ this.pos++;
124
+ return { type: "openParen" /* OpenParen */, value: "(", position: start, length: 1 };
125
+ }
126
+ if (ch === ")") {
127
+ this.pos++;
128
+ return { type: "closeParen" /* CloseParen */, value: ")", position: start, length: 1 };
129
+ }
130
+ if (ch === "^") {
131
+ this.pos++;
132
+ return { type: "superscript" /* Superscript */, value: "^", position: start, length: 1 };
133
+ }
134
+ if (ch === "_") {
135
+ this.pos++;
136
+ return { type: "subscript" /* Subscript */, value: "_", position: start, length: 1 };
137
+ }
138
+ if (ch === "&") {
139
+ this.pos++;
140
+ return { type: "ampersand" /* Ampersand */, value: "&", position: start, length: 1 };
141
+ }
142
+ if (ch === "\\" && this.input[this.pos + 1] === "\\") {
143
+ }
144
+ if (/[0-9]/.test(ch)) {
145
+ this.pos++;
146
+ while (this.pos < this.input.length && /[0-9.]/.test(this.input[this.pos])) {
147
+ this.pos++;
148
+ }
149
+ return { type: "number" /* Number */, value: this.input.slice(start, this.pos), position: start, length: this.pos - start };
150
+ }
151
+ this.pos++;
152
+ while (this.pos < this.input.length && /[a-zA-Z0-9]/.test(this.input[this.pos])) {
153
+ this.pos++;
154
+ }
155
+ return { type: "text" /* Text */, value: this.input.slice(start, this.pos), position: start, length: this.pos - start };
156
+ }
157
+ };
158
+
159
+ // src/latex-parser.ts
160
+ var import_core = require("@sciexpr/core");
161
+ var import_core2 = require("@sciexpr/core");
162
+ var LatexParser = class {
163
+ id = "latex";
164
+ formats = ["latex", "tex"];
165
+ tokens = [];
166
+ pos = 0;
167
+ displayMode = false;
168
+ canParse(input) {
169
+ return /\\[a-zA-Z]+/.test(input) || /[\^_]/.test(input) || /[{}]/.test(input);
170
+ }
171
+ parse(input, options) {
172
+ this.displayMode = options?.displayMode ?? false;
173
+ const tokenizer = new LatexTokenizer(input);
174
+ this.tokens = tokenizer.tokenize();
175
+ this.pos = 0;
176
+ const children = this.parseExpressions();
177
+ return (0, import_core2.createRoot)(import_core.ExpressionKind.Math, children, {
178
+ displayMode: this.displayMode,
179
+ originalSource: input
180
+ });
181
+ }
182
+ // ==========================================================================
183
+ // Parse Expressions
184
+ // ==========================================================================
185
+ parseExpressions() {
186
+ const exprs = [];
187
+ while (!this.isEOF()) {
188
+ const expr = this.parseExpression();
189
+ if (expr) exprs.push(expr);
190
+ }
191
+ return exprs;
192
+ }
193
+ parseExpression() {
194
+ const token = this.peek();
195
+ if (!token) return null;
196
+ let expr = null;
197
+ switch (token.type) {
198
+ case "command" /* Command */:
199
+ expr = this.parseCommand();
200
+ break;
201
+ case "openBrace" /* OpenBrace */:
202
+ expr = this.parseGroup();
203
+ break;
204
+ case "closeBrace" /* CloseBrace */:
205
+ this.advance();
206
+ return null;
207
+ case "superscript" /* Superscript */:
208
+ case "subscript" /* Subscript */: {
209
+ this.advance();
210
+ const next = this.parseExpression();
211
+ if (next) {
212
+ expr = (0, import_core2.createScript)((0, import_core2.createText)(""), {
213
+ super: token.type === "superscript" /* Superscript */ ? next : void 0,
214
+ sub: token.type === "subscript" /* Subscript */ ? next : void 0
215
+ });
216
+ }
217
+ break;
218
+ }
219
+ case "text" /* Text */:
220
+ this.advance();
221
+ expr = (0, import_core2.createSymbol)(token.value, this.classifySymbol(token.value));
222
+ break;
223
+ case "number" /* Number */:
224
+ this.advance();
225
+ expr = (0, import_core2.createSymbol)(token.value, import_core.SymbolType.Number);
226
+ break;
227
+ case "whitespace" /* Whitespace */:
228
+ this.advance();
229
+ return null;
230
+ // Skip whitespace
231
+ case "openParen" /* OpenParen */:
232
+ this.advance();
233
+ const body = this.parseExpressions();
234
+ this.skipType("closeParen" /* CloseParen */);
235
+ expr = (0, import_core2.createDelimiter)("(", ")", body.length === 1 ? body[0] : (0, import_core2.createGroup)(body));
236
+ break;
237
+ case "openBracket" /* OpenBracket */:
238
+ this.advance();
239
+ const bracketBody = this.parseExpressions();
240
+ this.skipType("closeBracket" /* CloseBracket */);
241
+ expr = (0, import_core2.createDelimiter)("[", "]", bracketBody.length === 1 ? bracketBody[0] : (0, import_core2.createGroup)(bracketBody));
242
+ break;
243
+ case "eof" /* EOF */:
244
+ return null;
245
+ default:
246
+ this.advance();
247
+ expr = (0, import_core2.createText)(token.value);
248
+ break;
249
+ }
250
+ if (expr) {
251
+ expr = this.handleScripts(expr);
252
+ }
253
+ return expr;
254
+ }
255
+ // ==========================================================================
256
+ // Script Handling (shared)
257
+ // ==========================================================================
258
+ /** 处理上/下标:检查 ^ 和 _ 后附加到 base 节点 */
259
+ handleScripts(base) {
260
+ let result = base;
261
+ while (this.peek()?.type === "subscript" /* Subscript */ || this.peek()?.type === "superscript" /* Superscript */) {
262
+ const scriptType = this.advance().type;
263
+ const scriptContent = this.parseScriptContent();
264
+ if (scriptType === "subscript" /* Subscript */) {
265
+ result = (0, import_core2.createScript)(result, { sub: scriptContent });
266
+ } else {
267
+ result = (0, import_core2.createScript)(result, { super: scriptContent });
268
+ }
269
+ }
270
+ return result;
271
+ }
272
+ // ==========================================================================
273
+ // Parse Commands
274
+ // ==========================================================================
275
+ parseCommand() {
276
+ const token = this.advance();
277
+ const command = token.value;
278
+ if (command === "\\frac") {
279
+ const num = this.parseBraceGroup();
280
+ const den = this.parseBraceGroup();
281
+ return this.handleScripts((0, import_core2.createFraction)(num, den));
282
+ }
283
+ if (command === "\\sqrt") {
284
+ let index;
285
+ if (this.peek()?.type === "openBracket" /* OpenBracket */) {
286
+ this.advance();
287
+ index = this.parseSingleExpression();
288
+ this.skipType("closeBracket" /* CloseBracket */);
289
+ }
290
+ const radicand = this.parseBraceGroup();
291
+ return this.handleScripts((0, import_core2.createRadical)(radicand, index));
292
+ }
293
+ const greekLower = {
294
+ "\\alpha": "\u03B1",
295
+ "\\beta": "\u03B2",
296
+ "\\gamma": "\u03B3",
297
+ "\\delta": "\u03B4",
298
+ "\\epsilon": "\u03B5",
299
+ "\\zeta": "\u03B6",
300
+ "\\eta": "\u03B7",
301
+ "\\theta": "\u03B8",
302
+ "\\iota": "\u03B9",
303
+ "\\kappa": "\u03BA",
304
+ "\\lambda": "\u03BB",
305
+ "\\mu": "\u03BC",
306
+ "\\pi": "\u03C0",
307
+ "\\sigma": "\u03C3",
308
+ "\\tau": "\u03C4",
309
+ "\\phi": "\u03C6",
310
+ "\\omega": "\u03C9",
311
+ "\\xi": "\u03BE",
312
+ "\\rho": "\u03C1",
313
+ "\\psi": "\u03C8"
314
+ };
315
+ if (command in greekLower) {
316
+ return this.handleScripts((0, import_core2.createSymbol)(command, import_core.SymbolType.Greek));
317
+ }
318
+ if (command === "\\text") {
319
+ const content = this.parseBraceGroup();
320
+ if (content.kind === "group") {
321
+ const group = content;
322
+ const textContent = group.children.map((c) => {
323
+ if (c.kind === "symbol") return c.value;
324
+ return "";
325
+ }).join("");
326
+ return this.handleScripts((0, import_core2.createText)(textContent));
327
+ }
328
+ return this.handleScripts(content);
329
+ }
330
+ const specialSymbols = ["\\infty", "\\partial", "\\nabla", "\\forall", "\\exists", "\\emptyset", "\\neg"];
331
+ if (specialSymbols.includes(command)) {
332
+ return this.handleScripts((0, import_core2.createSymbol)(command, import_core.SymbolType.Operator));
333
+ }
334
+ return this.handleScripts((0, import_core2.createSymbol)(command, import_core.SymbolType.Ordinary));
335
+ }
336
+ // ==========================================================================
337
+ // Helpers
338
+ // ==========================================================================
339
+ /** 解析 {} 包裹的分组 */
340
+ parseBraceGroup() {
341
+ if (this.peek()?.type === "openBrace" /* OpenBrace */) {
342
+ return this.parseGroup();
343
+ }
344
+ return this.parseSingleExpression();
345
+ }
346
+ /** 解析 { expressions } 分组 */
347
+ parseGroup() {
348
+ this.skipType("openBrace" /* OpenBrace */);
349
+ const children = this.parseExpressions();
350
+ this.skipType("closeBrace" /* CloseBrace */);
351
+ return (0, import_core2.createGroup)(children);
352
+ }
353
+ /** 解析单个表达式(用于上/下标参数) */
354
+ parseSingleExpression() {
355
+ const children = this.parseExpressions();
356
+ return children.length === 1 ? children[0] : (0, import_core2.createGroup)(children);
357
+ }
358
+ /** 解析上/下标内容 */
359
+ parseScriptContent() {
360
+ if (this.peek()?.type === "openBrace" /* OpenBrace */) {
361
+ return this.parseGroup();
362
+ }
363
+ const expr = this.parseExpression();
364
+ return expr ?? (0, import_core2.createText)("");
365
+ }
366
+ // ==========================================================================
367
+ // Token Stream Helpers
368
+ // ==========================================================================
369
+ peek() {
370
+ return this.tokens[this.pos];
371
+ }
372
+ advance() {
373
+ const token = this.tokens[this.pos];
374
+ this.pos++;
375
+ return token;
376
+ }
377
+ skipType(type) {
378
+ if (this.peek()?.type === type) {
379
+ this.advance();
380
+ }
381
+ }
382
+ isEOF() {
383
+ return this.pos >= this.tokens.length || this.peek()?.type === "eof" /* EOF */;
384
+ }
385
+ /** 根据文本内容判断符号类型 */
386
+ classifySymbol(text) {
387
+ if (/^[a-zA-Z]$/.test(text)) return import_core.SymbolType.Variable;
388
+ if (/^[0-9.]+$/.test(text)) return import_core.SymbolType.Number;
389
+ if (/^[+\-*/=<>|]$/.test(text)) return import_core.SymbolType.Operator;
390
+ return import_core.SymbolType.Ordinary;
391
+ }
392
+ };
393
+
394
+ // src/chem-parser.ts
395
+ var import_core3 = require("@sciexpr/core");
396
+ var import_core4 = require("@sciexpr/core");
397
+ var ChemicalParser = class {
398
+ id = "chemical";
399
+ formats = ["chemical", "chem"];
400
+ canParse(input) {
401
+ return /^[A-Z][a-z]?\d*/.test(input) || /[A-Z][a-z]?[A-Z]/.test(input);
402
+ }
403
+ parse(input, _options) {
404
+ const elements = this.parseFormula(input);
405
+ return (0, import_core4.createRoot)(import_core3.ExpressionKind.Molecule, elements, {
406
+ originalSource: input
407
+ });
408
+ }
409
+ /**
410
+ * 解析化学式,如 "H2SO4" → [H, 2, S, O, 4]
411
+ */
412
+ parseFormula(formula) {
413
+ const nodes = [];
414
+ let i = 0;
415
+ while (i < formula.length) {
416
+ const ch = formula[i];
417
+ if (/[A-Z]/.test(ch)) {
418
+ let symbol = ch;
419
+ i++;
420
+ while (i < formula.length && /[a-z]/.test(formula[i])) {
421
+ symbol += formula[i];
422
+ i++;
423
+ }
424
+ let count;
425
+ if (i < formula.length && /[0-9]/.test(formula[i])) {
426
+ let numStr = "";
427
+ while (i < formula.length && /[0-9]/.test(formula[i])) {
428
+ numStr += formula[i];
429
+ i++;
430
+ }
431
+ count = parseInt(numStr, 10);
432
+ }
433
+ nodes.push((0, import_core4.createChemElement)(symbol, count ? { count } : void 0));
434
+ continue;
435
+ }
436
+ if (ch === "(") {
437
+ i++;
438
+ const groupStart = i;
439
+ let depth = 1;
440
+ while (i < formula.length && depth > 0) {
441
+ if (formula[i] === "(") depth++;
442
+ else if (formula[i] === ")") depth--;
443
+ i++;
444
+ }
445
+ const groupContent = formula.slice(groupStart, i - 1);
446
+ const groupAtoms = this.parseFormula(groupContent);
447
+ let groupCount;
448
+ if (i < formula.length && /[0-9]/.test(formula[i])) {
449
+ let numStr = "";
450
+ while (i < formula.length && /[0-9]/.test(formula[i])) {
451
+ numStr += formula[i];
452
+ i++;
453
+ }
454
+ groupCount = parseInt(numStr, 10);
455
+ if (groupCount && groupCount > 1) {
456
+ for (const atom of groupAtoms) {
457
+ atom.count = (atom.count ?? 1) * groupCount;
458
+ }
459
+ }
460
+ }
461
+ nodes.push((0, import_core4.createChemGroup)(groupAtoms));
462
+ continue;
463
+ }
464
+ if (ch === "^") {
465
+ i++;
466
+ let chargeStr = "";
467
+ while (i < formula.length && /[0-9+\-]/.test(formula[i])) {
468
+ chargeStr += formula[i];
469
+ i++;
470
+ }
471
+ const lastNode = nodes[nodes.length - 1];
472
+ if (lastNode && lastNode.kind === "chem-element") {
473
+ const chargeMatch = chargeStr.match(/^(\d*)([+\-])$/);
474
+ if (chargeMatch) {
475
+ const el = lastNode;
476
+ el.charge = parseInt(chargeMatch[1] || "1", 10);
477
+ el.chargeSign = chargeMatch[2];
478
+ }
479
+ }
480
+ continue;
481
+ }
482
+ if (ch === "=" || ch === "-" || ch === "<") {
483
+ i++;
484
+ continue;
485
+ }
486
+ if (ch === " " || ch === "+" || ch === ",") {
487
+ i++;
488
+ continue;
489
+ }
490
+ nodes.push((0, import_core4.createSymbol)(ch, import_core4.SymbolType.Ordinary));
491
+ i++;
492
+ }
493
+ return nodes;
494
+ }
495
+ };
496
+
497
+ // src/registry.ts
498
+ var ParserRegistry = class {
499
+ parsers = /* @__PURE__ */ new Map();
500
+ defaultParsers;
501
+ constructor() {
502
+ const latex = new LatexParser();
503
+ const chemical = new ChemicalParser();
504
+ this.defaultParsers = [latex, chemical];
505
+ this.register(latex);
506
+ this.register(chemical);
507
+ }
508
+ /** 注册自定义解析器 */
509
+ register(parser) {
510
+ this.parsers.set(parser.id, parser);
511
+ }
512
+ /** 移除解析器 */
513
+ unregister(id) {
514
+ return this.parsers.delete(id);
515
+ }
516
+ /** 根据 ID 获取解析器 */
517
+ get(id) {
518
+ return this.parsers.get(id);
519
+ }
520
+ /**
521
+ * 自动检测并选择合适的解析器
522
+ */
523
+ find(input) {
524
+ for (const parser of this.parsers.values()) {
525
+ if (parser.canParse(input)) {
526
+ return parser;
527
+ }
528
+ }
529
+ return null;
530
+ }
531
+ /**
532
+ * 解析输入
533
+ * @param input 输入文本
534
+ * @param options 解析选项
535
+ * @returns 解析后的 AST
536
+ */
537
+ parse(input, options) {
538
+ if (options?.hint && options.hint !== "auto") {
539
+ const parser2 = this.parsers.get(options.hint);
540
+ if (parser2) {
541
+ return parser2.parse(input, options);
542
+ }
543
+ }
544
+ const parser = this.find(input);
545
+ if (parser) {
546
+ return parser.parse(input, options);
547
+ }
548
+ return this.defaultParsers[0].parse(input, options);
549
+ }
550
+ /** 列出所有已注册的解析器 ID */
551
+ list() {
552
+ return Array.from(this.parsers.keys());
553
+ }
554
+ };
555
+ // Annotate the CommonJS export names for ESM import in node:
556
+ 0 && (module.exports = {
557
+ ChemicalParser,
558
+ LatexParser,
559
+ LatexTokenizer,
560
+ ParserRegistry,
561
+ TokenType
562
+ });