class PythonVisualizer {
constructor() {
this.code = '';
this.lines = [];
this.currentLine = 0;
this.executionState = [];
this.isRunning = false;
this.isPaused = false;
this.delay = 1000;
this.initializeElements();
this.attachEventListeners();
this.updateLineNumbers();
}
initializeElements() {
this.codeInput = document.getElementById('codeInput');
this.visualizer = document.getElementById('visualizer');
this.runBtn = document.getElementById('runBtn');
this.stepBtn = document.getElementById('stepBtn');
this.resetBtn = document.getElementById('resetBtn');
this.statusText = document.getElementById('statusText');
this.lineInfo = document.getElementById('lineInfo');
this.lineNumbers = document.getElementById('lineNumbers');
this.speedSlider = document.getElementById('speedSlider');
this.speedValue = document.getElementById('speedValue');
this.examplesBtn = document.getElementById('examplesBtn');
this.examplesMenu = document.getElementById('examplesMenu');
}
attachEventListeners() {
this.runBtn.addEventListener('click', () => this.run());
this.stepBtn.addEventListener('click', () => this.step());
this.resetBtn.addEventListener('click', () => this.reset());
this.codeInput.addEventListener('input', () => this.updateLineNumbers());
this.codeInput.addEventListener('scroll', () => this.syncScroll());
this.speedSlider.addEventListener('input', (e) => {
this.delay = parseInt(e.target.value);
this.speedValue.textContent = (this.delay / 1000).toFixed(1) + 's';
});
this.examplesBtn.addEventListener('click', () => {
this.examplesMenu.classList.toggle('show');
});
document.querySelectorAll('.example-item').forEach(item => {
item.addEventListener('click', (e) => {
this.loadExample(e.target.dataset.example);
this.examplesMenu.classList.remove('show');
});
});
document.addEventListener('click', (e) => {
if (!this.examplesBtn.contains(e.target) && !this.examplesMenu.contains(e.target)) {
this.examplesMenu.classList.remove('show');
}
});
}
updateLineNumbers() {
const lines = this.codeInput.value.split('\n');
this.lineNumbers.innerHTML = lines.map((_, i) => `
${i + 1}
`).join('');
}
syncScroll() {
// Synchronize scrolling between line numbers and code input
this.lineNumbers.scrollTop = this.codeInput.scrollTop;
}
loadExample(example) {
const examples = {
fibonacci: `# Fibonacci sequence example
def fibonacci(n):
a, b = 0, 1
result = []
for i in range(n):
result.append(a)
a, b = b, a + b
return result
# Calculate first 8 Fibonacci numbers
fib_numbers = fibonacci(8)
print(fib_numbers)`,
bubble: `# Bubble Sort Algorithm
def bubble_sort(arr):
n = len(arr)
for i in range(n):
swapped = False
for j in range(n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
swapped = True
if not swapped:
break
return arr
# Sort a list
numbers = [64, 34, 25, 12, 22, 11, 90]
sorted_nums = bubble_sort(numbers.copy())
print(sorted_nums)`,
factorial: `# Factorial calculation
def factorial(n):
if n <= 1:
return 1
result = 1
for i in range(2, n + 1):
result *= i
return result
# Calculate factorials
for num in range(1, 8):
fact = factorial(num)
print(f"{num}! = {fact}")`,
prime: `# Find prime numbers
def is_prime(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
# Find primes up to 30
primes = []
for num in range(2, 31):
if is_prime(num):
primes.append(num)
print("Primes:", primes)`,
nested: `# Nested loops example
def multiplication_table(size):
table = []
for i in range(1, size + 1):
row = []
for j in range(1, size + 1):
product = i * j
row.append(product)
table.append(row)
return table
# Generate 5x5 table
table = multiplication_table(5)
for row in table:
print(row)`
};
this.codeInput.value = examples[example] || examples.fibonacci;
this.updateLineNumbers();
this.reset();
}
parseCode() {
this.code = this.codeInput.value;
this.lines = this.code.split('\n');
this.executionState = this.simulateExecution();
}
simulateExecution() {
const states = [];
const lines = this.lines;
let globalScope = { name: 'Global', type: 'global', variables: {} };
let currentScope = globalScope;
let scopes = [globalScope];
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line === '' || line.startsWith('#')) continue;
const state = {
line: i,
scopes: JSON.parse(JSON.stringify(scopes)),
message: ''
};
// Function definition
if (line.startsWith('def ')) {
const funcName = line.match(/def\s+(\w+)/)[1];
const funcScope = { name: funcName, type: 'function', variables: {}, parent: 0 };
scopes.push(funcScope);
currentScope = funcScope;
state.message = `Defining function: ${funcName}`;
}
// Variable assignment
else if (line.includes('=') && !line.includes('==')) {
const parts = line.split('=');
if (parts.length >= 2) {
const varNames = parts[0].trim().split(',').map(v => v.trim());
const values = this.evaluateExpression(parts[1].trim());
varNames.forEach((varName, idx) => {
if (varName && !varName.includes('[')) {
currentScope.variables[varName] = values[idx] || values;
state.message = `${varName} = ${values[idx] || values}`;
}
});
}
}
// For loop
else if (line.startsWith('for ')) {
const match = line.match(/for\s+(\w+)\s+in\s+range\((\d+)/);
if (match) {
const loopVar = match[1];
const limit = parseInt(match[2]);
for (let j = 0; j < Math.min(limit, 5); j++) {
const loopScope = {
name: `Loop iteration ${j}`,
type: 'loop',
variables: { [loopVar]: j },
parent: scopes.length - 1
};
const loopState = {
line: i,
scopes: [...scopes, loopScope],
message: `Loop: ${loopVar} = ${j}`
};
states.push(loopState);
}
}
}
// If statement
else if (line.startsWith('if ')) {
const condition = line.substring(3, line.length - 1);
state.message = `Checking condition: ${condition}`;
}
// Return statement
else if (line.startsWith('return')) {
if (scopes.length > 1) {
scopes.pop();
currentScope = scopes[scopes.length - 1];
}
state.message = 'Returning from function';
}
states.push(state);
}
return states;
}
evaluateExpression(expr) {
expr = expr.trim();
// String
if (expr.startsWith('"') || expr.startsWith("'")) {
return expr.slice(1, -1);
}
// List
if (expr.startsWith('[')) {
return '[]';
}
// Number
if (!isNaN(expr)) {
return parseInt(expr);
}
// Boolean
if (expr === 'True' || expr === 'False') {
return expr;
}
// Multiple values
if (expr.includes(',')) {
return expr.split(',').map(v => this.evaluateExpression(v.trim()));
}
// Function call
if (expr.includes('(')) {
return 'function_result';
}
return expr;
}
visualizeState(state) {
this.visualizer.innerHTML = '';
state.scopes.forEach((scope, idx) => {
const scopeDiv = document.createElement('div');
scopeDiv.className = scope.parent !== undefined ? 'scope nested-scope' : 'scope';
const header = document.createElement('div');
header.className = 'scope-header';
header.innerHTML = `
${scope.name}
${scope.type}
`;
scopeDiv.appendChild(header);
Object.entries(scope.variables).forEach(([name, value]) => {
const varDiv = document.createElement('div');
varDiv.className = 'variable';
let valueClass = 'var-value';
if (typeof value === 'string') valueClass += ' string';
else if (typeof value === 'number') valueClass += ' number';
else if (typeof value === 'boolean') valueClass += ' boolean';
varDiv.innerHTML = `
${name} (${typeof value})
${JSON.stringify(value)}
`;
scopeDiv.appendChild(varDiv);
});
if (Object.keys(scope.variables).length === 0) {
scopeDiv.innerHTML += 'No variables yet
';
}
this.visualizer.appendChild(scopeDiv);
});
if (state.message) {
const messageDiv = document.createElement('div');
messageDiv.style.cssText = 'background: #e3f2fd; padding: 10px; border-radius: 6px; margin-top: 10px; color: #1976d2;';
messageDiv.textContent = `💡 ${state.message}`;
this.visualizer.appendChild(messageDiv);
}
this.highlightLine(state.line);
this.lineInfo.textContent = `Line: ${state.line + 1}`;
}
highlightLine(lineNumber) {
const lines = this.codeInput.value.split('\n');
const lineElements = this.lineNumbers.children;
Array.from(lineElements).forEach((el, idx) => {
if (idx === lineNumber) {
el.style.background = 'rgba(255, 235, 59, 0.3)';
el.style.fontWeight = 'bold';
} else {
el.style.background = 'transparent';
el.style.fontWeight = 'normal';
}
});
}
async run() {
if (this.isRunning) {
this.isPaused = !this.isPaused;
this.runBtn.textContent = this.isPaused ? '▶️ Resume' : '⏸️ Pause';
return;
}
this.parseCode();
this.isRunning = true;
this.isPaused = false;
this.currentLine = 0;
this.runBtn.textContent = '⏸️ Pause';
this.stepBtn.disabled = true;
this.statusText.textContent = 'Running...';
for (let i = 0; i < this.executionState.length; i++) {
if (!this.isRunning) break;
while (this.isPaused) {
await new Promise(resolve => setTimeout(resolve, 100));
}
this.visualizeState(this.executionState[i]);
await new Promise(resolve => setTimeout(resolve, this.delay));
}
this.isRunning = false;
this.runBtn.textContent = '▶️ Run';
this.stepBtn.disabled = false;
this.statusText.textContent = 'Completed';
}
step() {
if (!this.executionState.length) {
this.parseCode();
}
if (this.currentLine < this.executionState.length) {
this.visualizeState(this.executionState[this.currentLine]);
this.currentLine++;
this.statusText.textContent = `Step ${this.currentLine}/${this.executionState.length}`;
} else {
this.statusText.textContent = 'Completed';
}
}
reset() {
this.isRunning = false;
this.isPaused = false;
this.currentLine = 0;
this.executionState = [];
this.runBtn.textContent = '▶️ Run';
this.stepBtn.disabled = false;
this.statusText.textContent = 'Ready';
this.lineInfo.textContent = 'Line: -';
this.visualizer.innerHTML = 'Click "Run" or "Step" to visualize code execution
';
Array.from(this.lineNumbers.children).forEach(el => {
el.style.background = 'transparent';
el.style.fontWeight = 'normal';
});
}
}
// Initialize the visualizer
const visualizer = new PythonVisualizer();