UNPKG

python2ib

Version:

Convert Python code to IB Pseudocode format

403 lines 15 kB
/** * Base visitor class for AST traversal using the Visitor pattern */ /** Abstract base visitor class */ export class BaseVisitor { config; constructor(config) { this.config = config; } /** Main visit method - dispatches to specific visit methods */ visit(node) { if (!node) { throw new Error('Cannot visit null or undefined node'); } const methodName = `visit${node.type}`; const method = this[methodName]; if (typeof method === 'function') { return method.call(this, node); } // Fallback for unsupported node types return this.visitUnsupported(node); } /** Visit multiple nodes and return array of IRs */ visitMany(nodes) { return nodes.map(node => this.visit(node)).filter(ir => ir !== null); } /** Handle unsupported node types */ visitUnsupported(node) { const message = `Unsupported Python construct: ${node.type}`; console.warn(message); return { kind: 'comment', text: `// ${message}`, children: [], meta: { lineNumber: node.lineno } }; } /** Extract string representation from expression node */ extractExpression(node, depth = 0) { if (!node) { console.warn('extractExpression called with null/undefined node'); return ''; } if (!node.type) { console.warn('extractExpression called with node missing type:', node); return ''; } if (depth > 100) { console.error(`extractExpression: Maximum recursion depth (100) exceeded for node type: ${node.type}`); return `/* MAX_DEPTH_EXCEEDED: ${node.type} */`; } switch (node.type) { case 'Constant': return this.formatConstant(node.value); case 'Name': return this.mapVariableName(node.id); case 'BinOp': return this.extractBinaryOperation(node, depth + 1); case 'UnaryOp': return this.extractUnaryOperation(node, depth + 1); case 'Compare': return this.extractComparison(node, depth + 1); case 'BoolOp': return this.extractBooleanOperation(node, depth + 1); case 'Call': return this.extractCall(node, depth + 1); case 'Subscript': return this.extractSubscript(node, depth + 1); case 'JoinedStr': return this.extractJoinedString(node, depth + 1); case 'Tuple': // For tuple expressions, join elements with commas return node.elts.map((elt) => this.extractExpression(elt, depth + 1)).join(', '); case 'Attribute': // Handle attribute access (e.g., obj.method) return this.extractAttribute(node, depth); default: return `/* ${node.type} */`; } } /** Format constant values */ formatConstant(value) { if (value === null) return 'NULL'; if (typeof value === 'boolean') return value ? 'true' : 'false'; if (typeof value === 'string') return `"${value}"`; if (typeof value === 'number' && value < 0) return String(value); // Keep negative numbers without extra space return String(value); } /** Extract binary operation expression */ extractBinaryOperation(node, depth = 0) { const left = this.extractExpression(node.left, depth); const right = this.extractExpression(node.right, depth); const operator = this.convertBinaryOperator(node.op); return `${left} ${operator} ${right}`; } /** Extract unary operation expression */ extractUnaryOperation(node, depth = 0) { const operand = this.extractExpression(node.operand, depth); const operator = this.convertUnaryOperator(node.op); // For negative numbers, don't add space between operator and operand if (node.op === 'USub') { return `${operator}${operand}`; } return `${operator} ${operand}`; } /** Extract comparison expression */ extractComparison(node, depth = 0) { if (!node.left) { console.warn('extractComparison: node.left is undefined', node); return ''; } if (!node.ops || !Array.isArray(node.ops)) { console.warn('extractComparison: node.ops is invalid', node); return this.extractExpression(node.left, depth); } if (!node.comparators || !Array.isArray(node.comparators)) { console.warn('extractComparison: node.comparators is invalid', node); return this.extractExpression(node.left, depth); } let result = this.extractExpression(node.left, depth); for (let i = 0; i < node.ops.length; i++) { const operator = this.convertComparisonOperator(node.ops[i]); const comparator = this.extractExpression(node.comparators[i], depth); // Special case: convert "variable ≠ 0" to "NOT variable = 0" if (operator === '≠' && comparator === '0') { result = `NOT ${result} = ${comparator}`; } else { result += ` ${operator} ${comparator}`; } } return result; } /** Extract boolean operation expression */ extractBooleanOperation(node, depth = 0) { const operator = node.op === 'And' ? 'AND' : 'OR'; const values = node.values.map((value) => this.extractExpression(value, depth)); return values.join(` ${operator} `); } /** Extract function call expression */ extractCall(node, depth = 0) { const funcName = this.extractExpression(node.func, depth); const args = node.args.map((arg) => this.extractExpression(arg, depth)); return `${funcName}(${args.join(', ')})`; } /** Extract subscript expression (array/list access) */ extractSubscript(node, depth = 0) { let value = this.extractExpression(node.value, depth); const slice = this.extractExpression(node.slice, depth); // Apply variable name mapping if the value is a simple variable name if (node.value.type === 'Name') { value = this.mapVariableName(node.value.id); } return `${value}[${slice}]`; } /** Extract attribute access expression (e.g., obj.method) */ extractAttribute(node, depth = 0) { const value = this.extractExpression(node.value, depth); const attr = node.attr; // Convert Python method names to IB Pseudocode equivalents const methodMapping = { 'has_next': 'hasNext', 'get_next': 'getNext', 'reset_next': 'resetNext', 'is_empty': 'isEmpty' }; const mappedAttr = methodMapping[attr] || attr; return `${value}.${mappedAttr}`; } /** Extract joined string (f-string) expression */ extractJoinedString(node, _depth = 0) { if (!node.values || !Array.isArray(node.values)) { return '""'; } // For f-strings like f"Hello {name}", we need to parse the string and extract variables const firstValue = node.values[0]; if (firstValue && firstValue.type === 'Constant') { const stringValue = firstValue.value; // Simple f-string parsing: extract variables from {variable} patterns const parts = []; const regex = /\{([^}]+)\}/g; let lastIndex = 0; let match; while ((match = regex.exec(stringValue)) !== null) { // Add text before the variable if (match.index > lastIndex) { const textPart = stringValue.substring(lastIndex, match.index); if (textPart) { parts.push(`"${textPart}"`); } } // Add the variable (convert to uppercase) const variableName = match[1].trim(); parts.push(this.mapVariableName(variableName)); lastIndex = regex.lastIndex; } // Add remaining text after the last variable if (lastIndex < stringValue.length) { const textPart = stringValue.substring(lastIndex); if (textPart) { parts.push(`"${textPart}"`); } } // Join parts with " + " return parts.length > 0 ? parts.join(' + ') : '""'; } return '""'; } /** Convert Python binary operators to IB Pseudocode */ convertBinaryOperator(op) { const operators = { 'Add': '+', 'Sub': '-', 'Mult': '*', 'Div': '/', 'FloorDiv': 'div', 'Mod': 'mod', 'Pow': '^' }; return operators[op] || op; } /** Convert Python unary operators to IB Pseudocode */ convertUnaryOperator(op) { const operators = { 'Not': 'NOT', 'USub': '-', 'UAdd': '+' }; return operators[op] || op; } /** Convert Python comparison operators to IB Pseudocode */ convertComparisonOperator(op) { if (!op) { console.warn('convertComparisonOperator: op is undefined/null'); return '?'; } const operators = { 'Eq': '=', 'NotEq': '≠', 'Lt': '<', 'LtE': '<=', 'Gt': '>', 'GtE': '>=', 'Is': '=', 'IsNot': '≠', 'In': 'IN', 'NotIn': 'NOT IN' }; return operators[op] || op; } /** Check if a function has return statements */ hasReturnStatement(body) { for (const stmt of body) { if (stmt.type === 'Return') { return true; } // Check nested structures if (stmt.type === 'If' && stmt.body) { if (this.hasReturnStatement(stmt.body)) return true; if (stmt.orelse && this.hasReturnStatement(stmt.orelse)) return true; } if (stmt.type === 'While' && stmt.body) { if (this.hasReturnStatement(stmt.body)) return true; } if (stmt.type === 'For' && stmt.body) { if (this.hasReturnStatement(stmt.body)) return true; } } return false; } /** Apply variable name mapping if configured */ mapVariableName(name) { // First check for custom mapping const mapped = this.config.variableMapping[name]; if (mapped) { return mapped; } // Special mappings for common variable names const specialMappings = { 'list_arr': 'LIST', 'arr': 'ARR', 'array': 'ARR' }; if (specialMappings[name]) { return specialMappings[name]; } // Convert to uppercase for IB Pseudocode convention return name.toUpperCase(); } /** Apply function name mapping if configured */ mapFunctionName(name) { // Check for explicit mapping first if (this.config.functionMapping[name]) { return this.config.functionMapping[name]; } // Convert snake_case to camelCase return this.snakeToCamelCase(name); } /** Convert snake_case to camelCase */ snakeToCamelCase(str) { return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); } /** Create error IR node */ createError(message, lineNumber) { return { kind: 'comment', text: `// ERROR: ${message}`, children: [], meta: { lineNumber } }; } /** Create warning IR node */ createWarning(message, lineNumber) { return { kind: 'comment', text: `// WARNING: ${message}`, children: [], meta: { lineNumber } }; } } /** Utility functions for visitor implementations */ export const VisitorUtils = { /** Check if assignment is simple (single target) */ isSimpleAssignment(node) { return node.type === 'Assign' && node.targets.length === 1 && (node.targets[0].type === 'Name' || node.targets[0].type === 'Subscript' || node.targets[0].type === 'Tuple'); }, /** Check if node is a function call */ isFunctionCall(node) { return node.type === 'Call'; }, /** Check if node is a print statement */ isPrintCall(node) { return node.type === 'Call' && node.func.type === 'Name' && node.func.id === 'print'; }, /** Check if node is an input statement */ isInputCall(node) { return node.type === 'Call' && node.func.type === 'Name' && node.func.id === 'input'; }, /** Check if node is an input call wrapped in type conversion (e.g., int(input())) */ isWrappedInputCall(node) { if (node.type === 'Call' && node.func.type === 'Name' && ['int', 'float', 'str'].includes(node.func.id) && node.args.length === 1) { return this.isInputCall(node.args[0]); } return false; }, /** Extract input call from wrapped input (e.g., extract input() from int(input())) */ extractInputFromWrapped(node) { if (this.isWrappedInputCall(node)) { return node.args[0]; } return null; }, /** Check if node is a range call */ isRangeCall(node) { return node.type === 'Call' && node.func.type === 'Name' && node.func.id === 'range'; }, /** Extract function name from call node */ getFunctionName(node) { if (node.type === 'Call') { if (node.func.type === 'Name') { return node.func.id; } else if (node.func.type === 'Attribute') { // For method calls like obj.method(), return just the method name return node.func.attr; } } return ''; }, /** Extract variable name from assignment target */ getAssignmentTarget(node) { if (node.type === 'Name') { return node.id; } if (node.type === 'Subscript') { // For subscript, we need to return the full expression like "names[0]" // This will be handled by extractExpression in the visitor return ''; } return ''; } }; //# sourceMappingURL=base-visitor.js.map