UNPKG

python2igcse

Version:

Convert Python code to IGCSE Pseudocode format

375 lines 15.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefinitionVisitor = void 0; const ir_1 = require("../types/ir"); const expression_visitor_1 = require("./expression-visitor"); const base_parser_1 = require("./base-parser"); /** * Visitor class responsible for processing function and class definitions */ class DefinitionVisitor extends base_parser_1.BaseParser { /** * Parse execution (not used in DefinitionVisitor) */ parse(_source) { throw new Error('DefinitionVisitor.parse() should not be called directly'); } constructor() { super(); this.expressionVisitor = new expression_visitor_1.ExpressionVisitor(); } /** * Set context */ setContext(context) { this.context = context; } /** * Process function definition */ visitFunctionDef(node) { const funcName = this.capitalizeFirstLetter(node.name); const params = this.extractParameters(node.args, node.name); const paramText = params.map(p => `${p.name} : ${p.type}`).join(', '); // Enter function scope this.enterScope(funcName, 'function'); this.increaseIndent(); // Register parameters as variables params.forEach(param => { this.registerVariable(param.name, param.type, node.lineno); }); // Process function body (to collect function call information) const bodyChildren = node.body.map((child) => this.visitNode ? this.visitNode(child) : this.createIRNode('comment', '// Unprocessed node')); // Infer return type after processing function body const hasReturn = this.hasReturnStatement(node.body); const returnType = hasReturn ? this.inferReturnType(node, params) : null; let funcText; if (returnType) { funcText = `FUNCTION ${funcName}(${paramText}) RETURNS ${returnType}`; } else { funcText = `PROCEDURE ${funcName}(${paramText})`; } this.decreaseIndent(); this.exitScope(); // Add end statement const endText = returnType ? `ENDFUNCTION` : `ENDPROCEDURE`; const endIR = this.createIRNode('statement', endText); bodyChildren.push(endIR); return this.createIRNode('function', funcText, bodyChildren); } /** * Process class definition */ visitClassDef(node) { const className = node.name; // Determine whether to treat as record type const isRecordType = this.shouldTreatAsRecordType(node); // Treat as record type if (isRecordType) { return this.createRecordType(node, className); } // Treat as regular class return this.createClass(node, className); } /** * Create record type */ createRecordType(node, className) { const recordTypeName = `${className}Record`; const typeText = `TYPE ${recordTypeName}`; // Extract attributes from __init__ method const constructor = node.body.find((item) => item.type === 'FunctionDef' && item.name === '__init__'); const children = []; if (constructor) { // Extract actual field names and types from constructor const attributes = this.extractAttributesFromConstructor(constructor); for (const attr of attributes) { const attrDeclaration = `DECLARE ${attr}`; children.push(this.createIRNode('statement', attrDeclaration)); } } return this.createIRNode('type', typeText, children); } /** * Create regular class */ createClass(node, className) { const baseClass = node.bases.length > 0 ? node.bases[0].id : null; let classText = `CLASS ${className}`; if (baseClass) { classText += ` INHERITS ${baseClass}`; } this.enterScope(className, 'class'); this.increaseIndent(); const members = []; // Process class attributes and methods for (const item of node.body) { if (item.type === 'FunctionDef') { if (item.name === '__init__') { // Extract attributes from constructor const attributes = this.extractAttributesFromConstructor(item); attributes.forEach(attr => { members.push(this.createIRNode('statement', `PRIVATE ${attr}`)); }); } members.push(this.visitNode ? this.visitNode(item) : this.createIRNode('comment', '// Unprocessed node')); } else if (item.type === 'Assign') { // Class attributes const attrIR = this.visitNode ? this.visitNode(item) : this.createIRNode('comment', '// Unprocessed node'); attrIR.text = `PRIVATE ${attrIR.text}`; members.push(attrIR); } } this.decreaseIndent(); this.exitScope(); const endClassIR = this.createIRNode('statement', 'ENDCLASS'); members.push(endClassIR); return this.createIRNode('class', classText, members); } /** * Extract function parameters */ extractParameters(args, functionName) { const params = []; if (args.args) { args.args.forEach((arg, index) => { const name = arg.arg; let type = this.convertPythonTypeToIGCSE(arg.annotation); // If no type annotation, infer type from function call information if (type === 'STRING' && !arg.annotation && functionName) { const callInfo = this.getFunctionCallInfo(functionName); if (callInfo && callInfo.argumentTypes[index]) { type = callInfo.argumentTypes[index]; } } params.push({ name, type }); }); } return params; } /** * Convert Python type annotation to IGCSE type */ convertPythonTypeToIGCSE(annotation) { if (!annotation) return 'STRING'; if (annotation.type === 'Name') { switch (annotation.id) { case 'int': return 'INTEGER'; case 'str': return 'STRING'; case 'bool': return 'BOOLEAN'; case 'float': return 'REAL'; default: return 'STRING'; } } return 'STRING'; } /** * Extract attributes from constructor */ extractAttributesFromConstructor(constructor) { const attributes = []; // Get type information from constructor parameters const paramTypes = new Map(); if (constructor.args && constructor.args.args) { constructor.args.args.forEach((arg) => { if (arg.arg !== 'self') { const type = this.convertPythonTypeToIGCSE(arg.annotation); paramTypes.set(arg.arg, type); } }); } // Look for self.attribute = value format for (const stmt of constructor.body) { if (stmt.type === 'Assign') { const target = stmt.targets[0]; if (target.type === 'Attribute' && target.value.id === 'self') { const attrName = target.attr; // If assigned value is a parameter, use parameter type let attrType = 'STRING'; if (stmt.value.type === 'Name' && paramTypes.has(stmt.value.id)) { attrType = paramTypes.get(stmt.value.id); } else { // If not a parameter, infer type from value attrType = this.expressionVisitor.inferTypeFromValue(stmt.value); } attributes.push(`${attrName} : ${attrType}`); } } } return attributes; } /** * Determine whether to treat as record type */ shouldTreatAsRecordType(node) { // Classes with inheritance are not treated as record types if (node.bases && node.bases.length > 0) { return false; } // Check if this class is used as parent class of other classes if (this.isUsedAsBaseClass(node.name)) { return false; } // Determine if class is used as record type const methods = node.body.filter((item) => item.type === 'FunctionDef'); // If only has __init__ method if (methods.length === 1 && methods[0].name === '__init__') { const initMethod = methods[0]; // Check if __init__ contains only simple field assignments return this.isSimpleConstructor(initMethod); } // If no methods (data class usage) if (methods.length === 0) { // Treat as record type if only has class attributes const hasOnlyAttributes = node.body.every((item) => item.type === 'Assign' || item.type === 'AnnAssign'); return hasOnlyAttributes; } return false; } /** * Determine if __init__ method is a simple constructor */ isSimpleConstructor(initMethod) { // Check __init__ body for (const stmt of initMethod.body) { if (stmt.type === 'Assign') { // Check if it's self.field = parameter format const target = stmt.targets[0]; if (target.type === 'Attribute' && target.value.type === 'Name' && target.value.id === 'self') { // Simple field assignment continue; } else { // Complex assignment exists, so don't treat as record type return false; } } else if (stmt.type === 'Expr' && stmt.value.type === 'Constant') { // Allow docstrings continue; } else { // Other complex processing exists, so don't treat as record type return false; } } return true; } /** * Check if this class is used as parent class of other classes */ isUsedAsBaseClass(className) { // Get all class definitions from context and check inheritance relationships if (this.context && this.context.classDefinitions) { for (const [, classDef] of Object.entries(this.context.classDefinitions)) { if (classDef.bases && classDef.bases.includes(className)) { return true; } } } return false; } /** * Determine if there are return statements */ hasReturnStatement(body) { return body.some(stmt => stmt.type === 'Return' || (stmt.body && this.hasReturnStatement(stmt.body))); } /** * Infer return type */ inferReturnType(node, params) { // Prioritize Python type hints if available if (node.returns && node.returns.id) { switch (node.returns.id) { case 'int': return 'INTEGER'; case 'str': return 'STRING'; case 'bool': return 'BOOLEAN'; case 'float': return 'REAL'; default: return 'STRING'; } } // Get argument types from function call information const functionCallInfo = this.context.functionCalls?.get(node.name); const argumentTypes = functionCallInfo?.argumentTypes || []; // Simple return type inference (recursively search Return statements) const findReturnType = (statements) => { for (const stmt of statements) { if (stmt.type === 'Return' && stmt.value) { // If return value is operation of arguments, consider function call information and argument types if (stmt.value.type === 'BinOp') { const leftType = this.getOperandTypeWithCallInfo(stmt.value.left, params, argumentTypes); const rightType = this.getOperandTypeWithCallInfo(stmt.value.right, params, argumentTypes); // If both arguments are same type, return that type if (leftType && rightType && leftType === rightType) { return leftType; } // If one is INTEGER, prioritize INTEGER if (leftType === 'INTEGER' || rightType === 'INTEGER') { return 'INTEGER'; } } const inferredType = this.expressionVisitor.inferTypeFromValue(stmt.value); return inferredType; } // Search nested structures (if statements, for statements, etc.) if (stmt.body && Array.isArray(stmt.body)) { const nestedType = findReturnType(stmt.body); if (nestedType) return nestedType; } if (stmt.orelse && Array.isArray(stmt.orelse)) { const elseType = findReturnType(stmt.orelse); if (elseType) return elseType; } } return null; }; const returnType = findReturnType(node.body); return returnType || 'STRING'; } /** * Get operand types for operators (utilizing function call information) */ getOperandTypeWithCallInfo(operand, params, argumentTypes) { if (operand.type === 'Name') { // Parameter name case: first get type from function call information if (params && argumentTypes) { const paramIndex = params.findIndex(p => p.name === operand.id); if (paramIndex >= 0 && paramIndex < argumentTypes.length) { return argumentTypes[paramIndex]; } } // If no function call information, return parameter types if (params) { const param = params.find(p => p.name === operand.id); if (param) { return param.type; } } } // Other cases use normal type inference return this.expressionVisitor.inferTypeFromValue(operand); } createIRNode(kind, text, children = [], meta) { return (0, ir_1.createIR)(kind, text, children, meta); } /** * Capitalize first character of string */ capitalizeFirstLetter(str) { if (!str) return str; return str.charAt(0).toUpperCase() + str.slice(1); } } exports.DefinitionVisitor = DefinitionVisitor; //# sourceMappingURL=definition-visitor.js.map