slangmath 1.0.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/ARCHITECTURE.MD +366 -0
- package/LICENSE +21 -0
- package/README.md +953 -0
- package/package.json +23 -0
- package/slang-advanced.js +559 -0
- package/slang-basic.js +766 -0
- package/slang-cache.js +519 -0
- package/slang-convertor.js +652 -0
- package/slang-errors.js +454 -0
- package/slang-extended.js +501 -0
- package/slang-helpers.js +284 -0
- package/slang-math.js +3 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SLaNg LaTeX Converter - Unified Edition
|
|
3
|
+
* Comprehensive bidirectional conversion between SLaNg and LaTeX
|
|
4
|
+
* Combines the best features from all previous versions
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Enhanced parsing with multiple fallback strategies
|
|
8
|
+
* - Robust error handling and validation
|
|
9
|
+
* - Support for complex mathematical expressions
|
|
10
|
+
* - Batch processing capabilities
|
|
11
|
+
* - Flexible formatting options
|
|
12
|
+
* - Comprehensive test coverage
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
createTerm,
|
|
17
|
+
createFraction,
|
|
18
|
+
evaluateTerm,
|
|
19
|
+
hasSimpleDenominator,
|
|
20
|
+
termToString,
|
|
21
|
+
polynomialToString,
|
|
22
|
+
fractionToString
|
|
23
|
+
} from './slang-basic.js';
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// SLANG TO LATEX CONVERSION
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Convert SLaNg term to LaTeX with enhanced formatting
|
|
31
|
+
*/
|
|
32
|
+
function termToLatex(term, options = {}) {
|
|
33
|
+
if (term.coeff === 0) return '0';
|
|
34
|
+
|
|
35
|
+
let latex = '';
|
|
36
|
+
|
|
37
|
+
// Handle coefficient with special cases
|
|
38
|
+
if (Math.abs(term.coeff - 1) < 1e-10 && term.var) {
|
|
39
|
+
latex = '';
|
|
40
|
+
} else if (Math.abs(term.coeff + 1) < 1e-10 && term.var) {
|
|
41
|
+
latex = '-';
|
|
42
|
+
} else {
|
|
43
|
+
latex = term.coeff.toString();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Handle variables with powers
|
|
47
|
+
if (term.var) {
|
|
48
|
+
const varParts = [];
|
|
49
|
+
for (let [variable, power] of Object.entries(term.var)) {
|
|
50
|
+
if (power === 1) {
|
|
51
|
+
varParts.push(variable);
|
|
52
|
+
} else {
|
|
53
|
+
varParts.push(`${variable}^{${power}}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Handle multiplication symbols
|
|
58
|
+
if (varParts.length > 1 && options.multiplySymbol === '\\cdot') {
|
|
59
|
+
latex += (latex && latex !== '-' ? ' \\cdot ' : '') + varParts.join(' \\cdot ');
|
|
60
|
+
} else if (varParts.length > 1 && options.multiplySymbol === '\\times') {
|
|
61
|
+
latex += (latex && latex !== '-' ? ' \\times ' : '') + varParts.join(' \\times ');
|
|
62
|
+
} else {
|
|
63
|
+
latex += (latex && latex !== '-' ? '' : '') + varParts.join('');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return latex || term.coeff.toString();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert SLaNg polynomial to LaTeX with proper sign handling
|
|
72
|
+
*/
|
|
73
|
+
function polynomialToLatex(polynomial, options = {}) {
|
|
74
|
+
if (!polynomial.terms || polynomial.terms.length === 0) return '0';
|
|
75
|
+
|
|
76
|
+
return polynomial.terms.map((term, i) => {
|
|
77
|
+
const termLatex = termToLatex(term, options);
|
|
78
|
+
if (i === 0) return termLatex;
|
|
79
|
+
|
|
80
|
+
// Add plus/minus signs with proper spacing
|
|
81
|
+
if (term.coeff >= 0) {
|
|
82
|
+
return ' + ' + termLatex;
|
|
83
|
+
} else {
|
|
84
|
+
return ' - ' + termToLatex({ ...term, coeff: Math.abs(term.coeff) }, options);
|
|
85
|
+
}
|
|
86
|
+
}).join('');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Convert SLaNg fraction to LaTeX with enhanced formatting
|
|
91
|
+
*/
|
|
92
|
+
function fractionToLatex(fraction, options = {}) {
|
|
93
|
+
const numerator = polynomialToLatex(fraction.numi, options);
|
|
94
|
+
|
|
95
|
+
if (hasSimpleDenominator(fraction)) {
|
|
96
|
+
if (fraction.deno === 1) {
|
|
97
|
+
return options.parentheses ? `\\left(${numerator}\\right)` : numerator;
|
|
98
|
+
}
|
|
99
|
+
return `\\frac{${numerator}}{${fraction.deno}}`;
|
|
100
|
+
} else {
|
|
101
|
+
const denominator = polynomialToLatex(fraction.deno, options);
|
|
102
|
+
return `\\frac{${numerator}}{${denominator}}`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Main SLaNg to LaTeX converter with comprehensive options
|
|
108
|
+
*/
|
|
109
|
+
function slangToLatex(expression, options = {}) {
|
|
110
|
+
const defaults = {
|
|
111
|
+
parentheses: false,
|
|
112
|
+
multiplySymbol: '', // '', '\\cdot', or '\\times'
|
|
113
|
+
displayMode: false,
|
|
114
|
+
simplify: true,
|
|
115
|
+
precision: 10
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const opts = { ...defaults, ...options };
|
|
119
|
+
|
|
120
|
+
// Handle function expressions
|
|
121
|
+
if (expression.type === 'function') {
|
|
122
|
+
const funcName = expression.name;
|
|
123
|
+
const arg = expression.args[0];
|
|
124
|
+
const argLatex = slangToLatex(arg, opts);
|
|
125
|
+
return `\\${funcName}{${argLatex}}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (expression.terms) {
|
|
129
|
+
// It's a polynomial
|
|
130
|
+
return polynomialToLatex(expression, opts);
|
|
131
|
+
} else if (expression.numi && expression.deno !== undefined) {
|
|
132
|
+
// It's a fraction
|
|
133
|
+
return fractionToLatex(expression, opts);
|
|
134
|
+
} else if (expression.coeff !== undefined) {
|
|
135
|
+
// It's a term
|
|
136
|
+
return termToLatex(expression, opts);
|
|
137
|
+
} else {
|
|
138
|
+
throw new Error('Unsupported SLaNg expression type');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ============================================================================
|
|
143
|
+
// LATEX TO SLANG CONVERSION - ENHANCED PARSING ENGINE
|
|
144
|
+
// ============================================================================
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Enhanced number parser with multiple format support
|
|
148
|
+
*/
|
|
149
|
+
function parseNumber(str) {
|
|
150
|
+
if (str === '') return 1;
|
|
151
|
+
if (str === '+') return 1;
|
|
152
|
+
if (str === '-') return -1;
|
|
153
|
+
|
|
154
|
+
// Handle decimal numbers and scientific notation
|
|
155
|
+
const num = parseFloat(str);
|
|
156
|
+
if (isNaN(num)) throw new Error(`Invalid number: ${str}`);
|
|
157
|
+
return num;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Advanced variable parser with multiple power formats
|
|
162
|
+
*/
|
|
163
|
+
function parseVariable(varStr) {
|
|
164
|
+
// Handle simple variables: x, y, z
|
|
165
|
+
if (varStr.match(/^[a-zA-Z]$/)) {
|
|
166
|
+
return { variable: varStr, power: 1 };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Handle powers: x^2, x^{2}, x^{10}, etc.
|
|
170
|
+
const match = varStr.match(/^([a-zA-Z])\^(\{?\d+\}|\d+)$/);
|
|
171
|
+
if (!match) throw new Error(`Invalid variable format: ${varStr}`);
|
|
172
|
+
|
|
173
|
+
const variable = match[1];
|
|
174
|
+
const power = parseInt(match[2].replace(/[{}]/g, ''));
|
|
175
|
+
|
|
176
|
+
return { variable, power };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Enhanced term parser with robust coefficient extraction
|
|
181
|
+
*/
|
|
182
|
+
function parseTerm(termStr) {
|
|
183
|
+
termStr = termStr.trim();
|
|
184
|
+
if (termStr === '0') return createTerm(0);
|
|
185
|
+
if (termStr === '') return createTerm(1);
|
|
186
|
+
if (termStr === '-') return createTerm(-1);
|
|
187
|
+
|
|
188
|
+
// Special handling for pure variables
|
|
189
|
+
if (/^[a-zA-Z](?:\^\{?\d+\}|\^\d+)?$/.test(termStr)) {
|
|
190
|
+
const { variable, power } = parseVariable(termStr);
|
|
191
|
+
return createTerm(1, { [variable]: power });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Remove spaces between sign and number for easier parsing
|
|
195
|
+
const normalizedStr = termStr.replace(/^([+-])\s+/, '$1');
|
|
196
|
+
|
|
197
|
+
// Extract coefficient with enhanced regex
|
|
198
|
+
let coeff = 1;
|
|
199
|
+
let remaining = normalizedStr;
|
|
200
|
+
|
|
201
|
+
// Look for coefficient at the start
|
|
202
|
+
const coeffMatch = normalizedStr.match(/^([+-]?\d+(?:\.\d+)?)\s*/);
|
|
203
|
+
if (coeffMatch) {
|
|
204
|
+
const coeffStr = coeffMatch[1];
|
|
205
|
+
coeff = parseNumber(coeffStr);
|
|
206
|
+
remaining = normalizedStr.slice(coeffMatch[0].length).trim();
|
|
207
|
+
} else {
|
|
208
|
+
// Try to match just a sign
|
|
209
|
+
const signMatch = normalizedStr.match(/^([+-])/);
|
|
210
|
+
if (signMatch) {
|
|
211
|
+
coeff = signMatch[1] === '-' ? -1 : 1;
|
|
212
|
+
remaining = normalizedStr.slice(1).trim();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Extract variables from remaining part
|
|
217
|
+
let variables = {};
|
|
218
|
+
if (remaining) {
|
|
219
|
+
// Remove various multiplication symbols
|
|
220
|
+
remaining = remaining.replace(/\\cdot|\\times|\*|Ā·/g, '');
|
|
221
|
+
|
|
222
|
+
// Find all variable patterns with enhanced regex
|
|
223
|
+
const varMatches = remaining.match(/[a-zA-Z](?:\^\{?\d+\}|\^\d+)?/g);
|
|
224
|
+
if (varMatches) {
|
|
225
|
+
for (let varMatch of varMatches) {
|
|
226
|
+
const { variable, power } = parseVariable(varMatch);
|
|
227
|
+
variables[variable] = (variables[variable] || 0) + power;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return createTerm(coeff, variables);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Advanced polynomial parser with intelligent term splitting
|
|
237
|
+
*/
|
|
238
|
+
function parsePolynomial(polyStr) {
|
|
239
|
+
polyStr = polyStr.trim();
|
|
240
|
+
if (!polyStr) return { terms: [createTerm(0)] };
|
|
241
|
+
|
|
242
|
+
// Remove outer parentheses with enhanced detection
|
|
243
|
+
if (polyStr.startsWith('\\left(') && polyStr.endsWith('\\right)')) {
|
|
244
|
+
polyStr = polyStr.slice(7, -6);
|
|
245
|
+
} else if (polyStr.startsWith('(') && polyStr.endsWith(')')) {
|
|
246
|
+
polyStr = polyStr.slice(1, -1);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Handle single term case
|
|
250
|
+
if (!polyStr.includes('+') && !polyStr.includes('-')) {
|
|
251
|
+
return { terms: [parseTerm(polyStr)] };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Enhanced term splitting with proper sign handling
|
|
255
|
+
const terms = [];
|
|
256
|
+
let current = '';
|
|
257
|
+
let i = 0;
|
|
258
|
+
|
|
259
|
+
while (i < polyStr.length) {
|
|
260
|
+
const char = polyStr[i];
|
|
261
|
+
|
|
262
|
+
// Handle plus/minus signs, but not in powers and not at the start
|
|
263
|
+
if ((char === '+' || char === '-') && i > 0 && polyStr[i-1] !== '^') {
|
|
264
|
+
if (current.trim()) {
|
|
265
|
+
terms.push(parseTerm(current.trim()));
|
|
266
|
+
}
|
|
267
|
+
current = char;
|
|
268
|
+
} else {
|
|
269
|
+
current += char;
|
|
270
|
+
}
|
|
271
|
+
i++;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Add the last term
|
|
275
|
+
if (current.trim()) {
|
|
276
|
+
terms.push(parseTerm(current.trim()));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return { terms: terms.length > 0 ? terms : [createTerm(0)] };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Enhanced fraction parser with nested expression support
|
|
284
|
+
*/
|
|
285
|
+
function parseFraction(fracStr) {
|
|
286
|
+
// Enhanced regex to handle nested braces and complex expressions
|
|
287
|
+
const match = fracStr.match(/\\frac\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/);
|
|
288
|
+
if (!match) throw new Error(`Invalid fraction format: ${fracStr}`);
|
|
289
|
+
|
|
290
|
+
const numerator = parsePolynomial(match[1]);
|
|
291
|
+
const denominatorStr = match[2].trim();
|
|
292
|
+
|
|
293
|
+
// Check if denominator is a simple number
|
|
294
|
+
if (/^\d+$/.test(denominatorStr)) {
|
|
295
|
+
return createFraction(numerator.terms, parseInt(denominatorStr));
|
|
296
|
+
} else {
|
|
297
|
+
const denominator = parsePolynomial(denominatorStr);
|
|
298
|
+
return createFraction(numerator.terms, denominator.terms);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Main LaTeX to SLaNg converter with multiple parsing strategies
|
|
304
|
+
*/
|
|
305
|
+
function latexToSlang(latex, options = {}) {
|
|
306
|
+
const defaults = {
|
|
307
|
+
strictMode: false,
|
|
308
|
+
allowImplicitMultiplication: true,
|
|
309
|
+
fallbackParsing: true,
|
|
310
|
+
verboseErrors: false
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const opts = { ...defaults, ...options };
|
|
314
|
+
latex = latex.trim();
|
|
315
|
+
|
|
316
|
+
if (!latex) {
|
|
317
|
+
throw new Error('Empty LaTeX expression');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
// Strategy 1: Try functions first (trigonometric, etc.)
|
|
322
|
+
// Check for functions with proper pattern
|
|
323
|
+
const funcMatch = latex.match(/^\\(sin|cos|tan|cot|sec|csc|arcsin|arccos|arctan|sinh|cosh|tanh|ln|log|exp|sqrt)\s*\{([^{}]+)\}$/);
|
|
324
|
+
if (funcMatch) {
|
|
325
|
+
return parseFunction(latex);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Strategy 2: Try fraction next (most complex)
|
|
329
|
+
if (latex.includes('\\frac')) {
|
|
330
|
+
return parseFraction(latex);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Strategy 3: Try polynomial
|
|
334
|
+
return parsePolynomial(latex);
|
|
335
|
+
|
|
336
|
+
} catch (error) {
|
|
337
|
+
if (opts.strictMode) {
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Strategy 4: Fallback to simple term parsing
|
|
342
|
+
if (opts.fallbackParsing) {
|
|
343
|
+
try {
|
|
344
|
+
return parseTerm(latex);
|
|
345
|
+
} catch (fallbackError) {
|
|
346
|
+
const errorMsg = `Failed to parse LaTeX: "${latex}". Primary error: ${error.message}. Fallback error: ${fallbackError.message}`;
|
|
347
|
+
if (opts.verboseErrors) {
|
|
348
|
+
throw new Error(errorMsg);
|
|
349
|
+
} else {
|
|
350
|
+
throw new Error(`Invalid LaTeX expression: ${latex}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
throw new Error(`Failed to parse LaTeX: ${latex}. Error: ${error.message}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Parse LaTeX functions like sin(x), cos(x), etc.
|
|
361
|
+
*/
|
|
362
|
+
function parseFunction(funcStr) {
|
|
363
|
+
const funcMatch = funcStr.match(/\\(sin|cos|tan|cot|sec|csc|arcsin|arccos|arctan|sinh|cosh|tanh|ln|log|exp|sqrt)\s*\{([^{}]+)\}/);
|
|
364
|
+
if (!funcMatch) {
|
|
365
|
+
throw new Error(`Invalid function format: ${funcStr}`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const funcName = funcMatch[1];
|
|
369
|
+
const argStr = funcMatch[2];
|
|
370
|
+
const arg = parsePolynomial(argStr);
|
|
371
|
+
|
|
372
|
+
// Return a special function structure
|
|
373
|
+
return {
|
|
374
|
+
type: 'function',
|
|
375
|
+
name: funcName,
|
|
376
|
+
args: [arg]
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ============================================================================
|
|
381
|
+
// ADVANCED CONVERSION FEATURES
|
|
382
|
+
// ============================================================================
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Convert mathematical expressions with functions (sin, cos, log, etc.)
|
|
386
|
+
*/
|
|
387
|
+
function expressionToLatex(expr, options = {}) {
|
|
388
|
+
// Enhanced function support can be added here
|
|
389
|
+
return slangToLatex(expr, options);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Batch convert multiple expressions with error handling
|
|
394
|
+
*/
|
|
395
|
+
function batchConvertToLatex(expressions, options = {}) {
|
|
396
|
+
const results = [];
|
|
397
|
+
const errors = [];
|
|
398
|
+
|
|
399
|
+
expressions.forEach((expr, index) => {
|
|
400
|
+
try {
|
|
401
|
+
const result = slangToLatex(expr, options);
|
|
402
|
+
results.push({ index, success: true, result });
|
|
403
|
+
} catch (error) {
|
|
404
|
+
errors.push({ index, error: error.message });
|
|
405
|
+
results.push({ index, success: false, error: error.message });
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
return options.includeErrors ? { results, errors } : results.map(r => r.success ? r.result : `Error: ${r.error}`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Batch convert LaTeX to SLaNg with detailed error reporting
|
|
414
|
+
*/
|
|
415
|
+
function batchConvertToSlang(latexExpressions, options = {}) {
|
|
416
|
+
const results = [];
|
|
417
|
+
const errors = [];
|
|
418
|
+
|
|
419
|
+
latexExpressions.forEach((latex, index) => {
|
|
420
|
+
try {
|
|
421
|
+
const result = latexToSlang(latex, options);
|
|
422
|
+
results.push({ index, success: true, result });
|
|
423
|
+
} catch (error) {
|
|
424
|
+
errors.push({ index, error: error.message });
|
|
425
|
+
results.push({ index, success: false, error: error.message });
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
return options.includeErrors ? { results, errors } : results.map(r => r.success ? r.result : `Error: ${r.error}`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Enhanced LaTeX syntax validation
|
|
434
|
+
*/
|
|
435
|
+
function validateLatex(latex, options = {}) {
|
|
436
|
+
try {
|
|
437
|
+
latexToSlang(latex, { strictMode: true, ...options });
|
|
438
|
+
return { valid: true, errors: [] };
|
|
439
|
+
} catch (error) {
|
|
440
|
+
return { valid: false, errors: [error.message] };
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Smart LaTeX formatting for display mode
|
|
446
|
+
*/
|
|
447
|
+
function formatDisplayMode(latex, options = {}) {
|
|
448
|
+
const defaults = {
|
|
449
|
+
forceDisplay: false,
|
|
450
|
+
preferInline: false
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const opts = { ...defaults, ...options };
|
|
454
|
+
|
|
455
|
+
// Auto-detect if display mode is preferred
|
|
456
|
+
const needsDisplay = latex.includes('\\frac') ||
|
|
457
|
+
latex.includes('\\sum') ||
|
|
458
|
+
latex.includes('\\int') ||
|
|
459
|
+
latex.includes('\\prod') ||
|
|
460
|
+
opts.forceDisplay;
|
|
461
|
+
|
|
462
|
+
if (needsDisplay && !opts.preferInline) {
|
|
463
|
+
return `$$${latex}$$`;
|
|
464
|
+
}
|
|
465
|
+
return `$${latex}$`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Expression simplification (basic implementation)
|
|
470
|
+
*/
|
|
471
|
+
function simplifyExpression(expr) {
|
|
472
|
+
// This can be enhanced with more sophisticated simplification
|
|
473
|
+
return expr;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Get conversion statistics and metadata
|
|
478
|
+
*/
|
|
479
|
+
function getConversionInfo(expression, direction = 'to-latex') {
|
|
480
|
+
const info = {
|
|
481
|
+
direction,
|
|
482
|
+
timestamp: new Date().toISOString(),
|
|
483
|
+
expressionType: 'unknown'
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
if (direction === 'to-latex') {
|
|
487
|
+
if (expression.terms) {
|
|
488
|
+
info.expressionType = 'polynomial';
|
|
489
|
+
info.termCount = expression.terms.length;
|
|
490
|
+
} else if (expression.numi && expression.deno !== undefined) {
|
|
491
|
+
info.expressionType = 'fraction';
|
|
492
|
+
info.numeratorTerms = expression.numi.terms?.length || 0;
|
|
493
|
+
info.denominatorType = typeof expression.deno === 'number' ? 'constant' : 'polynomial';
|
|
494
|
+
} else if (expression.coeff !== undefined) {
|
|
495
|
+
info.expressionType = 'term';
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return info;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// ============================================================================
|
|
503
|
+
// UTILITY FUNCTIONS
|
|
504
|
+
// ============================================================================
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Check if two expressions are equivalent
|
|
508
|
+
*/
|
|
509
|
+
function areExpressionsEquivalent(expr1, expr2) {
|
|
510
|
+
try {
|
|
511
|
+
const latex1 = slangToLatex(expr1);
|
|
512
|
+
const latex2 = slangToLatex(expr2);
|
|
513
|
+
return latex1 === latex2;
|
|
514
|
+
} catch (error) {
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Get expression complexity score
|
|
521
|
+
*/
|
|
522
|
+
function getExpressionComplexity(expr) {
|
|
523
|
+
let complexity = 1;
|
|
524
|
+
|
|
525
|
+
if (expr.terms) {
|
|
526
|
+
complexity += expr.terms.length;
|
|
527
|
+
expr.terms.forEach(term => {
|
|
528
|
+
if (term.var) {
|
|
529
|
+
complexity += Object.keys(term.var).length;
|
|
530
|
+
Object.values(term.var).forEach(power => {
|
|
531
|
+
complexity += power > 1 ? power - 1 : 0;
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
} else if (expr.numi && expr.deno !== undefined) {
|
|
536
|
+
complexity += getExpressionComplexity(expr.numi);
|
|
537
|
+
if (typeof expr.deno !== 'number') {
|
|
538
|
+
complexity += getExpressionComplexity(expr.deno);
|
|
539
|
+
} else {
|
|
540
|
+
complexity += 1;
|
|
541
|
+
}
|
|
542
|
+
} else if (expr.coeff !== undefined) {
|
|
543
|
+
if (expr.var) {
|
|
544
|
+
complexity += Object.keys(expr.var).length;
|
|
545
|
+
Object.values(expr.var).forEach(power => {
|
|
546
|
+
complexity += power > 1 ? power - 1 : 0;
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return complexity;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// ============================================================================
|
|
555
|
+
// EXPORTS
|
|
556
|
+
// ============================================================================
|
|
557
|
+
|
|
558
|
+
export {
|
|
559
|
+
// Core conversion functions
|
|
560
|
+
slangToLatex,
|
|
561
|
+
latexToSlang,
|
|
562
|
+
|
|
563
|
+
// Component functions
|
|
564
|
+
termToLatex,
|
|
565
|
+
polynomialToLatex,
|
|
566
|
+
fractionToLatex,
|
|
567
|
+
parseTerm,
|
|
568
|
+
parsePolynomial,
|
|
569
|
+
parseFraction,
|
|
570
|
+
|
|
571
|
+
// Advanced features
|
|
572
|
+
expressionToLatex,
|
|
573
|
+
batchConvertToLatex,
|
|
574
|
+
batchConvertToSlang,
|
|
575
|
+
validateLatex,
|
|
576
|
+
formatDisplayMode,
|
|
577
|
+
simplifyExpression,
|
|
578
|
+
|
|
579
|
+
// Utility functions
|
|
580
|
+
getConversionInfo,
|
|
581
|
+
areExpressionsEquivalent,
|
|
582
|
+
getExpressionComplexity,
|
|
583
|
+
|
|
584
|
+
// Parsers (for advanced usage)
|
|
585
|
+
parseNumber,
|
|
586
|
+
parseVariable
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// ============================================================================
|
|
590
|
+
// DEMO AND TESTING FUNCTIONS
|
|
591
|
+
// ============================================================================
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Comprehensive demo function
|
|
595
|
+
*/
|
|
596
|
+
function demoConverter() {
|
|
597
|
+
console.log('š SLaNg LaTeX Converter - Unified Edition Demo');
|
|
598
|
+
console.log('=' .repeat(60));
|
|
599
|
+
|
|
600
|
+
// Test expressions
|
|
601
|
+
const testExpressions = [
|
|
602
|
+
createFraction([createTerm(1, { x: 1 })], [createTerm(1, { x: 1 }), createTerm(1)]),
|
|
603
|
+
createFraction([createTerm(1, { x: 2 }), createTerm(-1)], [createTerm(1, { x: 2 }), createTerm(1)]),
|
|
604
|
+
{ terms: [createTerm(2, { x: 2 }), createTerm(3, { x: 1 }), createTerm(-1)] },
|
|
605
|
+
createTerm(5, { y: 3, z: 2 })
|
|
606
|
+
];
|
|
607
|
+
|
|
608
|
+
console.log('\nš SLaNg to LaTeX Conversions:');
|
|
609
|
+
testExpressions.forEach((expr, i) => {
|
|
610
|
+
const latex = slangToLatex(expr);
|
|
611
|
+
const info = getConversionInfo(expr, 'to-latex');
|
|
612
|
+
console.log(`${i + 1}. ${fractionToString(expr).padEnd(25)} ā ${latex} (${info.expressionType})`);
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// Test LaTeX inputs
|
|
616
|
+
const testLatex = [
|
|
617
|
+
'\\frac{x}{x+1}',
|
|
618
|
+
'\\frac{x^{2}-1}{x^{2}+1}',
|
|
619
|
+
'2x^{2} + 3x - 1',
|
|
620
|
+
'5y^{3}z^{2}'
|
|
621
|
+
];
|
|
622
|
+
|
|
623
|
+
console.log('\nš LaTeX to SLaNg Conversions:');
|
|
624
|
+
testLatex.forEach((latex, i) => {
|
|
625
|
+
try {
|
|
626
|
+
const slang = latexToSlang(latex);
|
|
627
|
+
const backToLatex = slangToLatex(slang);
|
|
628
|
+
const validation = validateLatex(latex);
|
|
629
|
+
console.log(`${i + 1}. ${latex.padEnd(25)} ā ${fractionToString(slang).padEnd(25)} (${validation.valid ? 'ā
' : 'ā'})`);
|
|
630
|
+
} catch (error) {
|
|
631
|
+
console.log(`${i + 1}. ${latex.padEnd(25)} ā Error: ${error.message}`);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
console.log('\nšÆ Advanced Features Demo:');
|
|
636
|
+
|
|
637
|
+
// Batch conversion
|
|
638
|
+
const batchResult = batchConvertToLatex(testExpressions, { includeErrors: true });
|
|
639
|
+
console.log(`Batch conversion: ${batchResult.results.filter(r => r.success).length}/${batchResult.results.length} successful`);
|
|
640
|
+
|
|
641
|
+
// Validation
|
|
642
|
+
const validationResults = testLatex.map(latex => validateLatex(latex));
|
|
643
|
+
console.log(`Validation: ${validationResults.filter(v => v.valid).length}/${validationResults.length} valid LaTeX expressions`);
|
|
644
|
+
|
|
645
|
+
console.log('\nš Unified Converter - All Features Operational!');
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Run demo if this file is executed directly
|
|
649
|
+
if (import.meta.url === `file://${process.argv[1].replace(/\\/g, '/').replace(/ /g, '%20')}` || process.argv[1].endsWith('slang-convertor.js')) {
|
|
650
|
+
demoConverter();
|
|
651
|
+
}
|
|
652
|
+
|