mathlex 0.4.1

Mathematical expression parser for LaTeX and plain text notation, producing a language-agnostic AST
Documentation
/// ComplexEvaluator.swift
/// Skeleton showing how to extend the evaluator pattern to complex numbers.
///
/// This file is intentionally illustrative. It demonstrates the structure
/// NumericSwift would use to evaluate `MathLexExpression` trees over ℂ.
/// The actual complex-number type would come from NumericSwift's own `Complex`
/// implementation (or the `Numerics` package's `Complex<Double>`).
///
/// Key insight: the recursive tree-walk structure from `Evaluator.swift` is
/// identical — only the leaf conversions and the math dispatch change.

import Foundation

// MARK: - Placeholder complex type

/// Minimal complex number for illustration. Replace with NumericSwift.Complex.
public struct ComplexDouble: CustomStringConvertible {
  public var real: Double
  public var imaginary: Double

  public init(_ real: Double, _ imaginary: Double = 0) {
    self.real = real
    self.imaginary = imaginary
  }

  public static let i = ComplexDouble(0, 1)

  public static func + (l: Self, r: Self) -> Self {
    Self(l.real + r.real, l.imaginary + r.imaginary)
  }
  public static func - (l: Self, r: Self) -> Self {
    Self(l.real - r.real, l.imaginary - r.imaginary)
  }
  public static func * (l: Self, r: Self) -> Self {
    Self(
      l.real * r.real - l.imaginary * r.imaginary,
      l.real * r.imaginary + l.imaginary * r.real)
  }
  public static func / (l: Self, r: Self) -> Self {
    let denom = r.real * r.real + r.imaginary * r.imaginary
    return Self(
      (l.real * r.real + l.imaginary * r.imaginary) / denom,
      (l.imaginary * r.real - l.real * r.imaginary) / denom)
  }
  public static prefix func - (v: Self) -> Self { Self(-v.real, -v.imaginary) }

  public var magnitude: Double { sqrt(real * real + imaginary * imaginary) }
  public var conjugate: Self { Self(real, -imaginary) }

  public var description: String {
    imaginary >= 0 ? "\(real)+\(imaginary)i" : "\(real)\(imaginary)i"
  }
}

// MARK: - Complex evaluator

/// Evaluates a `MathLexExpression` AST over ℂ (complex numbers).
///
/// Variable bindings and the return type are `ComplexDouble` rather than
/// `Double`. Everything else follows the same pattern as `Evaluator`.
public struct ComplexEvaluator {

  public typealias Env = [String: ComplexDouble]

  public init() {}

  /// Evaluates `expr` using `env` for variable bindings.
  public func evaluate(_ expr: MathLexExpression, env: Env = [:]) throws -> ComplexDouble {
    switch expr {
    case .integer(let n):
      return ComplexDouble(Double(n))
    case .float(let v):
      guard let v else { throw EvaluatorError.nonFiniteFloat }
      return ComplexDouble(v)
    case .variable(let name):
      guard let v = env[name] else { throw EvaluatorError.undefinedVariable(name) }
      return v
    case .constant(let c):
      return try resolveConstant(c)
    case .rational(let num, let den):
      return try evaluate(num, env: env) / evaluate(den, env: env)
    case .complex(let re, let im):
      // A Complex node from the AST carries real and imaginary sub-trees.
      let r = try evaluate(re, env: env)
      let i = try evaluate(im, env: env)
      // Both sub-trees must be purely real for this simple path.
      return ComplexDouble(r.real, i.real)
    case .binary(let op, let lhs, let rhs):
      return try applyBinary(op, lhs, rhs, env: env)
    case .unary(let op, let operand):
      return try applyUnary(op, operand, env: env)
    case .function(let name, let args):
      return try applyFunction(name, args, env: env)
    default:
      throw EvaluatorError.unsupportedNode(
        String(describing: expr).components(separatedBy: "(").first ?? "?")
    }
  }

  // MARK: Constants

  private func resolveConstant(_ c: MathConstant) throws -> ComplexDouble {
    switch c {
    case .pi: return ComplexDouble(Double.pi)
    case .e: return ComplexDouble(M_E)
    case .i: return ComplexDouble.i
    case .infinity: return ComplexDouble(Double.infinity)
    case .negInfinity: return ComplexDouble(-Double.infinity)
    case .nan: return ComplexDouble(Double.nan)
    case .j, .k:
      throw EvaluatorError.unsupportedNode("Quaternion basis \(c) requires quaternion arithmetic")
    }
  }

  // MARK: Binary operators

