UNPKG

python2igcse

Version:

Convert Python code to IGCSE Pseudocode format

1,232 lines 44.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PythonASTVisitor = void 0; const ir_1 = require("../types/ir"); const base_parser_1 = require("./base-parser"); const statement_visitor_1 = require("./statement-visitor"); const definition_visitor_1 = require("./definition-visitor"); /** * Visitor for converting Python AST to IR */ class PythonASTVisitor extends base_parser_1.BaseParser { constructor() { super(); this.statementVisitor = new statement_visitor_1.StatementVisitor(); this.definitionVisitor = new definition_visitor_1.DefinitionVisitor(); // Share context with visitors this.statementVisitor.setContext(this.context); this.definitionVisitor.setContext(this.context); } /** * Main parse function */ parse(source) { this.startParsing(); this.resetContext(); try { // 実際の実装では、PythonのASTパーサーを使用 // Provide simplified implementation here const ast = this.parseToAST(source); // 2パス処理: まずすべてのクラス定義を事前登録 this.preRegisterAllClasses(ast.body); // Re-share latest context with visitors after class definition registration this.statementVisitor.setContext(this.context); this.definitionVisitor.setContext(this.context); const ir = this.visitNode(ast); // Return child elements if IR is not an array if (ir.kind === 'compound' && ir.children) { return this.createParseResult(ir.children); } return this.createParseResult([ir]); } catch (error) { this.addError(`Parse failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'syntax_error'); // Return empty IR on error const emptyIR = (0, ir_1.createIR)('statement', '', []); return this.createParseResult([emptyIR]); } } /** * Simple AST parser (uses external library in actual implementation) */ parseToAST(source) { // In actual implementation, use python-ast or pyodide // Simplified implementation here const lines = source.split('\n'); const nodes = []; const processedLines = new Set(); let i = 0; while (i < lines.length) { if (processedLines.has(i)) { i++; continue; } const line = lines[i]; const trimmed = line.trim(); if (trimmed.startsWith('#')) { // Process comment lines const commentNode = { type: 'Comment', value: trimmed.substring(1).trim(), lineno: i + 1, }; nodes.push(commentNode); processedLines.add(i); i++; } else if (trimmed) { const result = this.parseStatement(lines, i); if (result.node) { nodes.push(result.node); // Mark processed lines for (let j = i; j < result.nextIndex; j++) { processedLines.add(j); } } i = result.nextIndex; } else { processedLines.add(i); i++; } } return { type: 'Module', body: nodes, }; } /** * Parse statements and their child blocks */ parseStatement(lines, startIndex) { const line = lines[startIndex]; const trimmed = line.trim(); const indent = line.length - line.trimStart().length; // Create basic statement node const node = this.parseLineToASTNode(trimmed, startIndex + 1); if (!node) { return { node: null, nextIndex: startIndex + 1 }; } // For statements ending with colon (block statements), parse child blocks if (trimmed.endsWith(':')) { const bodyNodes = []; let i = startIndex + 1; // Parse child block from next line while (i < lines.length) { const childLine = lines[i]; const childTrimmed = childLine.trim(); const childIndent = childLine.length - childLine.trimStart().length; // Skip empty lines and comment lines if (!childTrimmed || childTrimmed.startsWith('#')) { i++; continue; } // For IF statements, handle ELIF and ELSE statements specially if (node.type === 'If' && childIndent === indent) { if (childTrimmed.startsWith('elif ')) { // Process ELIF statement as new IF statement and add to orelse const elifResult = this.parseStatement(lines, i); if (elifResult.node) { node.orelse = [elifResult.node]; } i = elifResult.nextIndex; break; } else if (childTrimmed.startsWith('else:')) { // Process ELSE clause const elseNodes = []; i++; // else行をスキップ // Parse child blocks of ELSE clause while (i < lines.length) { const elseChildLine = lines[i]; const elseChildTrimmed = elseChildLine.trim(); const elseChildIndent = elseChildLine.length - elseChildLine.trimStart().length; // Skip empty lines and comment lines if (!elseChildTrimmed || elseChildTrimmed.startsWith('#')) { i++; continue; } // End ELSE clause if indent is same or less if (elseChildIndent <= indent) { break; } // Parse child statements of ELSE clause const elseChildResult = this.parseStatement(lines, i); if (elseChildResult.node) { elseNodes.push(elseChildResult.node); } i = elseChildResult.nextIndex; } // Set ELSE clause to node node.orelse = elseNodes; break; } } // End block if indent is same or less if (childIndent <= indent) { break; } // Parse child statements const childResult = this.parseStatement(lines, i); if (childResult.node) { bodyNodes.push(childResult.node); } i = childResult.nextIndex; } // Set child blocks to node if (node.type === 'If' || node.type === 'For' || node.type === 'While' || node.type === 'FunctionDef' || node.type === 'ClassDef') { node.body = bodyNodes; } return { node, nextIndex: i }; } return { node, nextIndex: startIndex + 1 }; } /** * Convert single line to AST node */ parseLineToASTNode(line, lineNumber) { const trimmed = line.trim(); // Detect IF statements if (trimmed.startsWith('if ')) { return this.parseIfStatement(trimmed, lineNumber); } // Detect ELIF statements (process as IF statements) if (trimmed.startsWith('elif ')) { // Replace 'elif' with 'if' and process const ifLine = 'if ' + trimmed.substring(5); return this.parseIfStatement(ifLine, lineNumber); } // Detect FOR statements if (trimmed.startsWith('for ')) { return this.parseForStatement(trimmed, lineNumber); } // Detect WHILE statements if (trimmed.startsWith('while ')) { return this.parseWhileStatement(trimmed, lineNumber); } // Detect class definitions if (trimmed.startsWith('class ')) { return this.parseClassDef(trimmed, lineNumber); } // Detect function definitions if (trimmed.startsWith('def ')) { return this.parseFunctionDef(trimmed, lineNumber); } // Detect type-annotated assignment statements (e.g., items: list[str] = []) if (trimmed.includes(': ') && trimmed.includes(' = ')) { const colonIndex = trimmed.indexOf(': '); const equalIndex = trimmed.indexOf(' = '); // Type-annotated assignment if colon comes before equals if (colonIndex < equalIndex) { const varName = trimmed.substring(0, colonIndex).trim(); const typeAnnotation = trimmed.substring(colonIndex + 2, equalIndex).trim(); const value = trimmed.substring(equalIndex + 3).trim(); if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(varName)) { return { type: 'AnnAssign', target: { type: 'Name', id: varName, ctx: 'Store', }, annotation: { type: 'Subscript', value: { type: 'Name', id: typeAnnotation.includes('[') ? typeAnnotation.substring(0, typeAnnotation.indexOf('[')) : typeAnnotation, }, slice: typeAnnotation.includes('[') ? { type: 'Name', id: typeAnnotation.substring(typeAnnotation.indexOf('[') + 1, typeAnnotation.indexOf(']')), } : null, }, value: value ? this.parseExpression(value) : null, lineno: lineNumber, }; } } } // Detect assignment statements if (trimmed.includes(' = ')) { // Check before and after = to determine if it's an assignment statement const equalIndex = trimmed.indexOf(' = '); const beforeEqual = trimmed.substring(0, equalIndex).trim(); const afterEqual = trimmed.substring(equalIndex + 3).trim(); // Detect array element assignment (e.g., data[1] = 100) const arrayAssignMatch = beforeEqual.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\[(.+)\]$/); if (arrayAssignMatch && afterEqual.length > 0) { const [, arrayName, indexExpr] = arrayAssignMatch; return { type: 'Assign', targets: [ { type: 'Subscript', value: { type: 'Name', id: arrayName, ctx: 'Load', }, slice: { type: 'Index', value: { type: 'Constant', value: parseInt(indexExpr), kind: null, }, }, ctx: 'Store', }, ], value: this.parseExpression(afterEqual), lineno: lineNumber, }; } // Detect attribute assignment (e.g., self.name = value) const attrAssignMatch = beforeEqual.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)$/); if (attrAssignMatch && afterEqual.length > 0) { const [, objName, attrName] = attrAssignMatch; return { type: 'Assign', targets: [ { type: 'Attribute', value: { type: 'Name', id: objName, ctx: 'Load', }, attr: attrName, ctx: 'Store', }, ], value: this.parseExpression(afterEqual), lineno: lineNumber, }; } // Assignment statement if left side is simple variable name and right side exists if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(beforeEqual) && afterEqual.length > 0) { return this.parseAssignStatement(trimmed, lineNumber); } } // Detect print statements if (trimmed.startsWith('print(')) { return this.parsePrintStatement(trimmed, lineNumber); } // Detect function definitions if (trimmed.startsWith('def ')) { return this.parseFunctionDef(trimmed, lineNumber); } // Detect class definitions if (trimmed.startsWith('class ')) { return this.parseClassDef(trimmed, lineNumber); } // Detect return statements if (trimmed.startsWith('return')) { return this.parseReturnStatement(trimmed, lineNumber); } // Detect break statements if (trimmed === 'break') { return { type: 'Break', lineno: lineNumber, }; } // Detect continue statements if (trimmed === 'continue') { return { type: 'Continue', lineno: lineNumber, }; } // Detect augmented assignment statements (+=, -=, *=, /=, %=) if (/^[a-zA-Z_][a-zA-Z0-9_]*\s*[+\-*/%]=/.test(trimmed)) { return this.parseAugAssignStatement(trimmed, lineNumber); } // Detect function calls const callMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\((.*)\)$/); if (callMatch) { const funcName = callMatch[1]; const argsStr = callMatch[2]; const args = this.parseArguments(argsStr); return { type: 'Expr', lineno: lineNumber, value: { type: 'Call', func: { type: 'Name', id: funcName }, args: args, }, }; } // Process as other expression statements return { type: 'Expr', lineno: lineNumber, value: { type: 'Call', func: { type: 'Name', id: 'unknown' }, args: [], raw: trimmed, }, }; } /** * Parse augmented assignment statements */ parseAugAssignStatement(line, lineNumber) { const match = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*([+\-*/%])=\s*(.+)$/); if (!match) { // Process as normal expression if no match return { type: 'Expr', lineno: lineNumber, value: { type: 'Call', func: { type: 'Name', id: 'unknown' }, args: [], raw: line, }, }; } const [, target, op, value] = match; return { type: 'AugAssign', lineno: lineNumber, target: { type: 'Name', id: target, }, op: { type: this.getAugAssignOpType(op), }, value: { type: 'Num', n: isNaN(Number(value)) ? value : Number(value), raw: value, }, }; } /** * Get type of augmented assignment operator */ getAugAssignOpType(op) { switch (op) { case '+': return 'Add'; case '-': return 'Sub'; case '*': return 'Mult'; case '/': return 'Div'; case '%': return 'Mod'; default: return 'Add'; } } parseIfStatement(line, lineNumber) { // Parse "if condition:" format const match = line.match(/^if\s+(.+):\s*$/); const condition = match ? match[1] : line.substring(3, line.length - 1); return { type: 'If', lineno: lineNumber, test: { type: 'Compare', left: { type: 'Name', id: 'condition' }, ops: ['>'], comparators: [{ type: 'Num', n: 0 }], raw: condition, }, body: [], orelse: [], }; } parseForStatement(line, lineNumber) { // Parse "for var in iterable:" format const match = line.match(/^for\s+(\w+)\s+in\s+(.+):\s*$/); const target = match ? match[1] : 'i'; const iter = match ? match[2] : 'range(1)'; // Parse range function arguments let args = []; if (iter.startsWith('range(') && iter.endsWith(')')) { const argsStr = iter.slice(6, -1); // "range(" と ")" を除去 if (argsStr.trim()) { const argParts = argsStr.split(',').map((arg) => arg.trim()); args = argParts.map((arg) => ({ type: 'Num', n: isNaN(Number(arg)) ? arg : Number(arg), raw: arg, })); } } // For direct iteration over arrays or lists if (!iter.startsWith('range(')) { return { type: 'For', lineno: lineNumber, target: { type: 'Name', id: target }, iter: { type: 'Name', id: iter, }, body: [], orelse: [], }; } return { type: 'For', lineno: lineNumber, target: { type: 'Name', id: target }, iter: { type: 'Call', func: { type: 'Name', id: 'range' }, args: args, raw: iter, }, body: [], orelse: [], }; } parseWhileStatement(line, lineNumber) { // Parse "while condition:" format const match = line.match(/^while\s+(.+):\s*$/); const condition = match ? match[1] : line.substring(6, line.length - 1); return { type: 'While', lineno: lineNumber, test: { type: 'Compare', raw: condition, }, body: [], orelse: [], }; } parseAssignStatement(line, lineNumber) { // Parse "var = value" format const parts = line.split(' = '); const target = parts[0].trim(); let value = parts.slice(1).join(' = ').trim(); // Extract inline comment part (after #) let inlineComment = ''; const commentIndex = value.indexOf('#'); if (commentIndex !== -1) { inlineComment = value.substring(commentIndex + 1).trim(); value = value.substring(0, commentIndex).trim(); } // Detect array literals if (value.startsWith('[') && value.endsWith(']')) { const elementsStr = value.slice(1, -1).trim(); const elements = elementsStr ? elementsStr.split(',').map((elem) => { const trimmed = elem.trim(); // Determine if it's a number if (/^-?\d+(\.\d+)?$/.test(trimmed)) { return { type: 'Num', n: parseFloat(trimmed), }; } else if (trimmed.startsWith('"') && trimmed.endsWith('"')) { return { type: 'Str', s: trimmed.slice(1, -1), }; } else if (trimmed.startsWith("'") && trimmed.endsWith("'")) { return { type: 'Str', s: trimmed.slice(1, -1), }; } else { return { type: 'Name', id: trimmed, }; } }) : []; return { type: 'Assign', lineno: lineNumber, targets: [{ type: 'Name', id: target }], value: { type: 'List', elts: elements, }, }; } // Detect array access (e.g., my_array[0]) const arrayAccessMatch = value.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\[(\d+)\]$/); if (arrayAccessMatch) { const [, arrayName, indexStr] = arrayAccessMatch; return { type: 'Assign', lineno: lineNumber, targets: [{ type: 'Name', id: target }], value: { type: 'Subscript', value: { type: 'Name', id: arrayName }, slice: { type: 'Num', n: parseInt(indexStr) }, }, }; } // Detect expressions containing comparison operators const valueNode = this.parseExpression(value); const assignNode = { type: 'Assign', lineno: lineNumber, targets: [ { type: 'Name', id: target, }, ], value: valueNode, }; if (inlineComment) { assignNode.inlineComment = inlineComment; } return assignNode; } /** * Parse expressions and convert to AST nodes */ parseExpression(expr) { const trimmed = expr.trim(); // Detect empty lists if (trimmed === '[]') { return { type: 'List', elts: [], ctx: 'Load', }; } // Detect list literals if (trimmed.startsWith('[') && trimmed.endsWith(']')) { const content = trimmed.slice(1, -1).trim(); if (!content) { return { type: 'List', elts: [], ctx: 'Load', }; } // Parse list elements const elements = content.split(',').map((elem) => { const elemTrimmed = elem.trim(); // Detect numbers if (/^\d+$/.test(elemTrimmed)) { return { type: 'Constant', value: parseInt(elemTrimmed), kind: null, }; } // String detection if ((elemTrimmed.startsWith('"') && elemTrimmed.endsWith('"')) || (elemTrimmed.startsWith("'") && elemTrimmed.endsWith("'"))) { return { type: 'Constant', value: elemTrimmed.slice(1, -1), kind: null, }; } // Detect variable names if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(elemTrimmed)) { return { type: 'Name', id: elemTrimmed, ctx: 'Load', }; } // Other expressions return { type: 'Name', id: elemTrimmed, ctx: 'Load', }; }); return { type: 'List', elts: elements, ctx: 'Load', }; } // Detect NOT operator (highest priority) if (trimmed.startsWith('not ')) { const operand = trimmed.substring(4).trim(); return { type: 'UnaryOp', op: { type: 'Not' }, operand: this.parseExpression(operand), }; } // Process expressions enclosed in parentheses if (trimmed.startsWith('(') && trimmed.endsWith(')')) { const innerExpr = trimmed.slice(1, -1); const innerNode = this.parseExpression(innerExpr); // Explicitly mark as parenthesized expression return { type: 'Expr', value: innerNode, parenthesized: true, }; } // Detect comparison operators const compareOps = ['==', '!=', '<=', '>=', '<', '>']; for (const op of compareOps) { const index = trimmed.indexOf(op); if (index !== -1) { const left = trimmed.substring(0, index).trim(); const right = trimmed.substring(index + op.length).trim(); return { type: 'Compare', left: this.parseSimpleExpression(left), ops: [this.getCompareOpNode(op)], comparators: [this.parseSimpleExpression(right)], }; } } // Detect logical operators if (trimmed.includes(' and ')) { const parts = trimmed.split(' and '); return { type: 'BoolOp', op: { type: 'And' }, values: parts.map((part) => this.parseExpression(part.trim())), }; } if (trimmed.includes(' or ')) { const parts = trimmed.split(' or '); return { type: 'BoolOp', op: { type: 'Or' }, values: parts.map((part) => this.parseExpression(part.trim())), }; } // Detect method calls (e.g., text.upper()) const methodCallMatch = trimmed.match(/^(.+)\.([a-zA-Z_][a-zA-Z0-9_]*)\((.*)\)$/); if (methodCallMatch) { const [, objectExpr, methodName, argsStr] = methodCallMatch; const args = this.parseArguments(argsStr); return { type: 'Call', func: { type: 'Attribute', value: this.parseSimpleExpression(objectExpr), attr: methodName, ctx: 'Load', }, args: args, }; } // Detect function calls const callMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\((.*)\)$/); if (callMatch) { const [, funcName, argsStr] = callMatch; const args = this.parseArguments(argsStr); return { type: 'Call', func: { type: 'Name', id: funcName }, args: args, }; } // Detect arithmetic operators (detect longer operators first) const arithOps = ['//', '+', '-', '*', '/', '%']; for (const op of arithOps) { const index = trimmed.indexOf(op); if (index !== -1) { const left = trimmed.substring(0, index).trim(); const right = trimmed.substring(index + op.length).trim(); return { type: 'BinOp', left: this.parseSimpleExpression(left), op: this.getArithOpNode(op), right: this.parseSimpleExpression(right), }; } } // Process as simple expression return this.parseSimpleExpression(trimmed); } /** * Parse simple expressions (variables, literals) */ parseSimpleExpression(expr) { const trimmed = expr.trim(); // Detect attribute access (e.g., path[0].x) const attrMatch = trimmed.match(/^(.+)\.([a-zA-Z_][a-zA-Z0-9_]*)$/); if (attrMatch) { const [, valueExpr, attr] = attrMatch; return { type: 'Attribute', value: this.parseSimpleExpression(valueExpr), attr: attr, ctx: 'Load', }; } // Detect array indexing (e.g., path[0]) const subscriptMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\[(.+)\]$/); if (subscriptMatch) { const [, arrayName, indexExpr] = subscriptMatch; return { type: 'Subscript', value: { type: 'Name', id: arrayName, ctx: 'Load', }, slice: this.parseSimpleExpression(indexExpr), ctx: 'Load', }; } // String literal if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) { return { type: 'Str', s: trimmed.slice(1, -1), }; } // Check for unclosed string literals if ((trimmed.startsWith('"') && !trimmed.endsWith('"')) || (trimmed.startsWith("'") && !trimmed.endsWith("'"))) { this.context.errors.push({ message: `Unterminated string literal: ${trimmed}`, line: 0, column: 0, type: 'syntax_error', severity: 'error', }); return { type: 'Str', s: trimmed, }; } // Numeric literals if (/^-?\d+(\.\d+)?$/.test(trimmed)) { return { type: 'Num', n: parseFloat(trimmed), }; } // Boolean values if (trimmed === 'True' || trimmed === 'False') { return { type: 'NameConstant', value: trimmed === 'True', }; } // Variable names return { type: 'Name', id: trimmed, }; } /** * Get AST node for comparison operators */ getCompareOpNode(op) { switch (op) { case '==': return { type: 'Eq' }; case '!=': return { type: 'NotEq' }; case '<': return { type: 'Lt' }; case '<=': return { type: 'LtE' }; case '>': return { type: 'Gt' }; case '>=': return { type: 'GtE' }; default: return { type: 'Eq' }; } } /** * Get AST node for arithmetic operators */ getArithOpNode(op) { switch (op) { case '+': return { type: 'Add' }; case '-': return { type: 'Sub' }; case '*': return { type: 'Mult' }; case '/': return { type: 'Div' }; case '//': return { type: 'FloorDiv' }; case '%': return { type: 'Mod' }; default: return { type: 'Add' }; } } parsePrintStatement(line, lineNumber) { // Parse "print(...)" format const match = line.match(/^print\((.*)\)\s*$/); const args = match ? match[1] : ''; return { type: 'Expr', lineno: lineNumber, value: { type: 'Call', func: { type: 'Name', id: 'print' }, args: [ { type: 'Str', s: args, raw: args, }, ], }, }; } /** * Convert AST nodes to IR */ visitNode(node) { if (!node) { return (0, ir_1.createIR)('statement', '', []); } // Set visitNode method to visitors this.statementVisitor.visitNode = this.visitNode.bind(this); this.definitionVisitor.visitNode = this.visitNode.bind(this); switch (node.type) { case 'Module': return this.visitModule(node); // Delegate statement processing case 'Assign': return this.statementVisitor.visitAssign(node); case 'AugAssign': return this.statementVisitor.visitAugAssign(node); case 'AnnAssign': return this.statementVisitor.visitAnnAssign(node); case 'If': return this.statementVisitor.visitIf(node); case 'For': return this.statementVisitor.visitFor(node); case 'While': return this.statementVisitor.visitWhile(node); case 'Return': return this.statementVisitor.visitReturn(node); case 'Call': return this.statementVisitor.visitCall(node); case 'Expr': return this.statementVisitor.visitExpr(node); case 'Comment': return this.statementVisitor.visitComment(node); case 'Pass': return this.statementVisitor.visitPass(node); case 'Break': return this.statementVisitor.visitBreak(node); case 'Continue': return this.statementVisitor.visitContinue(node); case 'Import': case 'ImportFrom': return this.statementVisitor.visitImport(node); case 'Try': return this.statementVisitor.visitTry(node); case 'Raise': return this.statementVisitor.visitRaise(node); case 'With': return this.statementVisitor.visitWith(node); case 'Assert': return this.statementVisitor.visitAssert(node); case 'Global': case 'Nonlocal': return this.statementVisitor.visitGlobal(node); case 'Delete': return this.statementVisitor.visitDelete(node); // Delegate definition processing case 'FunctionDef': return this.definitionVisitor.visitFunctionDef(node); case 'ClassDef': // Class definitions are already registered by preRegisterAllClasses return this.definitionVisitor.visitClassDef(node); default: // Output as comment for unsupported node types return this.createIRNode('comment', `// Unsupported node type: ${node.type}`); } } visitModule(node) { const children = []; // Check if node.body exists and is an array if (node.body && Array.isArray(node.body)) { for (const child of node.body) { const childIR = this.visitNode(child); children.push(childIR); } } return this.createIRNode('compound', '', children); } /** * Parse function definitions */ parseFunctionDef(line, lineNumber) { // Parse "def function_name(params):" format const match = line.match(/^def\s+(\w+)\s*\(([^)]*)\)\s*(?:->\s*([^:]+))?:\s*$/); if (!match) { // Process as basic function definition if no match return { type: 'FunctionDef', name: 'unknown_function', args: { args: [] }, returns: null, body: [], lineno: lineNumber, }; } const [, funcName, paramsStr, returnType] = match; // Parse parameters const params = this.parseParameters(paramsStr); return { type: 'FunctionDef', name: funcName, args: { args: params }, returns: returnType ? { type: 'Name', id: returnType.trim() } : null, body: [], lineno: lineNumber, }; } /** * Parse argument lists */ parseArguments(argsStr) { if (!argsStr.trim()) { return []; } // Split arguments considering parentheses balance const args = this.splitArgumentsRespectingParentheses(argsStr); return args.map((arg) => { const trimmed = arg.trim(); // For function calls (containing parentheses) if (trimmed.includes('(') && trimmed.includes(')')) { return this.parseExpression(trimmed); } // For string literals if (trimmed.startsWith('"') && trimmed.endsWith('"')) { return { type: 'Str', s: trimmed.slice(1, -1), }; } // For numbers if (/^\d+$/.test(trimmed)) { return { type: 'Num', n: parseInt(trimmed), }; } // For variable names return { type: 'Name', id: trimmed, }; }); } /** * Split arguments considering parentheses balance */ splitArgumentsRespectingParentheses(argsStr) { const args = []; let currentArg = ''; let parenDepth = 0; let inString = false; let stringChar = ''; for (let i = 0; i < argsStr.length; i++) { const char = argsStr[i]; if (!inString) { if (char === '"' || char === "'") { inString = true; stringChar = char; } else if (char === '(') { parenDepth++; } else if (char === ')') { parenDepth--; } else if (char === ',' && parenDepth === 0) { args.push(currentArg.trim()); currentArg = ''; continue; } } else { if (char === stringChar && (i === 0 || argsStr[i - 1] !== '\\')) { inString = false; stringChar = ''; } } currentArg += char; } if (currentArg.trim()) { args.push(currentArg.trim()); } return args; } /** * Parse parameter lists */ parseParameters(paramsStr) { if (!paramsStr.trim()) { return []; } return paramsStr.split(',').map((param) => { const trimmed = param.trim(); // With type annotation: "param: type" const typeMatch = trimmed.match(/^(\w+)\s*:\s*(.+)$/); if (typeMatch) { const [, paramName, paramType] = typeMatch; return { arg: paramName, annotation: { type: 'Name', id: paramType.trim() }, }; } // Without type annotation return { arg: trimmed, annotation: null, }; }); } /** * Parse return statements */ parseReturnStatement(line, lineNumber) { const match = line.match(/^return\s*(.*)$/); const value = match ? match[1].trim() : ''; return { type: 'Return', value: value ? { type: 'Name', id: value, raw: value, } : null, lineno: lineNumber, }; } /** * Parse class definitions */ parseClassDef(line, lineNumber) { const match = line.match(/^class\s+(\w+)(?:\s*\(([^)]*)\))?\s*:/); if (!match) { this.addError(`Invalid class definition: ${line}`, 'syntax_error'); return { type: 'Unknown', lineno: lineNumber, }; } const [, className, baseClasses] = match; const bases = baseClasses ? baseClasses.split(',').map((base) => ({ type: 'Name', id: base.trim(), })) : []; return { type: 'ClassDef', name: className, bases, body: [], lineno: lineNumber, }; } /** * Register class definitions in context */ registerClassDefinition(node) { const className = node.name; // Extract attributes from __init__ method const constructor = node.body.find((item) => item.type === 'FunctionDef' && item.name === '__init__'); const attributes = []; if (constructor) { // Get attribute names from constructor parameters if (constructor.args && constructor.args.args) { constructor.args.args.forEach((arg) => { if (arg.arg !== 'self') { attributes.push(arg.arg); } }); } } // Extract inheritance information const bases = []; if (node.bases && node.bases.length > 0) { node.bases.forEach((base) => { if (base.type === 'Name') { bases.push(base.id); } }); } // Register in context if (!this.context.classDefinitions) { this.context.classDefinitions = {}; } this.context.classDefinitions[className] = { attributes: attributes, bases: bases, }; } /** * Pre-register all class definitions (first pass of 2-pass processing) */ preRegisterAllClasses(nodes) { if (!nodes || !Array.isArray(nodes)) { return; } for (const node of nodes) { if (node && node.type === 'ClassDef') { this.registerClassDefinition(node); } } } /** * Helper for creating IR nodes */ createIRNode(kind, text, children = [], meta) { return (0, ir_1.createIR)(kind, text, children, meta); } /** * Create parse results */ createParseResult(ir) { const parseTime = Date.now() - this.context.startTime; return { ir, errors: this.getErrors(), warnings: this.getWarnings(), stats: { parseTime, linesProcessed: 0, nodesGenerated: ir.reduce((sum, node) => sum + (0, ir_1.countIRNodes)(node), 0), functionsFound: 0, classesFound: 0, variablesFound: 0, }, success: this.getErrors().length === 0, parseTime, }; } } exports.PythonASTVisitor = PythonASTVisitor; //# sourceMappingURL=visitor.js.map