@latex-math/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/LICENCE +21 -0
- package/dist/ast/index.d.ts +87 -0
- package/dist/calculus/index.d.ts +3 -0
- package/dist/errors/index.d.ts +24 -0
- package/dist/evaluator/index.d.ts +7 -0
- package/dist/index.cjs +1523 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1470 -0
- package/dist/math/index.d.ts +28 -0
- package/dist/parser/index.d.ts +4 -0
- package/dist/parser/normalizer.d.ts +11 -0
- package/dist/printer/index.d.ts +2 -0
- package/dist/simplifier/index.d.ts +5 -0
- package/dist/tokenizer/index.d.ts +21 -0
- package/package.json +40 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1523 @@
|
|
|
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
|
+
Complex: () => Complex,
|
|
24
|
+
DimensionMismatchError: () => DimensionMismatchError,
|
|
25
|
+
DivisionByZeroError: () => DivisionByZeroError,
|
|
26
|
+
EvaluationError: () => EvaluationError,
|
|
27
|
+
LatexParseError: () => LatexParseError,
|
|
28
|
+
LatexTokenError: () => LatexTokenError,
|
|
29
|
+
MatrixValue: () => MatrixValue,
|
|
30
|
+
TokenType: () => TokenType,
|
|
31
|
+
UndefinedVariableError: () => UndefinedVariableError,
|
|
32
|
+
UnsupportedExpressionError: () => UnsupportedExpressionError,
|
|
33
|
+
differentiate: () => differentiate,
|
|
34
|
+
evaluate: () => evaluate,
|
|
35
|
+
evaluateLatex: () => evaluateLatex,
|
|
36
|
+
expand: () => expand,
|
|
37
|
+
factor: () => factor,
|
|
38
|
+
integrate: () => integrate,
|
|
39
|
+
mathAdd: () => mathAdd,
|
|
40
|
+
mathDiv: () => mathDiv,
|
|
41
|
+
mathMul: () => mathMul,
|
|
42
|
+
mathPow: () => mathPow,
|
|
43
|
+
mathSub: () => mathSub,
|
|
44
|
+
normalizeLatex: () => normalizeLatex,
|
|
45
|
+
parseLatex: () => parseLatex,
|
|
46
|
+
simplify: () => simplify,
|
|
47
|
+
substitute: () => substitute,
|
|
48
|
+
toLatex: () => toLatex,
|
|
49
|
+
tokenize: () => tokenize
|
|
50
|
+
});
|
|
51
|
+
module.exports = __toCommonJS(index_exports);
|
|
52
|
+
|
|
53
|
+
// src/errors/index.ts
|
|
54
|
+
var LatexParseError = class extends Error {
|
|
55
|
+
constructor(message, position, token) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.position = position;
|
|
58
|
+
this.token = token;
|
|
59
|
+
this.name = "LatexParseError";
|
|
60
|
+
}
|
|
61
|
+
position;
|
|
62
|
+
token;
|
|
63
|
+
};
|
|
64
|
+
var LatexTokenError = class extends Error {
|
|
65
|
+
constructor(message, position) {
|
|
66
|
+
super(message);
|
|
67
|
+
this.position = position;
|
|
68
|
+
this.name = "LatexTokenError";
|
|
69
|
+
}
|
|
70
|
+
position;
|
|
71
|
+
};
|
|
72
|
+
var EvaluationError = class extends Error {
|
|
73
|
+
constructor(message) {
|
|
74
|
+
super(message);
|
|
75
|
+
this.name = "EvaluationError";
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var UnsupportedExpressionError = class extends Error {
|
|
79
|
+
constructor(message) {
|
|
80
|
+
super(message);
|
|
81
|
+
this.name = "UnsupportedExpressionError";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var DimensionMismatchError = class extends Error {
|
|
85
|
+
constructor(message) {
|
|
86
|
+
super(message);
|
|
87
|
+
this.name = "DimensionMismatchError";
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var UndefinedVariableError = class extends Error {
|
|
91
|
+
constructor(message) {
|
|
92
|
+
super(message);
|
|
93
|
+
this.name = "UndefinedVariableError";
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var DivisionByZeroError = class extends Error {
|
|
97
|
+
constructor(message) {
|
|
98
|
+
super(message);
|
|
99
|
+
this.name = "DivisionByZeroError";
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/tokenizer/index.ts
|
|
104
|
+
var TokenType = /* @__PURE__ */ ((TokenType2) => {
|
|
105
|
+
TokenType2["Number"] = "Number";
|
|
106
|
+
TokenType2["Identifier"] = "Identifier";
|
|
107
|
+
TokenType2["Command"] = "Command";
|
|
108
|
+
TokenType2["Operator"] = "Operator";
|
|
109
|
+
TokenType2["LeftParen"] = "LeftParen";
|
|
110
|
+
TokenType2["RightParen"] = "RightParen";
|
|
111
|
+
TokenType2["LeftBrace"] = "LeftBrace";
|
|
112
|
+
TokenType2["RightBrace"] = "RightBrace";
|
|
113
|
+
TokenType2["LeftBracket"] = "LeftBracket";
|
|
114
|
+
TokenType2["RightBracket"] = "RightBracket";
|
|
115
|
+
TokenType2["Caret"] = "Caret";
|
|
116
|
+
TokenType2["Underscore"] = "Underscore";
|
|
117
|
+
TokenType2["EOF"] = "EOF";
|
|
118
|
+
return TokenType2;
|
|
119
|
+
})(TokenType || {});
|
|
120
|
+
function tokenize(input) {
|
|
121
|
+
const tokens = [];
|
|
122
|
+
let current = 0;
|
|
123
|
+
while (current < input.length) {
|
|
124
|
+
let char = input[current];
|
|
125
|
+
if (/\s/.test(char)) {
|
|
126
|
+
current++;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (/[0-9]/.test(char)) {
|
|
130
|
+
let value = "";
|
|
131
|
+
const start = current;
|
|
132
|
+
while (current < input.length && /[0-9\.]/.test(input[current])) {
|
|
133
|
+
value += input[current];
|
|
134
|
+
current++;
|
|
135
|
+
}
|
|
136
|
+
tokens.push({ type: "Number" /* Number */, value, position: start });
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (/[a-zA-Z]/.test(char)) {
|
|
140
|
+
tokens.push({ type: "Identifier" /* Identifier */, value: char, position: current });
|
|
141
|
+
current++;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (char === "\\") {
|
|
145
|
+
let value = "\\";
|
|
146
|
+
const start = current;
|
|
147
|
+
current++;
|
|
148
|
+
if (current < input.length && /^[\\,;!:]$/.test(input[current])) {
|
|
149
|
+
value += input[current];
|
|
150
|
+
current++;
|
|
151
|
+
} else {
|
|
152
|
+
while (current < input.length && /[a-zA-Z]/.test(input[current])) {
|
|
153
|
+
value += input[current];
|
|
154
|
+
current++;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (["\\,", "\\;", "\\:", "\\!", "\\quad", "\\qquad"].includes(value)) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
tokens.push({ type: "Command" /* Command */, value, position: start });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (char === "+") {
|
|
164
|
+
tokens.push({ type: "Operator" /* Operator */, value: char, position: current });
|
|
165
|
+
current++;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (char === "-") {
|
|
169
|
+
tokens.push({ type: "Operator" /* Operator */, value: char, position: current });
|
|
170
|
+
current++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (char === "*") {
|
|
174
|
+
tokens.push({ type: "Operator" /* Operator */, value: char, position: current });
|
|
175
|
+
current++;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (char === "/") {
|
|
179
|
+
tokens.push({ type: "Operator" /* Operator */, value: char, position: current });
|
|
180
|
+
current++;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (char === "|") {
|
|
184
|
+
tokens.push({ type: "Operator" /* Operator */, value: char, position: current });
|
|
185
|
+
current++;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (char === "!") {
|
|
189
|
+
tokens.push({ type: "Operator" /* Operator */, value: char, position: current });
|
|
190
|
+
current++;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (char === "=") {
|
|
194
|
+
tokens.push({ type: "Operator" /* Operator */, value: char, position: current });
|
|
195
|
+
current++;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (char === "<") {
|
|
199
|
+
if (input[current + 1] === "=") {
|
|
200
|
+
tokens.push({ type: "Operator" /* Operator */, value: "<=", position: current });
|
|
201
|
+
current += 2;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
tokens.push({ type: "Operator" /* Operator */, value: char, position: current });
|
|
205
|
+
current++;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (char === ">") {
|
|
209
|
+
if (input[current + 1] === "=") {
|
|
210
|
+
tokens.push({ type: "Operator" /* Operator */, value: ">=", position: current });
|
|
211
|
+
current += 2;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
tokens.push({ type: "Operator" /* Operator */, value: char, position: current });
|
|
215
|
+
current++;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (char === "&") {
|
|
219
|
+
tokens.push({ type: "Command" /* Command */, value: "&", position: current });
|
|
220
|
+
current++;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (char === "(") {
|
|
224
|
+
tokens.push({ type: "LeftParen" /* LeftParen */, value: char, position: current });
|
|
225
|
+
current++;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (char === ")") {
|
|
229
|
+
tokens.push({ type: "RightParen" /* RightParen */, value: char, position: current });
|
|
230
|
+
current++;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (char === "{") {
|
|
234
|
+
tokens.push({ type: "LeftBrace" /* LeftBrace */, value: char, position: current });
|
|
235
|
+
current++;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (char === "}") {
|
|
239
|
+
tokens.push({ type: "RightBrace" /* RightBrace */, value: char, position: current });
|
|
240
|
+
current++;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (char === "[") {
|
|
244
|
+
tokens.push({ type: "LeftBracket" /* LeftBracket */, value: char, position: current });
|
|
245
|
+
current++;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (char === "]") {
|
|
249
|
+
tokens.push({ type: "RightBracket" /* RightBracket */, value: char, position: current });
|
|
250
|
+
current++;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (char === "^") {
|
|
254
|
+
tokens.push({ type: "Caret" /* Caret */, value: char, position: current });
|
|
255
|
+
current++;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (char === "_") {
|
|
259
|
+
tokens.push({ type: "Underscore" /* Underscore */, value: char, position: current });
|
|
260
|
+
current++;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
throw new LatexTokenError(`Unexpected character: "${char}"`, current);
|
|
264
|
+
}
|
|
265
|
+
tokens.push({ type: "EOF" /* EOF */, value: "", position: current });
|
|
266
|
+
return tokens;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/parser/normalizer.ts
|
|
270
|
+
function normalizeLatex(input) {
|
|
271
|
+
if (!input || typeof input !== "string") return "";
|
|
272
|
+
let i = 0;
|
|
273
|
+
function skipWhitespace() {
|
|
274
|
+
while (i < input.length && /\s/.test(input[i])) {
|
|
275
|
+
i++;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function readGroupOrToken() {
|
|
279
|
+
skipWhitespace();
|
|
280
|
+
if (i >= input.length) return "";
|
|
281
|
+
if (input[i] === "{") {
|
|
282
|
+
let depth = 1;
|
|
283
|
+
const start = i;
|
|
284
|
+
i++;
|
|
285
|
+
while (i < input.length && depth > 0) {
|
|
286
|
+
if (input[i] === "{") depth++;
|
|
287
|
+
else if (input[i] === "}") depth--;
|
|
288
|
+
i++;
|
|
289
|
+
}
|
|
290
|
+
const inner = input.slice(start + 1, i - 1);
|
|
291
|
+
return `{${normalizeLatex(inner)}}`;
|
|
292
|
+
}
|
|
293
|
+
if (input[i] === "\\") {
|
|
294
|
+
const start = i;
|
|
295
|
+
i++;
|
|
296
|
+
if (i < input.length && /^[\\,;!:]$/.test(input[i])) {
|
|
297
|
+
i++;
|
|
298
|
+
} else {
|
|
299
|
+
while (i < input.length && /[a-zA-Z]/.test(input[i])) {
|
|
300
|
+
i++;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const cmd = input.slice(start, i);
|
|
304
|
+
return `{${cmd}}`;
|
|
305
|
+
}
|
|
306
|
+
const char = input[i];
|
|
307
|
+
i++;
|
|
308
|
+
return `{${char}}`;
|
|
309
|
+
}
|
|
310
|
+
let result = "";
|
|
311
|
+
while (i < input.length) {
|
|
312
|
+
if (input.startsWith("\\sqrt", i) && (i + 5 === input.length || !/[a-zA-Z]/.test(input[i + 5]))) {
|
|
313
|
+
result += "\\sqrt";
|
|
314
|
+
i += 5;
|
|
315
|
+
skipWhitespace();
|
|
316
|
+
if (i < input.length && input[i] === "[") {
|
|
317
|
+
const bracketStart = i;
|
|
318
|
+
while (i < input.length && input[i] !== "]") i++;
|
|
319
|
+
if (i < input.length) i++;
|
|
320
|
+
const innerBracket = input.slice(bracketStart + 1, i - 1);
|
|
321
|
+
result += `[${normalizeLatex(innerBracket)}]`;
|
|
322
|
+
}
|
|
323
|
+
skipWhitespace();
|
|
324
|
+
const hadExplicitBrace = i < input.length && input[i] === "{";
|
|
325
|
+
const arg = readGroupOrToken();
|
|
326
|
+
result += arg;
|
|
327
|
+
if (!hadExplicitBrace && i < input.length && /[+\-*\/=]/.test(input[i])) {
|
|
328
|
+
const op = input[i];
|
|
329
|
+
i++;
|
|
330
|
+
skipWhitespace();
|
|
331
|
+
result += ` ${op} `;
|
|
332
|
+
}
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (input.startsWith("\\frac", i) && (i + 5 === input.length || !/[a-zA-Z]/.test(input[i + 5]))) {
|
|
336
|
+
result += "\\frac";
|
|
337
|
+
i += 5;
|
|
338
|
+
skipWhitespace();
|
|
339
|
+
const hadExplicitBrace = i < input.length && input[i] === "{";
|
|
340
|
+
const num = readGroupOrToken();
|
|
341
|
+
const den = readGroupOrToken();
|
|
342
|
+
result += num + den;
|
|
343
|
+
if (!hadExplicitBrace && i < input.length && /[+\-*\/=]/.test(input[i])) {
|
|
344
|
+
const op = input[i];
|
|
345
|
+
i++;
|
|
346
|
+
skipWhitespace();
|
|
347
|
+
result += ` ${op} `;
|
|
348
|
+
}
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (input.startsWith("\\binom", i) && (i + 6 === input.length || !/[a-zA-Z]/.test(input[i + 6]))) {
|
|
352
|
+
result += "\\binom";
|
|
353
|
+
i += 6;
|
|
354
|
+
skipWhitespace();
|
|
355
|
+
const n = readGroupOrToken();
|
|
356
|
+
const k = readGroupOrToken();
|
|
357
|
+
result += n + k;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
result += input[i];
|
|
361
|
+
i++;
|
|
362
|
+
}
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/parser/index.ts
|
|
367
|
+
function extractDx(expr) {
|
|
368
|
+
if (expr.type === "BinaryOperation" && expr.operator === "*") {
|
|
369
|
+
if (expr.right.type === "BinaryOperation" && expr.right.operator === "*" && expr.right.implicit && expr.right.left.type === "Variable" && expr.right.left.name === "d" && expr.right.right.type === "Variable") {
|
|
370
|
+
return { integrand: expr.left, variable: expr.right.right.name };
|
|
371
|
+
}
|
|
372
|
+
if (expr.right.type === "Variable" && expr.left.type === "BinaryOperation" && expr.left.operator === "*" && expr.left.right.type === "Variable" && expr.left.right.name === "d") {
|
|
373
|
+
return { integrand: expr.left.left, variable: expr.right.name };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
function parseLatex(input) {
|
|
379
|
+
const normalized = normalizeLatex(input);
|
|
380
|
+
const tokens = tokenize(normalized);
|
|
381
|
+
let current = 0;
|
|
382
|
+
function peek() {
|
|
383
|
+
return tokens[current];
|
|
384
|
+
}
|
|
385
|
+
function consume(type) {
|
|
386
|
+
const token = peek();
|
|
387
|
+
if (type && token.type !== type) {
|
|
388
|
+
throw new LatexParseError(`Expected token type ${type}, but got ${token.type}`, token.position);
|
|
389
|
+
}
|
|
390
|
+
current++;
|
|
391
|
+
return token;
|
|
392
|
+
}
|
|
393
|
+
function parseExpression() {
|
|
394
|
+
let left = parseAddition();
|
|
395
|
+
const token = peek();
|
|
396
|
+
if (token.type === "Operator" /* Operator */ && ["=", "<", ">", "<=", ">="].includes(token.value)) {
|
|
397
|
+
consume();
|
|
398
|
+
const right = parseAddition();
|
|
399
|
+
return { type: "Equation", operator: token.value, left, right };
|
|
400
|
+
}
|
|
401
|
+
if (token.type === "Command" /* Command */ && ["\\leq", "\\geq", "\\le", "\\ge"].includes(token.value)) {
|
|
402
|
+
const cmd = consume().value;
|
|
403
|
+
const right = parseAddition();
|
|
404
|
+
let op = "<=";
|
|
405
|
+
if (cmd === "\\geq" || cmd === "\\ge") op = ">=";
|
|
406
|
+
return { type: "Equation", operator: op, left, right };
|
|
407
|
+
}
|
|
408
|
+
return left;
|
|
409
|
+
}
|
|
410
|
+
function parseAddition() {
|
|
411
|
+
let left = parseMultiplication();
|
|
412
|
+
while (peek().type === "Operator" /* Operator */ && (peek().value === "+" || peek().value === "-")) {
|
|
413
|
+
const opToken = consume();
|
|
414
|
+
const right = parseMultiplication();
|
|
415
|
+
left = {
|
|
416
|
+
type: "BinaryOperation",
|
|
417
|
+
operator: opToken.value,
|
|
418
|
+
left,
|
|
419
|
+
right
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
return left;
|
|
423
|
+
}
|
|
424
|
+
function parseMultiplication() {
|
|
425
|
+
let left = parseUnary();
|
|
426
|
+
while (true) {
|
|
427
|
+
const token = peek();
|
|
428
|
+
if (token.type === "EOF" /* EOF */) break;
|
|
429
|
+
if (token.type === "Operator" /* Operator */ && (token.value === "*" || token.value === "/")) {
|
|
430
|
+
const opToken = consume();
|
|
431
|
+
const right = parseUnary();
|
|
432
|
+
left = {
|
|
433
|
+
type: "BinaryOperation",
|
|
434
|
+
operator: opToken.value,
|
|
435
|
+
left,
|
|
436
|
+
right
|
|
437
|
+
};
|
|
438
|
+
} else if (token.type === "Command" /* Command */ && (token.value === "\\cdot" || token.value === "\\times")) {
|
|
439
|
+
consume();
|
|
440
|
+
const right = parseUnary();
|
|
441
|
+
left = {
|
|
442
|
+
type: "BinaryOperation",
|
|
443
|
+
operator: "*",
|
|
444
|
+
left,
|
|
445
|
+
right
|
|
446
|
+
};
|
|
447
|
+
} else if (token.type === "Number" /* Number */ || token.type === "Identifier" /* Identifier */ || token.type === "LeftParen" /* LeftParen */ || token.type === "LeftBrace" /* LeftBrace */ || token.type === "Command" /* Command */ && !["\\right", "\\end", "\\\\", "&"].includes(token.value)) {
|
|
448
|
+
const right = parseUnary();
|
|
449
|
+
left = {
|
|
450
|
+
type: "BinaryOperation",
|
|
451
|
+
operator: "*",
|
|
452
|
+
left,
|
|
453
|
+
right,
|
|
454
|
+
implicit: true
|
|
455
|
+
};
|
|
456
|
+
} else {
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return left;
|
|
461
|
+
}
|
|
462
|
+
function parseUnary() {
|
|
463
|
+
const token = peek();
|
|
464
|
+
if (token.type === "Operator" /* Operator */ && (token.value === "+" || token.value === "-")) {
|
|
465
|
+
const opToken = consume();
|
|
466
|
+
const operand = parsePower();
|
|
467
|
+
return {
|
|
468
|
+
type: "UnaryOperation",
|
|
469
|
+
operator: opToken.value,
|
|
470
|
+
operand
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
return parsePower();
|
|
474
|
+
}
|
|
475
|
+
function parsePower() {
|
|
476
|
+
let left = parsePrimary();
|
|
477
|
+
if (peek() && peek().type === "Command" /* Command */ && peek().value === "\\!") {
|
|
478
|
+
consume();
|
|
479
|
+
left = { type: "Factorial", operand: left };
|
|
480
|
+
} else if (peek() && peek().type === "Operator" /* Operator */ && peek().value === "!") {
|
|
481
|
+
consume();
|
|
482
|
+
left = { type: "Factorial", operand: left };
|
|
483
|
+
}
|
|
484
|
+
const token = peek();
|
|
485
|
+
if (token.type === "Caret" /* Caret */) {
|
|
486
|
+
consume();
|
|
487
|
+
let exponent;
|
|
488
|
+
if (peek().type === "LeftBrace" /* LeftBrace */) {
|
|
489
|
+
consume();
|
|
490
|
+
exponent = parseExpression();
|
|
491
|
+
consume("RightBrace" /* RightBrace */);
|
|
492
|
+
} else {
|
|
493
|
+
exponent = parsePrimary();
|
|
494
|
+
}
|
|
495
|
+
left = { type: "Power", base: left, exponent };
|
|
496
|
+
}
|
|
497
|
+
return left;
|
|
498
|
+
}
|
|
499
|
+
function parsePrimary() {
|
|
500
|
+
const token = peek();
|
|
501
|
+
if (token.type === "Number" /* Number */) {
|
|
502
|
+
consume();
|
|
503
|
+
return {
|
|
504
|
+
type: "Number",
|
|
505
|
+
value: parseFloat(token.value)
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
if (token.type === "Identifier" /* Identifier */) {
|
|
509
|
+
consume();
|
|
510
|
+
if (token.value === "e" || token.value === "i") {
|
|
511
|
+
return { type: "Constant", name: token.value };
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
type: "Variable",
|
|
515
|
+
name: token.value
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
if (token.type === "LeftParen" /* LeftParen */) {
|
|
519
|
+
consume();
|
|
520
|
+
const expr = parseExpression();
|
|
521
|
+
consume("RightParen" /* RightParen */);
|
|
522
|
+
return expr;
|
|
523
|
+
}
|
|
524
|
+
if (token.type === "LeftBrace" /* LeftBrace */) {
|
|
525
|
+
consume();
|
|
526
|
+
const expr = parseExpression();
|
|
527
|
+
consume("RightBrace" /* RightBrace */);
|
|
528
|
+
return expr;
|
|
529
|
+
}
|
|
530
|
+
if (token.type === "Command" /* Command */ && token.value === "|") {
|
|
531
|
+
consume();
|
|
532
|
+
const expr = parseExpression();
|
|
533
|
+
const nextToken = consume();
|
|
534
|
+
if (nextToken.type === "Command" /* Command */ && nextToken.value === "|") {
|
|
535
|
+
} else if (nextToken.type === "Operator" /* Operator */ && nextToken.value === "|") {
|
|
536
|
+
} else {
|
|
537
|
+
throw new LatexParseError("Expected closing |", nextToken.position);
|
|
538
|
+
}
|
|
539
|
+
return { type: "AbsoluteValue", expression: expr };
|
|
540
|
+
}
|
|
541
|
+
if (token.type === "Operator" /* Operator */ && token.value === "|") {
|
|
542
|
+
consume();
|
|
543
|
+
const expr = parseExpression();
|
|
544
|
+
const nextToken = consume();
|
|
545
|
+
if (nextToken.type === "Command" /* Command */ && nextToken.value === "|") {
|
|
546
|
+
} else if (nextToken.type === "Operator" /* Operator */ && nextToken.value === "|") {
|
|
547
|
+
} else {
|
|
548
|
+
throw new LatexParseError("Expected closing |", nextToken.position);
|
|
549
|
+
}
|
|
550
|
+
return { type: "AbsoluteValue", expression: expr };
|
|
551
|
+
}
|
|
552
|
+
if (token.type === "Command" /* Command */) {
|
|
553
|
+
consume();
|
|
554
|
+
if (token.value === "\\left") {
|
|
555
|
+
const next = consume();
|
|
556
|
+
if (next.type === "LeftParen" /* LeftParen */) {
|
|
557
|
+
const expr = parseExpression();
|
|
558
|
+
const rightCmd = consume("Command" /* Command */);
|
|
559
|
+
if (rightCmd.value !== "\\right") throw new LatexParseError("Expected \\right", rightCmd.position);
|
|
560
|
+
consume("RightParen" /* RightParen */);
|
|
561
|
+
return expr;
|
|
562
|
+
} else if (next.type === "Command" /* Command */ && next.value === "|") {
|
|
563
|
+
const expr = parseExpression();
|
|
564
|
+
const rightCmd = consume("Command" /* Command */);
|
|
565
|
+
if (rightCmd.value !== "\\right") throw new LatexParseError("Expected \\right", rightCmd.position);
|
|
566
|
+
const barCmd = consume();
|
|
567
|
+
if (barCmd.type !== "Command" /* Command */ || barCmd.value !== "|") throw new LatexParseError("Expected |", barCmd.position);
|
|
568
|
+
return { type: "AbsoluteValue", expression: expr };
|
|
569
|
+
} else if (next.type === "Operator" /* Operator */ && next.value === "|") {
|
|
570
|
+
const expr = parseExpression();
|
|
571
|
+
const rightCmd = consume("Command" /* Command */);
|
|
572
|
+
if (rightCmd.value !== "\\right") throw new LatexParseError("Expected \\right", rightCmd.position);
|
|
573
|
+
const barCmd = consume();
|
|
574
|
+
if (barCmd.type !== "Operator" /* Operator */ || barCmd.value !== "|") throw new LatexParseError("Expected |", barCmd.position);
|
|
575
|
+
return { type: "AbsoluteValue", expression: expr };
|
|
576
|
+
} else {
|
|
577
|
+
throw new LatexParseError("Unsupported \\left delimiter", next.position);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (token.value === "\\pi") return { type: "Constant", name: "pi" };
|
|
581
|
+
if (token.value === "\\begin") {
|
|
582
|
+
consume("LeftBrace" /* LeftBrace */);
|
|
583
|
+
let envNameStr = "";
|
|
584
|
+
while (peek().type === "Identifier" /* Identifier */) {
|
|
585
|
+
envNameStr += consume("Identifier" /* Identifier */).value;
|
|
586
|
+
}
|
|
587
|
+
consume("RightBrace" /* RightBrace */);
|
|
588
|
+
if (envNameStr !== "pmatrix" && envNameStr !== "bmatrix" && envNameStr !== "vmatrix") {
|
|
589
|
+
throw new LatexParseError("Unsupported environment", token.position);
|
|
590
|
+
}
|
|
591
|
+
const rows = [];
|
|
592
|
+
let currentRow = [];
|
|
593
|
+
while (true) {
|
|
594
|
+
if (peek().type === "Command" /* Command */ && peek().value === "\\end") {
|
|
595
|
+
consume();
|
|
596
|
+
consume("LeftBrace" /* LeftBrace */);
|
|
597
|
+
let endNameStr = "";
|
|
598
|
+
while (peek().type === "Identifier" /* Identifier */) {
|
|
599
|
+
endNameStr += consume("Identifier" /* Identifier */).value;
|
|
600
|
+
}
|
|
601
|
+
consume("RightBrace" /* RightBrace */);
|
|
602
|
+
if (endNameStr !== envNameStr) throw new LatexParseError("Mismatched environment", peek().position);
|
|
603
|
+
if (currentRow.length > 0) rows.push(currentRow);
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
if (peek().type === "Command" /* Command */ && peek().value === "\\\\") {
|
|
607
|
+
consume();
|
|
608
|
+
rows.push(currentRow);
|
|
609
|
+
currentRow = [];
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (peek().type === "Command" /* Command */ && peek().value === "&") {
|
|
613
|
+
consume();
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
currentRow.push(parseExpression());
|
|
617
|
+
}
|
|
618
|
+
return { type: "Matrix", rows };
|
|
619
|
+
}
|
|
620
|
+
const functions = ["\\sin", "\\cos", "\\tan", "\\arcsin", "\\arccos", "\\arctan", "\\ln", "\\log", "\\exp", "\\det", "\\transpose", "\\arg"];
|
|
621
|
+
if (functions.includes(token.value)) {
|
|
622
|
+
let funcName = token.value.substring(1);
|
|
623
|
+
let power = null;
|
|
624
|
+
if (peek().type === "Caret" /* Caret */) {
|
|
625
|
+
consume();
|
|
626
|
+
const hasBrace = peek().type === "LeftBrace" /* LeftBrace */;
|
|
627
|
+
if (hasBrace) {
|
|
628
|
+
consume();
|
|
629
|
+
power = parseExpression();
|
|
630
|
+
consume("RightBrace" /* RightBrace */);
|
|
631
|
+
} else {
|
|
632
|
+
power = parsePrimary();
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
let isInverse = false;
|
|
636
|
+
if (power) {
|
|
637
|
+
if (power.type === "Number" && power.value === -1) {
|
|
638
|
+
isInverse = true;
|
|
639
|
+
} else if (power.type === "UnaryOperation" && power.operator === "-" && power.operand.type === "Number" && power.operand.value === 1) {
|
|
640
|
+
isInverse = true;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (isInverse) {
|
|
644
|
+
if (funcName === "sin") {
|
|
645
|
+
funcName = "arcsin";
|
|
646
|
+
power = null;
|
|
647
|
+
} else if (funcName === "cos") {
|
|
648
|
+
funcName = "arccos";
|
|
649
|
+
power = null;
|
|
650
|
+
} else if (funcName === "tan") {
|
|
651
|
+
funcName = "arctan";
|
|
652
|
+
power = null;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
let arg;
|
|
656
|
+
if (peek().type === "LeftParen" /* LeftParen */) {
|
|
657
|
+
consume();
|
|
658
|
+
arg = parseExpression();
|
|
659
|
+
consume("RightParen" /* RightParen */);
|
|
660
|
+
} else if (peek().type === "LeftBrace" /* LeftBrace */) {
|
|
661
|
+
consume();
|
|
662
|
+
arg = parseExpression();
|
|
663
|
+
consume("RightBrace" /* RightBrace */);
|
|
664
|
+
} else if (peek().type === "Command" /* Command */ && peek().value === "\\left") {
|
|
665
|
+
arg = parsePrimary();
|
|
666
|
+
} else {
|
|
667
|
+
arg = parsePrimary();
|
|
668
|
+
}
|
|
669
|
+
const funcNode = { type: "Function", name: funcName, args: [arg] };
|
|
670
|
+
if (power) {
|
|
671
|
+
return { type: "Power", base: funcNode, exponent: power };
|
|
672
|
+
}
|
|
673
|
+
return funcNode;
|
|
674
|
+
}
|
|
675
|
+
if (token.value === "\\binom") {
|
|
676
|
+
let n;
|
|
677
|
+
if (peek().type === "LeftBrace" /* LeftBrace */) {
|
|
678
|
+
consume("LeftBrace" /* LeftBrace */);
|
|
679
|
+
n = parseExpression();
|
|
680
|
+
consume("RightBrace" /* RightBrace */);
|
|
681
|
+
} else {
|
|
682
|
+
n = parsePrimary();
|
|
683
|
+
}
|
|
684
|
+
let k;
|
|
685
|
+
if (peek().type === "LeftBrace" /* LeftBrace */) {
|
|
686
|
+
consume("LeftBrace" /* LeftBrace */);
|
|
687
|
+
k = parseExpression();
|
|
688
|
+
consume("RightBrace" /* RightBrace */);
|
|
689
|
+
} else {
|
|
690
|
+
k = parsePrimary();
|
|
691
|
+
}
|
|
692
|
+
return { type: "Combinatorics", n, k };
|
|
693
|
+
}
|
|
694
|
+
if (token.value === "\\frac") {
|
|
695
|
+
let numerator;
|
|
696
|
+
if (peek().type === "LeftBrace" /* LeftBrace */) {
|
|
697
|
+
consume("LeftBrace" /* LeftBrace */);
|
|
698
|
+
numerator = parseExpression();
|
|
699
|
+
consume("RightBrace" /* RightBrace */);
|
|
700
|
+
} else {
|
|
701
|
+
numerator = parsePrimary();
|
|
702
|
+
}
|
|
703
|
+
let denominator;
|
|
704
|
+
if (peek().type === "LeftBrace" /* LeftBrace */) {
|
|
705
|
+
consume("LeftBrace" /* LeftBrace */);
|
|
706
|
+
denominator = parseExpression();
|
|
707
|
+
consume("RightBrace" /* RightBrace */);
|
|
708
|
+
} else {
|
|
709
|
+
denominator = parsePrimary();
|
|
710
|
+
}
|
|
711
|
+
if (numerator.type === "Variable" && numerator.name === "d") {
|
|
712
|
+
if (denominator.type === "BinaryOperation" && denominator.operator === "*" && denominator.implicit && denominator.left.type === "Variable" && denominator.left.name === "d" && denominator.right.type === "Variable") {
|
|
713
|
+
const variable = denominator.right.name;
|
|
714
|
+
const expr = parsePower();
|
|
715
|
+
return { type: "Derivative", variable, expression: expr };
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return { type: "Fraction", numerator, denominator };
|
|
719
|
+
}
|
|
720
|
+
if (token.value === "\\int") {
|
|
721
|
+
let lowerBound = null;
|
|
722
|
+
let upperBound = null;
|
|
723
|
+
if (peek().type === "Underscore" /* Underscore */) {
|
|
724
|
+
consume();
|
|
725
|
+
lowerBound = peek().type === "LeftBrace" /* LeftBrace */ ? parsePrimary() : parsePrimary();
|
|
726
|
+
}
|
|
727
|
+
if (peek().type === "Caret" /* Caret */) {
|
|
728
|
+
consume();
|
|
729
|
+
upperBound = peek().type === "LeftBrace" /* LeftBrace */ ? parsePrimary() : parsePrimary();
|
|
730
|
+
}
|
|
731
|
+
const body = parseExpression();
|
|
732
|
+
const extracted = extractDx(body);
|
|
733
|
+
let integrand = body;
|
|
734
|
+
let variable = "x";
|
|
735
|
+
if (extracted) {
|
|
736
|
+
integrand = extracted.integrand;
|
|
737
|
+
variable = extracted.variable;
|
|
738
|
+
}
|
|
739
|
+
return { type: "Integral", variable, lowerBound, upperBound, expression: integrand };
|
|
740
|
+
}
|
|
741
|
+
if (token.value === "\\sum") {
|
|
742
|
+
let indexVariable = "";
|
|
743
|
+
let lowerBound = null;
|
|
744
|
+
let upperBound = null;
|
|
745
|
+
if (peek().type === "Underscore" /* Underscore */) {
|
|
746
|
+
consume();
|
|
747
|
+
if (peek().type === "LeftBrace" /* LeftBrace */) {
|
|
748
|
+
consume();
|
|
749
|
+
const eq = parseExpression();
|
|
750
|
+
if (eq.type === "Equation" && eq.operator === "=" && eq.left.type === "Variable") {
|
|
751
|
+
indexVariable = eq.left.name;
|
|
752
|
+
lowerBound = eq.right;
|
|
753
|
+
} else {
|
|
754
|
+
throw new LatexParseError("Expected equation in summation subscript", peek().position);
|
|
755
|
+
}
|
|
756
|
+
consume("RightBrace" /* RightBrace */);
|
|
757
|
+
} else {
|
|
758
|
+
throw new LatexParseError("Expected { after _ in \\sum", peek().position);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (peek().type === "Caret" /* Caret */) {
|
|
762
|
+
consume();
|
|
763
|
+
if (peek().type === "LeftBrace" /* LeftBrace */) {
|
|
764
|
+
consume();
|
|
765
|
+
upperBound = parseExpression();
|
|
766
|
+
consume("RightBrace" /* RightBrace */);
|
|
767
|
+
} else {
|
|
768
|
+
upperBound = parsePrimary();
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (!lowerBound || !upperBound) {
|
|
772
|
+
throw new LatexParseError("Summation requires lower and upper bounds", token.position);
|
|
773
|
+
}
|
|
774
|
+
const body = parseExpression();
|
|
775
|
+
return { type: "Summation", indexVariable, lowerBound, upperBound, expression: body };
|
|
776
|
+
}
|
|
777
|
+
if (token.value === "\\sqrt") {
|
|
778
|
+
let index = { type: "Number", value: 2 };
|
|
779
|
+
if (peek().type === "LeftBracket" /* LeftBracket */) {
|
|
780
|
+
consume();
|
|
781
|
+
index = parseExpression();
|
|
782
|
+
consume("RightBracket" /* RightBracket */);
|
|
783
|
+
}
|
|
784
|
+
let radicand;
|
|
785
|
+
if (peek().type === "LeftBrace" /* LeftBrace */) {
|
|
786
|
+
consume("LeftBrace" /* LeftBrace */);
|
|
787
|
+
radicand = parseExpression();
|
|
788
|
+
consume("RightBrace" /* RightBrace */);
|
|
789
|
+
} else {
|
|
790
|
+
radicand = parsePrimary();
|
|
791
|
+
}
|
|
792
|
+
return { type: "Root", radicand, index };
|
|
793
|
+
}
|
|
794
|
+
if (token.value === "\\left") {
|
|
795
|
+
const delim = consume();
|
|
796
|
+
if (delim.type === "LeftParen" /* LeftParen */) {
|
|
797
|
+
const expr = parseExpression();
|
|
798
|
+
const rightDelim = consume("Command" /* Command */);
|
|
799
|
+
if (rightDelim.value !== "\\right") throw new LatexParseError("Expected \\right", rightDelim.position);
|
|
800
|
+
consume("RightParen" /* RightParen */);
|
|
801
|
+
return expr;
|
|
802
|
+
} else if (delim.type === "Operator" /* Operator */ && delim.value === "|") {
|
|
803
|
+
const expr = parseExpression();
|
|
804
|
+
const rightDelim = consume("Command" /* Command */);
|
|
805
|
+
if (rightDelim.value !== "\\right") throw new LatexParseError("Expected \\right", rightDelim.position);
|
|
806
|
+
const rightPipe = consume("Operator" /* Operator */);
|
|
807
|
+
if (rightPipe.value !== "|") throw new LatexParseError("Expected |", rightPipe.position);
|
|
808
|
+
return { type: "AbsoluteValue", expression: expr };
|
|
809
|
+
} else {
|
|
810
|
+
throw new LatexParseError(`Unsupported \\left delimiter: ${delim.value}`, delim.position);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (token.type === "Operator" /* Operator */ && token.value === "|") {
|
|
815
|
+
consume();
|
|
816
|
+
const expr = parseExpression();
|
|
817
|
+
const right = consume("Operator" /* Operator */);
|
|
818
|
+
if (right.value !== "|") throw new LatexParseError("Expected |", right.position);
|
|
819
|
+
return { type: "AbsoluteValue", expression: expr };
|
|
820
|
+
}
|
|
821
|
+
throw new LatexParseError(`Unexpected token: ${token.value}`, token.position);
|
|
822
|
+
}
|
|
823
|
+
const ast = parseExpression();
|
|
824
|
+
if (peek().type !== "EOF" /* EOF */) {
|
|
825
|
+
throw new LatexParseError(`Unexpected token at end of input: ${peek().value}`, peek().position);
|
|
826
|
+
}
|
|
827
|
+
return ast;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// src/math/index.ts
|
|
831
|
+
var Complex = class _Complex {
|
|
832
|
+
constructor(re, im) {
|
|
833
|
+
this.re = re;
|
|
834
|
+
this.im = im;
|
|
835
|
+
}
|
|
836
|
+
re;
|
|
837
|
+
im;
|
|
838
|
+
add(other) {
|
|
839
|
+
if (typeof other === "number") return new _Complex(this.re + other, this.im);
|
|
840
|
+
return new _Complex(this.re + other.re, this.im + other.im);
|
|
841
|
+
}
|
|
842
|
+
sub(other) {
|
|
843
|
+
if (typeof other === "number") return new _Complex(this.re - other, this.im);
|
|
844
|
+
return new _Complex(this.re - other.re, this.im - other.im);
|
|
845
|
+
}
|
|
846
|
+
mul(other) {
|
|
847
|
+
if (typeof other === "number") return new _Complex(this.re * other, this.im * other);
|
|
848
|
+
return new _Complex(
|
|
849
|
+
this.re * other.re - this.im * other.im,
|
|
850
|
+
this.re * other.im + this.im * other.re
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
div(other) {
|
|
854
|
+
if (typeof other === "number") return new _Complex(this.re / other, this.im / other);
|
|
855
|
+
const denom = other.re * other.re + other.im * other.im;
|
|
856
|
+
if (denom === 0) throw new Error("Division by zero in complex numbers");
|
|
857
|
+
return new _Complex(
|
|
858
|
+
(this.re * other.re + this.im * other.im) / denom,
|
|
859
|
+
(this.im * other.re - this.re * other.im) / denom
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
exp() {
|
|
863
|
+
const r = Math.exp(this.re);
|
|
864
|
+
return new _Complex(r * Math.cos(this.im), r * Math.sin(this.im));
|
|
865
|
+
}
|
|
866
|
+
ln() {
|
|
867
|
+
const r = Math.sqrt(this.re * this.re + this.im * this.im);
|
|
868
|
+
const theta = Math.atan2(this.im, this.re);
|
|
869
|
+
return new _Complex(Math.log(r), theta);
|
|
870
|
+
}
|
|
871
|
+
pow(other) {
|
|
872
|
+
if (typeof other === "number") {
|
|
873
|
+
if (other === 0) return new _Complex(1, 0);
|
|
874
|
+
if (this.re === 0 && this.im === 0) return new _Complex(0, 0);
|
|
875
|
+
const r = Math.sqrt(this.re * this.re + this.im * this.im);
|
|
876
|
+
const theta = Math.atan2(this.im, this.re);
|
|
877
|
+
const r_n = Math.pow(r, other);
|
|
878
|
+
return new _Complex(r_n * Math.cos(other * theta), r_n * Math.sin(other * theta));
|
|
879
|
+
}
|
|
880
|
+
if (this.re === 0 && this.im === 0) return new _Complex(0, 0);
|
|
881
|
+
return this.ln().mul(other).exp();
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
var MatrixValue = class _MatrixValue {
|
|
885
|
+
constructor(rows) {
|
|
886
|
+
this.rows = rows;
|
|
887
|
+
}
|
|
888
|
+
rows;
|
|
889
|
+
add(other) {
|
|
890
|
+
if (this.rows.length !== other.rows.length || this.rows[0].length !== other.rows[0].length) {
|
|
891
|
+
throw new Error("DimensionMismatchError: Cannot add matrices of different dimensions");
|
|
892
|
+
}
|
|
893
|
+
const newRows = this.rows.map(
|
|
894
|
+
(row, i) => row.map((val, j) => mathAdd(val, other.rows[i][j]))
|
|
895
|
+
);
|
|
896
|
+
return new _MatrixValue(newRows);
|
|
897
|
+
}
|
|
898
|
+
mul(other) {
|
|
899
|
+
if (other instanceof _MatrixValue) {
|
|
900
|
+
if (this.rows[0].length !== other.rows.length) {
|
|
901
|
+
throw new Error("DimensionMismatchError: Invalid dimensions for matrix multiplication");
|
|
902
|
+
}
|
|
903
|
+
const newRows = [];
|
|
904
|
+
for (let i = 0; i < this.rows.length; i++) {
|
|
905
|
+
const row = [];
|
|
906
|
+
for (let j = 0; j < other.rows[0].length; j++) {
|
|
907
|
+
let sum = 0;
|
|
908
|
+
for (let k = 0; k < this.rows[0].length; k++) {
|
|
909
|
+
sum = mathAdd(sum, mathMul(this.rows[i][k], other.rows[k][j]));
|
|
910
|
+
}
|
|
911
|
+
row.push(sum);
|
|
912
|
+
}
|
|
913
|
+
newRows.push(row);
|
|
914
|
+
}
|
|
915
|
+
return new _MatrixValue(newRows);
|
|
916
|
+
} else {
|
|
917
|
+
const newRows = this.rows.map(
|
|
918
|
+
(row) => row.map((val) => mathMul(val, other))
|
|
919
|
+
);
|
|
920
|
+
return new _MatrixValue(newRows);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
transpose() {
|
|
924
|
+
const newRows = [];
|
|
925
|
+
for (let i = 0; i < this.rows[0].length; i++) {
|
|
926
|
+
newRows.push(this.rows.map((row) => row[i]));
|
|
927
|
+
}
|
|
928
|
+
return new _MatrixValue(newRows);
|
|
929
|
+
}
|
|
930
|
+
det() {
|
|
931
|
+
if (this.rows.length !== this.rows[0].length) {
|
|
932
|
+
throw new Error("DimensionMismatchError: Matrix must be square");
|
|
933
|
+
}
|
|
934
|
+
return this._det(this.rows);
|
|
935
|
+
}
|
|
936
|
+
_det(matrix) {
|
|
937
|
+
const n = matrix.length;
|
|
938
|
+
if (n === 1) return matrix[0][0];
|
|
939
|
+
if (n === 2) {
|
|
940
|
+
return mathSub(
|
|
941
|
+
mathMul(matrix[0][0], matrix[1][1]),
|
|
942
|
+
mathMul(matrix[0][1], matrix[1][0])
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
let d = 0;
|
|
946
|
+
for (let j = 0; j < n; j++) {
|
|
947
|
+
const subMatrix = matrix.slice(1).map((row) => row.filter((_, col) => col !== j));
|
|
948
|
+
const cofactor = mathMul(matrix[0][j], this._det(subMatrix));
|
|
949
|
+
if (j % 2 === 0) d = mathAdd(d, cofactor);
|
|
950
|
+
else d = mathSub(d, cofactor);
|
|
951
|
+
}
|
|
952
|
+
return d;
|
|
953
|
+
}
|
|
954
|
+
inv() {
|
|
955
|
+
const d = this.det();
|
|
956
|
+
if (d === 0) throw new Error("Matrix is singular");
|
|
957
|
+
const n = this.rows.length;
|
|
958
|
+
if (n === 1) return new _MatrixValue([[mathDiv(1, this.rows[0][0])]]);
|
|
959
|
+
const cofactors = [];
|
|
960
|
+
for (let i = 0; i < n; i++) {
|
|
961
|
+
const row = [];
|
|
962
|
+
for (let j = 0; j < n; j++) {
|
|
963
|
+
const sub = this.rows.filter((_, rowIdx) => rowIdx !== i).map((r) => r.filter((_, colIdx) => colIdx !== j));
|
|
964
|
+
let c = this._det(sub);
|
|
965
|
+
if ((i + j) % 2 !== 0) c = mathMul(-1, c);
|
|
966
|
+
row.push(c);
|
|
967
|
+
}
|
|
968
|
+
cofactors.push(row);
|
|
969
|
+
}
|
|
970
|
+
const adjugate = new _MatrixValue(cofactors).transpose();
|
|
971
|
+
return adjugate.mul(mathDiv(1, d));
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
function mathAdd(a, b) {
|
|
975
|
+
if (typeof a === "number" && typeof b === "number") return a + b;
|
|
976
|
+
if (a instanceof Complex) return a.add(b);
|
|
977
|
+
if (b instanceof Complex) return b.add(a);
|
|
978
|
+
if (a instanceof MatrixValue && b instanceof MatrixValue) return a.add(b);
|
|
979
|
+
throw new Error("Unsupported addition");
|
|
980
|
+
}
|
|
981
|
+
function mathSub(a, b) {
|
|
982
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
983
|
+
if (a instanceof Complex) return a.sub(b);
|
|
984
|
+
if (b instanceof Complex) return new Complex(-b.re, -b.im).add(a);
|
|
985
|
+
throw new Error("Unsupported subtraction");
|
|
986
|
+
}
|
|
987
|
+
function mathMul(a, b) {
|
|
988
|
+
if (typeof a === "number" && typeof b === "number") return a * b;
|
|
989
|
+
if (a instanceof Complex) return a.mul(b);
|
|
990
|
+
if (b instanceof Complex) return b.mul(a);
|
|
991
|
+
if (a instanceof MatrixValue) return a.mul(b);
|
|
992
|
+
if (b instanceof MatrixValue) return b.mul(a);
|
|
993
|
+
throw new Error("Unsupported multiplication");
|
|
994
|
+
}
|
|
995
|
+
function mathDiv(a, b) {
|
|
996
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
997
|
+
if (b === 0) throw new Error("Division by zero");
|
|
998
|
+
return a / b;
|
|
999
|
+
}
|
|
1000
|
+
if (a instanceof Complex) return a.div(b);
|
|
1001
|
+
if (b instanceof Complex && typeof a === "number") {
|
|
1002
|
+
return new Complex(a, 0).div(b);
|
|
1003
|
+
}
|
|
1004
|
+
throw new Error("Unsupported division");
|
|
1005
|
+
}
|
|
1006
|
+
function mathPow(base, exponent) {
|
|
1007
|
+
if (base instanceof MatrixValue && typeof exponent === "number" && exponent === -1) {
|
|
1008
|
+
return base.inv();
|
|
1009
|
+
}
|
|
1010
|
+
if (typeof base === "number" && typeof exponent === "number") {
|
|
1011
|
+
if (base < 0 && !Number.isInteger(exponent)) {
|
|
1012
|
+
return new Complex(base, 0).pow(exponent);
|
|
1013
|
+
}
|
|
1014
|
+
return Math.pow(base, exponent);
|
|
1015
|
+
}
|
|
1016
|
+
if (base instanceof Complex) {
|
|
1017
|
+
return base.pow(exponent);
|
|
1018
|
+
}
|
|
1019
|
+
if (typeof base === "number" && exponent instanceof Complex) {
|
|
1020
|
+
return new Complex(base, 0).pow(exponent);
|
|
1021
|
+
}
|
|
1022
|
+
throw new Error("Unsupported power operation");
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// src/evaluator/index.ts
|
|
1026
|
+
function evaluateLatex(input, options = {}) {
|
|
1027
|
+
let ast;
|
|
1028
|
+
if (typeof input === "string") {
|
|
1029
|
+
ast = parseLatex(input);
|
|
1030
|
+
} else {
|
|
1031
|
+
ast = input;
|
|
1032
|
+
}
|
|
1033
|
+
return evaluate(ast, options);
|
|
1034
|
+
}
|
|
1035
|
+
function cleanFloat(val) {
|
|
1036
|
+
return Math.abs(val) < 1e-14 ? 0 : val;
|
|
1037
|
+
}
|
|
1038
|
+
function evaluate(node, options = {}) {
|
|
1039
|
+
switch (node.type) {
|
|
1040
|
+
case "Number":
|
|
1041
|
+
return node.value;
|
|
1042
|
+
case "Variable": {
|
|
1043
|
+
const val = options.variables?.[node.name];
|
|
1044
|
+
if (val === void 0) {
|
|
1045
|
+
throw new EvaluationError(`Undefined variable: ${node.name}`);
|
|
1046
|
+
}
|
|
1047
|
+
return val;
|
|
1048
|
+
}
|
|
1049
|
+
case "Constant": {
|
|
1050
|
+
if (node.name === "pi") return Math.PI;
|
|
1051
|
+
if (node.name === "e") return Math.E;
|
|
1052
|
+
if (node.name === "i") return new Complex(0, 1);
|
|
1053
|
+
throw new EvaluationError(`Unknown constant: ${node.name}`);
|
|
1054
|
+
}
|
|
1055
|
+
case "BinaryOperation": {
|
|
1056
|
+
const left = evaluate(node.left, options);
|
|
1057
|
+
const right = evaluate(node.right, options);
|
|
1058
|
+
switch (node.operator) {
|
|
1059
|
+
case "+":
|
|
1060
|
+
return mathAdd(left, right);
|
|
1061
|
+
case "-":
|
|
1062
|
+
return mathSub(left, right);
|
|
1063
|
+
case "*":
|
|
1064
|
+
return mathMul(left, right);
|
|
1065
|
+
case "/":
|
|
1066
|
+
return mathDiv(left, right);
|
|
1067
|
+
default:
|
|
1068
|
+
throw new EvaluationError(`Unknown operator: ${node.operator}`);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
case "UnaryOperation": {
|
|
1072
|
+
const operand = evaluate(node.operand, options);
|
|
1073
|
+
if (node.operator === "-") {
|
|
1074
|
+
if (typeof operand === "number") return -operand;
|
|
1075
|
+
if (operand instanceof Complex) return new Complex(-operand.re, -operand.im);
|
|
1076
|
+
throw new EvaluationError("Unary minus not implemented for this type");
|
|
1077
|
+
}
|
|
1078
|
+
if (node.operator === "+") return operand;
|
|
1079
|
+
throw new EvaluationError(`Unknown unary operator: ${node.operator}`);
|
|
1080
|
+
}
|
|
1081
|
+
case "Fraction": {
|
|
1082
|
+
const num = evaluate(node.numerator, options);
|
|
1083
|
+
const den = evaluate(node.denominator, options);
|
|
1084
|
+
return mathDiv(num, den);
|
|
1085
|
+
}
|
|
1086
|
+
case "Power": {
|
|
1087
|
+
const base = evaluate(node.base, options);
|
|
1088
|
+
const exponent = evaluate(node.exponent, options);
|
|
1089
|
+
return mathPow(base, exponent);
|
|
1090
|
+
}
|
|
1091
|
+
case "Root": {
|
|
1092
|
+
const index = evaluate(node.index, options);
|
|
1093
|
+
const radicand = evaluate(node.radicand, options);
|
|
1094
|
+
if (typeof index === "number" && typeof radicand === "number") {
|
|
1095
|
+
if (index % 2 === 0 && radicand < 0) {
|
|
1096
|
+
throw new EvaluationError("Even root of negative number");
|
|
1097
|
+
}
|
|
1098
|
+
if (radicand < 0) {
|
|
1099
|
+
return -Math.pow(-radicand, 1 / index);
|
|
1100
|
+
}
|
|
1101
|
+
return Math.pow(radicand, 1 / index);
|
|
1102
|
+
}
|
|
1103
|
+
throw new EvaluationError("Root not implemented for complex/matrices");
|
|
1104
|
+
}
|
|
1105
|
+
case "Function": {
|
|
1106
|
+
const arg = evaluate(node.args[0], options);
|
|
1107
|
+
if (node.name === "det" || node.name === "transpose") {
|
|
1108
|
+
if (!(arg instanceof MatrixValue)) throw new EvaluationError(`${node.name} requires a matrix`);
|
|
1109
|
+
if (node.name === "det") return arg.det();
|
|
1110
|
+
return arg.transpose();
|
|
1111
|
+
}
|
|
1112
|
+
if (node.name === "arg") {
|
|
1113
|
+
if (arg instanceof Complex) return Math.atan2(arg.im, arg.re);
|
|
1114
|
+
if (typeof arg === "number") return arg >= 0 ? 0 : Math.PI;
|
|
1115
|
+
throw new EvaluationError(`arg requires a number or complex number`);
|
|
1116
|
+
}
|
|
1117
|
+
if (typeof arg !== "number") {
|
|
1118
|
+
throw new EvaluationError(`Function ${node.name} not implemented for complex/matrices`);
|
|
1119
|
+
}
|
|
1120
|
+
switch (node.name) {
|
|
1121
|
+
case "sin":
|
|
1122
|
+
return cleanFloat(Math.sin(arg));
|
|
1123
|
+
case "cos":
|
|
1124
|
+
return cleanFloat(Math.cos(arg));
|
|
1125
|
+
case "tan":
|
|
1126
|
+
return cleanFloat(Math.tan(arg));
|
|
1127
|
+
case "arcsin":
|
|
1128
|
+
return cleanFloat(Math.asin(arg));
|
|
1129
|
+
case "arccos":
|
|
1130
|
+
return cleanFloat(Math.acos(arg));
|
|
1131
|
+
case "arctan":
|
|
1132
|
+
return cleanFloat(Math.atan(arg));
|
|
1133
|
+
case "ln":
|
|
1134
|
+
return cleanFloat(Math.log(arg));
|
|
1135
|
+
case "log":
|
|
1136
|
+
return cleanFloat(Math.log10(arg));
|
|
1137
|
+
case "exp":
|
|
1138
|
+
return cleanFloat(Math.exp(arg));
|
|
1139
|
+
default:
|
|
1140
|
+
throw new EvaluationError(`Unknown function: ${node.name}`);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
case "AbsoluteValue": {
|
|
1144
|
+
const val = evaluate(node.expression, options);
|
|
1145
|
+
if (typeof val === "number") return Math.abs(val);
|
|
1146
|
+
if (val instanceof Complex) return Math.sqrt(val.re * val.re + val.im * val.im);
|
|
1147
|
+
throw new EvaluationError("Absolute value not implemented for matrices");
|
|
1148
|
+
}
|
|
1149
|
+
case "Matrix": {
|
|
1150
|
+
const evaluatedRows = node.rows.map(
|
|
1151
|
+
(row) => row.map((cell) => evaluate(cell, options))
|
|
1152
|
+
);
|
|
1153
|
+
return new MatrixValue(evaluatedRows);
|
|
1154
|
+
}
|
|
1155
|
+
case "Derivative": {
|
|
1156
|
+
throw new EvaluationError("Cannot numerically evaluate symbolic derivative. Use simplify(differentiate(ast, var)) instead.");
|
|
1157
|
+
}
|
|
1158
|
+
case "Summation": {
|
|
1159
|
+
const lowerBound = evaluate(node.lowerBound, options);
|
|
1160
|
+
const upperBound = evaluate(node.upperBound, options);
|
|
1161
|
+
if (typeof lowerBound !== "number" || typeof upperBound !== "number") {
|
|
1162
|
+
throw new EvaluationError("Summation bounds must be real numbers");
|
|
1163
|
+
}
|
|
1164
|
+
let sum = 0;
|
|
1165
|
+
for (let i = lowerBound; i <= upperBound; i++) {
|
|
1166
|
+
const iterOptions = { ...options, variables: { ...options?.variables, [node.indexVariable]: i } };
|
|
1167
|
+
const val = evaluate(node.expression, iterOptions);
|
|
1168
|
+
if (typeof val !== "number") {
|
|
1169
|
+
throw new EvaluationError("Summation body must evaluate to a real number");
|
|
1170
|
+
}
|
|
1171
|
+
sum += val;
|
|
1172
|
+
}
|
|
1173
|
+
return cleanFloat(sum);
|
|
1174
|
+
}
|
|
1175
|
+
case "Integral": {
|
|
1176
|
+
if (node.lowerBound && node.upperBound) {
|
|
1177
|
+
const a = evaluate(node.lowerBound, options);
|
|
1178
|
+
const b = evaluate(node.upperBound, options);
|
|
1179
|
+
if (typeof a !== "number" || typeof b !== "number") throw new EvaluationError("Integral bounds must be real numbers");
|
|
1180
|
+
const steps = 1e3;
|
|
1181
|
+
const h = (b - a) / steps;
|
|
1182
|
+
let sum = 0;
|
|
1183
|
+
for (let i = 0; i <= steps; i++) {
|
|
1184
|
+
const x = a + i * h;
|
|
1185
|
+
const val = evaluate(node.expression, { ...options, variables: { ...options?.variables, [node.variable]: x } });
|
|
1186
|
+
if (typeof val !== "number") throw new EvaluationError("Integrand must evaluate to real number");
|
|
1187
|
+
const weight = i === 0 || i === steps ? 1 : i % 2 === 0 ? 2 : 4;
|
|
1188
|
+
sum += weight * val;
|
|
1189
|
+
}
|
|
1190
|
+
return sum * h / 3;
|
|
1191
|
+
}
|
|
1192
|
+
throw new EvaluationError("Cannot numerically evaluate indefinite integral");
|
|
1193
|
+
}
|
|
1194
|
+
default:
|
|
1195
|
+
throw new EvaluationError(`Unsupported evaluation for node type: ${node.type}`);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// src/simplifier/index.ts
|
|
1200
|
+
function simplify(node) {
|
|
1201
|
+
switch (node.type) {
|
|
1202
|
+
case "BinaryOperation": {
|
|
1203
|
+
const left = simplify(node.left);
|
|
1204
|
+
const right = simplify(node.right);
|
|
1205
|
+
if (left.type === "Number" && right.type === "Number") {
|
|
1206
|
+
switch (node.operator) {
|
|
1207
|
+
case "+":
|
|
1208
|
+
return { type: "Number", value: left.value + right.value };
|
|
1209
|
+
case "-":
|
|
1210
|
+
return { type: "Number", value: left.value - right.value };
|
|
1211
|
+
case "*":
|
|
1212
|
+
return { type: "Number", value: left.value * right.value };
|
|
1213
|
+
case "/":
|
|
1214
|
+
if (right.value !== 0) return { type: "Number", value: left.value / right.value };
|
|
1215
|
+
break;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (node.operator === "*") {
|
|
1219
|
+
if (left.type === "Number") {
|
|
1220
|
+
if (left.value === 0) return { type: "Number", value: 0 };
|
|
1221
|
+
if (left.value === 1) return right;
|
|
1222
|
+
}
|
|
1223
|
+
if (right.type === "Number") {
|
|
1224
|
+
if (right.value === 0) return { type: "Number", value: 0 };
|
|
1225
|
+
if (right.value === 1) return left;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
if (node.operator === "+") {
|
|
1229
|
+
if (left.type === "Number" && left.value === 0) return right;
|
|
1230
|
+
if (right.type === "Number" && right.value === 0) return left;
|
|
1231
|
+
}
|
|
1232
|
+
if (node.operator === "-") {
|
|
1233
|
+
if (right.type === "Number" && right.value === 0) return left;
|
|
1234
|
+
if (left.type === "Number" && left.value === 0) {
|
|
1235
|
+
return { type: "UnaryOperation", operator: "-", operand: right };
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (node.operator === "/") {
|
|
1239
|
+
if (right.type === "Number" && right.value === 1) return left;
|
|
1240
|
+
}
|
|
1241
|
+
return { ...node, left, right };
|
|
1242
|
+
}
|
|
1243
|
+
case "UnaryOperation": {
|
|
1244
|
+
const operand = simplify(node.operand);
|
|
1245
|
+
if (operand.type === "Number") {
|
|
1246
|
+
if (node.operator === "-") return { type: "Number", value: -operand.value };
|
|
1247
|
+
if (node.operator === "+") return { type: "Number", value: operand.value };
|
|
1248
|
+
}
|
|
1249
|
+
return { ...node, operand };
|
|
1250
|
+
}
|
|
1251
|
+
case "Fraction": {
|
|
1252
|
+
const numerator = simplify(node.numerator);
|
|
1253
|
+
const denominator = simplify(node.denominator);
|
|
1254
|
+
if (numerator.type === "Number" && numerator.value === 0 && (denominator.type !== "Number" || denominator.value !== 0)) {
|
|
1255
|
+
return { type: "Number", value: 0 };
|
|
1256
|
+
}
|
|
1257
|
+
return { ...node, numerator, denominator };
|
|
1258
|
+
}
|
|
1259
|
+
case "Power": {
|
|
1260
|
+
const base = simplify(node.base);
|
|
1261
|
+
const exponent = simplify(node.exponent);
|
|
1262
|
+
if (exponent.type === "Number" && exponent.value === 1) return base;
|
|
1263
|
+
if (exponent.type === "Number" && exponent.value === 0) return { type: "Number", value: 1 };
|
|
1264
|
+
if (base.type === "Number" && base.value === 1) return { type: "Number", value: 1 };
|
|
1265
|
+
return { ...node, base, exponent };
|
|
1266
|
+
}
|
|
1267
|
+
case "Root": {
|
|
1268
|
+
const radicand = simplify(node.radicand);
|
|
1269
|
+
const index = simplify(node.index);
|
|
1270
|
+
return { ...node, radicand, index };
|
|
1271
|
+
}
|
|
1272
|
+
case "AbsoluteValue": {
|
|
1273
|
+
const expression = simplify(node.expression);
|
|
1274
|
+
if (expression.type === "Number") {
|
|
1275
|
+
return { type: "Number", value: Math.abs(expression.value) };
|
|
1276
|
+
}
|
|
1277
|
+
return { ...node, expression };
|
|
1278
|
+
}
|
|
1279
|
+
case "Function": {
|
|
1280
|
+
const args = node.args.map(simplify);
|
|
1281
|
+
return { ...node, args };
|
|
1282
|
+
}
|
|
1283
|
+
default:
|
|
1284
|
+
return node;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
function expand(node) {
|
|
1288
|
+
throw new UnsupportedExpressionError("Algebraic expansion is not fully implemented yet.");
|
|
1289
|
+
}
|
|
1290
|
+
function factor(node) {
|
|
1291
|
+
throw new UnsupportedExpressionError("Algebraic factorization is not fully implemented yet.");
|
|
1292
|
+
}
|
|
1293
|
+
function substitute(node, variables) {
|
|
1294
|
+
switch (node.type) {
|
|
1295
|
+
case "Variable": {
|
|
1296
|
+
const sub = variables[node.name];
|
|
1297
|
+
if (sub !== void 0) {
|
|
1298
|
+
if (typeof sub === "number") {
|
|
1299
|
+
return { type: "Number", value: sub };
|
|
1300
|
+
}
|
|
1301
|
+
return JSON.parse(JSON.stringify(sub));
|
|
1302
|
+
}
|
|
1303
|
+
return { ...node };
|
|
1304
|
+
}
|
|
1305
|
+
case "BinaryOperation":
|
|
1306
|
+
return {
|
|
1307
|
+
...node,
|
|
1308
|
+
left: substitute(node.left, variables),
|
|
1309
|
+
right: substitute(node.right, variables)
|
|
1310
|
+
};
|
|
1311
|
+
case "UnaryOperation":
|
|
1312
|
+
return {
|
|
1313
|
+
...node,
|
|
1314
|
+
operand: substitute(node.operand, variables)
|
|
1315
|
+
};
|
|
1316
|
+
case "Fraction":
|
|
1317
|
+
return {
|
|
1318
|
+
...node,
|
|
1319
|
+
numerator: substitute(node.numerator, variables),
|
|
1320
|
+
denominator: substitute(node.denominator, variables)
|
|
1321
|
+
};
|
|
1322
|
+
case "Power":
|
|
1323
|
+
return {
|
|
1324
|
+
...node,
|
|
1325
|
+
base: substitute(node.base, variables),
|
|
1326
|
+
exponent: substitute(node.exponent, variables)
|
|
1327
|
+
};
|
|
1328
|
+
case "Root":
|
|
1329
|
+
return {
|
|
1330
|
+
...node,
|
|
1331
|
+
radicand: substitute(node.radicand, variables),
|
|
1332
|
+
index: substitute(node.index, variables)
|
|
1333
|
+
};
|
|
1334
|
+
case "AbsoluteValue":
|
|
1335
|
+
return {
|
|
1336
|
+
...node,
|
|
1337
|
+
expression: substitute(node.expression, variables)
|
|
1338
|
+
};
|
|
1339
|
+
case "Function":
|
|
1340
|
+
return {
|
|
1341
|
+
...node,
|
|
1342
|
+
args: node.args.map((arg) => substitute(arg, variables))
|
|
1343
|
+
};
|
|
1344
|
+
default:
|
|
1345
|
+
return { ...node };
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// src/printer/index.ts
|
|
1350
|
+
function toLatex(node) {
|
|
1351
|
+
switch (node.type) {
|
|
1352
|
+
case "Number":
|
|
1353
|
+
return node.value.toString();
|
|
1354
|
+
case "Variable":
|
|
1355
|
+
return node.name;
|
|
1356
|
+
case "Constant":
|
|
1357
|
+
if (node.name === "pi") return "\\pi";
|
|
1358
|
+
return node.name;
|
|
1359
|
+
// e.g. e, i
|
|
1360
|
+
case "BinaryOperation": {
|
|
1361
|
+
const left = toLatex(node.left);
|
|
1362
|
+
const right = toLatex(node.right);
|
|
1363
|
+
const wrapLeft = (node.operator === "*" || node.operator === "/") && (node.left.type === "BinaryOperation" && (node.left.operator === "+" || node.left.operator === "-"));
|
|
1364
|
+
const wrapRight = (node.operator === "*" || node.operator === "/") && (node.right.type === "BinaryOperation" && (node.right.operator === "+" || node.right.operator === "-"));
|
|
1365
|
+
const leftStr = wrapLeft ? `\\left(${left}\\right)` : left;
|
|
1366
|
+
const rightStr = wrapRight ? `\\left(${right}\\right)` : right;
|
|
1367
|
+
if (node.operator === "*") {
|
|
1368
|
+
if (node.implicit) {
|
|
1369
|
+
if (node.left.type === "Number" && node.right.type === "Number") {
|
|
1370
|
+
return `${leftStr} \\cdot ${rightStr}`;
|
|
1371
|
+
}
|
|
1372
|
+
return `${leftStr}${rightStr}`;
|
|
1373
|
+
}
|
|
1374
|
+
return `${leftStr} \\cdot ${rightStr}`;
|
|
1375
|
+
}
|
|
1376
|
+
if (node.operator === "/") {
|
|
1377
|
+
return `\\frac{${leftStr}}{${rightStr}}`;
|
|
1378
|
+
}
|
|
1379
|
+
return `${leftStr} ${node.operator} ${rightStr}`;
|
|
1380
|
+
}
|
|
1381
|
+
case "UnaryOperation": {
|
|
1382
|
+
const operand = toLatex(node.operand);
|
|
1383
|
+
const wrap = node.operand.type === "BinaryOperation" && (node.operand.operator === "+" || node.operand.operator === "-");
|
|
1384
|
+
const opStr = wrap ? `\\left(${operand}\\right)` : operand;
|
|
1385
|
+
return `${node.operator}${opStr}`;
|
|
1386
|
+
}
|
|
1387
|
+
case "Fraction":
|
|
1388
|
+
return `\\frac{${toLatex(node.numerator)}}{${toLatex(node.denominator)}}`;
|
|
1389
|
+
case "Power": {
|
|
1390
|
+
const base = toLatex(node.base);
|
|
1391
|
+
const wrapBase = node.base.type === "BinaryOperation" || node.base.type === "Fraction" || node.base.type === "Root";
|
|
1392
|
+
const baseStr = wrapBase ? `\\left(${base}\\right)` : base;
|
|
1393
|
+
return `${baseStr}^{${toLatex(node.exponent)}}`;
|
|
1394
|
+
}
|
|
1395
|
+
case "Root": {
|
|
1396
|
+
const radicand = toLatex(node.radicand);
|
|
1397
|
+
if (node.index.type === "Number" && node.index.value === 2) {
|
|
1398
|
+
return `\\sqrt{${radicand}}`;
|
|
1399
|
+
}
|
|
1400
|
+
return `\\sqrt[${toLatex(node.index)}]{${radicand}}`;
|
|
1401
|
+
}
|
|
1402
|
+
case "AbsoluteValue":
|
|
1403
|
+
return `\\left|${toLatex(node.expression)}\\right|`;
|
|
1404
|
+
case "Function":
|
|
1405
|
+
return `\\${node.name}\\left(${toLatex(node.args[0])}\\right)`;
|
|
1406
|
+
default:
|
|
1407
|
+
return "";
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
// src/calculus/index.ts
|
|
1412
|
+
function differentiate(node, variable) {
|
|
1413
|
+
switch (node.type) {
|
|
1414
|
+
case "Number":
|
|
1415
|
+
case "Constant":
|
|
1416
|
+
return { type: "Number", value: 0 };
|
|
1417
|
+
case "Variable":
|
|
1418
|
+
return node.name === variable ? { type: "Number", value: 1 } : { type: "Number", value: 0 };
|
|
1419
|
+
case "BinaryOperation":
|
|
1420
|
+
if (node.operator === "+") {
|
|
1421
|
+
return { type: "BinaryOperation", operator: "+", left: differentiate(node.left, variable), right: differentiate(node.right, variable), implicit: false };
|
|
1422
|
+
}
|
|
1423
|
+
if (node.operator === "-") {
|
|
1424
|
+
return { type: "BinaryOperation", operator: "-", left: differentiate(node.left, variable), right: differentiate(node.right, variable), implicit: false };
|
|
1425
|
+
}
|
|
1426
|
+
if (node.operator === "*") {
|
|
1427
|
+
return {
|
|
1428
|
+
type: "BinaryOperation",
|
|
1429
|
+
operator: "+",
|
|
1430
|
+
implicit: false,
|
|
1431
|
+
left: { type: "BinaryOperation", operator: "*", implicit: false, left: differentiate(node.left, variable), right: node.right },
|
|
1432
|
+
right: { type: "BinaryOperation", operator: "*", implicit: false, left: node.left, right: differentiate(node.right, variable) }
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
if (node.operator === "/") {
|
|
1436
|
+
const num = {
|
|
1437
|
+
type: "BinaryOperation",
|
|
1438
|
+
operator: "-",
|
|
1439
|
+
implicit: false,
|
|
1440
|
+
left: { type: "BinaryOperation", operator: "*", implicit: false, left: differentiate(node.left, variable), right: node.right },
|
|
1441
|
+
right: { type: "BinaryOperation", operator: "*", implicit: false, left: node.left, right: differentiate(node.right, variable) }
|
|
1442
|
+
};
|
|
1443
|
+
const den = { type: "Power", base: node.right, exponent: { type: "Number", value: 2 } };
|
|
1444
|
+
return { type: "BinaryOperation", operator: "/", implicit: false, left: num, right: den };
|
|
1445
|
+
}
|
|
1446
|
+
break;
|
|
1447
|
+
case "Power":
|
|
1448
|
+
const n = node.exponent;
|
|
1449
|
+
const baseDeriv = differentiate(node.base, variable);
|
|
1450
|
+
const newExponent = { type: "BinaryOperation", operator: "-", implicit: false, left: n, right: { type: "Number", value: 1 } };
|
|
1451
|
+
return {
|
|
1452
|
+
type: "BinaryOperation",
|
|
1453
|
+
operator: "*",
|
|
1454
|
+
implicit: false,
|
|
1455
|
+
left: {
|
|
1456
|
+
type: "BinaryOperation",
|
|
1457
|
+
operator: "*",
|
|
1458
|
+
implicit: false,
|
|
1459
|
+
left: n,
|
|
1460
|
+
right: { type: "Power", base: node.base, exponent: newExponent }
|
|
1461
|
+
},
|
|
1462
|
+
right: baseDeriv
|
|
1463
|
+
};
|
|
1464
|
+
case "Function":
|
|
1465
|
+
const arg = node.args[0];
|
|
1466
|
+
const argDeriv = differentiate(arg, variable);
|
|
1467
|
+
let outer;
|
|
1468
|
+
switch (node.name) {
|
|
1469
|
+
case "sin":
|
|
1470
|
+
outer = { type: "Function", name: "cos", args: [arg] };
|
|
1471
|
+
break;
|
|
1472
|
+
case "cos":
|
|
1473
|
+
outer = { type: "BinaryOperation", operator: "*", implicit: false, left: { type: "Number", value: -1 }, right: { type: "Function", name: "sin", args: [arg] } };
|
|
1474
|
+
break;
|
|
1475
|
+
case "tan":
|
|
1476
|
+
outer = { type: "Power", base: { type: "Function", name: "cos", args: [arg] }, exponent: { type: "Number", value: -2 } };
|
|
1477
|
+
break;
|
|
1478
|
+
case "exp":
|
|
1479
|
+
outer = node;
|
|
1480
|
+
break;
|
|
1481
|
+
case "ln":
|
|
1482
|
+
outer = { type: "BinaryOperation", operator: "/", implicit: false, left: { type: "Number", value: 1 }, right: arg };
|
|
1483
|
+
break;
|
|
1484
|
+
default:
|
|
1485
|
+
throw new Error(`Derivative not implemented for ${node.name}`);
|
|
1486
|
+
}
|
|
1487
|
+
return { type: "BinaryOperation", operator: "*", implicit: false, left: outer, right: argDeriv };
|
|
1488
|
+
}
|
|
1489
|
+
throw new Error(`Derivative not implemented for ${node.type}`);
|
|
1490
|
+
}
|
|
1491
|
+
function integrate(node, variable) {
|
|
1492
|
+
throw new UnsupportedExpressionError("Symbolic integration is not fully implemented yet.");
|
|
1493
|
+
}
|
|
1494
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1495
|
+
0 && (module.exports = {
|
|
1496
|
+
Complex,
|
|
1497
|
+
DimensionMismatchError,
|
|
1498
|
+
DivisionByZeroError,
|
|
1499
|
+
EvaluationError,
|
|
1500
|
+
LatexParseError,
|
|
1501
|
+
LatexTokenError,
|
|
1502
|
+
MatrixValue,
|
|
1503
|
+
TokenType,
|
|
1504
|
+
UndefinedVariableError,
|
|
1505
|
+
UnsupportedExpressionError,
|
|
1506
|
+
differentiate,
|
|
1507
|
+
evaluate,
|
|
1508
|
+
evaluateLatex,
|
|
1509
|
+
expand,
|
|
1510
|
+
factor,
|
|
1511
|
+
integrate,
|
|
1512
|
+
mathAdd,
|
|
1513
|
+
mathDiv,
|
|
1514
|
+
mathMul,
|
|
1515
|
+
mathPow,
|
|
1516
|
+
mathSub,
|
|
1517
|
+
normalizeLatex,
|
|
1518
|
+
parseLatex,
|
|
1519
|
+
simplify,
|
|
1520
|
+
substitute,
|
|
1521
|
+
toLatex,
|
|
1522
|
+
tokenize
|
|
1523
|
+
});
|