import { create, all, type EvalFunction } from 'mathjs'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const math = create(all, { number: 'number', precision: 14 } as any); function buildScope(variables: Record): Record { const scope: Record = {}; for (const [name, expr] of Object.entries(variables)) { if (!expr.trim()) continue; try { scope[name] = math.evaluate(expr); } catch { // skip invalid variables } } return scope; } export interface EvalResult { result: string; error: boolean; assignedName?: string; assignedValue?: string; } export function evaluateExpression( expression: string, variables: Record = {} ): EvalResult { const trimmed = expression.trim(); if (!trimmed) return { result: '', error: false }; try { const scope = buildScope(variables); // eslint-disable-next-line @typescript-eslint/no-explicit-any const raw = math.evaluate(trimmed, scope as any); const formatted = formatValue(raw); // Detect assignment: "name = expr" or "name(args) = expr" const assignMatch = trimmed.match(/^([a-zA-Z_]\w*)\s*(?:\([^)]*\))?\s*=/); if (assignMatch) { return { result: formatted, error: false, assignedName: assignMatch[1], assignedValue: formatted, }; } return { result: formatted, error: false }; } catch (err) { const msg = err instanceof Error ? err.message : String(err); return { result: msg.replace(/^Error: /, ''), error: true }; } } function formatValue(value: unknown): string { if (value === null || value === undefined) return 'null'; if (typeof value === 'boolean') return String(value); if (typeof value === 'number') { if (!isFinite(value)) return value > 0 ? 'Infinity' : '-Infinity'; if (value === 0) return '0'; const abs = Math.abs(value); if (abs >= 1e13 || (abs < 1e-7 && abs > 0)) { return value.toExponential(6).replace(/\.?0+(e)/, '$1'); } return parseFloat(value.toPrecision(12)).toString(); } try { return math.format(value as never, { precision: 10 }); } catch { return String(value); } } // Compilation cache for fast repeated graph evaluation const compileCache = new Map(); function getCompiled(expr: string): EvalFunction | null { if (!compileCache.has(expr)) { try { compileCache.set(expr, math.compile(expr) as EvalFunction); } catch { return null; } if (compileCache.size > 200) { compileCache.delete(compileCache.keys().next().value!); } } return compileCache.get(expr) ?? null; } export function evaluateAt( expression: string, x: number, variables: Record ): number { const compiled = getCompiled(expression); if (!compiled) return NaN; try { const scope = buildScope(variables); const result = compiled.evaluate({ ...scope, x }); return typeof result === 'number' && isFinite(result) ? result : NaN; } catch { return NaN; } } export interface GraphPoint { x: number; y: number; } export function sampleFunction( expression: string, xMin: number, xMax: number, numPoints: number, variables: Record = {} ): Array { if (!expression.trim()) return []; const compiled = getCompiled(expression); if (!compiled) return []; const scope = buildScope(variables); const points: Array = []; const step = (xMax - xMin) / numPoints; let prevY: number | null = null; const jumpThreshold = Math.abs(xMax - xMin) * 4; for (let i = 0; i <= numPoints; i++) { const x = xMin + i * step; let y: number; try { const r = compiled.evaluate({ ...scope, x }); y = typeof r === 'number' ? r : NaN; } catch { y = NaN; } if (!isFinite(y) || isNaN(y)) { if (points.length > 0 && points[points.length - 1] !== null) { points.push(null); } prevY = null; } else { if (prevY !== null && Math.abs(y - prevY) > jumpThreshold) { points.push(null); } points.push({ x, y }); prevY = y; } } return points; }