python2igcse
Version:
Convert Python code to IGCSE Pseudocode format
375 lines • 15.1 kB
JavaScript
"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