Skip to main content

Overview

The Interpreter is the fifth phase of compilation. Instead of generating machine code, it directly executes the Abstract Syntax Tree, allowing programs to run immediately without a separate assembly/execution step.
Interpreter vs. Compiler:Compiler:
Source → Tokens → AST → Assembly → Machine Code → Execute
Interpreter:
Source → Tokens → AST → Execute ✓
Interpreter is faster to start, but slower to run (no optimization).

How It Works

The interpreter walks the AST recursively, executing each node:
1

Initialize

Create empty runtime environment:
self.variables = {}  # Variable name → value mapping
2

Execute Statements

For each statement in the program:
  • Evaluate expressions
  • Update variables
  • Perform I/O (print)
3

Complete

All statements executed, program terminates.

Runtime Environment

The interpreter maintains a variable store:
class Interprete:
    def __init__(self):
        self.variables = {}  # Dict[str, int]
Example during execution:
# After: let x = 5;
variables = {'x': 5}

# After: let y = 10;
variables = {'x': 5, 'y': 10}

# After: let z = x + y;
variables = {'x': 5, 'y': 10, 'z': 15}
Semantic analysis already verified all variables are declared before use, so no runtime checks needed.

Statement Execution

def ejecutar_sentencia(self, sentencia):
    if isinstance(sentencia, DeclaracionVariable):
        # Evaluate the right-hand expression
        valor = self.evaluar(sentencia.expresion)
        
        # Store in variable map
        self.variables[sentencia.nombre.lexema] = valor
Example:
let x = 5 + 3;
Execution:
1. evaluar(ExpresionBinaria(+, 5, 3)) → 8
2. variables['x'] = 8

Expression Evaluation

The evaluar() method recursively evaluates expressions:
def evaluar(self, expr):
    if isinstance(expr, NumeroLiteral):
        return expr.valor
Example:
42returns: 42

Execution Examples

Simple Program

let x = 5;
let y = 10;
print x + y;

Complex Expression

let a = 5;
let b = 10;
let c = a + b * 2;
print c;

Integer Division

The interpreter uses floor division (//), not floating-point division.Example:
let x = 7 / 2;
print x;
Output:
     Resultado:
                → 3  // Not 3.5
This matches typical compiler behavior for integer types.

Runtime Errors

The interpreter does not catch runtime errors like:
  1. Division by zero (with variables):
    let x = 0;
    let y = 10 / x;  // Python ZeroDivisionError
    
    Semantic analysis only catches literal zero division.
  2. Integer overflow:
    let x = 999999 * 999999;  // May overflow
    
    Python integers have unlimited precision, but in a real compiler this would overflow.
  3. Uninitialized variables: Already prevented by semantic analysis.

Why Interpret the AST?

Fast Development

Immediate execution - no assembly/linking step.Great for:
  • Testing
  • Debugging
  • Learning

Portability

Runs anywhere Python runs - no architecture-specific code.

Simplicity

Easy to implement - just recursive evaluation.~50 lines of code vs. hundreds for code generator.

Debugging

Can inspect variables, trace execution, set breakpoints.Harder with compiled code.

Interpreter vs. Code Generator

Both execute the same AST, but differently:
Direct execution:
def evaluar(self, expr):
    if isinstance(expr, ExpresionBinaria):
        izq = self.evaluar(expr.izquierda)   # Evaluate left
        der = self.evaluar(expr.derecha)      # Evaluate right
        if expr.operador.tipo == TipoToken.SUMA:
            return izq + der                  # Compute result
Pros:
  • Simple, fast to write
  • No separate execution phase
  • Easy to debug
Cons:
  • Slower runtime (function calls, type checks)
  • No optimization
  • Requires Python runtime

Implementation Details

class Interprete:
    def __init__(self):
        self.variables = {}  # Runtime variable storage
    
    def ejecutar(self, programa):
        """Execute all statements in program"""
        for sentencia in programa.sentencias:
            self.ejecutar_sentencia(sentencia)
    
    def ejecutar_sentencia(self, sentencia):
        """Execute one statement"""
        if isinstance(sentencia, DeclaracionVariable):
            valor = self.evaluar(sentencia.expresion)
            self.variables[sentencia.nombre.lexema] = valor
        elif isinstance(sentencia, SentenciaPrint):
            valor = self.evaluar(sentencia.expresion)
            print(f"                → {valor}")
    
    def evaluar(self, expr):
        """Recursively evaluate expression, return integer"""
        # Dispatch by expression type

Performance

Time Complexity

O(n) where n = number of operationsEach AST node visited/executed once.But slower than compiled code due to:
  • Function call overhead
  • Type checking
  • Dictionary lookups

Space Complexity

O(v + d) where:
  • v = number of variables
  • d = max expression depth (recursion stack)
No optimization, so temporary values live on call stack.

Comparison: Interpreted vs. Compiled Execution

For program: let x = 5 + 3; print x;
Runtime:
1. evaluar(ExpresionBinaria(+)):
     izq = evaluar(NumeroLiteral(5)) → 5
     der = evaluar(NumeroLiteral(3)) → 3
     return 5 + 38
2. variables['x'] = 8
3. print variables['x']
Time: ~microseconds (Python overhead)
Speed difference: Compiled code is 100-1000x faster for compute-heavy tasks. But for small educational programs, interpreter is fast enough and much simpler.

Source Code Reference

Implementation

File: compfinal.pyLines: 1156-1215Key Class:
  • Interprete - AST interpreter
Main Methods:
  • ejecutar(programa) - Entry point, executes all statements
  • ejecutar_sentencia(sentencia) - Execute one statement
  • evaluar(expr) - Recursively evaluate expression, return integer value
Data Structures:
  • variables: Dict[str, int] - Runtime variable storage

Next Steps

Code Generation

See how the AST is converted to x86 assembly instead

API Reference

Detailed API documentation for the Interprete class