UNPKG

mathrok

Version:

AI-powered symbolic mathematics library combining traditional Computer Algebra System (CAS) capabilities with natural language processing for math problem solving

1,386 lines (1,380 loc) 1.65 MB
import nerdamer from 'nerdamer'; import * as Algebrite from 'algebrite'; import * as math from 'mathjs'; /** * Core type definitions for the Mathrok library * Defines fundamental mathematical structures and interfaces */ /** * Types of AST nodes */ var NodeType; (function (NodeType) { NodeType["NUMBER"] = "number"; NodeType["VARIABLE"] = "variable"; NodeType["FUNCTION"] = "function"; NodeType["OPERATOR"] = "operator"; NodeType["PARENTHESES"] = "parentheses"; NodeType["EQUATION"] = "equation"; NodeType["INEQUALITY"] = "inequality"; })(NodeType || (NodeType = {})); /** * Mathematical operations enumeration */ var MathOperation; (function (MathOperation) { // Arithmetic MathOperation["ADDITION"] = "addition"; MathOperation["SUBTRACTION"] = "subtraction"; MathOperation["MULTIPLICATION"] = "multiplication"; MathOperation["DIVISION"] = "division"; MathOperation["EXPONENTIATION"] = "exponentiation"; MathOperation["ROOT"] = "root"; // Algebraic MathOperation["SIMPLIFICATION"] = "simplification"; MathOperation["EXPANSION"] = "expansion"; MathOperation["FACTORING"] = "factoring"; MathOperation["SUBSTITUTION"] = "substitution"; // Equation solving MathOperation["ISOLATION"] = "isolation"; MathOperation["ELIMINATION"] = "elimination"; MathOperation["QUADRATIC_FORMULA"] = "quadratic_formula"; // Calculus MathOperation["DIFFERENTIATION"] = "differentiation"; MathOperation["INTEGRATION"] = "integration"; MathOperation["LIMIT"] = "limit"; // Trigonometry MathOperation["TRIG_SIMPLIFICATION"] = "trig_simplification"; MathOperation["TRIG_IDENTITY"] = "trig_identity"; // Matrix MathOperation["MATRIX_MULTIPLICATION"] = "matrix_multiplication"; MathOperation["MATRIX_INVERSION"] = "matrix_inversion"; MathOperation["DETERMINANT"] = "determinant"; // Other MathOperation["EVALUATION"] = "evaluation"; MathOperation["CONVERSION"] = "conversion"; })(MathOperation || (MathOperation = {})); /** * Error types for mathematical operations */ var MathErrorType; (function (MathErrorType) { MathErrorType["PARSE_ERROR"] = "parse_error"; MathErrorType["SYNTAX_ERROR"] = "syntax_error"; MathErrorType["DOMAIN_ERROR"] = "domain_error"; MathErrorType["DIVISION_BY_ZERO"] = "division_by_zero"; MathErrorType["UNDEFINED_VARIABLE"] = "undefined_variable"; MathErrorType["UNDEFINED_FUNCTION"] = "undefined_function"; MathErrorType["COMPUTATION_ERROR"] = "computation_error"; MathErrorType["TIMEOUT_ERROR"] = "timeout_error"; MathErrorType["MEMORY_ERROR"] = "memory_error"; MathErrorType["UNSUPPORTED_OPERATION"] = "unsupported_operation"; })(MathErrorType || (MathErrorType = {})); /** * Mathematical error with detailed information */ class MathError extends Error { constructor(type, message, expression, position, suggestions) { super(message); this.name = 'MathError'; this.type = type; this.expression = expression; this.position = position; this.suggestions = suggestions; } } /** * Mathematical expression lexer * Tokenizes mathematical expressions into a stream of tokens */ /** * Token types for mathematical expressions */ var TokenType; (function (TokenType) { TokenType["NUMBER"] = "NUMBER"; TokenType["VARIABLE"] = "VARIABLE"; TokenType["FUNCTION"] = "FUNCTION"; TokenType["OPERATOR"] = "OPERATOR"; TokenType["LEFT_PAREN"] = "LEFT_PAREN"; TokenType["RIGHT_PAREN"] = "RIGHT_PAREN"; TokenType["LEFT_BRACKET"] = "LEFT_BRACKET"; TokenType["RIGHT_BRACKET"] = "RIGHT_BRACKET"; TokenType["COMMA"] = "COMMA"; TokenType["EQUALS"] = "EQUALS"; TokenType["LESS_THAN"] = "LESS_THAN"; TokenType["GREATER_THAN"] = "GREATER_THAN"; TokenType["LESS_EQUAL"] = "LESS_EQUAL"; TokenType["GREATER_EQUAL"] = "GREATER_EQUAL"; TokenType["NOT_EQUAL"] = "NOT_EQUAL"; TokenType["WHITESPACE"] = "WHITESPACE"; TokenType["EOF"] = "EOF"; TokenType["UNKNOWN"] = "UNKNOWN"; })(TokenType || (TokenType = {})); /** * Mathematical operators with precedence and associativity */ const OPERATORS = { '+': { symbol: '+', precedence: 1, associativity: 'left', arity: 2 }, '-': { symbol: '-', precedence: 1, associativity: 'left', arity: 2 }, '*': { symbol: '*', precedence: 2, associativity: 'left', arity: 2 }, '/': { symbol: '/', precedence: 2, associativity: 'left', arity: 2 }, '^': { symbol: '^', precedence: 3, associativity: 'right', arity: 2 }, '**': { symbol: '**', precedence: 3, associativity: 'right', arity: 2 }, '%': { symbol: '%', precedence: 2, associativity: 'left', arity: 2 }, '!': { symbol: '!', precedence: 4, associativity: 'left', arity: 1 }, // Unary operators 'u+': { symbol: '+', precedence: 4, associativity: 'right', arity: 1 }, 'u-': { symbol: '-', precedence: 4, associativity: 'right', arity: 1 }, }; /** * Mathematical functions */ const FUNCTIONS = new Set([ 'sin', 'cos', 'tan', 'cot', 'sec', 'csc', 'asin', 'acos', 'atan', 'acot', 'asec', 'acsc', 'sinh', 'cosh', 'tanh', 'coth', 'sech', 'csch', 'asinh', 'acosh', 'atanh', 'acoth', 'asech', 'acsch', 'log', 'ln', 'log10', 'log2', 'exp', 'sqrt', 'cbrt', 'abs', 'floor', 'ceil', 'round', 'min', 'max', 'gcd', 'lcm', 'factorial', 'gamma', 'beta', 'erf', 'erfc', 'integrate', 'derivative', 'diff', 'sum', 'product', 'limit', 'solve', 'simplify', 'expand', 'factor', 'det', 'inv', 'transpose', ]); /** * Mathematical constants */ const CONSTANTS = new Set([ 'pi', 'e', 'i', 'infinity', 'inf', 'euler', 'golden', ]); /** * Lexical analyzer for mathematical expressions */ class MathLexer { constructor(input) { this.position = 0; this.line = 1; this.column = 1; this.tokens = []; this.input = input.trim(); } /** * Tokenize the input expression */ tokenize() { this.position = 0; this.line = 1; this.column = 1; this.tokens.length = 0; while (this.position < this.input.length) { this.scanToken(); } // Add EOF token this.addToken(TokenType.EOF, '', this.position, this.position); return [...this.tokens]; } /** * Scan and identify the next token */ scanToken() { const start = this.position; const char = this.advance(); switch (char) { case ' ': case '\t': case '\r': // Skip whitespace break; case '\n': this.line++; this.column = 1; break; case '(': this.addToken(TokenType.LEFT_PAREN, char, start, this.position); break; case ')': this.addToken(TokenType.RIGHT_PAREN, char, start, this.position); break; case '[': this.addToken(TokenType.LEFT_BRACKET, char, start, this.position); break; case ']': this.addToken(TokenType.RIGHT_BRACKET, char, start, this.position); break; case ',': this.addToken(TokenType.COMMA, char, start, this.position); break; case '=': if (this.match('=')) { this.addToken(TokenType.EQUALS, '==', start, this.position); } else { this.addToken(TokenType.EQUALS, char, start, this.position); } break; case '<': if (this.match('=')) { this.addToken(TokenType.LESS_EQUAL, '<=', start, this.position); } else { this.addToken(TokenType.LESS_THAN, char, start, this.position); } break; case '>': if (this.match('=')) { this.addToken(TokenType.GREATER_EQUAL, '>=', start, this.position); } else { this.addToken(TokenType.GREATER_THAN, char, start, this.position); } break; case '!': if (this.match('=')) { this.addToken(TokenType.NOT_EQUAL, '!=', start, this.position); } else { this.addToken(TokenType.OPERATOR, char, start, this.position); } break; case '+': case '-': case '%': this.addToken(TokenType.OPERATOR, char, start, this.position); break; case '*': if (this.match('*')) { this.addToken(TokenType.OPERATOR, '**', start, this.position); } else { this.addToken(TokenType.OPERATOR, char, start, this.position); } break; case '/': this.addToken(TokenType.OPERATOR, char, start, this.position); break; case '^': this.addToken(TokenType.OPERATOR, char, start, this.position); break; default: if (this.isDigit(char)) { this.scanNumber(start); } else if (this.isAlpha(char)) { this.scanIdentifier(start); } else { this.addToken(TokenType.UNKNOWN, char, start, this.position); throw new MathError(MathErrorType.SYNTAX_ERROR, `Unexpected character: ${char}`, this.input, { start, end: this.position }); } break; } } /** * Scan a number token (integer or decimal) */ scanNumber(start) { while (this.isDigit(this.peek())) { this.advance(); } // Look for decimal point if (this.peek() === '.' && this.isDigit(this.peekNext())) { this.advance(); // consume '.' while (this.isDigit(this.peek())) { this.advance(); } } // Look for scientific notation if (this.peek() === 'e' || this.peek() === 'E') { this.advance(); // consume 'e' or 'E' if (this.peek() === '+' || this.peek() === '-') { this.advance(); // consume sign } if (!this.isDigit(this.peek())) { throw new MathError(MathErrorType.SYNTAX_ERROR, 'Invalid scientific notation', this.input, { start, end: this.position }); } while (this.isDigit(this.peek())) { this.advance(); } } const value = this.input.substring(start, this.position); this.addToken(TokenType.NUMBER, value, start, this.position); } /** * Scan an identifier (variable, function, or constant) */ scanIdentifier(start) { while (this.isAlphaNumeric(this.peek()) || this.peek() === '_') { this.advance(); } const value = this.input.substring(start, this.position); const lowerValue = value.toLowerCase(); // Determine token type let tokenType; if (FUNCTIONS.has(lowerValue)) { tokenType = TokenType.FUNCTION; } else if (CONSTANTS.has(lowerValue)) { tokenType = TokenType.VARIABLE; // Constants are treated as special variables } else { tokenType = TokenType.VARIABLE; } this.addToken(tokenType, value, start, this.position); } /** * Add a token to the tokens array */ addToken(type, value, start, end) { this.tokens.push({ type, value, position: { start, end }, line: this.line, column: this.column - value.length, }); } /** * Advance to the next character */ advance() { if (this.position >= this.input.length) { return '\0'; } this.column++; return this.input.charAt(this.position++); } /** * Check if the current character matches the expected character */ match(expected) { if (this.position >= this.input.length) { return false; } if (this.input.charAt(this.position) !== expected) { return false; } this.position++; this.column++; return true; } /** * Peek at the current character without advancing */ peek() { if (this.position >= this.input.length) { return '\0'; } return this.input.charAt(this.position); } /** * Peek at the next character without advancing */ peekNext() { if (this.position + 1 >= this.input.length) { return '\0'; } return this.input.charAt(this.position + 1); } /** * Check if character is a digit */ isDigit(char) { return char >= '0' && char <= '9'; } /** * Check if character is alphabetic */ isAlpha(char) { return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || char === '_'; } /** * Check if character is alphanumeric */ isAlphaNumeric(char) { return this.isAlpha(char) || this.isDigit(char); } /** * Get operator information */ static getOperatorInfo(operator) { return OPERATORS[operator]; } /** * Check if a string is a known function */ static isFunction(name) { return FUNCTIONS.has(name.toLowerCase()); } /** * Check if a string is a known constant */ static isConstant(name) { return CONSTANTS.has(name.toLowerCase()); } } /** * Abstract Syntax Tree implementation for mathematical expressions * Builds and manipulates AST nodes for mathematical expressions */ /** * AST Node implementation */ class ASTNode { constructor(type, value, children, metadata) { this.type = type; this.value = value; this.children = children ? [...children] : undefined; this.metadata = metadata; } /** * Create a number node */ static number(value, metadata) { return new ASTNode('number', value, undefined, metadata); } /** * Create a variable node */ static variable(name, metadata) { return new ASTNode('variable', name, undefined, metadata); } /** * Create a function node */ static function(name, args, metadata) { return new ASTNode('function', name, args, metadata); } /** * Create an operator node */ static operator(operator, operands, metadata) { return new ASTNode('operator', operator, operands, metadata); } /** * Create an equation node */ static equation(left, right, metadata) { return new ASTNode('equation', '=', [left, right], metadata); } /** * Create an inequality node */ static inequality(operator, left, right, metadata) { return new ASTNode('inequality', operator, [left, right], metadata); } /** * Clone this node */ clone() { var _a; const clonedChildren = (_a = this.children) === null || _a === void 0 ? void 0 : _a.map(child => child instanceof ASTNode ? child.clone() : child); return new ASTNode(this.type, this.value, clonedChildren, this.metadata); } /** * Get all variables in this subtree */ getVariables() { const variables = new Set(); if (this.type === 'variable' && typeof this.value === 'string') { variables.add(this.value); } if (this.children) { for (const child of this.children) { if (child instanceof ASTNode) { const childVars = child.getVariables(); childVars.forEach(v => variables.add(v)); } } } return variables; } /** * Get all functions in this subtree */ getFunctions() { const functions = new Set(); if (this.type === 'function' && typeof this.value === 'string') { functions.add(this.value); } if (this.children) { for (const child of this.children) { if (child instanceof ASTNode) { const childFuncs = child.getFunctions(); childFuncs.forEach(f => functions.add(f)); } } } return functions; } /** * Calculate complexity score for this subtree */ getComplexity() { let complexity = 1; // Add complexity based on node type switch (this.type) { case 'number': complexity = 0.5; break; case 'variable': complexity = 1; break; case 'operator': complexity = 1.5; break; case 'function': complexity = 2; break; case 'equation': case 'inequality': complexity = 3; break; } // Add complexity from children if (this.children) { for (const child of this.children) { if (child instanceof ASTNode) { complexity += child.getComplexity(); } } } return complexity; } /** * Convert to string representation */ toString() { switch (this.type) { case 'number': case 'variable': return String(this.value); case 'function': if (!this.children || this.children.length === 0) { return `${this.value}()`; } const args = this.children.map(child => child instanceof ASTNode ? child.toString() : String(child)).join(', '); return `${this.value}(${args})`; case 'operator': if (!this.children) { return String(this.value); } if (this.children.length === 1) { // Unary operator const operand = this.children[0]; const operandStr = operand instanceof ASTNode ? operand.toString() : String(operand); return `${this.value}${operandStr}`; } else if (this.children.length === 2) { // Binary operator const left = this.children[0]; const right = this.children[1]; const leftStr = left instanceof ASTNode ? left.toString() : String(left); const rightStr = right instanceof ASTNode ? right.toString() : String(right); // Add parentheses if needed based on precedence return `${leftStr} ${this.value} ${rightStr}`; } break; case 'equation': if (this.children && this.children.length === 2) { const left = this.children[0]; const right = this.children[1]; const leftStr = left instanceof ASTNode ? left.toString() : String(left); const rightStr = right instanceof ASTNode ? right.toString() : String(right); return `${leftStr} = ${rightStr}`; } break; case 'inequality': if (this.children && this.children.length === 2) { const left = this.children[0]; const right = this.children[1]; const leftStr = left instanceof ASTNode ? left.toString() : String(left); const rightStr = right instanceof ASTNode ? right.toString() : String(right); return `${leftStr} ${this.value} ${rightStr}`; } break; } return `[${this.type}:${this.value}]`; } } /** * AST Builder using recursive descent parsing */ class ASTBuilder { constructor() { this.tokens = []; this.current = 0; } /** * Build AST from tokens */ build(tokens) { this.tokens = tokens.filter(token => token.type !== TokenType.WHITESPACE); this.current = 0; if (this.tokens.length === 0 || (this.tokens.length === 1 && this.tokens[0].type === TokenType.EOF)) { throw new MathError(MathErrorType.PARSE_ERROR, 'Empty expression', ''); } const ast = this.parseExpression(); if (!this.isAtEnd()) { const token = this.peek(); throw new MathError(MathErrorType.SYNTAX_ERROR, `Unexpected token: ${token.value}`, '', token.position); } return ast; } /** * Parse expression (handles equations and inequalities) */ parseExpression() { let expr = this.parseComparison(); while (this.match(TokenType.EQUALS)) { const operator = this.previous(); const right = this.parseComparison(); expr = ASTNode.equation(expr, right, { position: operator.position, }); } return expr; } /** * Parse comparison operators */ parseComparison() { let expr = this.parseAddition(); while (this.match(TokenType.LESS_THAN, TokenType.GREATER_THAN, TokenType.LESS_EQUAL, TokenType.GREATER_EQUAL, TokenType.NOT_EQUAL)) { const operator = this.previous(); const right = this.parseAddition(); expr = ASTNode.inequality(operator.value, expr, right, { position: operator.position, }); } return expr; } /** * Parse addition and subtraction */ parseAddition() { var _a, _b; let expr = this.parseMultiplication(); while (this.check(TokenType.OPERATOR) && (this.peek().value === '+' || this.peek().value === '-')) { const operator = this.advance(); const right = this.parseMultiplication(); expr = ASTNode.operator(operator.value, [expr, right], { position: operator.position, precedence: (_a = MathLexer.getOperatorInfo(operator.value)) === null || _a === void 0 ? void 0 : _a.precedence, associativity: (_b = MathLexer.getOperatorInfo(operator.value)) === null || _b === void 0 ? void 0 : _b.associativity, }); } return expr; } /** * Parse multiplication, division, and modulo */ parseMultiplication() { var _a, _b; let expr = this.parseExponentiation(); while (this.check(TokenType.OPERATOR) && ['*', '/', '%'].includes(this.peek().value)) { const operator = this.advance(); const right = this.parseExponentiation(); expr = ASTNode.operator(operator.value, [expr, right], { position: operator.position, precedence: (_a = MathLexer.getOperatorInfo(operator.value)) === null || _a === void 0 ? void 0 : _a.precedence, associativity: (_b = MathLexer.getOperatorInfo(operator.value)) === null || _b === void 0 ? void 0 : _b.associativity, }); } return expr; } /** * Parse exponentiation (right-associative) */ parseExponentiation() { var _a, _b; let expr = this.parseUnary(); if (this.check(TokenType.OPERATOR) && (this.peek().value === '^' || this.peek().value === '**')) { const operator = this.advance(); const right = this.parseExponentiation(); // Right-associative expr = ASTNode.operator(operator.value === '**' ? '^' : operator.value, [expr, right], { position: operator.position, precedence: (_a = MathLexer.getOperatorInfo(operator.value)) === null || _a === void 0 ? void 0 : _a.precedence, associativity: (_b = MathLexer.getOperatorInfo(operator.value)) === null || _b === void 0 ? void 0 : _b.associativity, }); } return expr; } /** * Parse unary operators */ parseUnary() { if (this.check(TokenType.OPERATOR) && (this.peek().value === '+' || this.peek().value === '-')) { const operator = this.advance(); const expr = this.parseUnary(); return ASTNode.operator(`u${operator.value}`, [expr], { position: operator.position, precedence: 4, // High precedence for unary operators associativity: 'right', }); } return this.parsePostfix(); } /** * Parse postfix operators (like factorial) */ parsePostfix() { var _a, _b; let expr = this.parsePrimary(); while (this.check(TokenType.OPERATOR) && this.peek().value === '!') { const operator = this.advance(); expr = ASTNode.operator(operator.value, [expr], { position: operator.position, precedence: (_a = MathLexer.getOperatorInfo(operator.value)) === null || _a === void 0 ? void 0 : _a.precedence, associativity: (_b = MathLexer.getOperatorInfo(operator.value)) === null || _b === void 0 ? void 0 : _b.associativity, }); } return expr; } /** * Parse primary expressions (numbers, variables, functions, parentheses) */ parsePrimary() { if (this.match(TokenType.NUMBER)) { const token = this.previous(); return ASTNode.number(parseFloat(token.value), { position: token.position, }); } if (this.match(TokenType.VARIABLE)) { const token = this.previous(); return ASTNode.variable(token.value, { position: token.position, }); } if (this.match(TokenType.FUNCTION)) { const functionToken = this.previous(); if (!this.check(TokenType.LEFT_PAREN)) { throw new MathError(MathErrorType.SYNTAX_ERROR, `Expected '(' after function name: ${functionToken.value}`, '', functionToken.position); } this.consume(TokenType.LEFT_PAREN, "Expected '(' after function name"); const args = []; if (!this.check(TokenType.RIGHT_PAREN)) { do { args.push(this.parseExpression()); } while (this.match(TokenType.COMMA)); } this.consume(TokenType.RIGHT_PAREN, "Expected ')' after function arguments"); return ASTNode.function(functionToken.value, args, { position: functionToken.position, }); } if (this.match(TokenType.LEFT_PAREN)) { const expr = this.parseExpression(); this.consume(TokenType.RIGHT_PAREN, "Expected ')' after expression"); return expr; } const token = this.peek(); throw new MathError(MathErrorType.SYNTAX_ERROR, `Unexpected token: ${token.value}`, '', token.position); } /** * Check if current token matches any of the given types */ match(...types) { for (const type of types) { if (this.check(type)) { this.advance(); return true; } } return false; } /** * Check if current token is of given type */ check(type) { if (this.isAtEnd()) { return false; } return this.peek().type === type; } /** * Advance to next token */ advance() { if (!this.isAtEnd()) { this.current++; } return this.previous(); } /** * Check if at end of tokens */ isAtEnd() { return this.peek().type === TokenType.EOF; } /** * Get current token */ peek() { return this.tokens[this.current] || { type: TokenType.EOF, value: '', position: { start: 0, end: 0 }, line: 1, column: 1 }; } /** * Get previous token */ previous() { return this.tokens[this.current - 1]; } /** * Consume token of expected type or throw error */ consume(type, message) { if (this.check(type)) { return this.advance(); } const token = this.peek(); throw new MathError(MathErrorType.SYNTAX_ERROR, message, '', token.position); } } /** * Expression validator * Validates mathematical expressions for syntax and semantic correctness */ /** * Expression validator */ class ExpressionValidator { constructor() { this.rules = [ this.createParenthesesRule(), this.createBracketsRule(), this.createOperatorRule(), this.createFunctionRule(), this.createNumberRule(), this.createVariableRule(), this.createComplexityRule(), ]; } /** * Validate tokens */ async validate(tokens, config) { const errors = []; const warnings = []; const suggestions = []; // Run all validation rules for (const rule of this.rules) { try { const issues = rule.check(tokens, config); for (const issue of issues) { if (issue.type === 'error') { errors.push(new MathError(MathErrorType.SYNTAX_ERROR, issue.message, '', issue.position, issue.suggestions)); } else { warnings.push(issue.message); if (issue.suggestions) { suggestions.push(...issue.suggestions); } } } } catch (error) { errors.push(new MathError(MathErrorType.SYNTAX_ERROR, `Validation rule '${rule.name}' failed: ${error.message}`)); } } return { isValid: errors.length === 0, errors, warnings, suggestions: [...new Set(suggestions)], // Remove duplicates }; } /** * Create parentheses validation rule */ createParenthesesRule() { return { name: 'parentheses', check: (tokens) => { const issues = []; let parenCount = 0; const parenStack = []; for (const token of tokens) { if (token.type === TokenType.LEFT_PAREN) { parenCount++; parenStack.push(token); } else if (token.type === TokenType.RIGHT_PAREN) { parenCount--; if (parenCount < 0) { issues.push({ type: 'error', message: 'Unmatched closing parenthesis', position: token.position, suggestions: ['Remove the extra closing parenthesis'], }); } else { parenStack.pop(); } } } if (parenCount > 0) { const lastOpen = parenStack[parenStack.length - 1]; issues.push({ type: 'error', message: 'Unmatched opening parenthesis', position: lastOpen === null || lastOpen === void 0 ? void 0 : lastOpen.position, suggestions: ['Add missing closing parenthesis'], }); } return issues; }, }; } /** * Create brackets validation rule */ createBracketsRule() { return { name: 'brackets', check: (tokens) => { const issues = []; let bracketCount = 0; const bracketStack = []; for (const token of tokens) { if (token.type === TokenType.LEFT_BRACKET) { bracketCount++; bracketStack.push(token); } else if (token.type === TokenType.RIGHT_BRACKET) { bracketCount--; if (bracketCount < 0) { issues.push({ type: 'error', message: 'Unmatched closing bracket', position: token.position, suggestions: ['Remove the extra closing bracket'], }); } else { bracketStack.pop(); } } } if (bracketCount > 0) { const lastOpen = bracketStack[bracketStack.length - 1]; issues.push({ type: 'error', message: 'Unmatched opening bracket', position: lastOpen === null || lastOpen === void 0 ? void 0 : lastOpen.position, suggestions: ['Add missing closing bracket'], }); } return issues; }, }; } /** * Create operator validation rule */ createOperatorRule() { return { name: 'operators', check: (tokens) => { const issues = []; let lastToken = null; for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; if (token.type === TokenType.OPERATOR) { // Check for consecutive operators if ((lastToken === null || lastToken === void 0 ? void 0 : lastToken.type) === TokenType.OPERATOR) { // Allow unary operators after binary operators if (!this.isUnaryOperator(token.value) || !this.isBinaryOperator(lastToken.value)) { issues.push({ type: 'error', message: `Consecutive operators: ${lastToken.value} ${token.value}`, position: token.position, suggestions: ['Remove one of the operators or add operand between them'], }); } } // Check for operators at start/end if (i === 0 && !this.isUnaryOperator(token.value)) { issues.push({ type: 'error', message: `Binary operator '${token.value}' at start of expression`, position: token.position, suggestions: ['Add operand before the operator'], }); } if (i === tokens.length - 1 && token.value !== '!') { issues.push({ type: 'error', message: `Operator '${token.value}' at end of expression`, position: token.position, suggestions: ['Add operand after the operator'], }); } } if (token.type !== TokenType.WHITESPACE) { lastToken = token; } } return issues; }, }; } /** * Create function validation rule */ createFunctionRule() { return { name: 'functions', check: (tokens) => { const issues = []; for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; if (token.type === TokenType.FUNCTION) { // Check if function is followed by parentheses const nextToken = tokens[i + 1]; if (!nextToken || nextToken.type !== TokenType.LEFT_PAREN) { issues.push({ type: 'error', message: `Function '${token.value}' must be followed by parentheses`, position: token.position, suggestions: [`Add parentheses: ${token.value}()`], }); } // Check if function is known if (!MathLexer.isFunction(token.value)) { issues.push({ type: 'warning', message: `Unknown function: ${token.value}`, position: token.position, suggestions: ['Check function name spelling'], }); } } } return issues; }, }; } /** * Create number validation rule */ createNumberRule() { return { name: 'numbers', check: (tokens) => { const issues = []; for (const token of tokens) { if (token.type === TokenType.NUMBER) { const value = parseFloat(token.value); // Check for invalid numbers if (isNaN(value)) { issues.push({ type: 'error', message: `Invalid number: ${token.value}`, position: token.position, suggestions: ['Check number format'], }); } // Check for very large numbers if (Math.abs(value) > Number.MAX_SAFE_INTEGER) { issues.push({ type: 'warning', message: `Very large number may lose precision: ${token.value}`, position: token.position, suggestions: ['Consider using scientific notation'], }); } // Check for very small numbers if (value !== 0 && Math.abs(value) < Number.MIN_VALUE) { issues.push({ type: 'warning', message: `Very small number may underflow: ${token.value}`, position: token.position, suggestions: ['Consider using scientific notation'], }); } } } return issues; }, }; } /** * Create variable validation rule */ createVariableRule() { return { name: 'variables', check: (tokens, config) => { const issues = []; const variables = new Set(); for (const token of tokens) { if (token.type === TokenType.VARIABLE) { variables.add(token.value); // Check variable name length if (token.value.length > 20) { issues.push({ type: 'warning', message: `Very long variable name: ${token.value}`, position: token.position, suggestions: ['Consider using shorter variable names'], }); } // Check for reserved words const reservedWords = ['undefined', 'null', 'true', 'false', 'NaN', 'Infinity']; if (reservedWords.includes(token.value)) { issues.push({ type: 'warning', message: `Variable name '${token.value}' is a reserved word`, position: token.position, suggestions: ['Use a different variable name'], }); } } } // Check total number of variables const maxVariables = config.maxVariables || 100; if (variables.size > maxVariables) { issues.push({ type: 'warning', message: `Too many variables (${variables.size}), maximum recommended: ${maxVariables}`, suggestions: ['Consider simplifying the expression'], }); } return issues; }, }; } /** * Create complexity validation rule */ createComplexityRule() { return { name: 'complexity', check: (tokens, config) => { const issues = []; // Simple complexity estimation based on token count const complexity = tokens.length; const maxComplexity = config.maxComplexity || 1000; if (complexity > maxComplexity) { issues.push({ type: 'warning', message: `Expression is very complex (${complexity} tokens)`, suggestions: ['Consider breaking into smaller expressions'], }); } return issues; }, }; } /** * Check if operator is unary */ isUnaryOperator(operator) { return ['+', '-', '!'].includes(operator); } /** * Check if operator is binary */ isBinaryOperator(operator) { return ['+', '-', '*', '/', '^', '**', '%'].includes(operator); } } /** * Mathematical structure analyzer * Analyzes AST to identify mathematical structures and patterns */ /** * Mathematical structure analyzer */ class StructureAnalyzer { constructor() { this.patterns = [ this.createPolynomialPattern(), this.createRationalFunctionPattern(), this.createTrigonometricPattern(), this.createExponentialPattern(), this.createLogarithmicPattern(), this.createMatrixPattern(), this.createVectorPattern(), this.createEquationPattern(), this.createInequalityPattern(), this.createSystemPattern(), ]; } /** * Analyze AST for mathematical structures */ async analyze(ast) { const structures = []; // Analyze the entire tree this.analyzeNode(ast, structures); // Sort by confidence and remove duplicates return structures .sort((a, b) => b.properties.confidence - a.properties.confidence) .filter((structure, index, array) => index === array.findIndex(s => s.type === structure.type && s.position.start === structure.position.start && s.position.end === structure.position.end)); } /** * Analyze a single node and its children */ analyzeNode(node, structures) { // Check each pattern against this node for (const pattern of this.patterns) { const match = pattern.matcher(node); if (match && match.confidence > 0.5) { structures.push({ type: pattern.type, description: pattern.description, position: match.position, properties: Object.assign(Object.assign({}, match.properties), { confidence: match.confidence, pattern: pattern.name }), }); } } // Recursively analyze children if (node.children) { for (const child of node.children) { this.analyzeNode(child, structures); } } } /** * Create polynomial pattern matcher */ createPolynomialPattern() { return { type: 'POLYNOMIAL', name: 'polynomial', description: 'Polynomial expression', matcher: (node) => { if (!this.isPolynomial(node)) { return null; } const degree = this.getPolynomialDegree(node); const variables = this.getVariables(node); return { confidence: 0.9, properties: { degree, variables: Array.from(variables), isUnivariate: variables.size === 1, isMultivariate: variables.size > 1, }, position: this.getNodePosition(node), }; }, }; } /** * Create rational function pattern matcher */ createRationalFunctionPattern() { return { type: 'RATIONAL_FUNCTION', name: 'rational_function', description: 'Rational function (polynomial divided by polynomial)', matcher: (node) => { if (node.type !== 'operator' || node.value !== '/') { return null; } const [numerator, denominator] = node.children || []; if (!numerator || !denominator) { return null; } if (this.isPolynomial(numerator) && this.isPolynomial(denominator)) { return { confidence: 0.85, properties: { numeratorDegree: this.getPolynomialDegree(numerator), denominatorDegree: this.getPolynomialDegree(denominator), variables: Array.from(this.getVariables(node)), }, position: this.getNodePosition(node), }; } return null; }, }; } /** * Create trigonometric pattern matcher */ createTrigonometricPattern() { return { type: 'TRIGONOMETRIC', name: 'trigonometric', description: 'Trigonometric expression', matcher: (node) => { var _a; const trigFunctions = new Set([ 'sin', 'cos', 'tan', 'cot', 'sec', 'csc', 'asin', 'acos', 'atan', 'acot', 'asec', 'acsc', 'sinh', 'cosh', 'tanh', 'coth', 'sech', 'csch', ]); if (node.type === 'function' && typeof node.value === 'string' && trigFunctions.has(node.value.toLowerCase())) { return { confidence: 0.95, properties: { function: node.value, isInverse: node.value.startsWith('a'), isHyperbolic: node.value.includes('h'), argumentCount: ((_a = node.children) === null || _a === void 0 ? void 0 : _a.length) || 0, }, position: this.getNodePosition(node), }; } // Check for trigonometric expressions in subtree if (this.containsTrigonometric(node)) { return { confidence: 0.7, properties: { containsTrigonometric: true, trigFunctions: this.getTrigonometricFunctions(node), }, position: this.getNodePosition(node), }; } return null; }, }; } /** * Create exponential pattern matcher */ createExponentialPattern() { return { type: 'EXPONENTIAL', name: 'exponential', description: 'Exponential expression', matcher: (node) => { // Check for exp function if (node.type === 'function' && node.value === 'exp') { return { confidence: 0.95, properties: {