UNPKG

java2ib

Version:

TypeScript library that converts Java code into IB Computer Science pseudocode format

383 lines 16.3 kB
"use strict"; /** * Main converter class for Java to IB Pseudocode conversion * * This class provides the primary interface for converting Java source code * into IB Computer Science pseudocode format according to the official IB specification. * * @example * ```typescript * import { JavaToIBConverter } from 'java-to-ib-pseudocode'; * * const converter = new JavaToIBConverter(); * const result = converter.convert('int x = 5;'); * console.log(result.pseudocode); // Output: X = 5 * ``` * * @example * ```typescript * // Convert a method with options * const options = { preserveComments: true, indentSize: 2 }; * const result = converter.convert(` * public int add(int a, int b) { * return a + b; * } * `, options); * ``` */ Object.defineProperty(exports, "__esModule", { value: true }); exports.JavaToIBConverter = void 0; const types_1 = require("./types"); const lexer_1 = require("./lexer"); const parser_1 = require("./parser"); const transformer_1 = require("./transformer"); const code_generator_1 = require("./code-generator"); const ib_rules_engine_1 = require("./ib-rules-engine"); /** * The main converter class that handles Java to IB pseudocode conversion. * * This class orchestrates the entire conversion process through a multi-stage pipeline: * 1. Lexical analysis (tokenization) * 2. Parsing (AST generation) * 3. AST transformation (applying IB rules) * 4. Code generation (producing formatted pseudocode) * * The converter is designed to be stateless and thread-safe, allowing multiple * conversions to be performed concurrently. */ class JavaToIBConverter { /** * Creates a new JavaToIBConverter instance. * * The constructor initializes the internal components needed for conversion: * - IBRulesEngine for applying IB pseudocode formatting rules * - ASTTransformer for converting Java AST nodes to pseudocode format */ constructor() { // Performance optimization: Cache frequently used generators this.generatorCache = new Map(); // Performance optimization: Reuse lexer instances to avoid object creation overhead this.lexerPool = []; this.maxPoolSize = 5; this.ibRules = new ib_rules_engine_1.IBRulesEngine(); this.transformer = new transformer_1.ASTTransformer(); } /** * Convert Java code to IB pseudocode format. * * This method performs the complete conversion process from Java source code * to IB Computer Science pseudocode format. The conversion follows the official * IB specification for pseudocode syntax and formatting. * * @param javaCode - The Java source code to convert. Must be valid Java syntax. * @param options - Optional conversion settings to customize the output format. * @returns A ConversionResult object containing the converted pseudocode, * success status, any errors or warnings, and conversion metadata. * * @example * ```typescript * // Basic conversion * const result = converter.convert('int x = 10;'); * if (result.success) { * console.log(result.pseudocode); // "X = 10" * } * ``` * * @example * ```typescript * // Conversion with options * const options = { preserveComments: true, indentSize: 2 }; * const result = converter.convert(` * // Calculate area * public int calculateArea(int width, int height) { * return width * height; * } * `, options); * ``` * * @example * ```typescript * // Error handling * const result = converter.convert('invalid java code'); * if (!result.success) { * result.errors.forEach(error => { * console.error(`${error.type}: ${error.message} at line ${error.location.line}`); * }); * } * ``` */ convert(javaCode, options) { const startTime = Date.now(); // Use Date.now() for compatibility // Handle null/undefined input if (javaCode === null || javaCode === undefined) { return this.createErrorResult('Input code is null or undefined', 1, Date.now() - startTime, [{ type: types_1.ErrorType.CONVERSION_ERROR, message: 'Input code is null or undefined', location: { line: 1, column: 1 }, severity: types_1.ErrorSeverity.ERROR, }]); } // Performance optimization: Pre-calculate line count more efficiently const originalLines = this.countLines(javaCode); const errors = []; const warnings = []; try { // Validate input if (!javaCode || javaCode.trim().length === 0) { return this.createErrorResult('Empty or invalid Java code provided', originalLines, Date.now() - startTime, [{ type: types_1.ErrorType.CONVERSION_ERROR, message: 'Input code is empty or contains only whitespace', location: { line: 1, column: 1 }, severity: types_1.ErrorSeverity.ERROR, }]); } // Performance optimization: Early exit for very large files if (javaCode.length > 1000000) { // 1MB limit return this.createErrorResult('Input file too large (>1MB). Please split into smaller files.', originalLines, Date.now() - startTime, [{ type: types_1.ErrorType.CONVERSION_ERROR, message: 'Input file exceeds maximum size limit of 1MB', location: { line: 1, column: 1 }, severity: types_1.ErrorSeverity.ERROR, }]); } // Step 1: Lexical Analysis - Use pooled lexer for better performance const lexingStart = Date.now(); const lexer = this.getLexer(javaCode); const tokenizeResult = lexer.tokenize(); this.returnLexer(lexer); const lexingTime = Date.now() - lexingStart; if (tokenizeResult.errors.length > 0) { // Enhance lexer errors with better messages const enhancedLexErrors = tokenizeResult.errors.map(error => ({ ...error, message: this.enhanceErrorMessage(error.message, error.location) })); errors.push(...enhancedLexErrors); } if (tokenizeResult.tokens.length === 0) { return this.createErrorResult('No tokens found in input code', originalLines, Date.now() - startTime, errors.length > 0 ? errors : [{ type: types_1.ErrorType.LEXICAL_ERROR, message: 'No valid tokens could be extracted from the input code', location: { line: 1, column: 1 }, severity: types_1.ErrorSeverity.ERROR, }]); } // Step 2: Parsing const parsingStart = Date.now(); const parser = new parser_1.Parser(tokenizeResult.tokens); const parseResult = parser.parse(); const parsingTime = Date.now() - parsingStart; if (parseResult.errors.length > 0) { // Enhance parser errors with better messages const enhancedParseErrors = parseResult.errors.map(error => ({ ...error, message: this.enhanceErrorMessage(error.message, error.location) })); errors.push(...enhancedParseErrors); } if (!parseResult.ast) { return this.createErrorResult('Failed to parse Java code', originalLines, Date.now() - startTime, errors.length > 0 ? errors : [{ type: types_1.ErrorType.SYNTAX_ERROR, message: 'Could not build a valid syntax tree from the input code', location: { line: 1, column: 1 }, severity: types_1.ErrorSeverity.ERROR, }]); } // Step 3: AST Transformation const transformationStart = Date.now(); const transformResult = this.transformer.transform(parseResult.ast); const transformationTime = Date.now() - transformationStart; if (transformResult.errors.length > 0) { // Enhance transformation errors with better messages const enhancedTransformErrors = transformResult.errors.map(error => ({ ...error, message: this.enhanceErrorMessage(error.message, error.location) })); errors.push(...enhancedTransformErrors); } // Collect warnings from transformation if (transformResult.warnings && transformResult.warnings.length > 0) { warnings.push(...transformResult.warnings); } // If transformation failed completely, return error if (transformResult.pseudocodeAST.length === 0 && errors.length === 0) { return this.createErrorResult('Failed to transform Java code to pseudocode', originalLines, Date.now() - startTime, [{ type: types_1.ErrorType.CONVERSION_ERROR, message: 'No pseudocode could be generated from the input', location: { line: 1, column: 1 }, severity: types_1.ErrorSeverity.ERROR, }]); } // Step 4: Code Generation - Use cached generator for better performance const codeGenerationStart = Date.now(); const generator = this.getGenerator(options); const pseudocode = generator.generate(transformResult.pseudocodeAST); const codeGenerationTime = Date.now() - codeGenerationStart; const processingTime = Date.now() - startTime; const convertedLines = this.countLines(pseudocode); // Determine success based on whether we have critical errors const criticalErrors = errors.filter(e => e.severity === types_1.ErrorSeverity.ERROR); const success = criticalErrors.length === 0 && pseudocode.trim().length > 0; // Convert warnings to proper format const formattedWarnings = warnings.map(w => ({ type: w.type, message: w.message, location: w.location, severity: w.severity })); return { pseudocode, success, errors: criticalErrors, warnings: formattedWarnings, metadata: { originalLines, convertedLines, processingTime, performanceBreakdown: { lexingTime, parsingTime, transformationTime, codeGenerationTime, }, statistics: { tokenCount: tokenizeResult.tokens.length, astNodeCount: this.countASTNodes(parseResult.ast), inputSize: javaCode.length, outputSize: pseudocode.length, }, }, }; } catch (error) { const processingTime = Date.now() - startTime; if (error instanceof Error) { errors.push({ type: types_1.ErrorType.CONVERSION_ERROR, message: `Unexpected error during conversion: ${error.message}`, location: { line: 1, column: 1 }, severity: types_1.ErrorSeverity.ERROR, }); } else { errors.push({ type: types_1.ErrorType.CONVERSION_ERROR, message: 'An unknown error occurred during conversion', location: { line: 1, column: 1 }, severity: types_1.ErrorSeverity.ERROR, }); } return this.createErrorResult('Conversion failed due to unexpected error', originalLines, processingTime, errors); } } createErrorResult(message, originalLines, processingTime, errors) { // Enhance error messages with more context const enhancedErrors = errors.map(error => ({ ...error, message: this.enhanceErrorMessage(error.message, error.location) })); return { pseudocode: `// Conversion failed: ${message}`, success: false, errors: enhancedErrors, warnings: [], metadata: { originalLines, convertedLines: 1, processingTime, }, }; } enhanceErrorMessage(message, location) { const lineInfo = `at line ${location.line}, column ${location.column}`; // Add contextual information based on error patterns if (message.includes('Expected')) { return `${message} ${lineInfo}. Check for missing punctuation or incorrect syntax.`; } else if (message.includes('Unexpected token')) { return `${message} ${lineInfo}. Verify that all operators and identifiers are valid Java syntax.`; } else if (message.includes('Undefined') || message.includes('not found')) { return `${message} ${lineInfo}. Make sure all variables and methods are properly declared before use.`; } else if (message.includes('type')) { return `${message} ${lineInfo}. Check that variable types match their usage.`; } else { return `${message} ${lineInfo}`; } } /** * Performance optimization: Efficiently count lines without creating array */ countLines(text) { let count = 1; for (let i = 0; i < text.length; i++) { if (text[i] === '\n') { count++; } } return count; } /** * Performance optimization: Get a lexer from the pool or create new one */ getLexer(input) { if (this.lexerPool.length > 0) { const lexer = this.lexerPool.pop(); // Reset lexer with new input lexer.input = input; lexer.position = 0; lexer.line = 1; lexer.column = 1; lexer.errors = []; return lexer; } return new lexer_1.Lexer(input); } /** * Performance optimization: Return lexer to pool for reuse */ returnLexer(lexer) { if (this.lexerPool.length < this.maxPoolSize) { this.lexerPool.push(lexer); } } /** * Performance optimization: Get cached generator or create new one */ getGenerator(options) { const cacheKey = JSON.stringify(options || {}); if (this.generatorCache.has(cacheKey)) { return this.generatorCache.get(cacheKey); } const generator = (0, code_generator_1.createGeneratorFromConversionOptions)(options); // Limit cache size to prevent memory leaks if (this.generatorCache.size < 10) { this.generatorCache.set(cacheKey, generator); } return generator; } /** * Count the total number of AST nodes recursively */ countASTNodes(ast) { if (!ast) return 0; let count = 1; // Count this node if (ast.children && Array.isArray(ast.children)) { for (const child of ast.children) { count += this.countASTNodes(child); } } // Handle specific node types with additional child properties if (ast.declarations && Array.isArray(ast.declarations)) { for (const decl of ast.declarations) { count += this.countASTNodes(decl); } } return count; } } exports.JavaToIBConverter = JavaToIBConverter; //# sourceMappingURL=converter.js.map