mathrok
Version:
AI-powered symbolic mathematics library combining traditional Computer Algebra System (CAS) capabilities with natural language processing for math problem solving
1,387 lines (1,378 loc) • 1.65 MB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var nerdamer = require('nerdamer');
var Algebrite = require('algebrite');
var math = require('mathjs');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var Algebrite__namespace = /*#__PURE__*/_interopNamespaceDefault(Algebrite);
var math__namespace = /*#__PURE__*/_interopNamespaceDefault(math);
/**
* 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