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
package/slang-errors.js
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SLaNg Error Handling System
|
|
3
|
+
* Comprehensive error types and handling utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// ERROR CLASSES
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Base SLaNg Error class
|
|
12
|
+
*/
|
|
13
|
+
class SLaNgError extends Error {
|
|
14
|
+
constructor(message, code, context = {}) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'SLaNgError';
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.context = context;
|
|
19
|
+
this.timestamp = new Date().toISOString();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
toJSON() {
|
|
23
|
+
return {
|
|
24
|
+
name: this.name,
|
|
25
|
+
message: this.message,
|
|
26
|
+
code: this.code,
|
|
27
|
+
context: this.context,
|
|
28
|
+
timestamp: this.timestamp,
|
|
29
|
+
stack: this.stack
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parsing Error for LaTeX/SLaNg conversion issues
|
|
36
|
+
*/
|
|
37
|
+
class ParseError extends SLaNgError {
|
|
38
|
+
constructor(message, input, position = null, suggestions = []) {
|
|
39
|
+
super(message, 'PARSE_ERROR', {
|
|
40
|
+
input,
|
|
41
|
+
position,
|
|
42
|
+
suggestions
|
|
43
|
+
});
|
|
44
|
+
this.name = 'ParseError';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validation Error for invalid expressions
|
|
50
|
+
*/
|
|
51
|
+
class ValidationError extends SLaNgError {
|
|
52
|
+
constructor(message, expression, validationType) {
|
|
53
|
+
super(message, 'VALIDATION_ERROR', {
|
|
54
|
+
expression,
|
|
55
|
+
validationType
|
|
56
|
+
});
|
|
57
|
+
this.name = 'ValidationError';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Conversion Error for SLaNg ↔ LaTeX conversion failures
|
|
63
|
+
*/
|
|
64
|
+
class ConversionError extends SLaNgError {
|
|
65
|
+
constructor(message, fromType, toType, expression) {
|
|
66
|
+
super(message, 'CONVERSION_ERROR', {
|
|
67
|
+
fromType,
|
|
68
|
+
toType,
|
|
69
|
+
expression
|
|
70
|
+
});
|
|
71
|
+
this.name = 'ConversionError';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Type Error for unsupported expression types
|
|
77
|
+
*/
|
|
78
|
+
class TypeError extends SLaNgError {
|
|
79
|
+
constructor(message, expectedType, actualType, expression) {
|
|
80
|
+
super(message, 'TYPE_ERROR', {
|
|
81
|
+
expectedType,
|
|
82
|
+
actualType,
|
|
83
|
+
expression
|
|
84
|
+
});
|
|
85
|
+
this.name = 'TypeError';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ============================================================================
|
|
90
|
+
// ERROR FACTORIES
|
|
91
|
+
// ============================================================================
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Create parsing errors with helpful context
|
|
95
|
+
*/
|
|
96
|
+
export function createParseError(message, input, position = null) {
|
|
97
|
+
const suggestions = generateSuggestions(input, position);
|
|
98
|
+
return new ParseError(message, input, position, suggestions);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create validation errors
|
|
103
|
+
*/
|
|
104
|
+
export function createValidationError(message, expression, type = 'general') {
|
|
105
|
+
return new ValidationError(message, expression, type);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create conversion errors
|
|
110
|
+
*/
|
|
111
|
+
export function createConversionError(message, fromType, toType, expression) {
|
|
112
|
+
return new ConversionError(message, fromType, toType, expression);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create type errors
|
|
117
|
+
*/
|
|
118
|
+
export function createTypeError(message, expectedType, actualType, expression) {
|
|
119
|
+
return new TypeError(message, expectedType, actualType, expression);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// ERROR HANDLERS
|
|
124
|
+
// ============================================================================
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Enhanced error handler with multiple strategies
|
|
128
|
+
*/
|
|
129
|
+
export function handleError(error, options = {}) {
|
|
130
|
+
const {
|
|
131
|
+
logErrors = true,
|
|
132
|
+
throwOnError = false,
|
|
133
|
+
returnNull = false,
|
|
134
|
+
customHandler = null
|
|
135
|
+
} = options;
|
|
136
|
+
|
|
137
|
+
// Log error if requested
|
|
138
|
+
if (logErrors) {
|
|
139
|
+
logError(error);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Custom handler takes precedence
|
|
143
|
+
if (customHandler && typeof customHandler === 'function') {
|
|
144
|
+
return customHandler(error);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Handle based on options
|
|
148
|
+
if (throwOnError) {
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (returnNull) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Default: return error object
|
|
157
|
+
return {
|
|
158
|
+
success: false,
|
|
159
|
+
error: error.toJSON(),
|
|
160
|
+
message: error.message
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Batch error handler for multiple operations
|
|
166
|
+
*/
|
|
167
|
+
export function handleBatchErrors(errors, options = {}) {
|
|
168
|
+
const {
|
|
169
|
+
groupByType = true,
|
|
170
|
+
includeContext = true,
|
|
171
|
+
summaryOnly = false
|
|
172
|
+
} = options;
|
|
173
|
+
|
|
174
|
+
if (summaryOnly) {
|
|
175
|
+
return createErrorSummary(errors);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const processed = errors.map(error => ({
|
|
179
|
+
index: error.index || -1,
|
|
180
|
+
...error.toJSON()
|
|
181
|
+
}));
|
|
182
|
+
|
|
183
|
+
if (groupByType) {
|
|
184
|
+
return groupErrorsByType(processed);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
total: errors.length,
|
|
189
|
+
errors: processed
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// ERROR UTILITIES
|
|
195
|
+
// ============================================================================
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Generate helpful suggestions based on error context
|
|
199
|
+
*/
|
|
200
|
+
function generateSuggestions(input, position) {
|
|
201
|
+
const suggestions = [];
|
|
202
|
+
|
|
203
|
+
if (!input) {
|
|
204
|
+
suggestions.push('Empty input provided');
|
|
205
|
+
return suggestions;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Common LaTeX syntax issues
|
|
209
|
+
if (input.includes('\\frac') && !input.includes('{')) {
|
|
210
|
+
suggestions.push('LaTeX fractions require braces: \\frac{numerator}{denominator}');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (input.includes('^') && !input.includes('{')) {
|
|
214
|
+
suggestions.push('Consider using braces for powers: x^{2} instead of x^2');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (input.includes('{') && !input.includes('}')) {
|
|
218
|
+
suggestions.push('Unclosed braces detected');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Common parsing issues
|
|
222
|
+
if (position !== null && position > 0) {
|
|
223
|
+
const before = input.substring(0, position);
|
|
224
|
+
const after = input.substring(position);
|
|
225
|
+
|
|
226
|
+
if (before.endsWith('^') && after.startsWith('{')) {
|
|
227
|
+
suggestions.push('Power syntax looks correct');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (before.endsWith('\\frac') && !after.startsWith('{')) {
|
|
231
|
+
suggestions.push('Fraction requires opening brace after \\frac');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return suggestions;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Log errors with structured format
|
|
240
|
+
*/
|
|
241
|
+
function logError(error) {
|
|
242
|
+
const logEntry = {
|
|
243
|
+
timestamp: error.timestamp,
|
|
244
|
+
level: 'ERROR',
|
|
245
|
+
type: error.name,
|
|
246
|
+
code: error.code,
|
|
247
|
+
message: error.message,
|
|
248
|
+
context: error.context
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
console.error('🚨 SLaNg Error:', JSON.stringify(logEntry, null, 2));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Create error summary for batch operations
|
|
256
|
+
*/
|
|
257
|
+
function createErrorSummary(errors) {
|
|
258
|
+
const summary = {
|
|
259
|
+
total: errors.length,
|
|
260
|
+
byType: {},
|
|
261
|
+
mostCommon: null
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
errors.forEach(error => {
|
|
265
|
+
const type = error.name || 'Unknown';
|
|
266
|
+
summary.byType[type] = (summary.byType[type] || 0) + 1;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Find most common error type
|
|
270
|
+
const types = Object.entries(summary.byType);
|
|
271
|
+
if (types.length > 0) {
|
|
272
|
+
summary.mostCommon = types.reduce((a, b) => a[1] > b[1] ? a : b)[0];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return summary;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Group errors by type
|
|
280
|
+
*/
|
|
281
|
+
function groupErrorsByType(errors) {
|
|
282
|
+
const grouped = {};
|
|
283
|
+
|
|
284
|
+
errors.forEach(error => {
|
|
285
|
+
const type = error.name || 'Unknown';
|
|
286
|
+
if (!grouped[type]) {
|
|
287
|
+
grouped[type] = [];
|
|
288
|
+
}
|
|
289
|
+
grouped[type].push(error);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return grouped;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Validate error context
|
|
297
|
+
*/
|
|
298
|
+
export function validateErrorContext(context) {
|
|
299
|
+
const required = ['timestamp', 'code'];
|
|
300
|
+
const missing = required.filter(key => !(key in context));
|
|
301
|
+
|
|
302
|
+
if (missing.length > 0) {
|
|
303
|
+
throw new Error(`Missing required error context: ${missing.join(', ')}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Create error recovery suggestions
|
|
311
|
+
*/
|
|
312
|
+
export function createRecoveryPlan(error) {
|
|
313
|
+
const plan = {
|
|
314
|
+
error: error.message,
|
|
315
|
+
steps: [],
|
|
316
|
+
alternatives: []
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
switch (error.code) {
|
|
320
|
+
case 'PARSE_ERROR':
|
|
321
|
+
plan.steps.push('Check LaTeX syntax');
|
|
322
|
+
plan.steps.push('Verify all braces are matched');
|
|
323
|
+
plan.steps.push('Ensure fraction format is correct');
|
|
324
|
+
plan.alternatives.push('Try simpler expression');
|
|
325
|
+
break;
|
|
326
|
+
|
|
327
|
+
case 'VALIDATION_ERROR':
|
|
328
|
+
plan.steps.push('Review expression format');
|
|
329
|
+
plan.steps.push('Check for invalid characters');
|
|
330
|
+
plan.alternatives.push('Use validateLatex() first');
|
|
331
|
+
break;
|
|
332
|
+
|
|
333
|
+
case 'CONVERSION_ERROR':
|
|
334
|
+
plan.steps.push('Verify expression type');
|
|
335
|
+
plan.steps.push('Check converter options');
|
|
336
|
+
plan.alternatives.push('Try different conversion method');
|
|
337
|
+
break;
|
|
338
|
+
|
|
339
|
+
default:
|
|
340
|
+
plan.steps.push('Review error context');
|
|
341
|
+
plan.alternatives.push('Contact support');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return plan;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ============================================================================
|
|
348
|
+
// ERROR RECOVERY
|
|
349
|
+
// ============================================================================
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Attempt to recover from parsing errors
|
|
353
|
+
*/
|
|
354
|
+
export function attemptRecovery(error, originalInput) {
|
|
355
|
+
const recovery = {
|
|
356
|
+
success: false,
|
|
357
|
+
attempts: [],
|
|
358
|
+
result: null
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// Try common fixes
|
|
362
|
+
const fixes = [
|
|
363
|
+
{ name: 'Add missing braces', fix: addMissingBraces },
|
|
364
|
+
{ name: 'Fix fraction syntax', fix: fixFractionSyntax },
|
|
365
|
+
{ name: 'Normalize whitespace', fix: normalizeWhitespace },
|
|
366
|
+
{ name: 'Escape special chars', fix: escapeSpecialChars }
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
for (const fix of fixes) {
|
|
370
|
+
try {
|
|
371
|
+
const fixed = fix.fix(originalInput);
|
|
372
|
+
recovery.attempts.push({
|
|
373
|
+
name: fix.name,
|
|
374
|
+
input: fixed,
|
|
375
|
+
success: true
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
if (fixed !== originalInput) {
|
|
379
|
+
recovery.success = true;
|
|
380
|
+
recovery.result = fixed;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
} catch (attemptError) {
|
|
384
|
+
recovery.attempts.push({
|
|
385
|
+
name: fix.name,
|
|
386
|
+
error: attemptError.message,
|
|
387
|
+
success: false
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return recovery;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Recovery helper functions
|
|
397
|
+
*/
|
|
398
|
+
function addMissingBraces(input) {
|
|
399
|
+
// Add missing braces for fractions and powers
|
|
400
|
+
let fixed = input;
|
|
401
|
+
|
|
402
|
+
// Fix fractions
|
|
403
|
+
fixed = fixed.replace(/\\frac([^{}])/g, '\\frac{$1');
|
|
404
|
+
fixed = fixed.replace(/\\frac{([^{}]+)}([^{}])/g, '\\frac{$1}{$2}');
|
|
405
|
+
|
|
406
|
+
// Fix powers
|
|
407
|
+
fixed = fixed.replace(/\^([^{}])/g, '^{$1}');
|
|
408
|
+
|
|
409
|
+
return fixed;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function fixFractionSyntax(input) {
|
|
413
|
+
// Common fraction syntax fixes
|
|
414
|
+
return input
|
|
415
|
+
.replace(/\\frac\s*\{/g, '\\frac{')
|
|
416
|
+
.replace(/\}\s*\{/g, '}{');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function normalizeWhitespace(input) {
|
|
420
|
+
// Normalize whitespace around operators
|
|
421
|
+
return input
|
|
422
|
+
.replace(/\s*\+\s*/g, ' + ')
|
|
423
|
+
.replace(/\s*-\s*/g, ' - ')
|
|
424
|
+
.replace(/\s*\*\s*/g, ' * ')
|
|
425
|
+
.trim();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function escapeSpecialChars(input) {
|
|
429
|
+
// Escape problematic characters
|
|
430
|
+
return input
|
|
431
|
+
.replace(/&/g, '\\&')
|
|
432
|
+
.replace(/%/g, '\\%')
|
|
433
|
+
.replace(/#/g, '\\#');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ============================================================================
|
|
437
|
+
// EXPORTS
|
|
438
|
+
// ============================================================================
|
|
439
|
+
|
|
440
|
+
export {
|
|
441
|
+
SLaNgError,
|
|
442
|
+
ParseError,
|
|
443
|
+
ValidationError,
|
|
444
|
+
ConversionError,
|
|
445
|
+
TypeError
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// Error codes for reference
|
|
449
|
+
export const ERROR_CODES = {
|
|
450
|
+
PARSE_ERROR: 'PARSE_ERROR',
|
|
451
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
452
|
+
CONVERSION_ERROR: 'CONVERSION_ERROR',
|
|
453
|
+
TYPE_ERROR: 'TYPE_ERROR'
|
|
454
|
+
};
|