Está en la página 1de 13

Instituto Tecnológico de Mexicali

Ingeniería en Sistemas Computacionales

ANALIZADOR LEXICO
Y SINTACTICO
LENGUAJES y AUTOMATAS I

Jordan Rivera Rodriguez


19490954

Mexicali, Baja California, 2023-10-31


Introducción
Los analizadores léxicos y sintácticos son componentes cruciales en la
interpretación y compilación de lenguajes de programación. Este reporte
ofrece un estudio en profundidad de estos elementos, explicando sus
algoritmos, funcionalidades, desafíos y casos de uso.

Antecedentes Históricos
Los analizadores tienen su origen en la teoría de autómatas y la
lingüística computacional. A lo largo de los años, han evolucionado para
abordar lenguajes de programación más complejos y diversas aplicaciones.

Analizador Léxico

Conceptos Básicos
El analizador léxico se encarga de dividir el código fuente en una serie de
tokens. Estos tokens representan las unidades mínimas de significado en un
programa.

Algoritmos y Herramientas
Existen varios algoritmos para el análisis léxico, como los autómatas
finitos y las expresiones regulares. Herramientas como Lex facilitan este
proceso.

Desafíos y Soluciones
El manejo de ambigüedades y la optimización del rendimiento son desafíos
comunes en el diseño de analizadores léxicos. Soluciones como el uso de
algoritmos de retroceso o la implementación de tablas de símbolos pueden
ayudar a mitigar estos problemas.

Mexicali, Baja California, 2023-10-31


from collections import namedtuple
import re
from error_handler import ErrorHandler

class TokenDefinitions:
"""
Esta clase encapsula todas las definiciones de tokens.
"""
# Asignación de palabras clave a sus tipos de tokens
PALABRAS_CLAVE = {
'if': 'IF',
'else': 'ELSE',
'int': 'INT',
'var': 'VAR',
'print': 'PRINT',
'true': 'TRUE',
'false': 'FALSE',
'while': 'WHILE',
}

# Tokens simples: caracteres individuales o patrones muy básicos


SIMPLE_TOKENS = [
('SUMA', r'\+'),
('RESTA', r'-'),
('MULT', r'\*'),
('DIV', r'/'),
('IGUAL', r'='),
('LPAREN', r'\('),
('RPAREN', r'\)'),
('LLAVE_IZQ', r'\{'),
('LLAVE_DER', r'\}'),
('COMA', r','),
('PUNTO_Y_COMA', r';'),
('CADENA', r'"(?:\\.|[^"\\])*"'),
('NUMERO', r'\d+(\.\d+)?'),
('ID', r'[^\W\d]\w*'),
('NEWLINE', r'\n'),
('SKIP', r'[ \t]+'),
('MISMATCH', r'.'),
]

# Tokens complejos: operadores de múltiples caracteres y otros patrones


avanzados
COMPLEX_TOKENS = [
('SUMA_ASIGN', r'\+='),
('RESTA_ASIGN', r'-='),
('MULT_ASIGN', r'\*='),

Mexicali, Baja California, 2023-10-31


('DIV_ASIGN', r'/='),
('MENOR_IGUAL', r'<='),
('MAYOR_IGUAL', r'>='),
('IGUAL_IGUAL', r'=='),
('NO_IGUAL', r'!='),
('COMENTARIO', r'/\*.*?\*/|//.*?$'),
('MENOR', r'<'),
('MAYOR', r'>'),
]

@staticmethod
def get_compiled_regex():
"""
Devuelve un regex compilado para la coincidencia de tokens, combinando
tokens simples y complejos.
"""
# Nota: Los tokens complejos deben evaluarse primero para identificarlos
correctamente antes que los tokens simples.
all_tokens = TokenDefinitions.COMPLEX_TOKENS +
TokenDefinitions.SIMPLE_TOKENS
token_patterns = '|'.join(f'(?P<{name}>{pattern})' for name, pattern in
all_tokens)
return re.compile(token_patterns, re.DOTALL | re.MULTILINE | re.UNICODE)

Token = namedtuple('Token', ['tipo', 'valor', 'linea', 'columna'])

# Mover la compilación de regex fuera de la clase Lexer para reutilización y


rendimiento
COMPILED_REGEX = TokenDefinitions.get_compiled_regex()

class Lexer:
"""
Esta clase convierte el código fuente en una secuencia de tokens.
Se basa en un patrón regex precompilado para identificar tipos de tokens.
"""
def __init__(self, error_handler=None):
"""
Inicializa el lexer con un manejador de errores.
"""
self.linea_num = 1
self.linea_inicio = 0
self.error_handler = error_handler if error_handler else ErrorHandler()
self.comments = []

def _actualizar_posicion(self, match_object):


