Skip to main content

Overview

The Expresiones compiler uses the Visitor pattern to traverse the Abstract Syntax Tree (AST) generated by ANTLR. The Visitor.py file contains the semantic analysis and execution logic for the language.

Visitor Architecture

The Visitor Class

The Visitor class extends ExpresionesVisitor and maintains a symbol table:
class Visitor(ExpresionesVisitor):
    def __init__(self):
        self.tabla_simbolos = {}

Symbol Table

Variables are stored using the Simbolo class:
class Simbolo:
    def __init__(self, nombre, tipo, valor=None):
        self.nombre = nombre
        self.tipo = tipo
        self.valor = valor
This tracks:
  • nombre: Variable identifier
  • tipo: Data type (int, float, bool)
  • valor: Current value (None if uninitialized)

Visitor Methods

Each labeled rule in the grammar generates a corresponding visit* method.

Program Entry Point

def visitProg(self, ctx):
    print("Iniciando ejecución...")
    return self.visitChildren(ctx)
Called when parsing begins. Use this for initialization or setup tasks.

Variable Declarations

def visitInstrDecl(self, ctx):
    tipo = ctx.declaracion().TIPO().getText()
    nombre = ctx.declaracion().ID().getText()
    
    valor_inicial = None
    if ctx.declaracion().ASIGNACION():
        valor_inicial = self.visit(ctx.declaracion().expr())
    
    if nombre in self.tabla_simbolos:
        print(f"Error Semántico: Variable '{nombre}' ya declarada.")
    else:
        self.tabla_simbolos[nombre] = Simbolo(nombre, tipo, valor_inicial)
        print(f"Declaración: {nombre} ({tipo}) = {valor_inicial}")
    return None
Key operations:
  1. Extract type and variable name from context
  2. Evaluate initialization expression if present
  3. Check for duplicate declarations (semantic error)
  4. Add symbol to table

Variable Assignments

def visitInstrAsig(self, ctx):
    nombre = ctx.asignacion().ID().getText()
    valor = self.visit(ctx.asignacion().expr())
    
    if nombre in self.tabla_simbolos:
        self.tabla_simbolos[nombre].valor = valor
        print(f"Asignación: {nombre} = {valor}")
    else:
        print(f"Error: Variable '{nombre}' no declarada.")
    return valor
Validates that variables exist before assignment.

Arithmetic Expressions

def visitAritmetica(self, ctx):
    izq = self.visit(ctx.expr(0))
    der = self.visit(ctx.expr(1))
    op = ctx.getChild(1).getSymbol().type
    
    if op == ExpresionesParser.SUMA: return izq + der
    if op == ExpresionesParser.RESTA: return izq - der
    if op == ExpresionesParser.MULT: return izq * der
    if op == ExpresionesParser.DIV: return izq // der if der != 0 else 0
    return 0
Division by zero returns 0 instead of raising an error. Consider adding proper error handling for production use.

Relational Operators

def visitRelacional(self, ctx):
    izq = self.visit(ctx.expr(0))
    der = self.visit(ctx.expr(1))
    op = ctx.op.type
    
    if op == ExpresionesParser.MAYOR: return izq > der
    if op == ExpresionesParser.MENOR: return izq < der
    if op == ExpresionesParser.IGUAL: return izq == der
    if op == ExpresionesParser.DIFERENTE: return izq != der
    if op == ExpresionesParser.MAYOR_IGUAL: return izq >= der
    if op == ExpresionesParser.MENOR_IGUAL: return izq <= der
    return False
Returns boolean values for use in conditional statements.

Logical Operators

def visitLogica(self, ctx):
    izq = self.visit(ctx.condicion(0))
    der = self.visit(ctx.condicion(1))
    op = ctx.getChild(1).getSymbol().type
    if op == ExpresionesParser.Y_LOGICO: return bool(izq and der)
    if op == ExpresionesParser.O_LOGICO: return bool(izq or der)
    return False

def visitNotLogica(self, ctx):
    return not self.visit(ctx.condicion())
Implements AND, OR, and NOT operations.

Conditional Statements

def visitInstrIf(self, ctx):
    if self.visit(ctx.condicion()):
        return self.visit(ctx.bloque(0))
    elif ctx.bloque(1):
        return self.visit(ctx.bloque(1))
    return None
Evaluates condition and executes appropriate block (if or else).

Leaf Nodes

def visitNumero(self, ctx):
    val = ctx.NUMERO().getText()
    return float(val) if '.' in val else int(val)

def visitVariable(self, ctx):
    nombre = ctx.ID().getText()
    if nombre in self.tabla_simbolos:
        val = self.tabla_simbolos[nombre].valor
        return val if val is not None else 0
    return 0
Extract values from literals and variable references.

Adding New Language Features

Example: Adding a Print Statement

1
Step 1: Update Grammar
2
Add to Expresiones.g:
3
PRINT : 'print' ;

instrucciones
    : ...
    | PRINT PAR_IZQ expr PAR_DER PUNTO_COMA #InstrPrint
    ;
4
Step 2: Regenerate Parser
5
antlr4 -Dlanguage=Python3 -visitor Expresiones.g
6
Step 3: Implement Visitor Method
7
Add to Visitor.py:
8
def visitInstrPrint(self, ctx):
    valor = self.visit(ctx.expr())
    print(f"Output: {valor}")
    return None
9
Step 4: Test
10
Create a test program:
11
program {
    int x = 42;
    print(x);
}

