python2igcse
Version:
Convert Python code to IGCSE Pseudocode format
347 lines (337 loc) • 11.3 kB
JavaScript
"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