python2igcse
Version:
Convert Python code to IGCSE Pseudocode format
322 lines • 12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseEmitter = void 0;
const emitter_1 = require("../types/emitter");
/**
* Base class for emitters
* Provides functionality for converting IR to text
*/
class BaseEmitter {
constructor(options = {}) {
this.startTime = 0;
this.options = { ...(0, emitter_1.getDefaultEmitterOptions)(), ...options };
this.context = this.createInitialContext();
}
/**
* Create initial context
*/
createInitialContext() {
return {
indent: (0, emitter_1.createIndentInfo)(0, this.options.indentChar, this.options.indentSize),
output: [],
currentLine: 1,
errors: [],
warnings: [],
formatter: (0, emitter_1.getDefaultFormatterConfig)()
};
}
/**
* Add error
*/
addError(message, type, node) {
const error = (0, emitter_1.createEmitError)(message, type, node);
this.context.errors.push(error);
if (this.options.includeDebugInfo) {
console.error(`Emit Error: ${message}`);
}
}
/**
* Add warning
*/
addWarning(message, type, node) {
const warning = (0, emitter_1.createEmitWarning)(message, type, node);
this.context.warnings.push(warning);
if (this.options.includeDebugInfo) {
console.warn(`Emit Warning: ${message}`);
}
}
/**
* Increase indent level
*/
increaseIndent() {
this.context.indent = (0, emitter_1.createIndentInfo)(this.context.indent.level + 1, this.options.indentChar, this.options.indentSize);
}
/**
* Decrease indent level
*/
decreaseIndent() {
if (this.context.indent.level > 0) {
this.context.indent = (0, emitter_1.createIndentInfo)(this.context.indent.level - 1, this.options.indentChar, this.options.indentSize);
}
}
/**
* Output line
*/
emitLine(text, indent = true) {
const indentedText = indent ? this.context.indent.string + text : text;
// Line length check
if (this.options.maxLineLength && indentedText.length > this.options.maxLineLength) {
this.addWarning(`Line exceeds maximum length (${indentedText.length} > ${this.options.maxLineLength})`, 'long_line');
}
// Output with line numbers
if (this.options.includeLineNumbers) {
const lineNumber = this.context.currentLine.toString().padStart(3, ' ');
this.context.output.push(`${lineNumber}: ${indentedText}`);
}
else {
this.context.output.push(indentedText);
}
this.context.currentLine++;
}
/**
* Output blank line
*/
emitBlankLine() {
this.context.output.push('');
this.context.currentLine++;
}
/**
* Output comment
*/
emitComment(text) {
if (this.options.includeComments) {
this.emitLine(text);
}
}
/**
* Process child nodes
*/
emitChildren(node) {
if (node.children && Array.isArray(node.children)) {
for (const child of node.children) {
this.emitNode(child);
}
}
}
/**
* Format text (Python → IGCSE)
*/
formatText(text) {
let result = text;
// Convert operators
result = this.convertOperators(result);
// Uppercase keywords
result = this.uppercaseKeywords(result);
return result;
}
/**
* Uppercase keywords (only outside string literals)
*/
uppercaseKeywords(text) {
let result = text;
// Temporarily protect string literals
const stringLiterals = [];
result = result.replace(/(["'])((?:\\.|(?!\1)[^\\])*)\1/g, (match) => {
const index = stringLiterals.length;
stringLiterals.push(match);
return `__STRING_${index}__`;
});
// Uppercase IGCSE keywords (only outside string literals)
const keywords = [
'if', 'then', 'else', 'endif', 'elseif',
'for', 'to', 'step', 'next', 'while', 'endwhile',
'repeat', 'until', 'do',
'procedure', 'endprocedure', 'function', 'endfunction', 'return',
'declare', 'constant', 'array', 'of', 'type',
'input', 'output', 'read', 'write',
'and', 'or', 'not',
'true', 'false', 'null',
'case', 'of', 'otherwise', 'endcase',
'class', 'endclass', 'new', 'super', 'this',
'public', 'private', 'inherits'
];
keywords.forEach(keyword => {
const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
result = result.replace(regex, keyword.toUpperCase());
});
// Restore string literals
stringLiterals.forEach((literal, index) => {
result = result.replace(`__STRING_${index}__`, literal);
});
return result;
}
/**
* Convert operators (Python → IGCSE)
*/
convertOperators(text) {
let result = text;
// Protect comment sections (temporarily replace Python # comments and IGCSE // comments)
const commentMatches = [];
result = result.replace(/(#.*$|\/\/.*$)/gm, (match) => {
const index = commentMatches.length;
commentMatches.push(match);
return `__COMMENT_${index}__`;
});
// Temporarily protect string literals
const stringLiterals = [];
result = result.replace(/(["'])((?:\\.|(?!\1)[^\\])*)\1/g, (match) => {
const index = stringLiterals.length;
stringLiterals.push(match);
return `__STRING_${index}__`;
});
// Convert comparison operators (process before assignment operators)
result = result.replace(/!=/g, '≠');
result = result.replace(/<=/g, '≤');
result = result.replace(/>=/g, '≥');
result = result.replace(/==/g, '=');
// Convert assignment operators (only = that are not comparison operators)
// Convert variable = format from line start to ←
result = result.replace(/^(\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*/gm, '$1$2 ← ');
// Convert logical operators (using word boundaries)
result = result.replace(/\band\b/g, ' AND ');
result = result.replace(/\bor\b/g, ' OR ');
result = result.replace(/\bnot\b/g, 'NOT ');
// Restore string literals
stringLiterals.forEach((literal, index) => {
result = result.replace(`__STRING_${index}__`, literal);
});
// Convert string concatenation (convert + to & in lines containing string literals)
const lines = result.split('\n');
result = lines.map(line => {
// Check if line contains string literals (parts enclosed in " or ')
if (/["']/.test(line)) {
// Convert + for string concatenation to & (also adjust spacing)
// But exclude addition between numbers
return line.replace(/\s*\+\s*/g, (match, offset, string) => {
// Check characters before and after +
const before = string.substring(0, offset).trim();
const after = string.substring(offset + match.length).trim();
// Get word immediately before +
const beforeMatch = before.match(/([a-zA-Z_][a-zA-Z0-9_]*|\d+(?:\.\d+)?)$/);
const beforeToken = beforeMatch ? beforeMatch[1] : '';
// Get word immediately after +
const afterMatch = after.match(/^([a-zA-Z_][a-zA-Z0-9_]*|\d+(?:\.\d+)?)/);
const afterToken = afterMatch ? afterMatch[1] : '';
// Check if it's addition between numeric literals
const beforeIsNumber = /^\d+(\.\d+)?$/.test(beforeToken);
const afterIsNumber = /^\d+(\.\d+)?$/.test(afterToken);
// Check if it's addition between variable and number (like y + 1)
const beforeIsVar = /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(beforeToken);
const afterIsVar = /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(afterToken);
// Keep + for addition between numbers, variables and numbers, or variables
if ((beforeIsNumber && afterIsNumber) ||
(beforeIsVar && afterIsNumber) ||
(beforeIsNumber && afterIsVar) ||
(beforeIsVar && afterIsVar)) {
return ' + ';
}
// Convert to & for everything else (string concatenation)
return ' & ';
});
}
return line;
}).join('\n');
// Remove extra spaces (fix duplicate spaces around operators)
result = result.replace(/\s{2,}/g, ' ');
// Restore comments (convert Python # comments to IGCSE // comments)
commentMatches.forEach((comment, index) => {
let convertedComment = comment;
// Convert Python # comments to IGCSE // comments
if (comment.startsWith('#')) {
convertedComment = comment.replace(/^#/, '//');
}
result = result.replace(`__COMMENT_${index}__`, convertedComment);
});
return result;
}
/**
* Create emit result
*/
createEmitResult() {
const endTime = Date.now();
const emitTime = endTime - this.startTime;
const code = this.context.output.join(this.options.lineEnding);
const linesGenerated = this.context.output.length;
const charactersGenerated = code.length;
return {
code,
errors: [...this.context.errors],
warnings: [...this.context.warnings],
stats: {
linesGenerated,
lineCount: linesGenerated, // Alias for testing
charactersGenerated,
characterCount: charactersGenerated, // Alias for testing
nodesProcessed: 0, // Set during implementation
emitTime,
processingTime: emitTime, // Alias for testing
maxNestingDepth: this.context.indent.level,
maxLineLength: Math.max(...this.context.output.map(line => line.length))
}
};
}
/**
* Record emit start time
*/
startEmitting() {
this.startTime = Date.now();
}
/**
* Output debug information
*/
debug(_message) {
// Debug logging disabled
}
/**
* Reset context
*/
resetContext() {
this.context = this.createInitialContext();
}
/**
* Wrap long lines
*/
wrapLongLine(text, maxLength) {
const limit = maxLength || this.options.maxLineLength || 80;
if (text.length <= limit) {
return [text];
}
const words = text.split(' ');
const lines = [];
let currentLine = '';
for (const word of words) {
if (currentLine.length + word.length + 1 <= limit) {
currentLine += (currentLine ? ' ' : '') + word;
}
else {
if (currentLine) {
lines.push(currentLine);
}
currentLine = word;
}
}
if (currentLine) {
lines.push(currentLine);
}
return lines;
}
/**
* Update settings
*/
updateOptions(options) {
this.options = { ...this.options, ...options };
}
/**
* Update formatter configuration
*/
updateFormatterConfig(config) {
this.context.formatter = { ...this.context.formatter, ...config };
}
}
exports.BaseEmitter = BaseEmitter;
//# sourceMappingURL=base-emitter.js.map