UNPKG

python2igcse

Version:

Convert Python code to IGCSE Pseudocode format

347 lines (337 loc) 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PythonParser = void 0; // Main Python parser class const base_parser_1 = require("./base-parser"); const ir_1 = require("../types/ir"); const visitor_1 = require("./visitor"); /** * Parser for converting Python to IGCSE Pseudocode */ class PythonParser extends base_parser_1.BaseParser { constructor(options = {}) { super(options); } /** * Parse Python source code and convert to IR */ parse(source) { this.resetContext(); const preprocessedSource = this.preprocessSource(source); const result = this.parseToIR(preprocessedSource); // Update statistics result.stats.parseTime = Date.now() - this.context.startTime; return result; } /** * Parse to IR processing */ parseToIR(source) { this.context.startTime = Date.now(); // Source code preprocessing const processedSource = this.preprocessSource(source); // Convert from AST to IR using PythonASTVisitor const visitor = new visitor_1.PythonASTVisitor(); const visitorResult = visitor.parse(processedSource); // Get function call information from visitor const visitorFunctionCalls = visitor.getAllFunctionCalls(); for (const [name, info] of visitorFunctionCalls) { this.context.functionCalls.set(name, info); } const parseTime = Date.now() - this.context.startTime; const result = { ir: visitorResult.ir, errors: [...this.context.errors, ...visitorResult.errors], warnings: [...this.context.warnings, ...visitorResult.warnings], context: this.context, stats: { parseTime, linesProcessed: processedSource.split('\n').length, nodesGenerated: Array.isArray(visitorResult.ir) ? visitorResult.ir.reduce((sum, node) => sum + (0, ir_1.countIRNodes)(node), 0) : (0, ir_1.countIRNodes)(visitorResult.ir), functionsFound: Array.isArray(visitorResult.ir) ? visitorResult.ir.reduce((sum, node) => sum + this.countFunctionsFromIR(node), 0) : this.countFunctionsFromIR(visitorResult.ir), classesFound: Array.isArray(visitorResult.ir) ? visitorResult.ir.reduce((sum, node) => sum + this.countClassesFromIR(node), 0) : this.countClassesFromIR(visitorResult.ir), variablesFound: Array.isArray(visitorResult.ir) ? visitorResult.ir.reduce((sum, node) => sum + this.countVariablesFromIR(node), 0) : this.countVariablesFromIR(visitorResult.ir) } }; return result; } /** * Source code preprocessing */ preprocessSource(source) { return this.preprocess(source); } /** * Count functions from IR */ countFunctionsFromIR(ir) { let count = 0; if (ir.kind === 'function' || ir.kind === 'procedure') { count = 1; } if (ir.children) { for (const child of ir.children) { count += this.countFunctionsFromIR(child); } } return count; } /** * Count classes from IR */ countClassesFromIR(ir) { let count = 0; if (ir.kind === 'class') { count = 1; } if (ir.children) { for (const child of ir.children) { count += this.countClassesFromIR(child); } } return count; } /** * Count variables from IR */ countVariablesFromIR(ir) { let count = 0; if (ir.kind === 'assign' && ir.meta?.name) { count = 1; } if (ir.children) { for (const child of ir.children) { count += this.countVariablesFromIR(child); } } return count; } /** * Source code preprocessing (internal implementation) */ preprocess(source) { let processed = source; // Normalize line breaks processed = processed.replace(/\r\n/g, '\n'); processed = processed.replace(/\r/g, '\n'); // Convert tabs to spaces processed = processed.replace(/\t/g, ' '.repeat(this.options.indentSize)); // Remove trailing whitespace processed = processed.split('\n') .map(line => line.trimEnd()) .join('\n'); // Merge consecutive empty lines into one processed = processed.replace(/\n\s*\n\s*\n/g, '\n\n'); this.debug(`Preprocessed ${source.split('\n').length} lines`); return processed; } /** * IR post-processing */ /* private postprocess(ir: IR): IR { // IR optimization const optimized = this.optimizeIR(ir); // Validation this.validateIR(optimized); return optimized; } */ /** * IR optimization */ optimizeIR(ir) { // Recursively optimize child nodes const optimizedChildren = ir.children.map(child => this.optimizeIR(child)); // Remove empty nodes (but keep statement nodes with children) const filteredChildren = optimizedChildren .filter(child => { // Keep nodes with text if (child.text.trim() !== '') { return true; } // Keep statement nodes with children if (child.kind === 'statement' && child.children.length > 0) { return true; } // Keep important nodes like assign, input, output, if, for, while, etc. if (['assign', 'input', 'output', 'if', 'for', 'while', 'function', 'class'].includes(child.kind)) { return true; } // Remove other empty nodes return false; }); // Merge consecutive comments const mergedChildren = this.mergeConsecutiveComments(filteredChildren); return { ...ir, children: mergedChildren }; } /** * Merge consecutive comments */ mergeConsecutiveComments(children) { const result = []; let currentCommentGroup = []; for (const child of children) { if (child.kind === 'comment') { currentCommentGroup.push(child); } else { // Process comment group if (currentCommentGroup.length > 0) { if (currentCommentGroup.length === 1) { result.push(currentCommentGroup[0]); } else { // Merge multiple comments into one const mergedText = currentCommentGroup .map(comment => comment.text) .join('\n'); result.push({ ...currentCommentGroup[0], text: mergedText }); } currentCommentGroup = []; } result.push(child); } } // Process last comment group if (currentCommentGroup.length > 0) { if (currentCommentGroup.length === 1) { result.push(currentCommentGroup[0]); } else { const mergedText = currentCommentGroup .map(comment => comment.text) .join('\n'); result.push({ ...currentCommentGroup[0], text: mergedText }); } } return result; } /** * IR validation (temporarily disabled) */ /* private validateIR(ir: IR): void { this.validateNode(ir); } private validateNode(node: IR): void { // Validate required fields if (!node.kind) { this.addError('IR node missing kind', 'validation_error'); } if (node.text === undefined) { this.addError('IR node missing text', 'validation_error'); } // Validate child nodes if (node.children) { for (const child of node.children) { this.validateNode(child); } } // Validate specific node types this.validateSpecificNode(node); } private validateSpecificNode(node: IR): void { switch (node.kind) { case 'assign': if (!node.meta?.name) { this.addWarning( 'Assignment node missing variable name', 'style_suggestion' ); } break; case 'for': if (!node.meta?.startValue || !node.meta?.endValue) { this.addWarning( 'FOR loop missing start or end value', 'style_suggestion' ); } break; case 'function': case 'procedure': if (!node.meta?.name) { this.addError( 'Function/Procedure node missing name', 'validation_error' ); } break; case 'if': if (!node.meta?.condition) { this.addWarning( 'IF statement missing condition', 'style_suggestion' ); } break; } } */ /** * Get parser statistics */ getStats(ir) { return { totalVariables: ir ? this.countVariablesFromIR(ir) : 0, totalFunctions: ir ? this.countFunctionsFromIR(ir) : 0, totalScopes: this.context.scopeStack.length, maxNestingDepth: this.context.indentLevel }; } /** * Analyze variable usage */ analyzeVariableUsage() { const usage = new Map(); // Collect variables from all scopes for (const scope of this.context.scopeStack) { for (const [name, variable] of Array.from(scope.variables.entries())) { usage.set(name, { defined: true, used: false, // Actual usage analysis requires separate processing type: variable.type, scope: variable.scope }); } } return usage; } /** * Analyze function usage */ analyzeFunctionUsage() { const usage = new Map(); // Collect functions from all scopes for (const scope of this.context.scopeStack) { for (const [name, func] of Array.from(scope.functions.entries())) { usage.set(name, { defined: true, called: false, // Actual call analysis requires separate processing parameters: func.parameters, returnType: func.returnType }); } } return usage; } /** * Reset parser state */ reset() { this.resetContext(); this.debug('Parser state reset'); } } exports.PythonParser = PythonParser; //# sourceMappingURL=python-parser.js.map