"""
Actualiza el número de línea y la posición inicial de la línea actual

Mexicali, Baja California, 2023-10-31


en función de la posición del objeto de coincidencia de regex.
"""
nueva_linea_inicio = match_object.string.rfind('\n', 0,
match_object.start())
if nueva_linea_inicio >= 0:
self.linea_num += match_object.string.count('\n', self.linea_inicio,
match_object.start()) + 1
self.linea_inicio = nueva_linea_inicio + 1

def tokenize(self, codigo):


"""
Convierte el código fuente en tokens y produce tokens a medida que se
identifican.
Este método también maneja comentarios y errores.
"""
self.linea_num = 1
self.linea_inicio = 0
pos = 0
while pos < len(codigo):
mo = COMPILED_REGEX.match(codigo, pos)
if mo is not None:
tipo = mo.lastgroup
valor = mo.group()
self._actualizar_posicion(mo)
pos = mo.end()
columna = mo.start() - self.linea_inicio

if tipo == 'SKIP':
continue
elif tipo == 'COMENTARIO':
self.comments.append((valor, self.linea_num, columna + 1))
continue
elif tipo == 'NEWLINE':
self.linea_inicio = mo.end()
self.linea_num += 1
elif tipo == 'MISMATCH':
self.error_handler.handle(f'Carácter inesperado {valor!r}',
self.linea_num, columna)
elif tipo == 'ID' and valor in TokenDefinitions.PALABRAS_CLAVE:
tipo = TokenDefinitions.PALABRAS_CLAVE[valor]
yield Token(tipo, valor, self.linea_num, columna + 1)

Mexicali, Baja California, 2023-10-31


Analizador Sintáctico

Conceptos Básicos
El analizador sintáctico toma los tokens generados por el analizador léxico
y construye un Árbol de Sintaxis Abstracta (AST).

Algoritmos y Herramientas
Los algoritmos como el análisis LL, LR y LALR son comúnmente utilizados en
el análisis sintáctico. Herramientas como Yacc o Bison se emplean para
generar analizadores sintácticos.

Desafíos y Soluciones
Los principales desafíos incluyen el manejo de errores sintácticos y la
optimización del análisis. Técnicas como el análisis de recuperación o la
predicción pueden ser útiles aquí.

Casos de Uso
Los analizadores se utilizan en compiladores, intérpretes, editores de
texto con resaltado de sintaxis, y en diversas aplicaciones de
procesamiento de texto y datos.

Mexicali, Baja California, 2023-10-31


from ast_nodes import (
Number, Variable, BinaryOperation, VariableDeclaration,
IfStatement, WhileStatement, PrintStatement, Assignment,
String
)

class Parser:
def __init__(self, tokens):
self.tokens = tokens
self.index = 0
self.next_token = None
self._advance()

def _advance(self):
self.next_token = self.tokens[self.index] if self.index <
len(self.tokens) else None
self.index += 1

def _consume(self, tipo):


