@sciexpr/core 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.js ADDED
@@ -0,0 +1,517 @@
1
+ // src/types/ast.ts
2
+ var ExpressionKind = /* @__PURE__ */ ((ExpressionKind2) => {
3
+ ExpressionKind2["Math"] = "math";
4
+ ExpressionKind2["Molecule"] = "molecule";
5
+ ExpressionKind2["ChemicalEquation"] = "chemical-equation";
6
+ ExpressionKind2["Matrix"] = "matrix";
7
+ ExpressionKind2["Text"] = "text";
8
+ ExpressionKind2["Mixed"] = "mixed";
9
+ return ExpressionKind2;
10
+ })(ExpressionKind || {});
11
+ var SymbolType = /* @__PURE__ */ ((SymbolType2) => {
12
+ SymbolType2["Greek"] = "greek";
13
+ SymbolType2["Operator"] = "operator";
14
+ SymbolType2["Relation"] = "relation";
15
+ SymbolType2["Arrow"] = "arrow";
16
+ SymbolType2["Accent"] = "accent";
17
+ SymbolType2["Ordinary"] = "ordinary";
18
+ SymbolType2["Variable"] = "variable";
19
+ SymbolType2["Number"] = "number";
20
+ return SymbolType2;
21
+ })(SymbolType || {});
22
+
23
+ // src/types/validator.ts
24
+ var ValidationErrorCode = /* @__PURE__ */ ((ValidationErrorCode2) => {
25
+ ValidationErrorCode2["UNEXPECTED_NODE"] = "UNEXPECTED_NODE";
26
+ ValidationErrorCode2["EMPTY_EXPRESSION"] = "EMPTY_EXPRESSION";
27
+ ValidationErrorCode2["UNBALANCED_BRACE"] = "UNBALANCED_BRACE";
28
+ ValidationErrorCode2["UNBALANCED_BRACKET"] = "UNBALANCED_BRACKET";
29
+ ValidationErrorCode2["INVALID_SCRIPT"] = "INVALID_SCRIPT";
30
+ ValidationErrorCode2["DIVISION_BY_ZERO"] = "DIVISION_BY_ZERO";
31
+ ValidationErrorCode2["MATRIX_DIMENSION_MISMATCH"] = "MATRIX_DIMENSION_MISMATCH";
32
+ ValidationErrorCode2["NEGATIVE_RADICAL"] = "NEGATIVE_RADICAL";
33
+ ValidationErrorCode2["INVALID_ELEMENT_SYMBOL"] = "INVALID_ELEMENT_SYMBOL";
34
+ ValidationErrorCode2["INVALID_CHEMICAL_FORMULA"] = "INVALID_CHEMICAL_FORMULA";
35
+ ValidationErrorCode2["CHARGE_IMBALANCE"] = "CHARGE_IMBALANCE";
36
+ ValidationErrorCode2["ELEMENT_IMBALANCE"] = "ELEMENT_IMBALANCE";
37
+ ValidationErrorCode2["INVALID_BOND"] = "INVALID_BOND";
38
+ return ValidationErrorCode2;
39
+ })(ValidationErrorCode || {});
40
+
41
+ // src/node-factory.ts
42
+ var _nodeIdCounter = 0;
43
+ function generateNodeId() {
44
+ return `n${++_nodeIdCounter}`;
45
+ }
46
+ function resetNodeIdCounter() {
47
+ _nodeIdCounter = 0;
48
+ }
49
+ function createRoot(type, children, metadata) {
50
+ return {
51
+ kind: "root",
52
+ id: generateNodeId(),
53
+ type,
54
+ children,
55
+ metadata
56
+ };
57
+ }
58
+ function createSymbol(value, symbolType) {
59
+ return {
60
+ kind: "symbol",
61
+ id: generateNodeId(),
62
+ value,
63
+ symbolType
64
+ };
65
+ }
66
+ function createFraction(numerator, denominator) {
67
+ return {
68
+ kind: "frac",
69
+ id: generateNodeId(),
70
+ numerator,
71
+ denominator
72
+ };
73
+ }
74
+ function createScript(base, options) {
75
+ return {
76
+ kind: "script",
77
+ id: generateNodeId(),
78
+ base,
79
+ ...options
80
+ };
81
+ }
82
+ function createRadical(radicand, index) {
83
+ return {
84
+ kind: "radical",
85
+ id: generateNodeId(),
86
+ radicand,
87
+ index
88
+ };
89
+ }
90
+ function createDelimiter(left, right, body) {
91
+ return {
92
+ kind: "delimiter",
93
+ id: generateNodeId(),
94
+ left,
95
+ right,
96
+ body
97
+ };
98
+ }
99
+ function createMatrix(rows, options) {
100
+ return {
101
+ kind: "matrix",
102
+ id: generateNodeId(),
103
+ rows,
104
+ ...options
105
+ };
106
+ }
107
+ function createText(content) {
108
+ return {
109
+ kind: "text",
110
+ id: generateNodeId(),
111
+ content
112
+ };
113
+ }
114
+ function createSpace(width) {
115
+ return {
116
+ kind: "space",
117
+ id: generateNodeId(),
118
+ width
119
+ };
120
+ }
121
+ function createAccent(accentType, base) {
122
+ return {
123
+ kind: "accent",
124
+ id: generateNodeId(),
125
+ accentType,
126
+ base
127
+ };
128
+ }
129
+ function createLargeOp(opType, body, options) {
130
+ return {
131
+ kind: "largeop",
132
+ id: generateNodeId(),
133
+ opType,
134
+ body,
135
+ ...options
136
+ };
137
+ }
138
+ function createColor(color, body) {
139
+ return {
140
+ kind: "color",
141
+ id: generateNodeId(),
142
+ color,
143
+ body
144
+ };
145
+ }
146
+ function createGroup(children) {
147
+ return {
148
+ kind: "group",
149
+ id: generateNodeId(),
150
+ children
151
+ };
152
+ }
153
+ function createChemElement(symbol, options) {
154
+ return {
155
+ kind: "chem-element",
156
+ id: generateNodeId(),
157
+ symbol,
158
+ ...options
159
+ };
160
+ }
161
+ function createChemBond(bondType, left, right) {
162
+ return {
163
+ kind: "chem-bond",
164
+ id: generateNodeId(),
165
+ bondType,
166
+ left,
167
+ right
168
+ };
169
+ }
170
+ function createChemReaction(reactants, products, arrowType, conditions) {
171
+ return {
172
+ kind: "chem-reaction",
173
+ id: generateNodeId(),
174
+ reactants,
175
+ products,
176
+ arrowType,
177
+ conditions
178
+ };
179
+ }
180
+ function createChemGroup(atoms, name) {
181
+ return {
182
+ kind: "chem-group",
183
+ id: generateNodeId(),
184
+ name,
185
+ atoms
186
+ };
187
+ }
188
+ function cloneNode(node) {
189
+ const cloned = JSON.parse(JSON.stringify(node));
190
+ cloned.id = generateNodeId();
191
+ return cloned;
192
+ }
193
+ function isNodeType(node, kind) {
194
+ return node.kind === kind;
195
+ }
196
+ function flattenNode(node) {
197
+ const result = [node];
198
+ const children = getNodeChildren(node);
199
+ for (const child of children) {
200
+ result.push(...flattenNode(child));
201
+ }
202
+ return result;
203
+ }
204
+ function getNodeChildren(node) {
205
+ const n = node;
206
+ switch (n.kind) {
207
+ case "root":
208
+ case "group":
209
+ return n.children;
210
+ case "frac":
211
+ return [n.numerator, n.denominator];
212
+ case "script":
213
+ return [n.base, n.sub, n.super].filter(Boolean);
214
+ case "radical":
215
+ return [n.radicand, n.index].filter(Boolean);
216
+ case "delimiter":
217
+ return [n.body];
218
+ case "matrix":
219
+ return n.rows.flat();
220
+ case "color":
221
+ return [n.body];
222
+ case "accent":
223
+ return [n.base];
224
+ case "largeop":
225
+ return [n.body, n.lower, n.upper].filter(Boolean);
226
+ case "chem-bond":
227
+ return [n.left, n.right];
228
+ case "chem-reaction":
229
+ return [...n.reactants, ...n.products, n.conditions].filter(Boolean);
230
+ case "chem-group":
231
+ return n.atoms;
232
+ case "symbol":
233
+ case "text":
234
+ case "space":
235
+ case "chem-element":
236
+ return [];
237
+ default:
238
+ return [];
239
+ }
240
+ }
241
+
242
+ // src/ast-utils.ts
243
+ function traverseAST(node, visitor, options = {}) {
244
+ const { order = "pre" } = options;
245
+ let result;
246
+ if (order === "pre") {
247
+ result = visitNode(node, visitor);
248
+ }
249
+ const children = getChildren(node);
250
+ for (const child of children) {
251
+ traverseAST(child, visitor, options);
252
+ }
253
+ if (order === "post") {
254
+ result = visitNode(node, visitor);
255
+ }
256
+ return result;
257
+ }
258
+ function visitNode(node, visitor) {
259
+ let result;
260
+ const n = node;
261
+ switch (n.kind) {
262
+ case "root":
263
+ result = visitor.visitRoot?.(n);
264
+ break;
265
+ case "symbol":
266
+ result = visitor.visitSymbol?.(n);
267
+ break;
268
+ case "frac":
269
+ result = visitor.visitFraction?.(n);
270
+ break;
271
+ case "script":
272
+ result = visitor.visitScript?.(n);
273
+ break;
274
+ case "radical":
275
+ result = visitor.visitRadical?.(n);
276
+ break;
277
+ case "delimiter":
278
+ result = visitor.visitDelimiter?.(n);
279
+ break;
280
+ case "matrix":
281
+ result = visitor.visitMatrix?.(n);
282
+ break;
283
+ case "text":
284
+ result = visitor.visitText?.(n);
285
+ break;
286
+ case "space":
287
+ result = visitor.visitSpace?.(n);
288
+ break;
289
+ case "accent":
290
+ result = visitor.visitAccent?.(n);
291
+ break;
292
+ case "largeop":
293
+ result = visitor.visitLargeOp?.(n);
294
+ break;
295
+ case "color":
296
+ result = visitor.visitColor?.(n);
297
+ break;
298
+ case "group":
299
+ result = visitor.visitGroup?.(n);
300
+ break;
301
+ case "chem-element":
302
+ result = visitor.visitChemElement?.(n);
303
+ break;
304
+ case "chem-bond":
305
+ result = visitor.visitChemBond?.(n);
306
+ break;
307
+ case "chem-reaction":
308
+ result = visitor.visitChemReaction?.(n);
309
+ break;
310
+ case "chem-group":
311
+ result = visitor.visitChemGroup?.(n);
312
+ break;
313
+ default:
314
+ result = visitor.visitDefault?.(n);
315
+ break;
316
+ }
317
+ if (result === void 0 && visitor.visitDefault) {
318
+ result = visitor.visitDefault(node);
319
+ }
320
+ return result;
321
+ }
322
+ function getChildren(node) {
323
+ const n = node;
324
+ switch (n.kind) {
325
+ case "root":
326
+ case "group":
327
+ return n.children;
328
+ case "frac":
329
+ return [n.numerator, n.denominator];
330
+ case "script": {
331
+ const children = [n.base];
332
+ if (n.sub) children.push(n.sub);
333
+ if (n.super) children.push(n.super);
334
+ return children;
335
+ }
336
+ case "radical": {
337
+ const children = [n.radicand];
338
+ if (n.index) children.push(n.index);
339
+ return children;
340
+ }
341
+ case "delimiter":
342
+ return [n.body];
343
+ case "matrix":
344
+ return n.rows.flat();
345
+ case "color":
346
+ return [n.body];
347
+ case "accent":
348
+ return [n.base];
349
+ case "largeop": {
350
+ const children = [];
351
+ if (n.body) children.push(n.body);
352
+ if (n.lower) children.push(n.lower);
353
+ if (n.upper) children.push(n.upper);
354
+ return children;
355
+ }
356
+ case "chem-bond":
357
+ return [n.left, n.right];
358
+ case "chem-reaction": {
359
+ const children = [...n.reactants, ...n.products];
360
+ if (n.conditions) children.push(n.conditions);
361
+ return children;
362
+ }
363
+ case "chem-group":
364
+ return n.atoms;
365
+ case "symbol":
366
+ case "text":
367
+ case "space":
368
+ case "chem-element":
369
+ return [];
370
+ default:
371
+ return [];
372
+ }
373
+ }
374
+ function collectNodes(root, predicate) {
375
+ const results = [];
376
+ traverseAST(root, {
377
+ visitDefault(node) {
378
+ if (predicate(node)) {
379
+ results.push(node);
380
+ }
381
+ }
382
+ });
383
+ return results;
384
+ }
385
+ function findNode(root, predicate) {
386
+ return collectNodes(root, predicate)[0];
387
+ }
388
+ function countNodes(root) {
389
+ let count = 0;
390
+ traverseAST(root, {
391
+ visitDefault() {
392
+ count++;
393
+ }
394
+ });
395
+ return count;
396
+ }
397
+
398
+ // src/serializer.ts
399
+ function serializeAST(root) {
400
+ return JSON.stringify(root);
401
+ }
402
+ function deserializeAST(json) {
403
+ const parsed = JSON.parse(json);
404
+ if (!parsed || parsed.kind !== "root") {
405
+ throw new Error('Invalid AST JSON: root node must have kind "root"');
406
+ }
407
+ return parsed;
408
+ }
409
+ function safeDeserializeAST(json) {
410
+ try {
411
+ return deserializeAST(json);
412
+ } catch {
413
+ return null;
414
+ }
415
+ }
416
+ function serializeASTCompact(root) {
417
+ return JSON.stringify(root, (key, value) => {
418
+ if (key === "id" && typeof value === "string" && value.startsWith("n")) {
419
+ return void 0;
420
+ }
421
+ if (key === "source" && value === void 0) {
422
+ return void 0;
423
+ }
424
+ return value;
425
+ });
426
+ }
427
+ function serializeASTPretty(root) {
428
+ return JSON.stringify(root, null, 2);
429
+ }
430
+
431
+ // src/classifier.ts
432
+ var ExpressionClassifier = class {
433
+ /**
434
+ * 分类表达式
435
+ */
436
+ classify(root) {
437
+ if (root.type !== "mixed" /* Mixed */) {
438
+ return root.type;
439
+ }
440
+ const hasChemistryNodes = this.hasChemistryNodes(root);
441
+ const hasMathNodes = this.hasMathNodes(root);
442
+ if (hasChemistryNodes && !hasMathNodes) {
443
+ const hasReaction = collectNodes(root, (n) => n.kind === "chem-reaction").length > 0;
444
+ return hasReaction ? "chemical-equation" /* ChemicalEquation */ : "molecule" /* Molecule */;
445
+ }
446
+ if (hasMathNodes && hasChemistryNodes) {
447
+ return "mixed" /* Mixed */;
448
+ }
449
+ if (hasMathNodes) {
450
+ const hasMatrix = collectNodes(root, (n) => n.kind === "matrix").length > 0;
451
+ return hasMatrix ? "matrix" /* Matrix */ : "math" /* Math */;
452
+ }
453
+ return "text" /* Text */;
454
+ }
455
+ /**
456
+ * 检测是否包含化学相关节点
457
+ */
458
+ hasChemistryNodes(root) {
459
+ return collectNodes(
460
+ root,
461
+ (n) => ["chem-element", "chem-bond", "chem-reaction", "chem-group"].includes(n.kind)
462
+ ).length > 0;
463
+ }
464
+ /**
465
+ * 检测是否包含数学相关节点
466
+ */
467
+ hasMathNodes(root) {
468
+ return collectNodes(
469
+ root,
470
+ (n) => ["frac", "script", "radical", "delimiter", "matrix", "accent", "largeop"].includes(n.kind)
471
+ ).length > 0;
472
+ }
473
+ /**
474
+ * 检测是否为 display 模式(块级公式)
475
+ */
476
+ isDisplayMode(root) {
477
+ return root.metadata?.displayMode ?? false;
478
+ }
479
+ };
480
+ export {
481
+ ExpressionClassifier,
482
+ ExpressionKind,
483
+ SymbolType,
484
+ ValidationErrorCode,
485
+ cloneNode,
486
+ collectNodes,
487
+ countNodes,
488
+ createAccent,
489
+ createChemBond,
490
+ createChemElement,
491
+ createChemGroup,
492
+ createChemReaction,
493
+ createColor,
494
+ createDelimiter,
495
+ createFraction,
496
+ createGroup,
497
+ createLargeOp,
498
+ createMatrix,
499
+ createRadical,
500
+ createRoot,
501
+ createScript,
502
+ createSpace,
503
+ createSymbol,
504
+ createText,
505
+ deserializeAST,
506
+ findNode,
507
+ flattenNode,
508
+ generateNodeId,
509
+ getNodeChildren,
510
+ isNodeType,
511
+ resetNodeIdCounter,
512
+ safeDeserializeAST,
513
+ serializeAST,
514
+ serializeASTCompact,
515
+ serializeASTPretty,
516
+ traverseAST
517
+ };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@sciexpr/core",
3
+ "version": "0.1.0",
4
+ "description": "Scientific Expression Engine - Core types, AST, schema and validators",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "sideEffects": false,
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "keywords": [
22
+ "scientific",
23
+ "expression",
24
+ "math",
25
+ "chemistry",
26
+ "latex",
27
+ "ast"
28
+ ],
29
+ "license": "MIT",
30
+ "author": {
31
+ "name": "haoguodong09-svg",
32
+ "email": "haoguodong09@gmail.com"
33
+ },
34
+ "homepage": "https://github.com/haoguodong09-svg/scientific-expression-engine#readme",
35
+ "bugs": {
36
+ "url": "https://github.com/haoguodong09-svg/scientific-expression-engine/issues"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/haoguodong09-svg/scientific-expression-engine",
41
+ "directory": "packages/core"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "devDependencies": {
47
+ "tsup": "^8.0.0",
48
+ "typescript": "^5.5.0",
49
+ "vitest": "^2.0.0",
50
+ "rimraf": "^5.0.0"
51
+ },
52
+ "scripts": {
53
+ "build": "tsup",
54
+ "dev": "tsup --watch",
55
+ "lint": "tsc --noEmit",
56
+ "clean": "rimraf dist",
57
+ "test": "vitest run"
58
+ }
59
+ }