Example: Adding a While Loop

1
Step 1: Update Grammar
2
WHILE : 'while' ;

instrucciones
    : ...
    | WHILE PAR_IZQ condicion PAR_DER bloque #InstrWhile
    ;
3
Step 2: Implement Visitor Method
4
def visitInstrWhile(self, ctx):
    while self.visit(ctx.condicion()):
        self.visit(ctx.bloque())
    return None
5
This implementation doesn’t prevent infinite loops. Consider adding a maximum iteration limit for safety.
6
Step 3: Test
7
program {
    int i = 0;
    while (i < 5) {
        i = i + 1;
    }
}

Example: Adding String Support

1
Step 1: Update Grammar
2
TIPO : 'int' | 'float' | 'bool' | 'string' ;
STRING_LIT : '"' ~['"']* '"' ;

expr: ...
    | STRING_LIT  #StringLiteral
    ;
3
Step 2: Add String Concatenation
4
expr: expr PLUS expr  #Concat
    | ...
    ;
5
Step 3: Update Visitor
6
def visitStringLiteral(self, ctx):
    # Remove quotes from string
    return ctx.STRING_LIT().getText()[1:-1]

def visitConcat(self, ctx):
    izq = self.visit(ctx.expr(0))
    der = self.visit(ctx.expr(1))
    # Handle both string concat and numeric addition
    if isinstance(izq, str) or isinstance(der, str):
        return str(izq) + str(der)
    return izq + der

Modifying Existing Visitor Methods

Adding Type Checking

Enhance visitInstrAsig to validate types:
def visitInstrAsig(self, ctx):
    nombre = ctx.asignacion().ID().getText()
    valor = self.visit(ctx.asignacion().expr())
    
    if nombre in self.tabla_simbolos:
        simbolo = self.tabla_simbolos[nombre]
        
        # Type checking
        if simbolo.tipo == 'int' and not isinstance(valor, int):
            print(f"Error de tipo: {nombre} es int, pero se asignó {type(valor).__name__}")
            return None
        
        simbolo.valor = valor
        print(f"Asignación: {nombre} = {valor}")
    else:
        print(f"Error: Variable '{nombre}' no declarada.")
    return valor

Adding Scoped Symbol Tables

Replace the flat dictionary with a stack of scopes:
class Visitor(ExpresionesVisitor):
    def __init__(self):
        self.scopes = [{}]  # Stack of symbol tables
    
    def enter_scope(self):
        self.scopes.append({})
    
    def exit_scope(self):
        self.scopes.pop()
    
    def lookup(self, name):
        # Search from innermost to outermost scope
        for scope in reversed(self.scopes):
            if name in scope:
                return scope[name]
        return None
    
    def declare(self, name, symbol):
        self.scopes[-1][name] = symbol
Then update block visiting:
def visitInstrIf(self, ctx):
    if self.visit(ctx.condicion()):
        self.enter_scope()
        result = self.visit(ctx.bloque(0))
        self.exit_scope()
        return result
    elif ctx.bloque(1):
        self.enter_scope()
        result = self.visit(ctx.bloque(1))
        self.exit_scope()
        return result
    return None

Working with Context Objects

Accessing Child Nodes

# Get specific terminal node
ctx.TIPO().getText()         # Returns "int", "float", etc.
ctx.ID().getText()           # Returns variable name

# Get child expressions (for binary operations)
ctx.expr(0)  # Left operand
ctx.expr(1)  # Right operand

# Check optional elements
if ctx.ASIGNACION():  # Returns None if not present
    # Handle initialization

Getting Token Information

# Get operator type
op = ctx.getChild(1).getSymbol().type

# Get line and column for error reporting
line = ctx.start.line
column = ctx.start.column
Use context methods to extract information rather than manually parsing text. This is more robust and handles edge cases better.

Best Practices

1. Separate Concerns

Consider splitting the visitor into multiple passes:
  • Pass 1: Build symbol table
  • Pass 2: Type checking
  • Pass 3: Code generation/execution

2. Error Handling

Improve error messages with context:
def error(self, ctx, message):
    line = ctx.start.line
    col = ctx.start.column
    print(f"Error at {line}:{col} - {message}")

3. Return Values

Be consistent about what visitor methods return:
  • Expressions: Return computed values
  • Statements: Return None or control flow indicators
  • Declarations: Return None

4. Document Visitor Methods

def visitInstrDecl(self, ctx):
    """
    Handles variable declarations.
    Checks for duplicate names and adds to symbol table.
    Supports optional initialization.
    """
    # Implementation...

Debugging Tips

def visit(self, tree):
    print(f"Visiting: {tree.__class__.__name__}")
    return super().visit(tree)

Trace Execution

def visitInstrAsig(self, ctx):
    nombre = ctx.asignacion().ID().getText()
    print(f"[TRACE] Assigning to {nombre}")
    valor = self.visit(ctx.asignacion().expr())
    print(f"[TRACE] Computed value: {valor}")
    # ...

Validate Symbol Table State

Add a method to dump the symbol table at any point:
def dump_symbols(self):
    print("\n--- SYMBOL TABLE ---")
    for name, symbol in self.tabla_simbolos.items():
        print(f"{name}: {symbol.tipo} = {symbol.valor}")

Next Steps

  • Learn about testing strategies to validate your extensions
  • Review the grammar structure to understand syntactic constraints
  • Explore advanced ANTLR features like listeners and error recovery

Build docs developers (and LLMs) love