Parser (Analizador Sintáctico)
Archivo: src/parser.rs
El Parser convierte la secuencia lineal de tokens producida por el Lexer en un Árbol de Sintaxis Abstracta (AST) estructurado. Utiliza una combinación de Análisis Sintáctico Recursivo Descendente para sentencias y declaraciones, y un Analizador Pratt (Pratt Parser) de precedencia para expresiones de operadores.
Estructura del Parser
Definición de Parser
pub struct Parser {
tokens: Vec<Token>,
pos: usize,
errores: Vec<ErrorForja>,
} tokens: La secuencia de tokens procesada del archivo fuente.pos: El índice que apunta al token actual bajo análisis.errores: Un vector que acumula errores de sintaxis detectados durante el parsing para posibilitar la recuperación de errores y evitar detener la compilación en el primer error.
El Algoritmo de Precedencia Pratt (Pratt Parsing)
Para parsear expresiones compuestas por operadores infijos y prefijos (ej: -5 + x * 2) sin caer en recursiones infinitas, Forja utiliza un Pratt Parser. Cada operador y símbolo del lenguaje tiene asignada una precedencia numérica:
| Precedencia (Enum) | Nivel de Unión | Asociados |
|---|---|---|
Ninguna | 0 | Literales, Identificadores |
Asignacion | 1 | = |
O_Logico | 2 | || |
Y_Logico | 3 | && |
Igualdad | 4 | ==, != |
Comparacion | 5 | >, <, >=, <= |
Suma | 6 | +, - |
Producto | 7 | *, /, % |
Prefijo | 8 | !, - unario, & préstamo |
Llamada | 9 | Llamada a función (), indexación [], acceso a miembros . |
Breakdown de Funciones del Parser
1. Control y Navegación de Tokens
peek(&self) -> &Token: Retorna el token actual apuntado porpos. Si se pasa del límite, devuelve el tokenEOF.- ¿Por qué?: Para examinar el tipo de token bajo análisis.
avanzar(&mut self) -> &Token: Incrementaposen 1 y devuelve el token anterior.- ¿Por qué?: Consumir tokens y mover el cursor sintáctico hacia adelante.
coincide(&self, kind: TokenKind) -> bool: Compara el token actual con un tipo de token esperado sin avanzar el puntero.- ¿Para qué?: Tomar decisiones de bifurcación condicional en la gramática.
esperar(&mut self, kind: TokenKind, msg: &str) -> Result<&Token, ErrorForja>: Valida que el token actual coincida conkindy avanza. Si no coincide, retorna un error de sintaxis educativo con el mensajemsg.- ¿Por qué?: Garantizar la estructura gramatical requerida (por ejemplo, exigir una llave de cierre o un punto y coma).
sincronizar(&mut self): Descarta tokens hasta encontrar un punto y coma (;) o una palabra clave de inicio de sentencia (clase,funcion,si, etc.).- ¿Por qué?: Mecanismo de recuperación ante errores sintácticos para limpiar el flujo y poder reportar más errores válidos posteriores.
2. Análisis de Declaraciones
pub fn parse(&mut self) -> Result<Programa, Vec<ErrorForja>>: Método de entrada principal.- ¿Cómo?: Ejecuta un loop procesando declaraciones (
parse_declaracion()) hasta EOF. Devuelve el AST de raízProgramao la colección de errores.
- ¿Cómo?: Ejecuta un loop procesando declaraciones (
parse_declaracion(&mut self) -> Result<Option<Declaracion>, ErrorForja>: Evalúa el token actual y lo delega a la función específica de parsing según la estructura.- ¿Cómo?: Coincide palabras clave como
variable,funcion,si,mientras, etc. Resuelve también comentarios de documentación antes de la declaración.
- ¿Cómo?: Coincide palabras clave como
parse_variable_decl(&mut self) -> Result<Option<Declaracion>, ErrorForja>: Resuelve declaraciones de variables (variable) y constantes (constante). Soporta desestructuración por tupla (variable (a, b) = canal()) y desestructuración por arreglo.parse_funcion(&mut self) -> Result<Option<Declaracion>, ErrorForja>: Parsea firmas de funciones, resolviendo parámetros genéricos<T, U>, firmas de parámetros con préstamos mutables (prestado mut x) y cuerpo.parse_clase(&mut self) -> Result<Option<Declaracion>, ErrorForja>: Resuelve constructores de clase, campos y métodos asociados de la clase.parse_si(&mut self),parse_mientras(&mut self),parse_para(&mut self),parse_repetir(&mut self): Resuelven las correspondientes estructuras de control y sus bloques lógicos internos.parse_seleccionar(&mut self): Resuelve el select de concurrencia multicanal, procesando cada brazo de recepción o el default (otro) y timeout (tiempo).
3. Análisis de Expresiones (Parser Pratt)
parse_expresion(&mut self) -> Result<Expresion, ErrorForja>: Wrapper inicial que invoca al loop de precedencia Pratt con la prioridad más baja (Precedencia::Ninguna).parse_precedencia(&mut self, prec: Precedencia) -> Result<Expresion, ErrorForja>: El núcleo del Pratt Parser.- ¿Cómo?:
- Resuelve el token actual como prefijo (número, string, identificador, negación
!, paréntesis). - En un bucle, verifica si el siguiente token tiene una precedencia mayor que el parámetro
prec. - Si tiene mayor precedencia, avanza y resuelve el token como infijo (operadores aritméticos, llamadas a función, acceso a miembro, indexación).
- Retorna la expresión consolidada.
- Resuelve el token actual como prefijo (número, string, identificador, negación
- ¿Cómo?: