UNPKG

java2ib

Version:

TypeScript library that converts Java code into IB Computer Science pseudocode format

1,033 lines 56.1 kB
"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