Exceptions and Error Taxonomy
This document provides a specification of TeXpr's error model, including error classification, typed exceptions, and recovery strategies.
Error Classification
TeXpr errors are classified into three distinct categories based on the processing stage where they occur:
┌─────────────────────────────────────────────────────────────────┐
│ Input String │
└─────────────────────────┬───────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────┐
│ TOKENIZER │ TokenizerException │
│ (Lexical Analysis) │ - Invalid characters │
│ │ - Malformed numbers │
│ │ - Unknown LaTeX commands │
└─────────────────────────┬───────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────┐
│ PARSER │ ParserException │
│ (Syntactic Analysis) │ - Unbalanced delimiters │
│ │ - Missing arguments │
│ │ - Unexpected tokens │
│ │ - Recursion depth exceeded │
└─────────────────────────┬───────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────┐
│ EVALUATOR │ EvaluatorException │
│ (Semantic Analysis) │ - Undefined variables │
│ │ - Division by zero │
│ │ - Domain errors │
│ │ - Type mismatches │
│ │ - Resource limits │
└─────────────────────────┴───────────────────────────────────────┘Exception Hierarchy
sealed class TexprException implements Exception {
String get message;
int? get position;
String? get expression;
String? get suggestion;
}
class TokenizerException extends TexprException { ... }
class ParserException extends TexprException { ... }
class EvaluatorException extends TexprException { ... }All exceptions are subclasses of the sealed TexprException base class, enabling exhaustive pattern matching in Dart 3.0+.
Exception Properties
| Property | Type | Description |
|---|---|---|
message | String | Human-readable error description |
position | int? | Character index in source (0-based) |
expression | String? | Original expression string |
suggestion | String? | Actionable fix recommendation |
Lexical Errors (TokenizerException)
Lexical errors occur during tokenization when the input contains invalid character sequences.
Error Codes
| Error | Cause | Example | Suggestion |
|---|---|---|---|
| Unknown command | Unrecognized \command | \unknownfunc{x} | "Did you mean '\sin'?" |
| Invalid character | Non-mathematical character | @#$ | "Remove invalid character" |
| Malformed number | Multiple decimal points | 1.2.3 | "Invalid number format" |
| Unterminated text | Missing closing brace in \text{} | \text{hello | "Add closing brace" |
Example
try {
texpr.parse(r'\sinn{x}');
} on TokenizerException catch (e) {
print(e.message); // "Unknown command: sinn"
print(e.position); // 0
print(e.suggestion); // "Did you mean 'sin'?"
}Syntactic Errors (ParserException)
Syntactic errors occur when the token sequence doesn't match the grammar rules.
Error Codes
| Error | Cause | Example | Suggestion |
|---|---|---|---|
| Unbalanced delimiters | Missing opening/closing | (x + 1 | "Expected ')'" |
| Missing argument | Incomplete construct | \frac{1} | "Expected second argument" |
| Unexpected token | Grammar violation | + + 1 | "Unexpected operator" |
| Recursion overflow | Too deeply nested | ((((...)))) | "Expression too deeply nested" |
| Empty expression | No content | `` | "Expected expression" |
Example
try {
texpr.parse(r'\frac{1}');
} on ParserException catch (e) {
print(e.message); // "Expected second argument for \\frac"
print(e.position); // 8
print(e.suggestion); // "Usage: \\frac{numerator}{denominator}"
}Semantic Errors (EvaluatorException)
Semantic errors occur during evaluation when the expression is syntactically valid but mathematically problematic.
Error Categories
Variable Errors
| Error | Cause | Example | Suggestion |
|---|---|---|---|
| Undefined variable | Missing binding | x + 1 (no x) | "Provide a value for 'x'" |
| Wrong variable type | Non-numeric variable | {'x': 'text'} | "Variable must be numeric" |
Domain Errors
| Error | Cause | Example | Suggestion |
|---|---|---|---|
| Division by zero | Zero denominator | 1/0 | "Denominator cannot be zero" |
| Log of zero | ln(0) | \ln{0} | "Logarithm undefined for zero" |
| Log of negative | ln(-x) real mode | \ln{-1} | "Logarithm undefined for negative (use complex)" |
| Sqrt of negative | Real mode only | \sqrt{-1} | "Use complex number support" |
| Factorial domain | Negative or non-integer | (-5)! | "Factorial requires non-negative integer" |
| Asin/acos domain | Outside [-1, 1] | \arcsin{2} | "Argument must be in [-1, 1]" |
Type Errors
| Error | Cause | Example | Suggestion |
|---|---|---|---|
| Matrix-scalar mismatch | Incompatible types | A + 5 (A is matrix) | "Cannot add matrix and scalar" |
| Matrix dimension mismatch | Wrong dimensions | A * B (incompatible) | "Matrix dimensions must match" |
| Non-numeric in calculus | Variable in bounds | \sum_{i=x}^{y} | "Bounds must be numeric" |
Resource Limit Errors
| Error | Cause | Limit | Suggestion |
|---|---|---|---|
| Recursion overflow | Deep nesting | 500 (default) | "Increase maxRecursionDepth" |
| Iteration overflow | Huge sum/product | 100,000 | "Reduce iteration bounds" |
| Factorial overflow | Too large | n > 170 | "Factorial(n) exceeds double range" |
| Fibonacci overflow | Too large | n > 1476 | "Fibonacci(n) exceeds double range" |
Example
try {
texpr.evaluate(r'\sum_{i=1}^{999999999} i');
} on EvaluatorException catch (e) {
print(e.message); // "Iteration limit exceeded"
print(e.suggestion); // "Maximum 100,000 iterations allowed"
}Handling Exceptions
Basic Pattern
try {
final result = texpr.evaluate(expression, variables);
// Use result...
} on TokenizerException catch (e) {
// Invalid input: unknown commands, bad characters
logError('Lexical error: ${e.message}');
} on ParserException catch (e) {
// Syntax error: unbalanced braces, missing args
logError('Syntax error at position ${e.position}: ${e.message}');
} on EvaluatorException catch (e) {
// Runtime error: undefined vars, domain errors
logError('Evaluation error: ${e.message}');
} on TexprException catch (e) {
// Catch-all for any TeXpr error
logError('Math error: ${e.message}');
}Exhaustive Matching (Dart 3.0+)
switch (error) {
case TokenizerException(message: var msg, position: var pos):
handleLexicalError(msg, pos);
case ParserException(message: var msg, suggestion: var sug):
handleSyntaxError(msg, sug);
case EvaluatorException(message: var msg):
handleRuntimeError(msg);
}ValidationResult
For validation without throwing exceptions, use texpr.validate():
final result = texpr.validate(r'\frac{1}{');
if (!result.isValid) {
print(result.errorMessage); // "Expected closing brace"
print(result.position); // 9
print(result.suggestion); // "Add '}' to close \\frac"
print(result.exceptionType); // ParserException
}Properties
| Property | Type | Description |
|---|---|---|
isValid | bool | true if expression is valid |
errorMessage | String? | Primary error description |
position | int? | Error position in source |
suggestion | String? | Recommended fix |
exceptionType | Type? | Exception class that would be thrown |
subErrors | List<ValidationError> | Additional errors found |
Multiple Error Detection
The validator attempts recovery to find multiple issues:
final result = texpr.validate(r'2 + ) + \unknownfunc{x}');
print(result.errorMessage); // Primary error
for (final sub in result.subErrors) {
print('${sub.position}: ${sub.errorMessage}');
}
// Output:
// 4: Unexpected ')'
// 8: Unknown command 'unknownfunc'Error Recovery Strategies
User Input Handling
String sanitizeAndEvaluate(String input) {
// 1. Validate first
final validation = texpr.validate(input);
if (!validation.isValid) {
return 'Error: ${validation.suggestion ?? validation.errorMessage}';
}
// 2. Evaluate with timeout protection
try {
final result = texpr.evaluate(input, context);
// 3. Check for special values
if (result.isNumeric) {
final value = result.asNumeric();
if (value.isNaN) return 'Error: Result is undefined';
if (value.isInfinite) return 'Error: Result is infinite';
return value.toString();
}
return result.toString();
} on TexprException catch (e) {
return 'Error: ${e.suggestion ?? e.message}';
}
}Graceful Degradation
double evaluateOrDefault(String expr, double defaultValue) {
try {
return texpr.evaluate(expr).asNumeric();
} on TexprException {
return defaultValue;
}
}Error Message Quality
TeXpr provides actionable suggestions for common errors:
| Error Pattern | Message | Suggestion |
|---|---|---|
\sin{x | "Expected closing brace" | "Add '}' after the argument" |
\sni{x} | "Unknown command: sni" | "Did you mean 'sin'?" |
x + | "Unexpected end of expression" | "Add right-hand operand" |
\log{0} | "Logarithm of zero is undefined" | "Ensure argument is positive" |
A + B (matrices) | "Matrix dimensions must match" | "A is 2×2, B is 3×3" |
API Stability
| Aspect | Stability Guarantee |
|---|---|
| Exception types | Stable within major versions |
message text | May change (not API) |
position accuracy | Best effort, may be approximate |
suggestion presence | Not guaranteed |
| New exception types | May be added in minor versions |
Summary
| Stage | Exception | Examples |
|---|---|---|
| Tokenization | TokenizerException | Unknown commands, invalid chars |
| Parsing | ParserException | Syntax errors, unbalanced delimiters |
| Evaluation | EvaluatorException | Domain errors, undefined variables |
Always wrap evaluate() calls in try-catch and handle all three exception types appropriately for robust applications.