UNPKG

python2igcse

Version:

Convert Python code to IGCSE Pseudocode format

942 lines 39 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StatementVisitor = void 0; const ir_1 = require("../types/ir"); const expression_visitor_1 = require("./expression-visitor"); const base_parser_1 = require("./base-parser"); /** * Visitor class responsible for processing statements */ class StatementVisitor extends base_parser_1.BaseParser { /** * Execute parsing (not used in StatementVisitor) */ parse(_source) { throw new Error('StatementVisitor.parse() should not be called directly'); } constructor() { super(); this.expressionVisitor = new expression_visitor_1.ExpressionVisitor(); } /** * Set context */ setContext(context) { this.context = context; } /** * Process assignment statements */ visitAssign(node) { // Detect array initialization first if (this.expressionVisitor.isArrayInitialization(node.value)) { return this.handleArrayInitialization(node); } // Handle input() function assignments specially (including nested function calls) if (this.containsInputCall(node.value)) { return this.handleInputAssignment(node); } // Detect class instantiation if (node.value.type === 'Call') { if (node.value.func.type === 'Name') { const funcName = node.value.func.id; const isClass = this.context.isClass(funcName); if (isClass) { return this.handleClassInstantiation(node); } } } const targetNode = node.targets[0]; // Handle array element assignment (data[1] = 100) if (targetNode.type === 'Subscript') { return this.handleElementAssign(targetNode, node.value); } // Handle attribute assignment (obj.field = value) if (targetNode.type === 'Attribute') { return this.handleAttributeAssign(targetNode, node.value); } // For class instantiation, process directly without going through expression-visitor if (node.value.type === 'Call') { const func = this.expressionVisitor.visitExpression(node.value.func); const args = node.value.args.map((arg) => this.expressionVisitor.visitExpression(arg)); // Process as class instantiation if not a built-in function const builtinResult = this.expressionVisitor.convertBuiltinFunction(func, args); if (!builtinResult && this.isClassInstantiation(node.value)) { console.log('DEBUG: Processing as class instantiation'); return this.handleClassInstantiation(node); } } const target = this.expressionVisitor.visitExpression(targetNode); const value = this.expressionVisitor.visitExpression(node.value); let text = `${target} ← ${value}`; // Add inline comment if present if (node.inlineComment) { text += ` // ${node.inlineComment}`; } // Infer and register variable type const dataType = this.expressionVisitor.inferTypeFromValue(node.value); if (targetNode.type === 'Name') { this.registerVariable(targetNode.id, dataType, node.lineno); } return this.createIRNode('assign', text); } /** * Process input() function assignments */ handleInputAssignment(node) { const targetNode = node.targets[0]; const target = this.expressionVisitor.visitExpression(targetNode); // Find input() function (considering nested function calls) const inputCall = this.findInputCall(node.value); if (!inputCall) { // Process as normal assignment if input() not found const value = this.expressionVisitor.visitExpression(node.value); const text = `${target} ← ${value}`; return this.createIRNode('assign', text); } const args = inputCall.args.map((arg) => this.expressionVisitor.visitExpression(arg)); // Create two IR nodes (OUTPUT and INPUT) when prompt is present if (args.length > 0) { const outputText = `OUTPUT ${args[0]}`; const inputText = `INPUT ${target}`; // Create compound IR node (containing OUTPUT and INPUT statements) const outputIR = this.createIRNode('output', outputText); const inputIR = this.createIRNode('input', inputText); // Infer and register variable type if (targetNode.type === 'Name') { // Register as INTEGER type for int(input(...)) if (node.value.type === 'Call' && node.value.func.type === 'Name' && node.value.func.id === 'int') { this.registerVariable(targetNode.id, 'INTEGER', node.lineno); } else { this.registerVariable(targetNode.id, 'STRING', node.lineno); } } // Return as compound node return this.createIRNode('compound', '', [outputIR, inputIR]); } else { // Only INPUT statement when no prompt const inputText = `INPUT ${target}`; // Infer and register variable type if (targetNode.type === 'Name') { // Register as INTEGER type for int(input(...)) if (node.value.type === 'Call' && node.value.func.type === 'Name' && node.value.func.id === 'int') { this.registerVariable(targetNode.id, 'INTEGER', node.lineno); } else { this.registerVariable(targetNode.id, 'STRING', node.lineno); } } return this.createIRNode('input', inputText); } } /** * Find input() from nested function calls */ findInputCall(node) { if (node.type === 'Call' && node.func.type === 'Name' && node.func.id === 'input') { return node; } // Recursively search nested function calls if (node.type === 'Call' && node.args) { for (const arg of node.args) { const result = this.findInputCall(arg); if (result) return result; } } return null; } /** * Check if node contains input() call */ containsInputCall(node) { return this.findInputCall(node) !== null; } /** * Process augmented assignment statements */ visitAugAssign(node) { const target = this.expressionVisitor.visitExpression(node.target); const value = this.expressionVisitor.visitExpression(node.value); const op = this.convertOperator(node.op); const text = `${target} ← ${target} ${op} ${value}`; return this.createIRNode('assign', text); } /** * Process annotated assignment statements (items: list[str] = []) */ visitAnnAssign(node) { const targetName = node.target.id; // Detect array type from type annotation if (this.isListTypeAnnotation(node.annotation)) { const elementType = this.extractListElementType(node.annotation); // Generate array declaration with default size for empty list if (node.value && node.value.type === 'List' && node.value.elts.length === 0) { const text = `DECLARE ${targetName} : ARRAY[1:100] OF ${elementType}`; this.registerVariable(targetName, 'ARRAY', node.lineno); return this.createIRNode('array', text); } // Process as normal array initialization when value is present if (node.value) { const fakeAssignNode = { type: 'Assign', targets: [node.target], value: node.value, lineno: node.lineno || 0, }; return this.handleArrayInitialization(fakeAssignNode); } } // Normal annotated assignment const target = this.expressionVisitor.visitExpression(node.target); const value = node.value ? this.expressionVisitor.visitExpression(node.value) : ''; if (value) { const text = `${target} ← ${value}`; return this.createIRNode('assign', text); } else { // Declaration only when no value const dataType = this.convertAnnotationToIGCSEType(node.annotation); const text = `DECLARE ${target} : ${dataType}`; this.registerVariable(targetName, dataType, node.lineno); return this.createIRNode('statement', text); } } /** * Process IF statements */ visitIf(node) { const condition = this.expressionVisitor.visitExpression(node.test); const ifText = `IF ${condition} THEN`; this.enterScope('if', 'block'); this.increaseIndent(); const bodyChildren = node.body.map((child) => this.visitNode ? this.visitNode(child) : this.createIRNode('comment', '// Unprocessed node')); this.decreaseIndent(); this.exitScope(); let children = bodyChildren; // Process ELSE clause if (node.orelse && node.orelse.length > 0) { const firstElse = node.orelse[0]; // Process as ELSE IF when first element is IF statement if (firstElse.type === 'If') { const condition = this.expressionVisitor.visitExpression(firstElse.test); const elseIfText = `ELSE IF ${condition} THEN`; const elseIfIR = this.createIRNode('elseif', elseIfText); this.enterScope('elseif', 'block'); this.increaseIndent(); const elseIfBodyChildren = firstElse.body.map((child) => this.visitNode ? this.visitNode(child) : this.createIRNode('comment', '// Unprocessed node')); this.decreaseIndent(); this.exitScope(); children = [...bodyChildren, elseIfIR, ...elseIfBodyChildren]; // Recursively process orelse clause of ELSE IF statement if (firstElse.orelse && firstElse.orelse.length > 0) { const nestedElseResult = this.visitIf({ ...firstElse, body: [], // bodyは空にして、orelseのみ処理 test: null, // testも不要 }); // Add child elements of nested ELSE/ELSE IF statements if (nestedElseResult.children) { children = [...children, ...nestedElseResult.children]; } } } else { // Normal ELSE clause const elseIR = this.createIRNode('else', 'ELSE'); this.enterScope('else', 'block'); this.increaseIndent(); const elseChildren = node.orelse.map((child) => this.visitNode ? this.visitNode(child) : this.createIRNode('comment', '// Unprocessed node')); this.decreaseIndent(); this.exitScope(); children = [...bodyChildren, elseIR, ...elseChildren]; } } return this.createIRNode('if', ifText, children); } /** * Process FOR statements */ visitFor(node) { const target = this.expressionVisitor.visitExpression(node.target); // Process for statement using range() function if (node.iter.type === 'Call' && node.iter.func.id === 'range') { return this.handleRangeFor(node, target); } // Direct iteration over arrays or lists if (node.iter.type === 'Name') { const arrayName = node.iter.id; const indexVar = 'i'; // Get array size (from context) let arraySize = '3'; // Default size // Get array size from context if (this.context && this.context.arrayInfo && this.context.arrayInfo[arrayName]) { arraySize = this.context.arrayInfo[arrayName].size.toString(); } // Format: FOR i ← 1 TO size const forText = `FOR ${indexVar} ← 1 TO ${arraySize}`; this.enterScope('for', 'block'); this.increaseIndent(); // Process body (directly reference array elements without using target variable) const bodyChildren = node.body.map((child) => { // Convert print(target) to OUTPUT array[i] // Convert print(target) to OUTPUT array[i] if (child.type === 'Expr' && child.value.type === 'Call' && child.value.func && child.value.func.type === 'Name' && child.value.func.id === 'print' && child.value.args.length === 1 && ((child.value.args[0].type === 'Name' && child.value.args[0].id === target) || (child.value.args[0].type === 'Str' && child.value.args[0].s === target))) { return this.createIRNode('output', `OUTPUT ${arrayName}[${indexVar}]`); } return this.visitNode ? this.visitNode(child) : this.createIRNode('comment', '// Unprocessed node'); }); this.decreaseIndent(); this.exitScope(); const nextIR = this.createIRNode('statement', `NEXT ${indexVar}`); bodyChildren.push(nextIR); return this.createIRNode('for', forText, bodyChildren); } // Normal for statement (other iterable objects) const iterable = this.expressionVisitor.visitExpression(node.iter); const forText = `FOR ${target} IN ${iterable}`; this.enterScope('for', 'block'); this.increaseIndent(); const bodyChildren = node.body.map((child) => this.visitNode ? this.visitNode(child) : this.createIRNode('comment', '// Unprocessed node')); this.decreaseIndent(); this.exitScope(); const nextIR = this.createIRNode('statement', `NEXT ${target}`); bodyChildren.push(nextIR); return this.createIRNode('for', forText, bodyChildren); } /** * Process WHILE statements */ visitWhile(node) { // Check if this is a REPEAT-UNTIL pattern: // while True: // ... // if condition: // break if (this.isRepeatUntilPattern(node)) { return this.createRepeatUntilIR(node); } // Regular while loop const condition = this.expressionVisitor.visitExpression(node.test); const whileText = `WHILE ${condition} DO`; this.enterScope('while', 'block'); this.increaseIndent(); const bodyChildren = node.body.map((child) => this.visitNode ? this.visitNode(child) : this.createIRNode('comment', '// Unprocessed node')); this.decreaseIndent(); this.exitScope(); const endwhileIR = this.createIRNode('endwhile', 'ENDWHILE'); bodyChildren.push(endwhileIR); return this.createIRNode('while', whileText, bodyChildren); } /** * Check if a while loop matches the REPEAT-UNTIL pattern * Pattern: while True with a conditional break at the end */ isRepeatUntilPattern(node) { // Check if condition is 'True' // The AST structure might vary, so we check both raw value and type if (node.test.type === 'Compare' && node.test.raw !== 'True' && node.test.type === 'NameConstant' && node.test.value !== true) { return false; } // Check if the last statement is an if with break const lastStatement = node.body[node.body.length - 1]; if (!lastStatement || lastStatement.type !== 'If') { return false; } // Check if the if body contains a break statement const ifBody = lastStatement.body; if (!ifBody || ifBody.length === 0) { return false; } // Check if the if body contains ONLY a break statement // This is important for the REPEAT-UNTIL pattern if (ifBody.length !== 1 || ifBody[0].type !== 'Break') { return false; } return true; } /** * Create a REPEAT-UNTIL IR from a while True loop with conditional break */ createRepeatUntilIR(node) { const repeatText = 'REPEAT'; this.enterScope('repeat', 'block'); this.increaseIndent(); // Process all statements except the last one (which is the if-break) const bodyStatements = node.body.slice(0, -1); // Special handling for input function in the body const bodyChildren = []; for (const child of bodyStatements) { // Check if this is an assignment with input() function if (child.type === 'Assign' && child.value && child.value.type === 'Call' && child.value.func && child.value.func.id === 'input') { // Get the target variable name const target = this.expressionVisitor.visitExpression(child.targets[0]); // Get the input prompt if any const args = child.value.args || []; if (args.length > 0) { // Create OUTPUT statement for the prompt const prompt = this.expressionVisitor.visitExpression(args[0]); const outputIR = this.createIRNode('output', `OUTPUT ${prompt}`); bodyChildren.push(outputIR); } // Create INPUT statement const inputIR = this.createIRNode('input', `INPUT ${target}`); bodyChildren.push(inputIR); } else { // Normal statement processing const ir = this.visitNode ? this.visitNode(child) : this.createIRNode('comment', '// Unprocessed node'); bodyChildren.push(ir); } } // Get the condition from the if statement const lastStatement = node.body[node.body.length - 1]; // The condition in the if statement is what we need for the UNTIL // In Python: if condition: break // In IGCSE: UNTIL condition const condition = this.expressionVisitor.visitExpression(lastStatement.test); this.decreaseIndent(); this.exitScope(); // Create UNTIL statement const untilIR = this.createIRNode('until', `UNTIL ${condition}`); bodyChildren.push(untilIR); return this.createIRNode('repeat', repeatText, bodyChildren); } /** * Process function call statements */ visitCall(node) { const func = this.expressionVisitor.visitExpression(node.func); const args = node.args.map((arg) => this.expressionVisitor.visitExpression(arg)); // Convert built-in functions if (func === 'print') { // Special handling for f-string with single argument if (args.length === 1 && node.args[0].type === 'JoinedStr') { const fstringResult = this.expressionVisitor.visitExpression(node.args[0]); const text = `OUTPUT ${fstringResult}`; return this.createIRNode('output', text); } const text = `OUTPUT ${args.join(', ')}`; return this.createIRNode('output', text); } if (func === 'input') { const prompt = args.length > 0 ? args[0] : ''; const text = prompt ? `INPUT ${prompt}` : 'INPUT'; return this.createIRNode('input', text); } // Normal function call (add CALL keyword) const capitalizedFunc = this.capitalizeFirstLetter(func); const text = `CALL ${capitalizedFunc}(${args.join(', ')})`; return this.createIRNode('statement', text); } /** * Process RETURN statements */ visitReturn(node) { if (node.value) { const value = this.expressionVisitor.visitExpression(node.value); return this.createIRNode('return', `RETURN ${value}`); } return this.createIRNode('return', 'RETURN'); } /** * Process expression statements */ visitExpr(node) { // Handle function calls specially if (node.value && node.value.type === 'Call') { return this.visitCall(node.value); } const expr = this.expressionVisitor.visitExpression(node.value); return this.createIRNode('statement', expr); } /** * Process comments */ visitComment(node) { return this.createIRNode('comment', `// ${node.value}`); } /** * Process PASS statements */ visitPass(_node) { return this.createIRNode('comment', '// pass'); } /** * Process BREAK statements */ visitBreak(_node) { return this.createIRNode('break', 'BREAK'); } /** * Process CONTINUE statements */ visitContinue(_node) { return this.createIRNode('statement', 'CONTINUE'); } /** * Process IMPORT statements */ visitImport(_node) { // Output as comment since import is not typically used in IGCSE return this.createIRNode('comment', `// import statement`); } /** * Process TRY statements */ visitTry(_node) { // Output as comment since exception handling is not typically used in IGCSE return this.createIRNode('comment', `// try-except statement`); } /** * Process RAISE statements */ visitRaise(_node) { return this.createIRNode('comment', `// raise statement`); } /** * Process WITH statements */ visitWith(_node) { return this.createIRNode('comment', `// with statement`); } /** * Process ASSERT statements */ visitAssert(_node) { return this.createIRNode('comment', `// assert statement`); } /** * Process GLOBAL statements */ visitGlobal(_node) { return this.createIRNode('comment', `// global statement`); } /** * Process DELETE statements */ visitDelete(_node) { return this.createIRNode('comment', `// delete statement`); } // Helper methods handleRangeFor(node, target) { const args = node.iter.args; let startValue = '0'; let endValue = '0'; let stepValue = '1'; if (args.length === 1) { // range(n) endValue = this.expressionVisitor.visitExpression(args[0]); } else if (args.length === 2) { // range(start, end) startValue = this.expressionVisitor.visitExpression(args[0]); endValue = this.expressionVisitor.visitExpression(args[1]); } else if (args.length === 3) { // range(start, end, step) startValue = this.expressionVisitor.visitExpression(args[0]); endValue = this.expressionVisitor.visitExpression(args[1]); stepValue = this.expressionVisitor.visitExpression(args[2]); } // Optimize for numeric constants // Subtract 1 from end value only when step is 1 if (stepValue === '1') { // Special handling for LENGTH() function and other function calls if (endValue.startsWith('LENGTH(') || endValue.includes('(')) { endValue = `${endValue} - 1`; } else if (this.expressionVisitor.isNumericConstant(args[args.length - 1])) { const endNum = this.expressionVisitor.getNumericValue(args[args.length - 1]); endValue = (endNum - 1).toString(); } else { endValue = `${endValue} - 1`; } } else { // Calculate the last reachable value when step is not 1 if (args.length === 3 && this.expressionVisitor.isNumericConstant(args[0]) && this.expressionVisitor.isNumericConstant(args[1]) && this.expressionVisitor.isNumericConstant(args[2])) { const start = this.expressionVisitor.getNumericValue(args[0]); const end = this.expressionVisitor.getNumericValue(args[1]); const step = this.expressionVisitor.getNumericValue(args[2]); // Calculate the last reachable value let lastValue = start; if (step > 0) { while (lastValue + step < end) { lastValue += step; } } else { while (lastValue + step > end) { lastValue += step; } } endValue = lastValue.toString(); } } const forText = stepValue === '1' ? `FOR ${target} ← ${startValue} TO ${endValue}` : `FOR ${target} ← ${startValue} TO ${endValue} STEP ${stepValue}`; this.enterScope('for', 'block'); this.increaseIndent(); const bodyChildren = node.body.map((child) => this.visitNode ? this.visitNode(child) : this.createIRNode('comment', '// Unprocessed node')); this.decreaseIndent(); this.exitScope(); const nextIR = this.createIRNode('statement', `NEXT ${target}`); bodyChildren.push(nextIR); return this.createIRNode('for', forText, bodyChildren); } handleArrayInitialization(node) { const target = this.expressionVisitor.visitExpression(node.targets[0]); const elements = node.value.elts; const size = elements.length; // Reconstruct string-split elements to detect object calls const reconstructedElements = this.reconstructObjectCalls(elements); // Determine if it's an array of objects const isObjectArray = reconstructedElements.length > 0 && this.isObjectCall(reconstructedElements[0]); if (isObjectArray) { // For object arrays const firstCall = reconstructedElements[0]; const className = this.extractClassName(firstCall); const recordTypeName = `${className}Record`; const children = []; // Array declaration (using actual element count) const actualSize = reconstructedElements.length; const declText = `DECLARE ${target} : ARRAY[1:${actualSize}] OF ${recordTypeName}`; children.push(this.createIRNode('statement', declText)); // Record array size information in context if (this.context && this.context.arrayInfo) { this.context.arrayInfo[target] = { size: actualSize, elementType: recordTypeName, currentIndex: 0, }; } // Process each element reconstructedElements.forEach((elementStr, index) => { const args = this.extractArguments(elementStr); // Need to get actual field names from class definition, but // Currently simplified to handle Point class as x, y if (className === 'Point' && args.length >= 2) { children.push(this.createIRNode('assign', `${target}[${index + 1}].x ← ${args[0]}`)); children.push(this.createIRNode('assign', `${target}[${index + 1}].y ← ${args[1]}`)); } else { // Generic handling for other classes args.forEach((arg, argIndex) => { const fieldName = `field${argIndex + 1}`; // Temporary field name children.push(this.createIRNode('assign', `${target}[${index + 1}].${fieldName} ← ${arg}`)); }); } }); return this.createIRNode('statement', '', children); } else { // For normal arrays const elementType = elements.length > 0 ? this.expressionVisitor.inferTypeFromValue(elements[0]) : 'STRING'; // Array declaration const declText = `DECLARE ${target} : ARRAY[1:${size}] OF ${elementType}`; const declIR = this.createIRNode('array', declText); // Record array size information in context if (this.context && this.context.arrayInfo) { this.context.arrayInfo[target] = { size: size, elementType: elementType, currentIndex: 0, }; } // Element assignment const assignments = []; elements.forEach((element, index) => { const value = this.expressionVisitor.visitExpression(element); const assignText = `${target}[${index + 1}] ← ${value}`; assignments.push(this.createIRNode('assign', assignText)); }); return this.createIRNode('statement', '', [declIR, ...assignments]); } } reconstructObjectCalls(elements) { const result = []; let i = 0; while (i < elements.length) { const element = elements[i]; if (element.type === 'Name' && element.id) { const elementStr = element.id; // Detect class name pattern (starts with uppercase, ends with parentheses) if (/^[A-Z]\w*\(/.test(elementStr)) { // Combine with next element to reconstruct object call let objectCall = elementStr; i++; // Join elements until closing parenthesis is found while (i < elements.length && !objectCall.includes(')')) { const nextElement = elements[i]; if (nextElement.type === 'Name' && nextElement.id) { objectCall += ', ' + nextElement.id; } i++; } result.push(objectCall); } else { result.push(elementStr); i++; } } else { result.push(this.expressionVisitor.visitExpression(element)); i++; } } return result; } isObjectCall(elementStr) { // Detect ClassName(arguments) pattern return /^[A-Z]\w*\(.+\)$/.test(elementStr); } extractClassName(objectCall) { const match = objectCall.match(/^([A-Z]\w*)\(/); return match ? match[1] : 'Unknown'; } extractArguments(objectCall) { const match = objectCall.match(/\((.+)\)$/); if (match) { return match[1].split(',').map((arg) => arg.trim()); } return []; } isClassInstantiation(node) { // Simple judgment: treat as class if function name starts with uppercase if (node.func.type === 'Name') { const isClass = /^[A-Z]/.test(node.func.id); console.log(`DEBUG: Checking if ${node.func.id} is class: ${isClass}`); return isClass; } console.log('DEBUG: Function type is not Name:', node.func.type); return false; } handleClassInstantiation(node) { const className = this.expressionVisitor.visitExpression(node.value.func); const target = this.expressionVisitor.visitExpression(node.targets[0]); const args = node.value.args.map((arg) => this.expressionVisitor.visitExpression(arg)); // When treating as record type, generate variable declaration and field assignments const recordTypeName = `${className}Record`; const children = []; // Variable declaration const declareText = `DECLARE ${target} : ${recordTypeName}`; console.log('DEBUG: Adding declaration:', declareText); children.push(this.createIRNode('statement', declareText)); // Get attribute names from class definition const classAttributes = this.getClassAttributes(className); console.log('DEBUG: classAttributes:', classAttributes); // Field assignment (based on argument order) for (let i = 0; i < Math.min(args.length, classAttributes.length); i++) { const attrName = classAttributes[i]; const assignText = `${target}.${attrName} ← ${args[i]}`; console.log('DEBUG: Adding assignment:', assignText); children.push(this.createIRNode('assign', assignText)); } console.log('DEBUG: Returning block with', children.length, 'children'); return this.createIRNode('block', '', children); } convertOperator(op) { switch (op.type) { case 'Add': return '+'; case 'Sub': return '-'; case 'Mult': return '*'; case 'Div': return '/'; case 'FloorDiv': return 'DIV'; case 'Mod': return 'MOD'; case 'Pow': return '^'; default: return '+'; } } /** * Process array element assignment (data[1] = 100) */ handleElementAssign(targetNode, valueNode) { const arrayName = this.expressionVisitor.visitExpression(targetNode.value); const value = this.expressionVisitor.visitExpression(valueNode); // Process index directly to avoid comments let adjustedIndex; let sliceNode = targetNode.slice; // Get contents if wrapped in Index node if (sliceNode.type === 'Index') { sliceNode = sliceNode.value; } if (sliceNode.type === 'Constant' && typeof sliceNode.value === 'number') { // For numeric literals, add 1 adjustedIndex = String(sliceNode.value + 1); } else if (sliceNode.type === 'Num') { // Old Python AST numeric node adjustedIndex = String(sliceNode.n + 1); } else if (sliceNode.type === 'Name') { // For variables, add +1 adjustedIndex = `${sliceNode.id} + 1`; } else { // For other cases, process as expression const index = this.expressionVisitor.visitExpression(targetNode.slice); adjustedIndex = this.convertIndexToOneBased(index); } const text = `${arrayName}[${adjustedIndex}] ← ${value}`; return this.createIRNode('element_assign', text); } /** * Process attribute assignment (obj.field = value) */ handleAttributeAssign(targetNode, valueNode) { const objectName = this.expressionVisitor.visitExpression(targetNode.value); const attributeName = targetNode.attr; const value = this.expressionVisitor.visitExpression(valueNode); const text = `${objectName}.${attributeName} ← ${value}`; return this.createIRNode('attribute_assign', text); } /** * Convert index from 0-based to 1-based */ convertIndexToOneBased(index) { // For numeric literals, add 1 if (/^\d+$/.test(index)) { return String(parseInt(index) + 1); } // For variables, add +1 return `${index} + 1`; } /** * Determine if type annotation is list type */ isListTypeAnnotation(annotation) { if (!annotation) return false; // list[type] format if (annotation.type === 'Subscript' && annotation.value.type === 'Name' && annotation.value.id === 'list') { return true; } // List[type] format (typing.List) if (annotation.type === 'Subscript' && annotation.value.type === 'Name' && annotation.value.id === 'List') { return true; } return false; } /** * Extract element type from list type annotation */ extractListElementType(annotation) { if (annotation.type === 'Subscript' && annotation.slice) { const elementType = annotation.slice; if (elementType.type === 'Name') { return this.convertPythonTypeToIGCSE(elementType.id); } } return 'STRING'; // Default } /** * Convert type annotation to IGCSE type */ convertAnnotationToIGCSEType(annotation) { if (!annotation) return 'STRING'; if (annotation.type === 'Name') { return this.convertPythonTypeToIGCSE(annotation.id); } if (this.isListTypeAnnotation(annotation)) { const elementType = this.extractListElementType(annotation); return `ARRAY[1:100] OF ${elementType}`; } return 'STRING'; } /** * Convert Python type name to IGCSE type */ convertPythonTypeToIGCSE(typeName) { switch (typeName) { case 'int': return 'INTEGER'; case 'str': return 'STRING'; case 'bool': return 'BOOLEAN'; case 'float': return 'REAL'; default: return 'STRING'; } } /** * Get attribute names from class definition */ getClassAttributes(className) { // Search for class definition from context if (this.context && this.context.classDefinitions) { const classDef = this.context.classDefinitions[className]; if (classDef && classDef.attributes) { return classDef.attributes.map((attr) => attr.split(' : ')[0]); } } // Default attribute names (for Student class) if (className === 'Student') { return ['name', 'age']; } // Default for other classes return ['x', 'y']; } /** * Capitalize first letter of string */ capitalizeFirstLetter(str) { if (!str) return str; return str.charAt(0).toUpperCase() + str.slice(1); } createIRNode(kind, text, children = [], meta) { return (0, ir_1.createIR)(kind, text, children, meta); } } exports.StatementVisitor = StatementVisitor; //# sourceMappingURL=statement-visitor.js.map