java2ib
Version:
TypeScript library that converts Java code into IB Computer Science pseudocode format
1,033 lines • 56.1 kB
JavaScript
"use strict";
/**
* AST Transformation System for converting Java AST to IB Pseudocode AST
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ArrayInitializationRule = exports.EnhancedForLoopRule = exports.ContinueStatementRule = exports.BreakStatementRule = exports.DefaultClauseRule = exports.CaseClauseRule = exports.SwitchStatementRule = exports.ASTTransformer = exports.ClassDeclarationRule = exports.ReturnStatementRule = exports.ProgramRule = exports.MethodCallRule = exports.ArrayAccessRule = exports.MethodDeclarationRule = exports.ForLoopRule = exports.WhileLoopRule = exports.IfStatementRule = exports.LiteralRule = exports.IdentifierRule = exports.BinaryExpressionRule = exports.AssignmentRule = exports.VariableDeclarationRule = exports.BaseTransformationRule = exports.PseudocodeNodeType = void 0;
const types_1 = require("./types");
const ib_rules_engine_1 = require("./ib-rules-engine");
var PseudocodeNodeType;
(function (PseudocodeNodeType) {
PseudocodeNodeType["STATEMENT"] = "statement";
PseudocodeNodeType["BLOCK"] = "block";
PseudocodeNodeType["EXPRESSION"] = "expression";
PseudocodeNodeType["COMMENT"] = "comment";
})(PseudocodeNodeType || (exports.PseudocodeNodeType = PseudocodeNodeType = {}));
// Base Transformation Rule Class
class BaseTransformationRule {
createPseudocodeNode(type, content, location, indentLevel = 0, children) {
return {
type,
content,
indentLevel,
children,
location
};
}
transformChildren(children, context) {
if (!children)
return [];
const result = [];
for (const child of children) {
const transformed = context.transformer.transformNode(child, context);
result.push(...transformed);
}
return result;
}
}
exports.BaseTransformationRule = BaseTransformationRule;
// Variable Declaration Transformation Rule
class VariableDeclarationRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.VARIABLE_DECLARATION;
}
transform(node, context) {
const varNode = node;
const pseudocodeName = context.ibRules.convertVariableName(varNode.name);
// Store variable info in context
const variableInfo = {
originalName: varNode.name,
pseudocodeName,
type: varNode.dataType,
scope: context.currentScope.name
};
context.variables.set(varNode.name, variableInfo);
let content;
if (varNode.initializer) {
// Check if initializer is a Scanner input operation
if (varNode.initializer.type === types_1.NodeType.METHOD_CALL) {
const methodCall = varNode.initializer;
if (methodCall.object &&
this.isScannerObject(methodCall.object) &&
this.isScannerInputMethod(methodCall.methodName)) {
// Convert Scanner input to INPUT statement
content = context.ibRules.convertIOStatement('input', pseudocodeName);
return [this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, content, node.location, context.indentLevel)];
}
}
// Transform the initializer expression
const initializerNodes = context.transformer.transformNode(varNode.initializer, context);
const initializerContent = initializerNodes.map(n => n.content).join(' ');
content = `${pseudocodeName} = ${initializerContent}`;
}
else {
// For array declarations without initialization, just declare with UPPERCASE name
// IB pseudocode typically doesn't require explicit type declarations
content = `${pseudocodeName}`;
}
return [this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, content, node.location, context.indentLevel)];
}
isScannerObject(objectNode) {
if (objectNode.type === types_1.NodeType.IDENTIFIER) {
const objectName = objectNode.name;
// Check if the object is a Scanner instance (common names: scanner, input, sc, etc.)
return objectName.toLowerCase().includes('scanner') ||
objectName === 'input' ||
objectName === 'sc' ||
objectName === 'in';
}
return false;
}
isScannerInputMethod(methodName) {
const scannerMethods = [
'nextInt', 'nextDouble', 'nextFloat', 'nextLong',
'nextBoolean', 'next', 'nextLine'
];
return scannerMethods.includes(methodName);
}
}
exports.VariableDeclarationRule = VariableDeclarationRule;
// Assignment Transformation Rule
class AssignmentRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.ASSIGNMENT;
}
transform(node, context) {
const assignNode = node;
// Transform left side (variable name)
const leftNodes = context.transformer.transformNode(assignNode.left, context);
const leftContent = leftNodes.map(n => n.content).join('');
// Check if right side is a Scanner input operation
if (assignNode.right.type === types_1.NodeType.METHOD_CALL) {
const methodCall = assignNode.right;
if (methodCall.object &&
this.isScannerObject(methodCall.object) &&
this.isScannerInputMethod(methodCall.methodName)) {
// Convert Scanner input to INPUT statement
const content = context.ibRules.convertIOStatement('input', leftContent);
return [this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, content, node.location, context.indentLevel)];
}
}
// Transform right side (expression)
const rightNodes = context.transformer.transformNode(assignNode.right, context);
const rightContent = rightNodes.map(n => n.content).join('');
// Convert operator (= stays =, but += becomes = with expanded expression)
let operator = assignNode.operator;
let finalRightContent = rightContent;
if (operator !== '=') {
// Convert compound assignment operators
const baseOperator = operator.slice(0, -1); // Remove '=' from '+=', '-=', etc.
const convertedOperator = context.ibRules.convertOperator(baseOperator);
finalRightContent = `${leftContent} ${convertedOperator} ${rightContent}`;
operator = '=';
}
const content = `${leftContent} ${operator} ${finalRightContent}`;
return [this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, content, node.location, context.indentLevel)];
}
isScannerObject(objectNode) {
if (objectNode.type === types_1.NodeType.IDENTIFIER) {
const objectName = objectNode.name;
// Check if the object is a Scanner instance (common names: scanner, input, sc, etc.)
return objectName.toLowerCase().includes('scanner') ||
objectName === 'input' ||
objectName === 'sc' ||
objectName === 'in';
}
return false;
}
isScannerInputMethod(methodName) {
const scannerMethods = [
'nextInt', 'nextDouble', 'nextFloat', 'nextLong',
'nextBoolean', 'next', 'nextLine'
];
return scannerMethods.includes(methodName);
}
}
exports.AssignmentRule = AssignmentRule;
// Binary Expression Transformation Rule
class BinaryExpressionRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.BINARY_EXPRESSION;
}
transform(node, context) {
const binaryNode = node;
// Handle increment/decrement operators (++, --)
if (binaryNode.operator === '++' || binaryNode.operator === '--') {
const leftNodes = context.transformer.transformNode(binaryNode.left, context);
const leftContent = leftNodes.map(n => n.content).join('');
// Check if this is postfix (right operand is empty) or prefix (left operand is empty)
const rightNodes = context.transformer.transformNode(binaryNode.right, context);
const rightContent = rightNodes.map(n => n.content).join('');
if (rightContent === '') {
// Postfix: i++ becomes I = I + 1
const operation = binaryNode.operator === '++' ? '+' : '-';
const content = `${leftContent} = ${leftContent} ${operation} 1`;
return [this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, content, node.location, context.indentLevel)];
}
else {
// Prefix: ++i becomes I = I + 1 (same result in pseudocode)
const operation = binaryNode.operator === '++' ? '+' : '-';
const content = `${rightContent} = ${rightContent} ${operation} 1`;
return [this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, content, node.location, context.indentLevel)];
}
}
// Handle unary expressions (where left operand is empty)
if (binaryNode.left && binaryNode.left.value === '') {
// This is a unary expression
const rightNodes = context.transformer.transformNode(binaryNode.right, context);
const rightContent = rightNodes.map(n => n.content).join('');
const convertedOperator = context.ibRules.convertOperator(binaryNode.operator);
const content = `${convertedOperator} ${rightContent}`;
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, content, node.location)];
}
// Transform left operand
const leftNodes = context.transformer.transformNode(binaryNode.left, context);
const leftContent = leftNodes.map(n => n.content).join('');
// Transform right operand
const rightNodes = context.transformer.transformNode(binaryNode.right, context);
const rightContent = rightNodes.map(n => n.content).join('');
// Convert operator using IB rules
const convertedOperator = context.ibRules.convertOperator(binaryNode.operator);
const content = `${leftContent} ${convertedOperator} ${rightContent}`;
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, content, node.location)];
}
}
exports.BinaryExpressionRule = BinaryExpressionRule;
// Identifier Transformation Rule
class IdentifierRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.IDENTIFIER;
}
transform(node, context) {
const identifierNode = node;
// Handle array.length special case
if (identifierNode.name.endsWith('.length')) {
const arrayName = identifierNode.name.replace('.length', '');
const content = context.ibRules.convertArrayLength(arrayName);
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, content, node.location)];
}
// Check if this identifier is a known variable
const variableInfo = context.variables.get(identifierNode.name);
const pseudocodeName = variableInfo
? variableInfo.pseudocodeName
: context.ibRules.convertVariableName(identifierNode.name);
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, pseudocodeName, node.location)];
}
}
exports.IdentifierRule = IdentifierRule;
// Literal Transformation Rule
class LiteralRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.LITERAL;
}
transform(node, context) {
const literalNode = node;
// Literals generally stay the same in IB pseudocode
let content = literalNode.value;
// Handle boolean literals
if (literalNode.dataType === 'boolean') {
content = content.toUpperCase(); // true -> TRUE, false -> FALSE
}
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, content, node.location)];
}
}
exports.LiteralRule = LiteralRule;
// If Statement Transformation Rule
class IfStatementRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.IF_STATEMENT;
}
transform(node, context) {
const ifNode = node;
const result = [];
// Transform condition
const conditionNodes = context.transformer.transformNode(ifNode.condition, context);
const conditionContent = conditionNodes.map(n => n.content).join('');
// Create if statement
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, `if ${conditionContent} then`, node.location, context.indentLevel));
// Transform then statement with increased indentation
const thenContext = { ...context, indentLevel: context.indentLevel + 1 };
const thenNodes = this.transformStatementBody(ifNode.thenStatement, thenContext);
result.push(...thenNodes);
// Transform else statement if present
if (ifNode.elseStatement) {
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, 'else', node.location, context.indentLevel));
const elseContext = { ...context, indentLevel: context.indentLevel + 1 };
const elseNodes = this.transformStatementBody(ifNode.elseStatement, elseContext);
result.push(...elseNodes);
}
// End if
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, 'end if', node.location, context.indentLevel));
return result;
}
transformStatementBody(statement, context) {
// Handle block statements (multiple statements)
if (statement.type === types_1.NodeType.PROGRAM && statement.children) {
const result = [];
for (const child of statement.children) {
const childNodes = context.transformer.transformNode(child, context);
result.push(...childNodes);
}
return result;
}
// Handle single statement
return context.transformer.transformNode(statement, context);
}
}
exports.IfStatementRule = IfStatementRule;
// While Loop Transformation Rule
class WhileLoopRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.WHILE_LOOP;
}
transform(node, context) {
const whileNode = node;
const result = [];
// Transform condition
const conditionNodes = context.transformer.transformNode(whileNode.condition, context);
const conditionContent = conditionNodes.map(n => n.content).join('');
// Create loop while statement
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, `loop while ${conditionContent}`, node.location, context.indentLevel));
// Transform body with increased indentation
const bodyContext = { ...context, indentLevel: context.indentLevel + 1 };
const bodyNodes = this.transformStatementBody(whileNode.body, bodyContext);
result.push(...bodyNodes);
// End loop
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, 'end loop', node.location, context.indentLevel));
return result;
}
transformStatementBody(statement, context) {
// Handle block statements (multiple statements)
if (statement.type === types_1.NodeType.PROGRAM && statement.children) {
const result = [];
for (const child of statement.children) {
const childNodes = context.transformer.transformNode(child, context);
result.push(...childNodes);
}
return result;
}
// Handle single statement
return context.transformer.transformNode(statement, context);
}
}
exports.WhileLoopRule = WhileLoopRule;
// For Loop Transformation Rule
class ForLoopRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.FOR_LOOP;
}
transform(node, context) {
const forNode = node;
const result = [];
// Check if this is an enhanced for loop over an array (for(Type item : array))
if (this.isEnhancedForLoop(forNode)) {
const enhancedInfo = this.extractEnhancedForInfo(forNode, context);
if (enhancedInfo) {
// Convert enhanced for loop to appropriate loop structure
// For arrays, we'll use: loop I from 0 to SIZE(ARRAY) - 1
const indexVar = 'I';
const arraySize = context.ibRules.convertArrayLength(enhancedInfo.arrayName);
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, `loop ${indexVar} from 0 to ${arraySize} - 1`, node.location, context.indentLevel));
// Add variable assignment inside the loop body
const bodyContext = { ...context, indentLevel: context.indentLevel + 1 };
// Add assignment: ITEM = ARRAY[I]
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, `${enhancedInfo.itemVariable} = ${enhancedInfo.arrayVariable}[${indexVar}]`, node.location, bodyContext.indentLevel));
// Transform original body
const bodyNodes = this.transformStatementBody(forNode.body, bodyContext);
result.push(...bodyNodes);
// End loop
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, 'end loop', node.location, context.indentLevel));
return result;
}
}
// Check if this is a simple counting for loop (for(int i = start; i < end; i++))
if (this.isSimpleCountingLoop(forNode)) {
const loopInfo = this.extractSimpleLoopInfo(forNode, context);
if (loopInfo) {
// Create IB-style counting loop: loop I from X to Y
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, `loop ${loopInfo.variable} from ${loopInfo.start} to ${loopInfo.end}`, node.location, context.indentLevel));
// Transform body with increased indentation
const bodyContext = { ...context, indentLevel: context.indentLevel + 1 };
const bodyNodes = this.transformStatementBody(forNode.body, bodyContext);
result.push(...bodyNodes);
// End loop
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, 'end loop', node.location, context.indentLevel));
return result;
}
}
// For complex for loops, convert to while loop equivalent
result.push(this.createPseudocodeNode(PseudocodeNodeType.COMMENT, '// Complex for loop converted to while loop', node.location, context.indentLevel));
// Transform initialization if present
if (forNode.initialization) {
const initNodes = context.transformer.transformNode(forNode.initialization, context);
result.push(...initNodes);
}
// Create while loop with condition
if (forNode.condition) {
const conditionNodes = context.transformer.transformNode(forNode.condition, context);
const conditionContent = conditionNodes.map(n => n.content).join('');
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, `loop while ${conditionContent}`, node.location, context.indentLevel));
// Transform body with increased indentation
const bodyContext = { ...context, indentLevel: context.indentLevel + 1 };
const bodyNodes = this.transformStatementBody(forNode.body, bodyContext);
result.push(...bodyNodes);
// Transform update statement if present
if (forNode.update) {
const updateNodes = context.transformer.transformNode(forNode.update, bodyContext);
result.push(...updateNodes);
}
// End loop
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, 'end loop', node.location, context.indentLevel));
}
return result;
}
isEnhancedForLoop(forNode) {
// Enhanced for loops in Java have the pattern: for(Type item : collection)
// In our AST, this would be represented differently than regular for loops
// For now, we'll detect this by checking if there's no condition or update
// and the initialization contains a colon-like pattern
// This is a simplified detection - in a full implementation,
// the lexer/parser would need to specifically handle enhanced for syntax
return false; // Placeholder - enhanced for loop parsing not fully implemented
}
extractEnhancedForInfo(forNode, context) {
// This would extract information from an enhanced for loop
// For now, return null as enhanced for loop parsing is not fully implemented
return null;
}
isSimpleCountingLoop(forNode) {
// Check if this matches pattern: for(int i = start; i < end; i++)
if (!forNode.initialization || !forNode.condition || !forNode.update) {
return false;
}
// Check initialization: int i = start
if (forNode.initialization.type !== types_1.NodeType.VARIABLE_DECLARATION) {
return false;
}
// Check condition: i < end or i <= end
if (forNode.condition.type !== types_1.NodeType.BINARY_EXPRESSION) {
return false;
}
const conditionNode = forNode.condition;
if (!['<', '<='].includes(conditionNode.operator)) {
return false;
}
// Check update: i++ or ++i
if (forNode.update.type !== types_1.NodeType.BINARY_EXPRESSION) {
return false;
}
const updateNode = forNode.update;
if (!['++', '--'].includes(updateNode.operator)) {
return false;
}
return true;
}
extractSimpleLoopInfo(forNode, context) {
try {
// Extract variable name from initialization
const initNode = forNode.initialization;
const variableName = context.ibRules.convertVariableName(initNode.name);
// Extract start value
let startValue = '0';
if (initNode.initializer) {
const startNodes = context.transformer.transformNode(initNode.initializer, context);
startValue = startNodes.map(n => n.content).join('');
}
// Extract end value from condition
const conditionNode = forNode.condition;
const endNodes = context.transformer.transformNode(conditionNode.right, context);
let endValue = endNodes.map(n => n.content).join('');
// Adjust end value for <= vs < comparison
if (conditionNode.operator === '<') {
// For i < n, the loop goes from start to n-1
endValue = `${endValue} - 1`;
}
return {
variable: variableName,
start: startValue,
end: endValue
};
}
catch (error) {
return null;
}
}
transformStatementBody(statement, context) {
// Handle block statements (multiple statements)
if (statement.type === types_1.NodeType.PROGRAM && statement.children) {
const result = [];
for (const child of statement.children) {
const childNodes = context.transformer.transformNode(child, context);
result.push(...childNodes);
}
return result;
}
// Handle single statement
return context.transformer.transformNode(statement, context);
}
}
exports.ForLoopRule = ForLoopRule;
// Method Declaration Transformation Rule
class MethodDeclarationRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.METHOD_DECLARATION;
}
transform(node, context) {
const methodNode = node;
const result = [];
// Store method info in context
const methodInfo = {
originalName: methodNode.name,
pseudocodeName: context.ibRules.convertVariableName(methodNode.name),
returnType: methodNode.returnType,
parameters: methodNode.parameters.map(param => ({
originalName: param.name,
pseudocodeName: context.ibRules.convertVariableName(param.name),
type: param.paramType
})),
isVoid: methodNode.isVoid,
isStatic: methodNode.isStatic
};
context.methods.set(methodNode.name, methodInfo);
// Convert parameters to UPPERCASE
const parameterList = methodNode.parameters.map(param => context.ibRules.convertVariableName(param.name)).join(', ');
if (methodNode.isVoid) {
// Transform void method to PROCEDURE format
let procedureHeader = parameterList
? `PROCEDURE ${methodInfo.pseudocodeName}(${parameterList})`
: `PROCEDURE ${methodInfo.pseudocodeName}()`;
// Add static modifier comment if applicable
if (methodNode.isStatic) {
result.push(this.createPseudocodeNode(PseudocodeNodeType.COMMENT, '// Static method', node.location, context.indentLevel));
}
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, procedureHeader, node.location, context.indentLevel));
}
else {
// Transform non-void method to FUNCTION format
let functionHeader = parameterList
? `FUNCTION ${methodInfo.pseudocodeName}(${parameterList})`
: `FUNCTION ${methodInfo.pseudocodeName}()`;
// Add static modifier comment if applicable
if (methodNode.isStatic) {
result.push(this.createPseudocodeNode(PseudocodeNodeType.COMMENT, '// Static method', node.location, context.indentLevel));
}
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, functionHeader, node.location, context.indentLevel));
}
// Transform method body with increased indentation
const bodyContext = {
...context,
indentLevel: context.indentLevel + 1,
currentScope: {
name: methodNode.name,
type: 'method',
parent: context.currentScope
}
};
// Add parameters to variable context
methodNode.parameters.forEach(param => {
const variableInfo = {
originalName: param.name,
pseudocodeName: context.ibRules.convertVariableName(param.name),
type: param.paramType,
scope: methodNode.name
};
bodyContext.variables.set(param.name, variableInfo);
});
// Transform method body
for (const statement of methodNode.body) {
const statementNodes = context.transformer.transformNode(statement, bodyContext);
result.push(...statementNodes);
}
// Add end statement
if (methodNode.isVoid) {
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, 'END PROCEDURE', node.location, context.indentLevel));
}
else {
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, 'END FUNCTION', node.location, context.indentLevel));
}
return result;
}
}
exports.MethodDeclarationRule = MethodDeclarationRule;
// Array Access Transformation Rule
class ArrayAccessRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.ARRAY_ACCESS;
}
transform(node, context) {
const arrayAccessNode = node;
// Transform array expression
const arrayNodes = context.transformer.transformNode(arrayAccessNode.array, context);
const arrayContent = arrayNodes.map(n => n.content).join('');
// Transform index expression
const indexNodes = context.transformer.transformNode(arrayAccessNode.index, context);
const indexContent = indexNodes.map(n => n.content).join('');
// Preserve bracket notation as per IB pseudocode specification
const content = `${arrayContent}[${indexContent}]`;
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, content, node.location)];
}
}
exports.ArrayAccessRule = ArrayAccessRule;
// Method Call Transformation Rule (Enhanced for array.length and string operations)
class MethodCallRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.METHOD_CALL;
}
transform(node, context) {
const methodCallNode = node;
// Handle array.length special case
if (methodCallNode.object && methodCallNode.methodName === 'length') {
const objectNodes = context.transformer.transformNode(methodCallNode.object, context);
const objectContent = objectNodes.map(n => n.content).join('');
// Check if this is string.length() or array.length
if (this.isStringObject(methodCallNode.object, context)) {
// String length: convert to LENGTH(STRING)
const content = context.ibRules.convertStringLength(objectContent);
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, content, node.location)];
}
else {
// Array length: convert to SIZE(ARRAY)
const content = context.ibRules.convertArrayLength(objectContent);
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, content, node.location)];
}
}
// Handle string operations
if (methodCallNode.object && this.isStringObject(methodCallNode.object, context)) {
const objectNodes = context.transformer.transformNode(methodCallNode.object, context);
const objectContent = objectNodes.map(n => n.content).join('');
// Handle common string methods
switch (methodCallNode.methodName) {
case 'equals':
if (methodCallNode.arguments.length === 1) {
const argNodes = context.transformer.transformNode(methodCallNode.arguments[0], context);
const argContent = argNodes.map(n => n.content).join('');
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, `${objectContent} = ${argContent}`, node.location)];
}
break;
case 'equalsIgnoreCase':
if (methodCallNode.arguments.length === 1) {
const argNodes = context.transformer.transformNode(methodCallNode.arguments[0], context);
const argContent = argNodes.map(n => n.content).join('');
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, `UPPER(${objectContent}) = UPPER(${argContent})`, node.location)];
}
break;
case 'substring':
if (methodCallNode.arguments.length >= 1) {
const argNodes = methodCallNode.arguments.map(arg => {
const nodes = context.transformer.transformNode(arg, context);
return nodes.map(n => n.content).join('');
});
if (methodCallNode.arguments.length === 1) {
// substring(start)
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, `SUBSTRING(${objectContent}, ${argNodes[0]})`, node.location)];
}
else {
// substring(start, end)
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, `SUBSTRING(${objectContent}, ${argNodes[0]}, ${argNodes[1]})`, node.location)];
}
}
break;
case 'charAt':
if (methodCallNode.arguments.length === 1) {
const argNodes = context.transformer.transformNode(methodCallNode.arguments[0], context);
const argContent = argNodes.map(n => n.content).join('');
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, `${objectContent}[${argContent}]`, node.location)];
}
break;
case 'toUpperCase':
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, `UPPER(${objectContent})`, node.location)];
case 'toLowerCase':
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, `LOWER(${objectContent})`, node.location)];
case 'indexOf':
if (methodCallNode.arguments.length === 1) {
const argNodes = context.transformer.transformNode(methodCallNode.arguments[0], context);
const argContent = argNodes.map(n => n.content).join('');
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, `POSITION(${argContent}, ${objectContent})`, node.location)];
}
break;
}
}
// Handle System.out.print/println for I/O transformation
if (methodCallNode.object &&
this.getObjectName(methodCallNode.object) === 'System.out' &&
(methodCallNode.methodName === 'print' || methodCallNode.methodName === 'println')) {
// Transform arguments
const argContents = methodCallNode.arguments.map(arg => {
const argNodes = context.transformer.transformNode(arg, context);
return argNodes.map(n => n.content).join('');
});
const outputContent = argContents.join(', ');
const content = context.ibRules.convertIOStatement('output', outputContent);
return [this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, content, node.location, context.indentLevel)];
}
// Handle Scanner input operations (nextInt, nextLine, next, nextDouble, etc.)
if (methodCallNode.object &&
this.isScannerObject(methodCallNode.object) &&
this.isScannerInputMethod(methodCallNode.methodName)) {
// Scanner input methods don't take arguments in the method call itself
// The variable assignment will be handled by the assignment transformation
// For now, we'll return a placeholder that will be replaced by assignment handling
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, 'INPUT_PLACEHOLDER', node.location)];
}
// Regular method call transformation
let objectContent = '';
if (methodCallNode.object) {
const objectNodes = context.transformer.transformNode(methodCallNode.object, context);
objectContent = objectNodes.map(n => n.content).join('') + '.';
}
// Transform method name to UPPERCASE
const methodName = context.ibRules.convertVariableName(methodCallNode.methodName);
// Transform arguments
const argContents = methodCallNode.arguments.map(arg => {
const argNodes = context.transformer.transformNode(arg, context);
return argNodes.map(n => n.content).join('');
});
const argumentList = argContents.join(', ');
const content = `${objectContent}${methodName}(${argumentList})`;
return [this.createPseudocodeNode(PseudocodeNodeType.EXPRESSION, content, node.location)];
}
getObjectName(objectNode) {
if (objectNode.type === types_1.NodeType.IDENTIFIER) {
return objectNode.name;
}
// Handle more complex object expressions if needed
return 'unknown';
}
isStringObject(objectNode, context) {
// Check if the object is a string based on variable type or literal
if (objectNode.type === types_1.NodeType.LITERAL) {
const literal = objectNode;
return literal.dataType === 'string';
}
if (objectNode.type === types_1.NodeType.IDENTIFIER) {
const identifier = objectNode;
const variableInfo = context.variables.get(identifier.name);
return variableInfo?.type === 'String' || variableInfo?.type === 'string';
}
// For method calls that return strings, we'd need more sophisticated type tracking
return false;
}
isScannerObject(objectNode) {
const objectName = this.getObjectName(objectNode);
// Check if the object is a Scanner instance (common names: scanner, input, sc, etc.)
// In a more complete implementation, we'd track variable types
return objectName.toLowerCase().includes('scanner') ||
objectName === 'input' ||
objectName === 'sc' ||
objectName === 'in';
}
isScannerInputMethod(methodName) {
const scannerMethods = [
'nextInt', 'nextDouble', 'nextFloat', 'nextLong',
'nextBoolean', 'next', 'nextLine'
];
return scannerMethods.includes(methodName);
}
}
exports.MethodCallRule = MethodCallRule;
// Program Transformation Rule
class ProgramRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.PROGRAM;
}
transform(node, context) {
const programNode = node;
const result = [];
// Transform all declarations in the program
if (programNode.declarations && Array.isArray(programNode.declarations)) {
for (const declaration of programNode.declarations) {
const declarationNodes = context.transformer.transformNode(declaration, context);
result.push(...declarationNodes);
}
}
else if (programNode.children && Array.isArray(programNode.children)) {
// Fallback to children if declarations is not available
for (const child of programNode.children) {
const childNodes = context.transformer.transformNode(child, context);
result.push(...childNodes);
}
}
return result;
}
}
exports.ProgramRule = ProgramRule;
// Return Statement Transformation Rule
class ReturnStatementRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.RETURN_STATEMENT;
}
transform(node, context) {
const returnNode = node;
if (returnNode.expression) {
// Transform the return expression
const expressionNodes = context.transformer.transformNode(returnNode.expression, context);
const expressionContent = expressionNodes.map(n => n.content).join('');
return [this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, `RETURN ${expressionContent}`, node.location, context.indentLevel)];
}
else {
// Void return (just return without value)
return [this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, 'RETURN', node.location, context.indentLevel)];
}
}
}
exports.ReturnStatementRule = ReturnStatementRule;
// Class Declaration Transformation Rule
class ClassDeclarationRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.CLASS_DECLARATION;
}
transform(node, context) {
const classNode = node;
const result = [];
// Check if this class contains a main method and should extract it
const mainMethod = this.findMainMethod(classNode);
const shouldExtractMain = mainMethod && this.shouldExtractMainMethod(classNode);
if (shouldExtractMain) {
// For educational Java code with main method, extract the main method logic
result.push(this.createPseudocodeNode(PseudocodeNodeType.COMMENT, '// Main program logic extracted from main method', node.location, context.indentLevel));
// Transform main method body directly (without the method declaration wrapper)
const mainContext = {
...context,
currentScope: {
name: 'main',
type: 'method',
parent: context.currentScope
}
};
for (const statement of mainMethod.body) {
const statementNodes = context.transformer.transformNode(statement, mainContext);
result.push(...statementNodes);
}
// Transform other non-main methods if any
const otherMethods = classNode.methods.filter(m => !this.isMainMethod(m));
for (const method of otherMethods) {
if (result.length > 0) {
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, '', node.location, context.indentLevel));
}
const methodNodes = context.transformer.transformNode(method, context);
result.push(...methodNodes);
}
return result;
}
// Regular class transformation (non-main class)
// Convert class name to UPPERCASE
const className = context.ibRules.convertVariableName(classNode.name);
// Create class declaration comment
let classHeader = `// CLASS ${className}`;
if (classNode.superClass) {
const superClassName = context.ibRules.convertVariableName(classNode.superClass);
classHeader += ` INHERITS FROM ${superClassName}`;
}
result.push(this.createPseudocodeNode(PseudocodeNodeType.COMMENT, classHeader, node.location, context.indentLevel));
// Create new scope for the class
const classContext = {
...context,
currentScope: {
name: classNode.name,
type: 'class',
parent: context.currentScope
}
};
// Transform class fields (instance variables)
if (classNode.fields.length > 0) {
result.push(this.createPseudocodeNode(PseudocodeNodeType.COMMENT, '// Class fields', node.location, context.indentLevel));
for (const field of classNode.fields) {
const fieldNodes = context.transformer.transformNode(field, classContext);
result.push(...fieldNodes);
}
}
// Transform class methods
for (const method of classNode.methods) {
// Add spacing between methods
if (result.length > 0) {
result.push(this.createPseudocodeNode(PseudocodeNodeType.STATEMENT, '', node.location, context.indentLevel));
}
const methodNodes = context.transformer.transformNode(method, classContext);
result.push(...methodNodes);
}
// Add end of class comment
result.push(this.createPseudocodeNode(PseudocodeNodeType.COMMENT, `// END CLASS ${className}`, node.location, context.indentLevel));
return result;
}
findMainMethod(classNode) {
return classNode.methods.find(method => this.isMainMethod(method)) || null;
}
isMainMethod(method) {
return method.name === 'main' &&
method.isStatic &&
method.isVoid &&
method.parameters.length === 1 &&
method.parameters[0].paramType === 'String[]';
}
shouldExtractMainMethod(classNode) {
// Extract main method only if:
// 1. The class has no fields (instance variables)
// 2. The class has no other methods besides main
// 3. AND the class name suggests it's a simple program (not just "MainClass" for OOP examples)
const hasFields = classNode.fields.length > 0;
const hasOtherMethods = classNode.methods.some(m => !this.isMainMethod(m));
const isSimpleProgramClass = /^(Test|Demo|Example|Program|Calculator|Hello|Simple|.*Game|.*Processor|.*Manipulator)/.test(classNode.name);
// Only extract if it's a simple class with only main method AND has a program-like name
return !hasFields && !hasOtherMethods && isSimpleProgramClass;
}
}
exports.ClassDeclarationRule = ClassDeclarationRule;
// Main AST Transformer Class
class ASTTransformer {
constructor() {
this.errors = [];
this.ibRules = new ib_rules_engine_1.IBRulesEngine();
this.rules = new Map();
this.initializeRules();
}
initializeRules() {
const rules = [
new ProgramRule(),
new ClassDeclarationRule(),
new VariableDeclarationRule(),
new AssignmentRule(),
new BinaryExpressionRule(),
new IdentifierRule(),
new LiteralRule(),
new IfStatementRule(),
new WhileLoopRule(),
new ForLoopRule(),
new MethodDeclarationRule(),
new ArrayAccessRule(),
new MethodCallRule(),
new ReturnStatementRule(),
new SwitchStatementRule(),
new CaseClauseRule(),
new DefaultClauseRule(),
new BreakStatementRule(),
new ContinueStatementRule(),
new EnhancedForLoopRule(),
new ArrayInitializationRule()
];
for (const rule of rules) {
this.rules.set(rule.nodeType, rule);
}
}
/**
* Transform a Java AST into IB Pseudocode AST
* @param ast - The Java AST to transform
* @returns Transformation result with pseudocode AST, errors, and warnings
*/
transform(ast) {
this.errors = [];
const context = {
variables: new Map(),
methods: new Map(),
currentScope: { name: 'global', type: 'global' },
ibRules: this.ibRules,
indentLevel: 0,
transformer: this // Add reference to transformer for recursive calls
};
try {
const pseudocodeAST = this.transformNode(ast, context);
// Separate errors and warnings
const errors = this.errors.filter(e => e.severity === types_1.ErrorSeverity.ERROR);
const warnings = this.errors.filter(e => e.severity === types_1.ErrorSeverity.WARNING);
return { pseudocodeAST, errors, warnings };
}
catch (error) {
if (error instanceof Error) {
this.addError(types_1.ErrorType.CONVERSION_ERROR, error.message, ast.location);
}
const errors = this.errors.filter(e => e.severity === types_1.ErrorSeverity.ERROR);
const warnings = this.errors.filter(e => e.severity === types_1.ErrorSeverity.WARNING);
return { pseudocodeAST: [], errors, warnings };
}
}
/**
* Transform a single AST node
* @param node - The AST node to transform
* @param context - The transformation context
* @returns Array of pseudocode nodes
*/
transformNode(node, context) {
const rule = this.rules.get(node.type);
if (!rule) {
this.addError(types_1.ErrorType.CONVERSION_ERROR, `No transformation rule found for node type: ${node.type}`, node.location);
return [];
}
try {
return rule.transform(node, context);
}
catch (error) {
if (error instanceof Error) {
this.addError(types_1.ErrorType.CONVERSION_ERROR, error.message, node.location);
}
return [];
}
}
addError(type, message, location) {
this.errors.push({
type,
message,
location,
severity: types_1.ErrorSeverity.ERROR
});
}
addWarning(type, message, location) {
this.errors.push({
type,
message,
location,
severity: types_1.ErrorSeverity.WARNING
});
}
/**
* Create a transformation context for testing
* @param ibRules - Optional IBRulesEngine instance
* @returns A new transformation context
*/
createContext(ibRules) {
return {
variables: new Map(),
methods: new Map(),
currentScope: { name: 'global', type: 'global' },
ibRules: ibRules || this.ibRules,
indentLevel: 0,
transformer: this
};
}
}
exports.ASTTransformer = ASTTransformer;
// Switch Statement Transformation Rule
class SwitchStatementRule extends BaseTransformationRule {
constructor() {
super(...arguments);
this.nodeType = types_1.NodeType.SWITCH_STATEMENT;
}
transform(node, context) {
const switchNode = node;
const discriminantNodes = context.transformer.transformNode(switchNode.discriminant, context);
const discriminantExpr = discriminantNodes.map(n => n.content).join(' ');
const result = [];
// Add switch header
result.push(this