The visitor pattern enables traversal and interpretation of the Abstract Syntax Tree (AST) produced by the parser. The Expresiones compiler uses two visitor classes:
ExpresionesVisitor - Base visitor class generated by ANTLR (provides default traversal)
Visitor - Custom implementation that performs semantic analysis and interpretation
Generated by ANTLR from Expresiones.g, this class provides empty visit methods for all grammar rules.Location: ~/workspace/source/ExpresionesVisitor.py
from antlr4 import *if "." in __name__: from .ExpresionesParser import ExpresionesParserelse: from ExpresionesParser import ExpresionesParserclass ExpresionesVisitor(ParseTreeVisitor): # Visit methods for all grammar rules...
The Visitor class extends ExpresionesVisitor to provide semantic analysis, symbol table management, and interpretation.Location: ~/workspace/source/Visitor.py
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
def visitAritmetica(self, ctx): izq = self.visit(ctx.expr(0)) # Left operand der = self.visit(ctx.expr(1)) # Right operand op = ctx.getChild(1).getSymbol().type # Operator token 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
Functionality:
Recursively evaluates left and right expressions
Determines operator type from middle child
Performs arithmetic operation based on operator
Handles division by zero (returns 0)
Division uses integer division (//). For floating-point division, modify to use /.
def visitRelacional(self, ctx): izq = self.visit(ctx.expr(0)) der = self.visit(ctx.expr(1)) op = ctx.op.type # Operator stored in ctx.op field 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
Functionality:
Evaluates both expressions
Retrieves operator from ctx.op field (set by parser)
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 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
Functionality:
Looks up variable in symbol table
Returns stored value or 0 if uninitialized/undeclared
Returning 0 for undeclared variables masks errors. Consider raising an exception instead.
Override the corresponding visit method in the Visitor class
3
Access Context Data
Use context methods to access tokens and child nodes
4
Implement Logic
Add semantic analysis, code generation, or interpretation logic
5
Return Value
Return appropriate value for parent visit methods
Example: Adding Type Checking
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: Se esperaba 'int', se obtuvo '{type(valor).__name__}'") return None elif simbolo.tipo == 'float' and not isinstance(valor, (int, float)): print(f"Error de Tipo: Se esperaba 'float', se obtuvo '{type(valor).__name__}'") return None simbolo.valor = valor print(f"Asignación: {nombre} = {valor}") else: print(f"Error: Variable '{nombre}' no declarada.") return valor