UNPKG

nerdamer-prime

Version:
1,684 lines (1,539 loc) 634 kB
/* * Author : Martin Donk * Website : http://www.nerdamer.com * Email : martin.r.donk@gmail.com * Source : https://github.com/jiggzson/nerdamer */ // Type imports for JSDoc ====================================================== // These typedefs provide type aliases for the interfaces defined in index.d.ts. // They enable proper type checking when working with the classes defined in this file. // // Usage patterns: // - For return types: @returns {NerdamerSymbolType} // - For parameters: @param {NerdamerSymbolType} symbol // - For variable declarations: /** @type {NerdamerSymbolType} */ // // Note: When casting local class instances to interface types, use the pattern: // /** @type {InterfaceType} */ (/** @type {unknown} */ (localInstance)) // This is needed because TypeScript sees local classes and interfaces as separate types. /** * Core type aliases from index.d.ts * * @typedef {import('./index').NerdamerCore.NerdamerSymbol} NerdamerSymbolType * * @typedef {import('./index').NerdamerCore.Frac} FracType * * @typedef {import('./index').NerdamerCore.Vector} VectorType * * @typedef {import('./index').NerdamerCore.Matrix} MatrixType * * @typedef {import('./index').NerdamerCore.Parser} ParserType * * @typedef {import('./index').NerdamerCore.Collection} CollectionType * * @typedef {import('./index').NerdamerCore.NerdamerSet} SetType * * @typedef {import('./index').NerdamerCore.Settings} SettingsType * * @typedef {import('./index').NerdamerExpression} ExpressionType * * @typedef {typeof import('./index')} NerdamerType * * @typedef {import('./index').NerdamerCore.Token} TokenType * * @typedef {import('./index').NerdamerCore.ScopeArray} ScopeArrayType * * Arithmetic operand type (Symbol, Vector, or Matrix) * * @typedef {import('./index').ArithmeticOperand} ArithmeticOperand * * Expand options type * * @typedef {import('./index').ExpandOptions} ExpandOptions * * LaTeX token types * * @typedef {import('./index').LaTeXToken} LaTeXTokenType * * @typedef {import('./index').FilteredLaTeXToken} FilteredLaTeXTokenType * * Output and parameter types * * @typedef {import('./index').OutputType} OutputType * * @typedef {import('./index').ExpressionParam} ExpressionParam * * @typedef {import('./index').SortFn<unknown>} SortFn * * Constructor types (for factory functions) * * @typedef {import('./index').NerdamerCore.FracConstructor} FracConstructor * * @typedef {import('./index').NerdamerCore.SymbolConstructor} SymbolConstructor * * @typedef {import('./index').NerdamerCore.VectorConstructor} VectorConstructor * * @typedef {import('./index').NerdamerCore.MatrixConstructor} MatrixConstructor * * @typedef {import('./index').NerdamerCore.ExpressionConstructor} ExpressionConstructor * * @typedef {import('./index').NerdamerCore.SetConstructor} SetConstructor * * @typedef {import('./index').NerdamerCore.CollectionConstructor} CollectionConstructor * * @typedef {import('./index').NerdamerCore.Fraction} FractionInterface * * @typedef {import('./index').NerdamerCore.ScientificConstructor} ScientificConstructor * * @typedef {import('./index').NerdamerCore.ParserConstructor} ParserConstructor * * @typedef {import('./index').NerdamerCore.LaTeX} LaTeXInterface * * @typedef {import('./index').NerdamerCore.Math2} Math2Interface * * @typedef {import('./index').NerdamerCore.Build} BuildInterface * * @typedef {import('./index').NerdamerCore.CoreUtils} CoreUtilsInterface * * @typedef {import('./index').NerdamerCore.Utils} UtilsInterface * * @typedef {import('./index').NerdamerCore.InternalParseResult} InternalParseResult * * Exceptions object type (for CoreDeps.exceptions) * * @typedef {{ * DivisionByZero: CustomErrorConstructor; * ParseError: CustomErrorConstructor; * OutOfFunctionDomainError: CustomErrorConstructor; * UndefinedError: CustomErrorConstructor; * MaximumIterationsReached: CustomErrorConstructor; * NerdamerTypeError: CustomErrorConstructor; * ParityError: CustomErrorConstructor; * OperatorError: CustomErrorConstructor; * OutOfRangeError: CustomErrorConstructor; * DimensionError: CustomErrorConstructor; * InvalidVariableNameError: CustomErrorConstructor; * ValueLimitExceededError: CustomErrorConstructor; * NerdamerValueError: CustomErrorConstructor; * SolveError: CustomErrorConstructor; * InfiniteLoopError: CustomErrorConstructor; * UnexpectedTokenError: CustomErrorConstructor; * }} ExceptionsType * Exception types * * @typedef {import('./index').NerdamerCore.DivisionByZero} DivisionByZeroType * * @typedef {import('./index').NerdamerCore.ParseError} ParseErrorType * * @typedef {import('./index').NerdamerCore.NerdamerTypeError} NerdamerTypeErrorType * * @typedef {import('./index').NerdamerCore.Core} CoreType * * @typedef {import('./index').NerdamerCore.PowerValue} PowerValueType * * @typedef {import('big-integer').BigInteger} BigIntegerType * * @typedef {import('big-integer').BigIntegerStatic} BigIntegerStaticType * * @typedef {import('decimal.js').Decimal} DecimalType * * @typedef {typeof import('decimal.js').default} DecimalStaticType * * Custom error constructor type - used for exception classes * * @typedef {new (message?: string) => Error} CustomErrorConstructor * * @typedef {Record< * string, * [Function, number] | [Function, number[]] | [Function, number, { name: string; params: string[]; body: string }] * >} FunctionMapType */ // externals ==================================================================== /* BigInteger.js v1.6.28 https://github.com/peterolson/BigInteger.js/blob/master/LICENSE */ const nerdamerBigInt = typeof globalThis.nerdamerBigInt === 'undefined' ? require('big-integer') : globalThis.nerdamerBigInt; /* Decimal.js v10.2.1 https://github.com/MikeMcl/decimal.js/LICENCE */ const nerdamerBigDecimal = typeof globalThis.nerdamerBigDecimal === 'undefined' ? require('decimal.js') : globalThis.nerdamerBigDecimal; // Set BigDecimal precision immediately after import nerdamerBigDecimal.set({ precision: 250 }); /* Mathematical constants */ const nerdamerConstants = typeof globalThis.nerdamerConstants === 'undefined' ? require('./constants.js') : globalThis.nerdamerConstants; // ============================================================================ // Runtime state variables - declared before CoreDeps to avoid forward references // ============================================================================ // Custom operators registry - populated by IIFE /** @type {{ [key: string]: { precedence: number; operator: string; action: string; postfix?: boolean } }} */ const CUSTOM_OPERATORS = {}; // Runtime state arrays - used by CoreDeps.state getters /** @type {ExpressionType[]} */ const EXPRESSIONS = []; /** @type {Record<string, NerdamerSymbolType>} */ const VARS_STORE = {}; /** @type {string[]} */ const RESERVED = []; /** @type {string[]} */ const WARNINGS = []; /** @type {string[]} */ const USER_FUNCTIONS = []; // Late-binding references container - populated after classes are defined // Used by CoreDeps getters to avoid forward reference issues /** * @type {{ * Settings: SettingsType | null; * Math2: Math2Interface | null; * }} */ const LateRefs = { Settings: /** @type {SettingsType | null} */ (null), Math2: /** @type {Math2Interface | null} */ (null), }; // CoreDeps - Centralized Dependency Registry ================================== // This single registry replaces 45+ scattered *Deps objects with a unified, // hierarchical structure. Benefits: // - Single source of truth for all shared dependencies // - Clear initialization order (externals -> constants -> classes -> parser) // - Lazy getters for values defined later in initialization // - Type-safe access patterns // // Structure: // CoreDeps.ext - External imports (bigInt, bigDec, constants) // CoreDeps.groups - Symbol group constants (N, P, S, EX, FN, PL, CB, CP) // CoreDeps.fnNames - Function name constants (SQRT, ABS, FACTORIAL, etc.) // CoreDeps.settings - Settings reference // CoreDeps.state - Runtime state (EXPRESSIONS, VARS, RESERVED, etc.) // CoreDeps.classes - Class constructors (Frac, NerdamerSymbol, Vector, etc.) // CoreDeps.utils - Utility functions // CoreDeps.parser - Parser instance (set during IIFE init) // CoreDeps.core - Core object C (set during IIFE init) /** * @type {{ * ext: { * bigInt: BigIntegerStaticType; * bigDec: DecimalStaticType; * PRIMES: number[]; * PRIMES_SET: Record<number, boolean>; * LONG_PI: string; * LONG_E: string; * BIG_LOG_CACHE: string[]; * }; * groups: { * N: 1; * P: 2; * S: 3; * EX: 4; * FN: 5; * PL: 6; * CB: 7; * CP: 8; * }; * fnNames: { * SQRT: 'sqrt'; * ABS: 'abs'; * FACTORIAL: 'factorial'; * DOUBLEFACTORIAL: 'dfactorial'; * PARENTHESIS: 'parens'; * LOG: 'log'; * CONST_HASH: '#'; * }; * settings: SettingsType; * state: { * EXPRESSIONS: ExpressionType[]; * VARS: Record<string, NerdamerSymbolType>; * CONSTANTS: Record<string, NerdamerSymbolType | string | number>; * RESERVED: string[]; * WARNINGS: string[]; * USER_FUNCTIONS: string[]; * CUSTOM_OPERATORS: { * [key: string]: { precedence: number; operator: string; action: string; postfix?: boolean }; * }; * }; * classes: { * Frac: FracConstructor; * Fraction: FractionInterface; * NerdamerSymbol: SymbolConstructor; * Vector: VectorConstructor; * Matrix: MatrixConstructor; * Expression: ExpressionConstructor; * Collection: CollectionConstructor; * NerdamerSet: SetConstructor; * Scientific: ScientificConstructor; * Parser: ParserConstructor; * LaTeX: LaTeXInterface; * Math2: Math2Interface; * Build: BuildInterface; * }; * utils: { * isSymbol: (x: unknown) => boolean; * isVector: (x: unknown) => boolean; * isMatrix: (x: unknown) => boolean; * isExpression: (x: unknown) => boolean; * isNumericSymbol: (symbol: NerdamerSymbolType) => boolean; * isFraction: (x: unknown) => boolean; * isArray: (arr: unknown) => boolean; * isInt: (n: number | string | unknown) => boolean; * text: (symbol: NerdamerSymbolType, opt?: OutputType, useGroup?: number, decp?: number) => string; * variables: (obj: NerdamerSymbolType | FracType, poly?: boolean, vars?: unknown) => string[]; * scientificToDecimal: (num: number) => string; * err: (msg: string, ErrorObj?: CustomErrorConstructor) => void; * block: (setting: string, f: Function, opt?: boolean, obj?: unknown) => unknown; * evaluate: (symbol: NerdamerSymbolType, o?: Record<string, ExpressionParam>) => NerdamerSymbolType; * reserveNames: (obj: object) => void; * nround: (x: string | number, s?: number) => string | number; * remove: (arr: unknown[], index: number) => unknown; * _setFunction: (fnName: string | Function, fnParams?: string[], fnBody?: string) => boolean; * _clearFunctions: () => void; * symfunction: (fname: string, args: NerdamerSymbolType[]) => NerdamerSymbolType; * callfunction: (fname: string, args: NerdamerSymbolType[]) => NerdamerSymbolType; * }; * exceptions: ExceptionsType; * parser: ParserType; * core: CoreType; * libExports: typeof nerdamer; * version: string; * }} */ const CoreDeps = { // External imports - available immediately ext: { bigInt: nerdamerBigInt, bigDec: nerdamerBigDecimal, PRIMES: nerdamerConstants.PRIMES, PRIMES_SET: nerdamerConstants.PRIMES_SET, LONG_PI: nerdamerConstants.LONG_PI, LONG_E: nerdamerConstants.LONG_E, BIG_LOG_CACHE: nerdamerConstants.BIG_LOG_CACHE, }, // Symbol group constants - available immediately groups: { N: 1, // A number P: 2, // A number with a rational power e.g. 2^(3/5) S: 3, // A single variable e.g. x EX: 4, // An exponential FN: 5, // A function PL: 6, // Same name, different powers e.g. 1/x + x^2 CB: 7, // Multiplication composite e.g. x*y CP: 8, // Addition composite e.g. x+1 or x+y }, // Function name constants - available immediately fnNames: { SQRT: 'sqrt', ABS: 'abs', FACTORIAL: 'factorial', DOUBLEFACTORIAL: 'dfactorial', PARENTHESIS: 'parens', LOG: 'log', CONST_HASH: '#', }, // Settings reference - getter using LateRefs for forward reference safety get settings() { return LateRefs.Settings; }, // Runtime state - arrays now defined before CoreDeps state: { get EXPRESSIONS() { return EXPRESSIONS; }, get VARS() { return VARS_STORE; }, CONSTANTS: /** @type {Record<string, NerdamerSymbolType | string>} */ ({}), get RESERVED() { return RESERVED; }, get WARNINGS() { return WARNINGS; }, get USER_FUNCTIONS() { return USER_FUNCTIONS; }, get CUSTOM_OPERATORS() { return CUSTOM_OPERATORS; }, }, // Class constructors - set by IIFE after class definitions classes: { Frac: /** @type {FracConstructor} */ (null), Fraction: /** @type {FractionInterface} */ (null), NerdamerSymbol: /** @type {SymbolConstructor} */ (null), Vector: /** @type {VectorConstructor} */ (null), Matrix: /** @type {MatrixConstructor} */ (null), Expression: /** @type {ExpressionConstructor} */ (null), Collection: /** @type {CollectionConstructor} */ (null), NerdamerSet: /** @type {SetConstructor} */ (null), Scientific: /** @type {ScientificConstructor} */ (null), Parser: /** @type {ParserConstructor} */ (null), LaTeX: /** @type {LaTeXInterface} */ (null), Math2: /** @type {Math2Interface} */ (null), Build: /** @type {BuildInterface} */ (null), }, // Utility functions - use getters for module-scope functions // symfunction and callfunction are set by IIFE since they need parser binding utils: { get isSymbol() { return isSymbol; }, get isVector() { return isVector; }, get isMatrix() { return isMatrix; }, get isExpression() { return isExpression; }, get isNumericSymbol() { return isNumericSymbol; }, get isFraction() { return isFraction; }, get isArray() { return isArray; }, get isInt() { return isInt; }, get text() { return text; }, get variables() { return variables; }, get scientificToDecimal() { return scientificToDecimal; }, get err() { return err; }, get block() { return block; }, get evaluate() { return evaluate; }, get reserveNames() { return reserveNames; }, get nround() { return nround; }, get remove() { return remove; }, get _setFunction() { return _setFunction; }, get _clearFunctions() { return _clearFunctions; }, // Parser-bound methods - set by IIFE after parser instantiation symfunction: /** @type {(fname: string, args: NerdamerSymbolType[]) => NerdamerSymbolType} */ (null), callfunction: /** @type {(fname: string, args: NerdamerSymbolType[]) => NerdamerSymbolType} */ (null), }, // Exception classes - assigned after exception definitions (see below Frac class) exceptions: /** @type {ExceptionsType} */ (null), // Parser instance - set by IIFE after Parser creation parser: /** @type {ParserType} */ (null), // Core object C - set by IIFE at end core: /** @type {CoreType} */ (null), // Library exports function - set by IIFE libExports: /** @type {typeof nerdamer} */ (null), // Version string version: '1.1.16', }; // Groups object - maps to CoreDeps.groups for external access const Groups = { N: CoreDeps.groups.N, P: CoreDeps.groups.P, S: CoreDeps.groups.S, EX: CoreDeps.groups.EX, FN: CoreDeps.groups.FN, PL: CoreDeps.groups.PL, CB: CoreDeps.groups.CB, CP: CoreDeps.groups.CP, }; // ============================================================================ // Math Polyfills // ============================================================================ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ Math.sign ||= function sign(x) { x = Number(x); // Convert to a number if (x === 0 || isNaN(x)) { return x; } return x > 0 ? 1 : -1; }; Math.cosh ||= function cosh(x) { const y = Math.exp(x); return (y + 1 / y) / 2; }; Math.sech ||= function sech(x) { return 1 / Math.cosh(x); }; Math.csch ||= function csch(x) { return 1 / Math.sinh(x); }; Math.coth ||= function coth(x) { return 1 / Math.tanh(x); }; Math.sinh ||= function sinh(x) { const y = Math.exp(x); return (y - 1 / y) / 2; }; Math.tanh ||= function tanh(x) { if (x === Infinity) { return 1; } if (x === -Infinity) { return -1; } const y = Math.exp(2 * x); return (y - 1) / (y + 1); }; Math.asinh ||= function asinh(x) { if (x === -Infinity) { return x; } return Math.log(x + Math.sqrt(x * x + 1)); }; Math.acosh ||= function acosh(x) { return Math.log(x + Math.sqrt(x * x - 1)); }; Math.atanh ||= function atanh(x) { return Math.log((1 + x) / (1 - x)) / 2; }; Math.trunc ||= function trunc(x) { if (isNaN(x)) { return NaN; } if (x > 0) { return Math.floor(x); } return Math.ceil(x); }; // ============================================================================ // Scientific notation helper // ============================================================================ // Extracted as standalone function to avoid forward-reference to Scientific class /** * Checks if a string is in scientific notation (e.g., "1.5e10", "2E-5") * * @param {string} num * @returns {boolean} */ function isScientificNotation(num) { return /\d+\.?\d*e[+-]*\d+/iu.test(num); } // Fraction Object ============================================================== // Extracted outside IIFE to enable proper TypeScript type inference. // This static utility object converts decimals to fractions. /** Static utility object for converting decimals to fractions. */ const Fraction = { /** * Converts a decimal to a fraction * * @param {number | string} value * @param {object} [_opts] * @returns {Array} An array containing the numerator and the denominator */ convert(value, _opts) { const numValue = Number(value); let frac; if (numValue === 0) { frac = [0, 1]; } else if (Math.abs(numValue) < 1e-6 || Math.abs(numValue) > 1e20) { const qc = this.quickConversion(numValue); if (qc[1] <= 1e16) { const abs = Math.abs(numValue); const sign = numValue / abs; frac = this.fullConversion(abs.toFixed(`${qc[1]}`.length - 1)); frac[0] *= sign; } else { frac = qc; } } else { frac = this.fullConversion(numValue); } return frac; }, /** * If the fraction is too small or too large this gets called instead of fullConversion method * * @param {number | string} value * @returns {Array} An array containing the numerator and the denominator as strings */ quickConversion(value) { const stripSign = function (s) { // Explicitely convert to a string if (typeof s !== 'string') { s = s.toString(); } let sign = ''; // Remove and store the sign const start = s.charAt(0); if (start === '-') { s = s.substr(1, s.length); sign = '-'; } else if (start === '+') { // Just remove the plus sign s = s.substr(1, s.length); } return { sign, value: s, }; }; function convert(val) { // Explicitely convert to a decimal if (isScientificNotation(val)) { val = scientificToDecimal(val); } // Split the value into the sign and the value const nparts = stripSign(val); // Split it at the decimal. We'll refer to it as the coeffient parts const cparts = nparts.value.split('.'); // Combine the entire number by removing leading zero and adding the decimal part // This would be teh same as moving the decimal point to the end let num; // We're dealing with integers if (cparts.length === 1) { num = cparts[0]; } else { num = cparts[0] + cparts[1]; } const n = cparts[1] ? cparts[1].length : 0; // Generate the padding for the zeros const den = `1${'0'.repeat(n)}`; if (num !== '0') { num = num.replace(/^0+/u, ''); } return [nparts.sign + num, den]; } return convert(value); }, /** * Returns a good approximation of a fraction. This method gets called by convert * http://mathforum.org/library/drmath/view/61772.html Decimal To Fraction Conversion - A Simpler Version Dr * Peterson * * @param {number | string} dec * @returns {Array} An array containing the numerator and the denominator */ fullConversion(dec) { const numDec = Number(dec); // This doesn't work for values approaching as small as epsilon const epsilon = Math.abs(numDec) > 1e10 ? 1e-16 : 1e-30; let done = false; // You can adjust the epsilon to a larger number if you don't need very high precision let n1 = 0; let d1 = 1; let n2 = 1; let d2 = 0; let n = 0; let q = numDec; let num; let den; // Relative epsilon for rounding large q values to nearest integer. // This fixes floating-point precision errors in reciprocals (e.g., 1/1e-15 = 999999999999999.9). // We use ~45x Number.EPSILON to allow for accumulated rounding errors. // This is independent of Settings.PRECISION since we're dealing with IEEE 754 double limits. const roundingEpsilon = 1e-14; // ~45 * Number.EPSILON (2.2e-16) while (!done) { n++; // For very large q values, round to nearest integer if within floating-point error let a; if (Math.abs(q) > 1e10) { const rounded = Math.round(q); const relDiff = Math.abs(q - rounded) / Math.abs(rounded); a = relDiff < roundingEpsilon ? rounded : Math.floor(q); } else { a = Math.floor(q); } num = n1 + a * n2; den = d1 + a * d2; const e = q - a; if (e < epsilon) { done = true; } q = 1 / e; n1 = n2; d1 = d2; n2 = num; d2 = den; if (Math.abs(num / den - numDec) < epsilon || n > 30) { done = true; } } return [num, den]; }, }; // Assign Fraction to CoreDeps immediately CoreDeps.classes.Fraction = /** @type {FractionInterface} */ (/** @type {unknown} */ (Fraction)); // CustomError Function ============================================================= // Extracted outside IIFE to enable proper TypeScript type inference. // This function creates custom error classes. /** * Creates a custom error class with the given name. * * @param {string} name - The name of the custom error class * @returns {new (message?: string) => Error} A custom error constructor */ function customError(name) { const E = function (message) { this.name = name; this.message = message === undefined ? '' : message; const error = new Error(this.message); error.name = this.name; this.stack = error.stack; }; // Create an empty error E.prototype = Object.create(Error.prototype); return E; } // DivisionByZero Error ================================================================ /** * Error thrown for division by zero. * * @type {new (message?: string) => Error} */ const DivisionByZero = customError('DivisionByZero'); // ParseError Error ==================================================================== /** * Error thrown if an error occurred during parsing. * * @type {new (message?: string) => Error} */ const ParseError = customError('ParseError'); // UndefinedError Error ================================================================ /** * Error thrown if the expression results in undefined. * * @type {new (message?: string) => Error} */ const UndefinedError = customError('UndefinedError'); // OutOfFunctionDomainError Error ====================================================== /** * Error thrown if input is out of the function domain. * * @type {new (message?: string) => Error} */ const OutOfFunctionDomainError = customError('OutOfFunctionDomainError'); // MaximumIterationsReached Error ====================================================== /** * Error thrown if a function exceeds maximum iterations. * * @type {new (message?: string) => Error} */ const MaximumIterationsReached = customError('MaximumIterationsReached'); // NerdamerTypeError Error ============================================================= /** * Error thrown if the parser receives an incorrect type. * * @type {new (message?: string) => Error} */ const NerdamerTypeError = customError('NerdamerTypeError'); // ParityError Error =================================================================== /** * Error thrown if bracket parity is not correct. * * @type {new (message?: string) => Error} */ const ParityError = customError('ParityError'); // OperatorError Error ================================================================= /** * Error thrown if an unexpected or incorrect operator is encountered. * * @type {new (message?: string) => Error} */ const OperatorError = customError('OperatorError'); // OutOfRangeError Error =============================================================== /** * Error thrown if an index is out of range. * * @type {new (message?: string) => Error} */ const OutOfRangeError = customError('OutOfRangeError'); // DimensionError Error ================================================================ /** * Error thrown if dimensions are incorrect (mostly for matrices). * * @type {new (message?: string) => Error} */ const DimensionError = customError('DimensionError'); // InvalidVariableNameError Error ====================================================== /** * Error thrown if variable name violates naming rule. * * @type {new (message?: string) => Error} */ const InvalidVariableNameError = customError('InvalidVariableNameError'); // ValueLimitExceededError Error ======================================================= /** * Error thrown if the limits of the library are exceeded for a function. * * @type {new (message?: string) => Error} */ const ValueLimitExceededError = customError('ValueLimitExceededError'); // NerdamerValueError Error ============================================================ /** * Error thrown if the value is an incorrect LH or RH value. * * @type {new (message?: string) => Error} */ const NerdamerValueError = customError('NerdamerValueError'); // SolveError Error ==================================================================== /** * Error thrown for solve-related errors. * * @type {new (message?: string) => Error} */ const SolveError = customError('SolveError'); // InfiniteLoopError Error ============================================================= /** * Error thrown for an infinite loop. * * @type {new (message?: string) => Error} */ const InfiniteLoopError = customError('InfiniteLoopError'); // UnexpectedTokenError Error ========================================================== /** * Error thrown if an operator is found when there shouldn't be one. * * @type {new (message?: string) => Error} */ const UnexpectedTokenError = customError('UnexpectedTokenError'); // Assign CoreDeps.exceptions now that all exception classes are defined CoreDeps.exceptions = { DivisionByZero, ParseError, OutOfFunctionDomainError, UndefinedError, MaximumIterationsReached, NerdamerTypeError, ParityError, OperatorError, OutOfRangeError, DimensionError, InvalidVariableNameError, ValueLimitExceededError, NerdamerValueError, SolveError, InfiniteLoopError, UnexpectedTokenError, }; // Frac Class =================================================================== // Extracted outside IIFE to enable proper TypeScript type inference. // Dependencies are accessed via CoreDeps for centralized management. /** * Dependency accessor for Frac class. Uses CoreDeps as the single source of truth for all dependencies. * * Note: bigInt and bigDec are typed as BigIntegerStaticType and DecimalStaticType. While these types don't expose * constructor signatures in TypeScript, the libraries support 'new' at runtime. Type assertions are used at call * sites. * * @type {{ * bigInt: BigIntegerStaticType; * bigDec: DecimalStaticType; * isInt: (n: number | string | unknown) => boolean; * scientificToDecimal: (num: number) => string; * DivisionByZero: CustomErrorConstructor; * Settings: SettingsType; * Fraction: typeof Fraction; * }} */ const FracDeps = { get bigInt() { return CoreDeps.ext.bigInt; }, get bigDec() { return CoreDeps.ext.bigDec; }, get isInt() { return CoreDeps.utils.isInt; }, get scientificToDecimal() { return CoreDeps.utils.scientificToDecimal; }, get DivisionByZero() { return DivisionByZero; }, get Settings() { return CoreDeps.settings; }, get Fraction() { return Fraction; }, }; /** * High-precision fraction class. * * @implements {FracType} */ class Frac { /** @type {BigIntegerType} */ num; /** @type {BigIntegerType} */ den; /** @param {number | string | Frac} [n] */ constructor(n) { if (n instanceof Frac) { // eslint-disable-next-line no-constructor-return -- Frac is designed to return existing instances return n; } if (n === undefined) { // eslint-disable-next-line no-constructor-return -- Early return for undefined return this; } try { if (FracDeps.isInt(n)) { try { // @ts-expect-error - bigInt accepts string | number at runtime this.num = FracDeps.bigInt(n); this.den = FracDeps.bigInt(1); } catch (e) { if (/** @type {Error} */ (e).message === 'timeout') { throw e; } // eslint-disable-next-line no-constructor-return -- Fallback to simple parsing return Frac.simple(n); } } else { const frac = /** @type {unknown} */ (n) instanceof FracDeps.bigDec ? FracDeps.Fraction.quickConversion(n) : FracDeps.Fraction.convert(n); // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types this.num = new FracDeps.bigInt(frac[0]); // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types this.den = new FracDeps.bigInt(frac[1]); } } catch (e) { if (/** @type {Error} */ (e).message === 'timeout') { throw e; } // eslint-disable-next-line no-constructor-return -- Fallback to simple parsing return Frac.simple(n); } } /** * Safe to use with negative numbers or other types * * @param {number | string | FracType} n * @returns {FracType} */ static create(n) { if (n instanceof Frac) { return n; } n = n.toString(); const isNeg = n.charAt(0) === '-'; if (isNeg) { n = n.substr(1, n.length - 1); } const frac = new Frac(n); if (isNeg) { frac.negate(); } return frac; } /** * @param {unknown} o * @returns {o is FracType} */ static isFrac(o) { return o instanceof Frac; } /** * @param {string | number} n * @param {string | number} d * @returns {FracType} */ static quick(n, d) { const frac = new Frac(); // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types frac.num = new FracDeps.bigInt(n); // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types frac.den = new FracDeps.bigInt(d); return frac; } /** * @param {number | string} n * @returns {FracType} */ static simple(n) { const nstr = String(FracDeps.scientificToDecimal(/** @type {number} */ (n))); const mDc = nstr.split('.'); const num = mDc.join(''); /** @type {string} */ let den = '1'; const l = (mDc[1] || '').length; for (let i = 0; i < l; i++) { den += '0'; } const frac = Frac.quick(num, den); return frac.simplify(); } /** * @param {FracType} m * @returns {FracType} */ multiply(m) { if (this.isOne()) { return m.clone(); } if (m.isOne()) { return this.clone(); } const c = this.clone(); c.num = c.num.multiply(m.num); c.den = c.den.multiply(m.den); return c.simplify(); } /** * @param {FracType} m * @returns {FracType} */ divide(m) { if (m.equals(0)) { throw new FracDeps.DivisionByZero('Division by zero not allowed!'); } return this.clone().multiply(m.clone().invert()).simplify(); } /** * @param {FracType} m * @returns {FracType} */ subtract(m) { return this.clone().add(m.clone().neg()); } /** * Alias for subtract * * @param {FracType} m * @returns {FracType} */ sub(m) { return this.subtract(m); } /** @returns {this} */ neg() { this.num = this.num.multiply(-1); return this; } /** * @param {FracType} m * @returns {FracType} */ add(m) { const n1 = this.den; const n2 = m.den; const c = this.clone(); const a = c.num; const b = m.num; if (n1.equals(n2)) { c.num = a.add(b); } else { c.num = a.multiply(n2).add(b.multiply(n1)); c.den = n1.multiply(n2); } return c.simplify(); } /** * @param {FracType} m * @returns {FracType} */ mod(m) { const a = this.clone(); const b = m.clone(); a.num = a.num.multiply(b.den); a.den = a.den.multiply(b.den); b.num = b.num.multiply(this.den); b.den = b.den.multiply(this.den); a.num = a.num.mod(b.num); return a.simplify(); } /** @returns {this} */ simplify() { const gcd = FracDeps.bigInt.gcd(this.num, this.den); this.num = this.num.divide(gcd); this.den = this.den.divide(gcd); return this; } /** @returns {FracType} */ clone() { const m = new Frac(); // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types m.num = new FracDeps.bigInt(this.num); // @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types m.den = new FracDeps.bigInt(this.den); return m; } /** * @param {number} [prec] * @returns {string} */ decimal(prec) { const sign = this.num.isNegative() ? '-' : ''; if (this.num.equals(this.den)) { return '1'; } prec ||= FracDeps.Settings.PRECISION; prec += 2; const narr = []; let n = this.num.abs(); const d = this.den; let i; for (i = 0; i < prec; i++) { const w = n.divide(d); const r = n.subtract(w.multiply(d)); narr.push(w); if (r.equals(0)) { break; } n = r.times(10); } const whole = narr.shift(); if (narr.length === 0) { return sign + whole.toString(); } if (i === prec) { const lt = []; for (let j = 0; j < 2; j++) { lt.unshift(narr.pop()); } narr.push(Math.round(Number(lt.join('.')))); } const dec = `${whole.toString()}.${narr.join('')}`; return sign + dec; } /** * @param {number} [prec] * @returns {string | number} */ toDecimal(prec) { prec ||= FracDeps.Settings.PRECISION; if (prec) { return this.decimal(prec); } return this.num.valueOf() / this.den.valueOf(); } /** * @param {FracType} n * @returns {[BigIntegerType, BigIntegerType]} */ qcompare(n) { return [this.num.multiply(n.den), n.num.multiply(this.den)]; } /** * @param {number | FracType} n * @returns {boolean} */ equals(n) { if (!isNaN(/** @type {number} */ (n))) { n = new Frac(/** @type {number} */ (n)); } const q = this.qcompare(/** @type {FracType} */ (n)); return q[0].equals(q[1]); } /** * @param {number | FracType} n * @returns {boolean} */ absEquals(n) { if (!isNaN(/** @type {number} */ (n))) { n = new Frac(/** @type {number} */ (n)); } const q = this.qcompare(/** @type {FracType} */ (n)); return q[0].abs().equals(q[1]); } /** * @param {number | FracType} n * @returns {boolean} */ greaterThan(n) { if (!isNaN(/** @type {number} */ (n))) { n = new Frac(/** @type {number} */ (n)); } const q = this.qcompare(/** @type {FracType} */ (n)); return q[0].gt(q[1]); } /** * Alias for greaterThan * * @param {number | FracType} n * @returns {boolean} */ gt(n) { return this.greaterThan(n); } /** * @param {number | FracType} n * @returns {boolean} */ gte(n) { return this.greaterThan(n) || this.equals(n); } /** * @param {number | FracType} n * @returns {boolean} */ lte(n) { return this.lessThan(n) || this.equals(n); } /** * @param {number | FracType} n * @returns {boolean} */ lessThan(n) { if (!isNaN(/** @type {number} */ (n))) { n = new Frac(/** @type {number} */ (n)); } const q = this.qcompare(/** @type {FracType} */ (n)); return q[0].lt(q[1]); } /** * Alias for lessThan * * @param {number | FracType} n * @returns {boolean} */ lt(n) { return this.lessThan(n); } /** @returns {boolean} */ isInteger() { return this.den.equals(1); } /** @returns {this} */ negate() { this.num = this.num.multiply(-1); return this; } /** @returns {this} */ invert() { const t = this.den; if (!this.num.equals(0)) { const isnegative = this.num.isNegative(); this.den = this.num.abs(); this.num = t; if (isnegative) { this.num = this.num.multiply(-1); } } return this; } /** @returns {boolean} */ isOne() { return this.num.equals(1) && this.den.equals(1); } /** @returns {-1 | 1} */ sign() { return this.num.isNegative() ? -1 : 1; } /** @returns {this} */ abs() { this.num = this.num.abs(); return this; } /** * @param {FracType} f * @returns {FracType} */ gcd(f) { // @ts-expect-error - bigInt.gcd accepts BigInteger at runtime return Frac.quick(FracDeps.bigInt.gcd(f.num, this.num), FracDeps.bigInt.lcm(f.den, this.den)); } /** @returns {string} */ toString() { return this.den.equals(1) ? this.num.toString() : `${this.num.toString()}/${this.den.toString()}`; } /** @returns {number | DecimalType} */ valueOf() { if (FracDeps.Settings.USE_BIG) { return new FracDeps.bigDec(this.num.toString()).div(new FracDeps.bigDec(this.den.toString())); } const retval = this.num.valueOf() / this.den.valueOf(); return retval; } /** @returns {boolean} */ isNegative() { return /** @type {number} */ (this.toDecimal()) < 0; } /** * Checks if this fraction contains the given number (i.e., is divisible by it) * * @param {number | FracType} n * @returns {boolean} */ contains(n) { const fracN = typeof n === 'number' ? new Frac(n) : n; return this.mod(fracN).equals(0); } } // Assign Frac to CoreDeps immediately for early access CoreDeps.classes.Frac = Frac; // NerdamerSet Class ==================================================================== // Extracted outside IIFE to enable proper TypeScript type inference. // Dependencies are accessed via CoreDeps for centralized management. /** * Dependency accessor for NerdamerSet class. Uses CoreDeps as the single source of truth. * * @type {{ * isVector: (x: unknown) => boolean; * Vector: VectorConstructor; * remove: (arr: unknown[], index: number) => unknown; * }} */ const SetDeps = { get isVector() { return CoreDeps.utils.isVector; }, get Vector() { return CoreDeps.classes.Vector; }, get remove() { return remove; }, // Remove is defined later in file }; /** * NerdamerSet class for mathematical set operations. * * @implements {SetType} */ class NerdamerSet { /** @type {NerdamerSymbolType[]} */ elements = []; /** * @param {VectorType | NerdamerSymbolType | undefined} [setArg] * @param {...NerdamerSymbolType} rest */ constructor(setArg, ...rest) { // If the first object isn't an array, convert it to one. if (typeof setArg === 'undefined') { // No arguments passed return; } let setVal = /** @type {VectorType} */ (setArg); if (!SetDeps.isVector(setArg)) { setVal = SetDeps.Vector.fromArray([/** @type {NerdamerSymbolType} */ (setArg), ...rest]); } if (setVal) { const { elements } = setVal; for (let i = 0, l = elements.length; i < l; i++) { this.add(/** @type {NerdamerSymbolType} */ (elements[i])); } } } /** * @param {NerdamerSymbolType[]} arr * @returns {SetType} */ static fromArray(arr) { const newSet = new NerdamerSet(); for (const item of arr) { newSet.add(item); } return newSet; } /** @param {NerdamerSymbolType} x */ add(x) { if (!this.contains(x)) { this.elements.push(x.clone()); } } /** * @param {NerdamerSymbolType} x * @returns {boolean} */ contains(x) { for (let i = 0; i < this.elements.length; i++) { const e = this.elements[i]; if (x.equals(e)) { return true; } } return false; } /** * @param {(e: NerdamerSymbolType, inputSet: SetType, i: number) => void} f * @returns {SetType} */ each(f) { const { elements } = this; const newSet = new NerdamerSet(); for (let i = 0, l = elements.length; i < l; i++) { const e = elements[i]; f.call(this, e, newSet, i); } return newSet; } /** @returns {SetType} */ clone() { const newSet = new NerdamerSet(); this.each(e => { newSet.add(e.clone()); }); return newSet; } /** * @param {SetType} inputSet * @returns {SetType} */ union(inputSet) { const _union = this.clone(); inputSet.each(e => { _union.add(e); }); return _union; } /** * @param {SetType} inputSet * @returns {SetType} */ difference(inputSet) { const diff = this.clone(); inputSet.each(e => { diff.remove(e); }); return diff; } /** * @param {NerdamerSymbolType} element * @returns {boolean} */ remove(element) { for (let i = 0, l = this.elements.length; i < l; i++) { const e = this.elements[i]; if (e.equals(element)) { SetDeps.remove(this.elements, i); return true; } } return false; } /** * @param {SetType} inputSet * @returns {SetType} */ intersection(inputSet) { const _intersection = new NerdamerSet(); const A = this; inputSet.each(e => { if (A.contains(e)) { _intersection.add(e); } }); return _intersection; } /** * @param {SetType} inputSet * @returns {boolean} */ intersects(inputSet) { return this.intersection(inputSet).elements.length > 0; } /** * @param {SetType} inputSet * @returns {boolean} */ isSubset(inputSet) { const { elements } = inputSet; for (let i = 0, l = elements.length; i < l; i++) { if (!this.contains(elements[i])) { return false; } } return true; } /** @returns {string} */ toString() { return `{${this.elements.join(',')}}`; } } // Assign NerdamerSet to CoreDeps immediately CoreDeps.classes.NerdamerSet = NerdamerSet; // Collection Class ================================================================= // Extracted outside IIFE to enable proper TypeScript type inference. // Dependencies are injected via CollectionDeps which is set by the IIFE after initialization. /** * Dependency container for Collection class. Populated by the IIFE during initialization. * * @type {{ * _: ParserType; * block: (setting: string, f: Function, opt?: boolean, obj?: unknown) => unknown; * }} */ const CollectionDeps = { get _() { return CoreDeps.parser; }, get block() { return CoreDeps.utils.block; }, }; /** * Class used to collect arguments for functions * * @implements {CollectionType} */ class Collection { /** @type {NerdamerSymbolType[]} */ elements = []; /** @param {NerdamerSymbolType} [e] */ constructor(e) { if (e) { this.elements.push(e); } } /** * @param {NerdamerSymbolType} [e] * @returns {CollectionType} */ static create(e) { return new Collection(e); } /** @param {NerdamerSymbolType} e */ append(e) { this.elements.push(e); } /** @returns {NerdamerSymbolType[]} */ getItems() { return this.elements; } /** @returns {string} */ toString() { return CollectionDeps._.prettyPrint(this.elements); } /** @returns {number} */ dimensions() { return this.elements.length; } /** * @param {string} [options] * @returns {string} */ text(options) { return `(${this.elements.map(e => e.text(options)).join(',')})`; } /** @returns {CollectionType} */ clone() { const c = Collection.create(); c.elements = this.elements.map(e => e.clone()); return c; } /** * @param {ExpandOptions} [options] * @returns {this} */ expand(options) { this.elements = /** @type {NerdamerSymbolType[]} */ ( this.elements.map(e => CollectionDeps._.expand(e, options)) ); return this; } /** * @param {Record<string, ExpressionParam>} [options] * @returns {this} */ evaluate(options) {