import { PropositionalFormula } from '../../../types.js';
/**
* Truth table formatting logic
* Handles formula formatting, operator precedence, and string representation
*/
export class TruthTableFormatter {
/**
* Format a propositional formula as a string with proper precedence
* @param formula The formula to format
* @returns Formatted string representation
*/
formatFormula(formula: PropositionalFormula): string {
switch (formula.type) {
case 'variable':
return formula.name;
case 'unary':
const unaryOpSymbol = formula.operator === 'not' ? '¬' : formula.operator;
const operand = this.formatFormula(formula.operand);
// Add parentheses if the operand is not a variable or another negation
if (formula.operand.type === 'binary') {
return `${unaryOpSymbol}(${operand})`;
} else {
return `${unaryOpSymbol}${operand}`;
}
case 'binary':
const left = this.formatFormula(formula.left);
const right = this.formatFormula(formula.right);
// Convert operator to symbol if needed
const binaryOpSymbol = this.operatorToSymbol(formula.operator);
// Add parentheses around left operand if needed
const leftStr = (formula.left.type === 'binary' &&
this.getOperatorPrecedence(formula.left.operator) <
this.getOperatorPrecedence(formula.operator))
? `(${left})`
: left;
// Add parentheses around right operand if needed
const rightStr = (formula.right.type === 'binary' &&
this.getOperatorPrecedence(formula.right.operator) <=
this.getOperatorPrecedence(formula.operator))
? `(${right})`
: right;
return `${leftStr} ${binaryOpSymbol} ${rightStr}`;
}
}
/**
* Convert operator names to symbols
* @param operator Operator name
* @returns Symbol representation
*/
private operatorToSymbol(operator: string): string {
switch (operator) {
case 'and': return '∧';
case 'or': return '∨';
case 'implies': return '→';
case 'iff': return '↔';
case 'xor': return '⊕';
case 'not': return '¬';
default: return operator;
}
}
/**
* Get the precedence level of an operator
* Higher numbers indicate higher precedence
* @param operator The operator
* @returns Precedence level (number)
*/
private getOperatorPrecedence(operator: string): number {
switch (operator) {
case 'not':
case '¬':
return 4;
case 'and':
case '∧':
return 3;
case 'or':
case '∨':
return 2;
case 'implies':
case '→':
return 1;
case 'iff':
case '↔':
case 'xor':
case '⊕':
return 0;
default:
return 0;
}
}
/**
* Calculate column widths for text formatting
* @param variables Variable names
* @param formulas Propositional formulas
* @param isArgument Whether this is an argument
* @returns Array of column widths
*/
calculateColumnWidths(
variables: string[],
formulas: PropositionalFormula[],
isArgument: boolean
): number[] {
const colWidths: number[] = [];
// Variable column widths
variables.forEach(variable => {
colWidths.push(Math.max(variable.length, 1));
});
// Formula column widths
formulas.forEach(formula => {
const formatted = this.formatFormula(formula);
colWidths.push(Math.max(formatted.length, 5));
});
// Validity column (for arguments)
if (isArgument) {
colWidths.push(6); // 'Valid?'
}
return colWidths;
}
/**
* Format a boolean value for display
* @param value Boolean value
* @returns Display string
*/
formatBoolean(value: boolean): string {
return value ? 'T' : 'F';
}
/**
* Format validity indicator
* @param isValid Whether the row/argument is valid
* @param format Output format
* @returns Formatted validity indicator
*/
formatValidity(isValid: boolean, format: 'text' | 'html' | 'svg'): string {
if (format === 'text') {
return isValid ? 'Yes' : 'No';
} else {
return isValid ? '✓' : '❌';
}
}
}