import {
MathematicalTerm,
MathematicalOperator
} from '../../types.js';
import { Loggers } from '../../utils/logger.js';
// Create logger for this module
const logger = Loggers.mathematical;
/**
* Mathematical expression evaluator
* Handles evaluation of mathematical expressions and operations
*/
export class MathematicalEvaluator {
/**
* Evaluate mathematical expression
* @param term The mathematical term to evaluate
* @param variables Optional variable values
* @returns Numerical result
*/
evaluateExpression(term: MathematicalTerm, variables: Map<string, number> = new Map()): number {
logger.debug('Evaluating expression', {
termType: term.type,
variableCount: variables.size
});
try {
switch (term.type) {
case 'number':
logger.debug('Evaluating number', { value: term.value });
return term.value;
case 'variable':
const value = variables.get(term.name);
if (value === undefined) {
logger.error('Undefined variable', { variable: term.name });
throw new Error(`Variable ${term.name} not defined`);
}
logger.debug('Evaluating variable', { name: term.name, value });
return value;
case 'operation':
logger.debug('Evaluating operation', {
operator: term.operator,
operandCount: term.operands.length
});
const operands = term.operands.map(op => this.evaluateExpression(op, variables));
return this.applyOperator(term.operator, operands);
default:
logger.error('Unknown term type', { type: (term as any).type });
throw new Error(`Cannot evaluate term type: ${(term as any).type}`);
}
} catch (error) {
logger.error('Expression evaluation failed', {
error: error instanceof Error ? error.message : String(error)
});
throw error;
}
}
/**
* Apply mathematical operator to operands
* @param operator The operator
* @param operands The operands
* @returns Result of operation
*/
applyOperator(operator: MathematicalOperator, operands: number[]): number {
logger.debug('Applying operator', { operator, operands });
let result: number;
switch (operator) {
case '+':
result = operands.reduce((a, b) => a + b, 0);
break;
case '-':
result = operands.reduce((a, b) => a - b);
break;
case '*':
result = operands.reduce((a, b) => a * b, 1);
break;
case '/':
if (operands.includes(0) && operands.indexOf(0) !== 0) {
logger.error('Division by zero attempted', { operands });
throw new Error('Division by zero');
}
result = operands.reduce((a, b) => a / b);
break;
case '%':
result = operands.reduce((a, b) => a % b);
break;
default:
logger.error('Unsupported operator', { operator });
throw new Error(`Unsupported operator: ${operator}`);
}
logger.debug('Operation result', { operator, operands, result });
return result;
}
/**
* Compare two values with an operator
* @param left Left value
* @param operator Comparison operator
* @param right Right value
* @returns Boolean result
*/
compare(left: number, operator: string, right: number): boolean {
logger.debug('Comparing values', { left, operator, right });
let result: boolean;
switch (operator) {
case '<': result = left < right; break;
case '>': result = left > right; break;
case '<=': result = left <= right; break;
case '>=': result = left >= right; break;
case '==': result = left === right; break;
case '!=': result = left !== right; break;
default:
logger.warn('Unknown comparison operator', { operator });
result = false;
}
logger.debug('Comparison result', { left, operator, right, result });
return result;
}
/**
* Get all variables in a term
* @param term The term to analyze
* @returns Array of variable names
*/
getVariables(term: MathematicalTerm): string[] {
logger.debug('Getting variables from term', { termType: term.type });
const variables: string[] = [];
switch (term.type) {
case 'variable':
variables.push(term.name);
break;
case 'operation':
for (const operand of term.operands) {
variables.push(...this.getVariables(operand));
}
break;
}
const uniqueVariables = [...new Set(variables)]; // Remove duplicates
logger.debug('Found variables', { count: uniqueVariables.length, variables: uniqueVariables });
return uniqueVariables;
}
/**
* Check if a term contains a specific variable
* @param term The term to check
* @param variable The variable to look for
* @returns Boolean indicating if variable is present
*/
containsVariable(term: MathematicalTerm, variable: string): boolean {
const contains = this.getVariables(term).includes(variable);
logger.debug('Checking variable presence', { variable, contains });
return contains;
}
}