  private func applyBinary(
    _ op: BinaryOp,
    _ lhs: MathLexExpression,
    _ rhs: MathLexExpression,
    env: Env
  ) throws -> ComplexDouble {
    let l = try evaluate(lhs, env: env)
    let r = try evaluate(rhs, env: env)
    switch op {
    case .add: return l + r
    case .sub: return l - r
    case .mul: return l * r
    case .div: return l / r
    case .pow:
      // Complex power: z^w = exp(w * ln(z)) — simplified real-exponent path
      if r.imaginary == 0 {
        return complexPow(l, r.real)
      }
      throw EvaluatorError.unsupportedNode("complex^complex power")
    case .mod:
      throw EvaluatorError.unsupportedNode("modulo over complex numbers")
    case .plusMinus, .minusPlus:
      throw EvaluatorError.unsupportedNode("BinaryOp(\(op))")
    }
  }

  // MARK: Unary operators

  private func applyUnary(
    _ op: UnaryOp,
    _ operand: MathLexExpression,
    env: Env
  ) throws -> ComplexDouble {
    let v = try evaluate(operand, env: env)
    switch op {
    case .neg: return -v
    case .pos: return v
    case .factorial:
      guard v.imaginary == 0, v.real >= 0 else {
        throw EvaluatorError.domainError("factorial requires non-negative real")
      }
      return ComplexDouble(tgamma(v.real + 1))
    case .transpose:
      throw EvaluatorError.unsupportedNode("transpose (matrix operation)")
    }
  }

  // MARK: Functions

  private func applyFunction(
    _ name: String,
    _ args: [MathLexExpression],
    env: Env
  ) throws -> ComplexDouble {
    let vals = try args.map { try evaluate($0, env: env) }
    if let result = applyBuiltin(name, vals) { return result }
    throw EvaluatorError.unknownFunction(name, argCount: vals.count)
  }

  /// Complex function dispatch. Handles real-valued and complex inputs.
  private func applyBuiltin(_ name: String, _ args: [ComplexDouble]) -> ComplexDouble? {
    // For purely real arguments delegate to the real evaluator path for clarity.
    let allReal = args.allSatisfy { $0.imaginary == 0 }
    let reals = args.map(\.real)

    switch (name, args.count) {
    case ("abs", 1):
      return ComplexDouble(args[0].magnitude)
    case ("conj", 1):
      return args[0].conjugate
    case ("re", 1):
      return ComplexDouble(args[0].real)
    case ("im", 1):
      return ComplexDouble(args[0].imaginary)
    // For trig/exp/log, use real fast paths when possible.
    case ("exp", 1) where allReal: return ComplexDouble(exp(reals[0]))
    case ("exp", 1):
      // exp(a+bi) = exp(a)(cos(b)+i·sin(b))
      let ea = exp(args[0].real)
      return ComplexDouble(ea * cos(args[0].imaginary), ea * sin(args[0].imaginary))
    case ("ln", 1) where allReal && reals[0] > 0: return ComplexDouble(log(reals[0]))
    case ("ln", 1):
      // ln(z) = ln|z| + i·arg(z)
      return ComplexDouble(
        log(args[0].magnitude),
        atan2(args[0].imaginary, args[0].real))
    case ("sin", 1) where allReal: return ComplexDouble(sin(reals[0]))
    case ("cos", 1) where allReal: return ComplexDouble(cos(reals[0]))
    case ("sqrt", 1) where allReal && reals[0] >= 0: return ComplexDouble(sqrt(reals[0]))
    case ("sqrt", 1):
      let r = args[0].magnitude
      let theta = atan2(args[0].imaginary, args[0].real)
      return ComplexDouble(sqrt(r) * cos(theta / 2), sqrt(r) * sin(theta / 2))
    case ("clamp", 3) where allReal:
      return ComplexDouble(min(max(reals[0], reals[1]), reals[2]))
    case ("lerp", 3):
      return args[0] + (args[1] - args[0]) * args[2]
    case ("rad", 1) where allReal: return ComplexDouble(reals[0] * (.pi / 180))
    case ("deg", 1) where allReal: return ComplexDouble(reals[0] * (180 / .pi))
    default: return nil
    }
  }

  // MARK: Helpers

  /// Real-exponent complex power: z^n.
  private func complexPow(_ z: ComplexDouble, _ n: Double) -> ComplexDouble {
    let r = pow(z.magnitude, n)
    let theta = atan2(z.imaginary, z.real) * n
    return ComplexDouble(r * cos(theta), r * sin(theta))
  }
}