if self.next_token.tipo != tipo:
raise ValueError(f'Error de sintaxis: se esperaba {tipo} pero se
obtuvo {self.next_token.tipo}')
self._advance()

def parse_expression(self):
return self._parse_binary_expression(0)

def _parse_binary_expression(self, min_precedence):


operator_precedence = {
'SUMA': 1, 'RESTA': 1,
'MULT': 2, 'DIV': 2,
'IGUAL_IGUAL': 0, 'MENOR': 0,
'MAYOR': 0, 'MENOR_IGUAL': 0, 'MAYOR_IGUAL': 0
}

left_expr = self._parse_primary()
while self.next_token and self.next_token.tipo in operator_precedence:
operator = self.next_token.tipo
precedence = operator_precedence[operator]

if precedence < min_precedence:


break

self._advance()

right_expr = self._parse_binary_expression(precedence + 1)
left_expr = BinaryOperation(left_expr, operator, right_expr)

Mexicali, Baja California, 2023-10-31


return left_expr

def _parse_primary(self):
if self.next_token.tipo == 'NUMERO':
node = Number(self.next_token.valor)
self._advance()
return node
elif self.next_token.tipo == 'ID':
node = Variable(self.next_token.valor)
self._advance()
return node
elif self.next_token.tipo == 'CADENA':
node = String(self.next_token.valor)
self._advance()
return node
elif self.next_token.tipo == 'TRUE':
node = Number(1)
self._advance()
return node
elif self.next_token.tipo == 'FALSE':
node = Number(0)
self._advance()
return node
else:
raise ValueError(f'Error de sintaxis: token inesperado
{self.next_token.tipo}')

def parse_statement(self):
if self.next_token.tipo == 'NEWLINE':
self._advance()
return None
elif self.next_token.tipo == 'CADENA':
node = String(self.next_token.valor)
self._advance()
return node
elif self.next_token.tipo == 'VAR':
return self._parse_var_declaration()
elif self.next_token.tipo == 'IF':
return self._parse_if_statement()
elif self.next_token.tipo == 'WHILE':
return self._parse_while_statement()
elif self.next_token.tipo == 'PRINT':
return self._parse_print_statement()
elif self.next_token.tipo == 'ID':

Mexicali, Baja California, 2023-10-31


return self._parse_id_related_statement()
elif self.next_token.tipo == 'PUNTO_Y_COMA':
self._advance()
return None
else:
raise ValueError(f'Error de sintaxis: token inesperado
{self.next_token.tipo}')

def _parse_id_related_statement(self):
var_name = self.next_token.valor
self._advance()
if self.next_token.tipo == 'IGUAL':
self._advance()
expr = self.parse_expression()
return Assignment(var_name, expr)
else:
raise ValueError(f'Error de sintaxis: token inesperado
{self.next_token.tipo}')

def _parse_var_declaration(self):
var_type = self.next_token.tipo
self._advance()
var_name = self.next_token.valor
self._advance()

initial_value = None
if self.next_token.tipo == 'IGUAL':
self._advance()
initial_value = self.parse_expression()

self._consume('PUNTO_Y_COMA')

return VariableDeclaration(var_type, var_name, initial_value)

def _parse_block_statements(self):
body_statements = []
while self.next_token.tipo != 'LLAVE_DER':
stmt = self.parse_statement()
if stmt:
body_statements.append(stmt)
return body_statements

def _parse_if_statement(self):
self._consume('IF')
self._consume('LPAREN')
condition = self._parse_binary_expression(0)

Mexicali, Baja California, 2023-10-31


self._consume('RPAREN')
self._consume('LLAVE_IZQ')
body = self._parse_block_statements()
self._consume('LLAVE_DER')

else_body = None
if self.next_token and self.next_token.tipo == 'ELSE':
self._consume('ELSE')
self._consume('LLAVE_IZQ')
else_body = self._parse_block_statements()
self._consume('LLAVE_DER')

return IfStatement(condition, body, else_body)

def _parse_while_statement(self):
self._consume('WHILE')
self._consume('LPAREN')
condition = self.parse_expression()
self._consume('RPAREN')
self._consume('LLAVE_IZQ')
body = self._parse_block_statements()
self._consume('LLAVE_DER')
return WhileStatement(condition, body)

def _parse_print_statement(self):
self._consume('PRINT')
self._consume('LPAREN')
expression = self.parse_expression()
self._consume('RPAREN')
self._consume('PUNTO_Y_COMA')
return PrintStatement(expression)

def parse_block(self):
statements = []
while self.next_token and self.next_token.tipo != 'LLAVE_DER':
stmt = self.parse_statement()
if stmt:
statements.append(stmt)
return statements

def parse_program(self):
return self.parse_block()

Mexicali, Baja California, 2023-10-31


lexer.py
● Objetivo: Realizar el análisis léxico del código fuente.
● Clase TokenDefinitions: Encapsula las definiciones de tokens y
palabras clave. Utiliza expresiones regulares para identificar
tokens.
● Clase Lexer: Se encarga de dividir el código fuente en tokens
utilizando las definiciones en TokenDefinitions.

parser.py
● Objetivo: Realizar el análisis sintáctico para construir un Árbol de
Sintaxis Abstracta (AST).
● Clase Parser: Utiliza los tokens generados por Lexer para crear un
AST. Incluye métodos como _advance y _consume para manipular la
secuencia de tokens.

ast_nodes.py
● Objetivo: Definir los nodos que compondrán el AST.
● Clases como ASTNode, BinaryOperation, etc.: Estas clases representan
diferentes tipos de nodos en el AST.

evaluator.py
● Objetivo: Evaluar el AST para ejecutar el programa.
● Clase Evaluator: Evalúa los nodos del AST. Utiliza un diccionario
para almacenar variables y su estado.

Mexicali, Baja California, 2023-10-31


Conclusión
Los analizadores léxicos y sintácticos son fundamentales en la informática
moderna. Su diseño y optimización requieren un profundo conocimiento tanto
de la teoría como de las prácticas de ingeniería de software.

Mexicali, Baja California, 2023-10-31


Fuentes Bibliográficas
Aho, A. V., Lam, M. S., Sethi, R., & Ullman, J. D. (2006). Compilers:
Principles, Techniques, and Tools (2nd ed.). Pearson.

Scott, M. L. (2009). Programming Language Pragmatics (3rd ed.).


Elsevier.

Mexicali, Baja California, 2023-10-31

También podría gustarte