//! Analizador sintáctico de awk: de tokens a `Programa`. //! //! La gramática de awk tiene tres rincones que obligan a escribir el parser de cierta forma, y los //! tres están marcados abajo donde aparecen: //! //! 1. **La concatenación no tiene operador.** `a b` es concatenar. Eso choca con el unario `-`: //! en `a - b` hay una resta, no la concatenación de `a` con `-b`. Se resuelve dando a la //! concatenación una precedencia propia, entre la comparación y la suma, y no dejándola empezar //! por un token que pueda continuar la expresión de la izquierda. //! 2. **`>` es ambiguo dentro de `print`.** `print a > "f"` REDIRIGE; no compara. Por eso el parser //! lleva una bandera `sin_mayor` mientras lee la lista de un `print`. //! 3. **`in` y `|` cambian de significado según el contexto**: `(i,j) in a`, `"cmd" | getline`. use crate::ast::*; use crate::lexer::{Lexer, Tok}; use crate::regex::Regex; use std::rc::Rc; const BUILTINS: &[&str] = &[ "length", "substr", "index", "split", "sub", "gsub", "match", "sprintf", "sin", "cos", "atan2", "exp", "log", "sqrt", "int", "rand", "srand", "tolower", "toupper", "system", "close", "fflush", ]; pub struct Parser { toks: Vec, i: usize, sin_mayor: bool, rangos: usize, funciones_vistas: Vec, } impl Parser { pub fn nuevo(src: &str) -> Result { let mut lx = Lexer::nuevo(src); let mut toks = Vec::new(); loop { let t = lx.siguiente()?; let fin = t == Tok::Fin; toks.push(t); if fin { break; } } Ok(Parser { toks, i: 0, sin_mayor: false, rangos: 0, funciones_vistas: Vec::new() }) } fn ver(&self) -> &Tok { &self.toks[self.i.min(self.toks.len() - 1)] } fn avanza(&mut self) -> Tok { let t = self.ver().clone(); self.i += 1; t } fn es_op(&self, o: &str) -> bool { matches!(self.ver(), Tok::Op(x) if x == o) } fn es_clave(&self, k: &str) -> bool { matches!(self.ver(), Tok::PalabraClave(x) if x == k) } fn come_op(&mut self, o: &str) -> bool { if self.es_op(o) { self.i += 1; true } else { false } } fn come_clave(&mut self, k: &str) -> bool { if self.es_clave(k) { self.i += 1; true } else { false } } fn exige_op(&mut self, o: &str) -> Result<(), String> { if self.come_op(o) { Ok(()) } else { Err(format!("esperaba `{o}` y encontré {:?}", self.ver())) } } /// Se come los saltos de línea y los `;` que separan sentencias. fn saltos(&mut self) { while matches!(self.ver(), Tok::Nueva) || self.es_op(";") { self.i += 1; } } fn solo_saltos(&mut self) { while matches!(self.ver(), Tok::Nueva) { self.i += 1; } } pub fn programa(&mut self) -> Result { let mut p = Programa::default(); self.saltos(); while !matches!(self.ver(), Tok::Fin) { if self.es_clave("function") { self.i += 1; let nombre = match self.avanza() { Tok::Ident(n) | Tok::Funcion(n) => n, t => return Err(format!("nombre de función esperado, encontré {t:?}")), }; self.exige_op("(")?; let mut params = Vec::new(); if !self.es_op(")") { loop { match self.avanza() { Tok::Ident(n) => params.push(n), t => return Err(format!("parámetro esperado, encontré {t:?}")), } self.solo_saltos(); if !self.come_op(",") { break; } self.solo_saltos(); } } self.exige_op(")")?; self.solo_saltos(); self.funciones_vistas.push(nombre.clone()); let cuerpo = self.bloque()?; p.funciones.push(Funcion { nombre, params, cuerpo }); } else { let regla = self.regla()?; p.reglas.push(regla); } self.saltos(); } p.rangos = self.rangos; Ok(p) } fn regla(&mut self) -> Result { let mut rango_id = usize::MAX; let patron = if self.come_clave("BEGIN") { Patron::Begin } else if self.come_clave("END") { Patron::End } else if self.es_op("{") { Patron::Siempre } else { let e1 = self.expr()?; if self.come_op(",") { self.solo_saltos(); let e2 = self.expr()?; rango_id = self.rangos; self.rangos += 1; Patron::Rango(e1, e2) } else { Patron::Expr(e1) } }; let accion = if self.es_op("{") { Some(self.bloque()?) } else { None }; Ok(Regla { patron, accion, rango_id }) } fn bloque(&mut self) -> Result, String> { self.exige_op("{")?; let mut v = Vec::new(); self.saltos(); while !self.es_op("}") { if matches!(self.ver(), Tok::Fin) { return Err("falta `}`".into()); } v.push(self.sentencia()?); self.saltos(); } self.exige_op("}")?; Ok(v) } /// Una sentencia, que puede ser un bloque o una simple. fn sentencia(&mut self) -> Result { if self.es_op("{") { return Ok(Sent::Bloque(self.bloque()?)); } if self.come_op(";") { return Ok(Sent::Nada); } if self.come_clave("if") { self.exige_op("(")?; let cond = self.expr()?; self.exige_op(")")?; self.solo_saltos(); let entonces = Box::new(self.sentencia()?); // El `else` puede venir tras saltos de línea y tras `;`. let guardado = self.i; self.saltos(); let si_no = if self.come_clave("else") { self.solo_saltos(); Some(Box::new(self.sentencia()?)) } else { self.i = guardado; None }; return Ok(Sent::Si(cond, entonces, si_no)); } if self.come_clave("while") { self.exige_op("(")?; let cond = self.expr()?; self.exige_op(")")?; self.solo_saltos(); // `while (c) ;` es un bucle con cuerpo vacío. if self.come_op(";") { return Ok(Sent::Mientras(cond, Box::new(Sent::Nada))); } return Ok(Sent::Mientras(cond, Box::new(self.sentencia()?))); } if self.come_clave("do") { self.solo_saltos(); let cuerpo = Box::new(self.sentencia()?); self.saltos(); if !self.come_clave("while") { return Err("`do` sin `while`".into()); } self.exige_op("(")?; let cond = self.expr()?; self.exige_op(")")?; return Ok(Sent::HazMientras(cuerpo, cond)); } if self.come_clave("for") { return self.para(); } if self.come_clave("break") { return Ok(Sent::Corta); } if self.come_clave("continue") { return Ok(Sent::Sigue); } if self.come_clave("next") { return Ok(Sent::Siguiente); } if self.come_clave("nextfile") { return Ok(Sent::SiguienteFichero); } if self.come_clave("exit") { let e = if self.fin_de_sentencia() { None } else { Some(self.expr()?) }; return Ok(Sent::Sale(e)); } if self.come_clave("return") { let e = if self.fin_de_sentencia() { None } else { Some(self.expr()?) }; return Ok(Sent::Retorna(e)); } if self.come_clave("delete") { let nombre = match self.avanza() { Tok::Ident(n) | Tok::Funcion(n) => n, t => return Err(format!("`delete` necesita un array, encontré {t:?}")), }; if self.come_op("[") { let mut idx = vec![self.expr()?]; while self.come_op(",") { idx.push(self.expr()?); } self.exige_op("]")?; return Ok(Sent::Borra(nombre, idx)); } // `delete a` y `delete a()` (la forma con paréntesis que acepta gawk) if self.come_op("(") { self.exige_op(")")?; } return Ok(Sent::BorraTodo(nombre)); } if self.es_clave("print") || self.es_clave("printf") { let es_printf = self.es_clave("printf"); self.i += 1; return self.print(es_printf); } Ok(Sent::Expr(self.expr()?)) } fn fin_de_sentencia(&self) -> bool { matches!(self.ver(), Tok::Nueva | Tok::Fin) || self.es_op(";") || self.es_op("}") } fn para(&mut self) -> Result { self.exige_op("(")?; // `for (k in a)` — se distingue de `for (expr; …)` mirando dos tokens adelante. if let Tok::Ident(k) = self.ver().clone() { if matches!(&self.toks[self.i + 1], Tok::PalabraClave(p) if p == "in") { self.i += 2; let arr = match self.avanza() { Tok::Ident(a) | Tok::Funcion(a) => a, t => return Err(format!("`for (k in a)` necesita un array, encontré {t:?}")), }; self.exige_op(")")?; self.solo_saltos(); return Ok(Sent::ParaEn(k, arr, Box::new(self.sentencia()?))); } } let init = if self.es_op(";") { None } else { Some(Box::new(self.sentencia()?)) }; self.exige_op(";")?; self.solo_saltos(); let cond = if self.es_op(";") { None } else { Some(self.expr()?) }; self.exige_op(";")?; self.solo_saltos(); let paso = if self.es_op(")") { None } else { Some(Box::new(self.sentencia()?)) }; self.exige_op(")")?; self.solo_saltos(); if self.come_op(";") { return Ok(Sent::Para(init, cond, paso, Box::new(Sent::Nada))); } Ok(Sent::Para(init, cond, paso, Box::new(self.sentencia()?))) } /// ⚠ RINCÓN 2: dentro de la lista de un `print`, un `>` REDIRIGE, no compara. `sin_mayor` lo /// dice, y se apaga en cuanto entramos en un paréntesis —`print (a > b)` sí compara. fn print(&mut self, es_printf: bool) -> Result { let mut args = Vec::new(); if !self.fin_de_sentencia() && !self.es_op(">") && !self.es_op(">>") && !self.es_op("|") { let previo = self.sin_mayor; self.sin_mayor = true; args.push(self.expr()?); while self.come_op(",") { self.solo_saltos(); args.push(self.expr()?); } self.sin_mayor = previo; } // `print (a, b) > "f"`: una lista entre paréntesis es la lista de argumentos, no una // expresión agrupada. Se detecta cuando el único argumento es un grupo con comas dentro — // eso lo resuelve `expr_lista_agrupada` al construirlo. let redir = if self.come_op(">") { Some(Redir::Fichero(Box::new(self.expr()?))) } else if self.come_op(">>") { Some(Redir::Anexa(Box::new(self.expr()?))) } else if self.come_op("|") { Some(Redir::Tuberia(Box::new(self.expr()?))) } else { None }; Ok(if es_printf { Sent::Printf(args, redir) } else { Sent::Print(args, redir) }) } // ── Expresiones, por precedencia de menor a mayor ──────────────────────────────────────────── pub fn expr(&mut self) -> Result { self.ternario() } fn ternario(&mut self) -> Result { let cond = self.o_logico()?; if self.come_op("?") { self.solo_saltos(); let a = self.ternario()?; self.solo_saltos(); self.exige_op(":")?; self.solo_saltos(); let b = self.ternario()?; return Ok(Expr::Ternario(Box::new(cond), Box::new(a), Box::new(b))); } // La asignación se reconoce DESPUÉS: en awk `a = b ? c : d` asigna el ternario entero, y // el lado izquierdo de una asignación siempre es un lugar simple. if let Some(op) = self.op_asignacion() { if let Some(lugar) = self.a_lugar(&cond) { self.i += 1; self.solo_saltos(); let val = self.ternario()?; return Ok(match op.as_str() { "=" => Expr::Asigna(Box::new(lugar), Box::new(val)), "+=" => Expr::AsignaOp(Box::new(lugar), BinOp::Suma, Box::new(val)), "-=" => Expr::AsignaOp(Box::new(lugar), BinOp::Resta, Box::new(val)), "*=" => Expr::AsignaOp(Box::new(lugar), BinOp::Mult, Box::new(val)), "/=" => Expr::AsignaOp(Box::new(lugar), BinOp::Div, Box::new(val)), "%=" => Expr::AsignaOp(Box::new(lugar), BinOp::Modulo, Box::new(val)), "^=" => Expr::AsignaOp(Box::new(lugar), BinOp::Potencia, Box::new(val)), _ => unreachable!(), }); } } Ok(cond) } fn op_asignacion(&self) -> Option { match self.ver() { Tok::Op(o) if matches!(o.as_str(), "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "^=") => Some(o.clone()), _ => None, } } fn a_lugar(&self, e: &Expr) -> Option { match e { Expr::Var(n) => Some(Lugar::Var(n.clone())), Expr::Campo(i) => Some(Lugar::Campo(i.clone())), Expr::Indice(n, idx) => Some(Lugar::Indice(n.clone(), idx.clone())), _ => None, } } fn o_logico(&mut self) -> Result { let mut izq = self.y_logico()?; while self.come_op("||") { self.solo_saltos(); let der = self.y_logico()?; izq = Expr::O(Box::new(izq), Box::new(der)); } Ok(izq) } fn y_logico(&mut self) -> Result { let mut izq = self.en_array()?; while self.come_op("&&") { self.solo_saltos(); let der = self.en_array()?; izq = Expr::Y(Box::new(izq), Box::new(der)); } Ok(izq) } fn en_array(&mut self) -> Result { let mut izq = self.coincidencia()?; while self.es_clave("in") { self.i += 1; let arr = match self.avanza() { Tok::Ident(a) | Tok::Funcion(a) => a, t => return Err(format!("`in` necesita un array, encontré {t:?}")), }; // `(i,j) in a` llega como un grupo con lista; `i in a`, como una expresión suelta. izq = match izq { Expr::Grupo(inner) => Expr::En(vec![*inner], arr), otro => Expr::En(vec![otro], arr), }; } Ok(izq) } fn coincidencia(&mut self) -> Result { let mut izq = self.comparacion()?; loop { if self.come_op("~") { let der = self.comparacion()?; izq = Expr::Coincide(Box::new(izq), Box::new(der), false); } else if self.come_op("!~") { let der = self.comparacion()?; izq = Expr::Coincide(Box::new(izq), Box::new(der), true); } else { break; } } Ok(izq) } /// POSIX: los relacionales NO son asociativos (`a < b < c` es un error en la gramática), pero /// todos los awk reales lo aceptan como `(a Result { let izq = self.concatenacion()?; let op = match self.ver() { Tok::Op(o) => match o.as_str() { "<" => CmpOp::Lt, "<=" => CmpOp::Le, "==" => CmpOp::Eq, "!=" => CmpOp::Ne, ">=" if !self.sin_mayor => CmpOp::Ge, ">" if !self.sin_mayor => CmpOp::Gt, _ => return Ok(izq), }, _ => return Ok(izq), }; self.i += 1; let der = self.concatenacion()?; Ok(Expr::Cmp(op, Box::new(izq), Box::new(der))) } /// ⚠ RINCÓN 1: la concatenación no tiene operador. Continúa mientras lo que viene PUEDE empezar /// una expresión unaria… salvo `+` y `-`, que se leen como binarios (`a - b` es una resta). fn concatenacion(&mut self) -> Result { let mut izq = self.aditiva()?; while self.empieza_valor() { let der = self.aditiva()?; izq = Expr::Concat(Box::new(izq), Box::new(der)); } Ok(izq) } fn empieza_valor(&self) -> bool { match self.ver() { Tok::Num(_) | Tok::Cad(_) | Tok::Ere(_) | Tok::Ident(_) | Tok::Funcion(_) => true, Tok::Op(o) => matches!(o.as_str(), "$" | "(" | "!" | "++" | "--"), Tok::PalabraClave(k) => k == "getline", _ => false, } } fn aditiva(&mut self) -> Result { let mut izq = self.multiplicativa()?; loop { if self.come_op("+") { let d = self.multiplicativa()?; izq = Expr::Bin(BinOp::Suma, Box::new(izq), Box::new(d)); } else if self.come_op("-") { let d = self.multiplicativa()?; izq = Expr::Bin(BinOp::Resta, Box::new(izq), Box::new(d)); } else { break; } } Ok(izq) } fn multiplicativa(&mut self) -> Result { let mut izq = self.unaria()?; loop { if self.come_op("*") { let d = self.unaria()?; izq = Expr::Bin(BinOp::Mult, Box::new(izq), Box::new(d)); } else if self.come_op("/") { let d = self.unaria()?; izq = Expr::Bin(BinOp::Div, Box::new(izq), Box::new(d)); } else if self.come_op("%") { let d = self.unaria()?; izq = Expr::Bin(BinOp::Modulo, Box::new(izq), Box::new(d)); } else { break; } } Ok(izq) } fn unaria(&mut self) -> Result { if self.come_op("!") { return Ok(Expr::No(Box::new(self.unaria()?))); } if self.come_op("-") { return Ok(Expr::Neg(Box::new(self.unaria()?))); } if self.come_op("+") { return Ok(Expr::Pos(Box::new(self.unaria()?))); } self.potencia() } /// `^` asocia a la DERECHA (`2^3^2` es 512), y su operando derecho admite unario: /// `2^-1` es válido. fn potencia(&mut self) -> Result { let base = self.postfija()?; if self.come_op("^") { let exp = self.unaria()?; return Ok(Expr::Bin(BinOp::Potencia, Box::new(base), Box::new(exp))); } Ok(base) } fn postfija(&mut self) -> Result { let e = self.primaria()?; if self.es_op("++") || self.es_op("--") { if let Some(l) = self.a_lugar(&e) { let delta = if self.es_op("++") { 1.0 } else { -1.0 }; self.i += 1; return Ok(Expr::Incr(Box::new(l), false, delta)); } } Ok(e) } fn primaria(&mut self) -> Result { // Pre-incremento. if self.es_op("++") || self.es_op("--") { let delta = if self.es_op("++") { 1.0 } else { -1.0 }; self.i += 1; let obj = self.primaria()?; let l = self.a_lugar(&obj).ok_or("`++` necesita una variable, un campo o un elemento")?; return Ok(Expr::Incr(Box::new(l), true, delta)); } if self.come_op("$") { let idx = self.primaria()?; let campo = Expr::Campo(Box::new(idx)); // `$1++` incrementa el campo. if self.es_op("++") || self.es_op("--") { let delta = if self.es_op("++") { 1.0 } else { -1.0 }; self.i += 1; if let Some(l) = self.a_lugar(&campo) { return Ok(Expr::Incr(Box::new(l), false, delta)); } } return Ok(campo); } if self.come_op("(") { let previo = self.sin_mayor; self.sin_mayor = false; // dentro de paréntesis, `>` vuelve a comparar self.solo_saltos(); let e = self.expr()?; // `(a, b) in arr` y `print (a, b)`: una lista. if self.es_op(",") { let mut lista = vec![e]; while self.come_op(",") { self.solo_saltos(); lista.push(self.expr()?); } self.exige_op(")")?; self.sin_mayor = previo; if self.es_clave("in") { self.i += 1; let arr = match self.avanza() { Tok::Ident(a) | Tok::Funcion(a) => a, t => return Err(format!("`in` necesita un array, encontré {t:?}")), }; return Ok(Expr::En(lista, arr)); } // Lista suelta: sólo tiene sentido como argumentos de print, y allí se desarma. return Ok(Expr::Llamada("@lista".into(), lista)); } self.exige_op(")")?; self.sin_mayor = previo; let g = Expr::Grupo(Box::new(e)); // `("cmd" | getline x)` return self.quizas_tuberia(g); } match self.avanza() { Tok::Num(n) => Ok(Expr::Num(n)), Tok::Cad(s) => self.quizas_tuberia(Expr::Cad(s)), Tok::Ere(r) => Ok(Expr::Ere(Rc::new(Regex::nueva(&r)?))), // ⚠ `length` sin paréntesis es el ÚNICO builtin al que POSIX se lo permite, y tiene // que ir ANTES del brazo general de identificador — si no, se lee como una variable // llamada `length`, que siempre vale vacío. El banco lo cazó con `{print length}`. Tok::Ident(n) if n == "length" && !self.es_op("[") => Ok(Expr::Builtin("length".into(), vec![])), Tok::Ident(n) => { if self.come_op("[") { let mut idx = vec![self.expr()?]; while self.come_op(",") { idx.push(self.expr()?); } self.exige_op("]")?; return Ok(Expr::Indice(n, idx)); } Ok(Expr::Var(n)) } Tok::Funcion(n) => { self.exige_op("(")?; let previo = self.sin_mayor; self.sin_mayor = false; let mut args = Vec::new(); self.solo_saltos(); if !self.es_op(")") { loop { args.push(self.expr()?); self.solo_saltos(); if !self.come_op(",") { break; } self.solo_saltos(); } } self.exige_op(")")?; self.sin_mayor = previo; if BUILTINS.contains(&n.as_str()) { Ok(Expr::Builtin(n, args)) } else { Ok(Expr::Llamada(n, args)) } } Tok::PalabraClave(k) if k == "getline" => { // `getline`, `getline var`, `getline < f`, `getline var < f` let lugar = self.lugar_opcional()?; if self.come_op("<") { let f = self.concatenacion()?; return Ok(Expr::Getline(Getline::DeFichero(lugar, Box::new(f)))); } Ok(Expr::Getline(Getline::Simple(lugar))) } t => Err(format!("expresión esperada, encontré {t:?}")), } } /// `expr | getline [lugar]` fn quizas_tuberia(&mut self, izq: Expr) -> Result { if self.es_op("|") && matches!(&self.toks[self.i + 1], Tok::PalabraClave(k) if k == "getline") { self.i += 2; let lugar = self.lugar_opcional()?; return Ok(Expr::Getline(Getline::DeTuberia(Box::new(izq), lugar))); } Ok(izq) } fn lugar_opcional(&mut self) -> Result>, String> { match self.ver().clone() { Tok::Ident(n) => { self.i += 1; if self.come_op("[") { let mut idx = vec![self.expr()?]; while self.come_op(",") { idx.push(self.expr()?); } self.exige_op("]")?; return Ok(Some(Box::new(Lugar::Indice(n, idx)))); } Ok(Some(Box::new(Lugar::Var(n)))) } Tok::Op(o) if o == "$" => { self.i += 1; let idx = self.primaria()?; Ok(Some(Box::new(Lugar::Campo(Box::new(idx))))) } _ => Ok(None), } } }