UNPKG

python2igcse

Version:

Convert Python code to IGCSE Pseudocode format

446 lines 17.3 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 { /** * Execute parsing (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 definitions */ visitFunctionDef(node) { const funcName = this.capitalizeFirstLetter(node.name); const params = this.extractParameters(node.args); const paramText = params.map((p) => `${p.name} : ${p.type}`).join(', '); // Infer return type const hasReturn = this.hasReturnStatement(node.body); const returnType = hasReturn ? this.inferReturnType(node) : null; let funcText; if (returnType) { funcText = `FUNCTION ${funcName}(${paramText}) RETURNS ${returnType}`; } else { funcText = `PROCEDURE ${funcName}(${paramText})`; } // 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 const bodyChildren = node.body.map((child) => this.visitNode ? this.visitNode(child) : this.createIRNode('comment', '// Unprocessed node')); 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 definitions */ visitClassDef(node) { const className = node.name; // Determine whether to treat as record type const isRecordType = this.shouldTreatAsRecordType(node); // If treating as record type if (isRecordType) { return this.createRecordType(node, className); } // If treating 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) { const params = []; if (args.args) { args.args.forEach((arg) => { const name = arg.arg; // Prioritize type annotations if available let type = this.convertPythonTypeToIGCSE(arg.annotation); // If no type annotation, infer INTEGER as default // (More appropriate default due to frequent numeric operations) if (!arg.annotation) { type = 'INTEGER'; } params.push({ name, type }); }); } return params; } /** * Convert Python type annotations to IGCSE types */ convertPythonTypeToIGCSE(annotation) { if (!annotation) return 'INTEGER'; // Changed default to INTEGER 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 'INTEGER'; // Unknown types also default to INTEGER } } return 'INTEGER'; } /** * 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 a parent class by 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 it 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 it 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, don't treat as record type return false; } } else if (stmt.type === 'Expr' && stmt.value.type === 'Constant') { // Documentation strings are allowed continue; } else { // Other complex processing exists, don't treat as record type return false; } } return true; } /** * Check if this class is used as a parent class by 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) { // 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 parameter type information const params = this.extractParameters(node.args); const paramTypes = new Map(); params.forEach((param) => { paramTypes.set(param.name, param.type); }); // Simple return type inference (recursively search for Return statements) const findReturnType = (statements) => { for (const stmt of statements) { if (stmt.type === 'Return' && stmt.value) { return this.inferReturnTypeFromExpression(stmt.value, paramTypes); } // Also search nested structures (if statements, for loops, 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 || 'INTEGER'; } /** * Return type inference considering parameter type information */ inferReturnTypeFromExpression(node, paramTypes) { if (!node) return 'INTEGER'; switch (node.type) { case 'Constant': if (typeof node.value === 'number') { return Number.isInteger(node.value) ? 'INTEGER' : 'REAL'; } if (typeof node.value === 'string') return 'STRING'; if (typeof node.value === 'boolean') return 'BOOLEAN'; break; case 'Num': return Number.isInteger(node.n) ? 'INTEGER' : 'REAL'; case 'Str': return 'STRING'; case 'Name': // Reference parameter type if (paramTypes.has(node.id)) { return paramTypes.get(node.id); } // Numeric literal if (node.id && /^\d+$/.test(node.id)) { return 'INTEGER'; } if (node.id && /^\d+\.\d+$/.test(node.id)) { return 'REAL'; } return 'INTEGER'; case 'BinOp': // Type inference for binary operations const leftType = this.inferReturnTypeFromExpression(node.left, paramTypes); const rightType = this.inferReturnTypeFromExpression(node.right, paramTypes); // For arithmetic operators if (['Add', 'Sub', 'Mult', 'Div', 'Mod', 'Pow'].includes(node.op.type)) { // String concatenation (+operator) - only for explicit string types if (node.op.type === 'Add' && ((leftType === 'STRING' && this.isExplicitStringNode(node.left)) || (rightType === 'STRING' && this.isExplicitStringNode(node.right)))) { return 'STRING'; } // Treat as numeric operation if numeric types are involved if (leftType === 'INTEGER' || leftType === 'REAL' || rightType === 'INTEGER' || rightType === 'REAL') { // Division results in REAL if (node.op.type === 'Div') { return 'REAL'; } // INTEGER if both are INTEGER if (leftType === 'INTEGER' && rightType === 'INTEGER') { return 'INTEGER'; } // REAL if either is REAL if (leftType === 'REAL' || rightType === 'REAL') { return 'REAL'; } // Default is INTEGER return 'INTEGER'; } // Default arithmetic operations inferred as INTEGER return 'INTEGER'; } // For comparison operators if (['Eq', 'NotEq', 'Lt', 'LtE', 'Gt', 'GtE'].includes(node.op.type)) { return 'BOOLEAN'; } return 'INTEGER'; } return 'INTEGER'; } /** * Check if it's an explicit string node */ isExplicitStringNode(node) { if (!node) return false; switch (node.type) { case 'Constant': return typeof node.value === 'string'; case 'Str': return true; case 'JoinedStr': // f-string return true; default: return false; } } createIRNode(kind, text, children = [], meta) { return (0, ir_1.createIR)(kind, text, children, meta); } /** * Capitalize the first character of a string */ capitalizeFirstLetter(str) { if (!str) return str; return str.charAt(0).toUpperCase() + str.slice(1); } } exports.DefinitionVisitor = DefinitionVisitor; //# sourceMappingURL=definition-visitor.js.map