Skip to content

Core Concepts ​

Before diving into the API, it helps to understand the key concepts that make TeXpr work.

LaTeX as Input ​

TeXpr uses LaTeX syntax because it's the standard for mathematical notation. You write expressions the same way you'd write them in a math document:

What you meanLaTeX syntaxTeXpr evaluates to
Square root of 16\sqrt{16}4
Fraction 1/2\frac{1}{2}0.5
Sin of π\sin{\pi}0
x squaredx^{2}depends on x

This isn't an arbitrary choice — if you're working with mathematical expressions, you (or your users) probably already know LaTeX.


The Three-Stage Pipeline ​

Every evaluation goes through three stages:

String → Tokenizer → Tokens → Parser → AST → Evaluator → Result

You don't need to understand the internals, but knowing they exist helps explain:

  • Why parsing once and evaluating many times is faster (skip tokenizer + parser)
  • Why some errors mention "tokenization" vs "parsing" vs "evaluation" (different stages)
  • Why caching works (results from each stage can be reused)

Learn more about internals →


Variable Binding ​

Expressions can contain variables. You provide values at evaluation time:

dart
// Expression contains x and y
final result = evaluator.evaluate(
  r'\sqrt{x^2 + y^2}', 
  {'x': 3.0, 'y': 4.0}  // Provide values
);
// Result: 5.0

Key points:

  • Variables are single letters by default: x, y, α
  • Variable names are case-sensitive: x ≠ X
  • Undefined variables throw an error with a suggestion
  • Some names are reserved: e (Euler's number), i (imaginary unit)

Implicit Multiplication ​

TeXpr can interpret adjacent items as multiplication:

dart
// These are equivalent when allowImplicitMultiplication is true (default)
evaluator.evaluate(r'2x');      // 2 * x
evaluator.evaluate(r'xy');      // x * y
evaluator.evaluate(r'\sin x \cos x');  // sin(x) * cos(x)

Why this exists: Textbooks write 2πr², not 2 * π * r^2. TeXpr matches this convention.

The trade-off: Multi-character variable names don't work. velocity becomes v * e * l * o * c * i * t * y.

Disable it when you need multi-character names:

dart
final evaluator = Texpr(allowImplicitMultiplication: false);
evaluator.evaluate('velocity * time', {'velocity': 10, 'time': 5});

Result Types ​

Evaluation returns EvaluationResult, a sealed class with four variants:

Result TypeWhen it's usedExample expression
NumericResultReal number2 + 3 → 5.0
ComplexResultComplex numbere^{i\pi} → -1+0i
MatrixResultMatrix\begin{pmatrix}1&2\\3&4\end{pmatrix}
VectorResultVector\nabla{x^2+y^2} → [2x, 2y]

Use pattern matching to handle results:

dart
final result = evaluator.evaluate(expression);
switch (result) {
  case NumericResult(:final value):
    print('Number: $value');
  case ComplexResult(:final value):
    print('Complex: $value');
  case MatrixResult(:final matrix):
    print('Matrix: ${matrix.rows}x${matrix.cols}');
  case VectorResult(:final vector):
    print('Vector: $vector');
}

Or use convenience methods when you know the type:

dart
// Throws if result isn't numeric
final number = evaluator.evaluateNumeric(r'\sqrt{16}');  // 4.0

Error Handling ​

TeXpr has three exception types, one for each pipeline stage:

ExceptionWhen it's thrownExample cause
TokenizerExceptionInvalid inputUnknown command \foo
ParserExceptionInvalid syntaxUnclosed brace \frac{1
EvaluatorExceptionInvalid operation\log{0}, undefined variable

All inherit from TexprException, so you can catch all or be specific:

dart
try {
  evaluator.evaluate(expression);
} on TokenizerException catch (e) {
  print('Invalid input at ${e.position}: ${e.message}');
} on ParserException catch (e) {
  print('Syntax error at ${e.position}: ${e.message}');
} on EvaluatorException catch (e) {
  print('Math error: ${e.message}');
  if (e.suggestion != null) print('Suggestion: ${e.suggestion}');
}

Parse Once, Evaluate Many ​

For repeated evaluation (plotting, animation, iteration), parse the expression once:

dart
// ✓ Efficient: Parse once
final ast = evaluator.parse(r'\sin{x} + \cos{x}');
for (var x = 0.0; x < 100; x += 0.01) {
  evaluator.evaluateParsed(ast, {'x': x});
}

// ✗ Less efficient: Re-parses internally (though L1 cache helps)
for (var x = 0.0; x < 100; x += 0.01) {
  evaluator.evaluate(r'\sin{x} + \cos{x}', {'x': x});
}

The internal cache mitigates the second approach, but explicit caching is more predictable.


Symbolic Operations ​

Beyond evaluation, TeXpr can manipulate expressions symbolically:

Differentiation ​

dart
final derivative = evaluator.differentiate(r'x^3', 'x');
// Returns AST representing 3*x^2
evaluator.evaluateParsed(derivative, {'x': 2});  // 12.0

Integration ​

dart
// Symbolic (indefinite)
final integral = evaluator.integrate(r'x^2', 'x');
// Returns AST representing x^3/3

// Numeric (definite)
evaluator.evaluate(r'\int_{0}^{1} x^2 dx');  // 0.333...

Evaluability ​

Not all parsed expressions can be evaluated numerically. TeXpr makes this explicit:

dart
final expr = evaluator.parse(r'\nabla f');
print(expr.getEvaluability());  // Evaluability.symbolic
EvaluabilityMeaningExample
numericCan compute a result2 + 3, \sin{\pi}
symbolicParse-only, no numeric result\nabla f, \iint
unevaluableMissing variable definitionsx + 1 (x undefined)

Check before evaluation to provide better user feedback:

dart
final expr = evaluator.parse(userInput);
final eval = expr.getEvaluability(definedVariables);

if (eval == Evaluability.numeric) {
  final result = evaluator.evaluateParsed(expr, variables);
} else if (eval == Evaluability.symbolic) {
  showMessage('This expression is symbolic and cannot be computed');
} else {
  showMessage('Define all variables first');
}

What's Next ​

Made with ❤️ by TeXpr, Docs Inspired by ElysiaJS