//! Analizador léxico de awk. //! //! Dos cosas hacen a este lexer distinto de uno corriente, y las dos vienen de la gramática de awk: //! //! 1. **`/` es ambiguo**: puede abrir una expresión regular (`/^foo/`) o ser una división (`a / b`). //! No se puede decidir mirando sólo el carácter. La regla —la misma que usan awk, sed y los //! lenguajes con literales de regex— es MIRAR EL TOKEN ANTERIOR: tras un valor (número, cadena, //! identificador, `)`, `]`, `$x`, un incremento) una `/` es división; en cualquier otro sitio //! abre una ERE. Por eso el lexer recuerda el último token emitido. //! //! 2. **El salto de línea es SIGNIFICATIVO**: termina una sentencia. Pero no siempre — tras `{`, //! `&&`, `||`, `,`, `do`, `else` y un `)` de `if`/`for`/`while`, la línea continúa. El lexer //! emite `Nueva` y es el parser quien decide cuándo ignorarla; mezclarlo acá haría falta un //! lexer que entienda de sintaxis. #[derive(Debug, Clone, PartialEq)] pub enum Tok { Num(f64), Cad(String), Ere(String), Ident(String), /// Nombre seguido de `(` SIN espacio: sólo así se llama a una función en awk. Funcion(String), PalabraClave(String), /// Operador o signo de puntuación, tal cual. Op(String), Nueva, Fin, } const CLAVES: &[&str] = &[ "BEGIN", "END", "function", "func", "if", "else", "while", "for", "do", "break", "continue", "next", "nextfile", "exit", "return", "delete", "in", "getline", "print", "printf", ]; pub struct Lexer { src: Vec, i: usize, anterior: Option, } impl Lexer { pub fn nuevo(src: &str) -> Lexer { Lexer { src: src.chars().collect(), i: 0, anterior: None } } fn ver(&self) -> Option { self.src.get(self.i).copied() } fn ver2(&self) -> Option { self.src.get(self.i + 1).copied() } /// ¿Una `/` aquí abre una ERE? Depende de lo último que se emitió. fn toca_ere(&self) -> bool { match &self.anterior { None => true, Some(Tok::Num(_)) | Some(Tok::Cad(_)) | Some(Tok::Ere(_)) | Some(Tok::Ident(_)) => false, Some(Tok::Op(o)) => !matches!(o.as_str(), ")" | "]" | "++" | "--" | "$"), Some(Tok::PalabraClave(k)) => !matches!(k.as_str(), "getline"), _ => true, } } fn espacios_y_comentarios(&mut self) { loop { match self.ver() { Some(' ') | Some('\t') | Some('\r') => { self.i += 1; } // `\` al final de línea: continuación explícita, no hay token de nueva línea. Some('\\') if self.ver2() == Some('\n') => { self.i += 2; } Some('\\') if self.ver2() == Some('\r') => { self.i += 2; if self.ver() == Some('\n') { self.i += 1; } } Some('#') => { while !matches!(self.ver(), None | Some('\n')) { self.i += 1; } } _ => return, } } } pub fn siguiente(&mut self) -> Result { let t = self.siguiente_crudo()?; self.anterior = Some(t.clone()); Ok(t) } fn siguiente_crudo(&mut self) -> Result { self.espacios_y_comentarios(); let c = match self.ver() { None => return Ok(Tok::Fin), Some(c) => c }; if c == '\n' { self.i += 1; return Ok(Tok::Nueva); } if c.is_ascii_digit() || (c == '.' && matches!(self.ver2(), Some(d) if d.is_ascii_digit())) { return self.numero(); } if c == '"' { return self.cadena(); } if c == '/' && self.toca_ere() { return self.ere(); } if c.is_alphabetic() || c == '_' { return Ok(self.palabra()); } // Operadores, los largos primero. let tres = self.tramo(3); if tres == "**=" { self.i += 3; return Ok(Tok::Op("^=".into())); } // `**` es el `^` histórico let dos = self.tramo(2); for op in ["==", "!=", "<=", ">=", "&&", "||", "++", "--", "+=", "-=", "*=", "/=", "%=", "^=", "!~", ">>"] { if dos == *op { self.i += 2; return Ok(Tok::Op(op.to_string())); } } if dos == "**" { self.i += 2; return Ok(Tok::Op("^".into())); } self.i += 1; let uno = c.to_string(); if "{}()[];,+-*/%^<>=!?:~$|&".contains(c) { return Ok(Tok::Op(uno)); } Err(format!("carácter inesperado: `{c}`")) } fn tramo(&self, n: usize) -> String { self.src[self.i..(self.i + n).min(self.src.len())].iter().collect() } fn numero(&mut self) -> Result { let ini = self.i; while matches!(self.ver(), Some(c) if c.is_ascii_digit()) { self.i += 1; } if self.ver() == Some('.') { self.i += 1; while matches!(self.ver(), Some(c) if c.is_ascii_digit()) { self.i += 1; } } if matches!(self.ver(), Some('e') | Some('E')) { let guardado = self.i; self.i += 1; if matches!(self.ver(), Some('+') | Some('-')) { self.i += 1; } if matches!(self.ver(), Some(c) if c.is_ascii_digit()) { while matches!(self.ver(), Some(c) if c.is_ascii_digit()) { self.i += 1; } } else { self.i = guardado; } } let t: String = self.src[ini..self.i].iter().collect(); t.parse::().map(Tok::Num).map_err(|_| format!("número mal formado: `{t}`")) } fn cadena(&mut self) -> Result { self.i += 1; // la comilla de apertura let mut s = String::new(); loop { match self.ver() { None | Some('\n') => return Err("cadena sin cerrar".into()), Some('"') => { self.i += 1; return Ok(Tok::Cad(s)); } Some('\\') => { self.i += 1; let e = self.ver().ok_or("`\\` al final de la cadena")?; self.i += 1; match e { 'n' => s.push('\n'), 't' => s.push('\t'), 'r' => s.push('\r'), '\\' => s.push('\\'), '"' => s.push('"'), '/' => s.push('/'), 'a' => s.push('\x07'), 'b' => s.push('\x08'), 'f' => s.push('\x0c'), 'v' => s.push('\x0b'), // Octal: `\101` es `A`. Hasta tres dígitos. '0'..='7' => { let mut v = e.to_digit(8).unwrap(); let mut n = 1; while n < 3 { match self.ver() { Some(d) if d.is_digit(8) => { v = v * 8 + d.to_digit(8).unwrap(); self.i += 1; n += 1; } _ => break, } } s.push(char::from_u32(v).unwrap_or('\0')); } // POSIX: una barra ante cualquier otro carácter queda INDEFINIDA. awk y gawk // conservan los dos caracteres, que es lo menos sorprendente. otro => { s.push('\\'); s.push(otro); } } } Some(c) => { s.push(c); self.i += 1; } } } } fn ere(&mut self) -> Result { self.i += 1; // la `/` de apertura let mut s = String::new(); let mut en_clase = false; loop { match self.ver() { None | Some('\n') => return Err("expresión regular sin cerrar".into()), // Dentro de `[...]` una `/` no cierra: `/[/]/` es la ERE de una barra. Some('[') if !en_clase => { en_clase = true; s.push('['); self.i += 1; } Some(']') if en_clase => { en_clase = false; s.push(']'); self.i += 1; } Some('/') if !en_clase => { self.i += 1; return Ok(Tok::Ere(s)); } Some('\\') => { self.i += 1; let e = self.ver().ok_or("`\\` al final de la expresión regular")?; self.i += 1; // `\/` es una barra literal; el resto de escapes se los queda el motor ERE. if e == '/' { s.push('/'); } else { s.push('\\'); s.push(e); } } Some(c) => { s.push(c); self.i += 1; } } } } fn palabra(&mut self) -> Tok { let ini = self.i; while matches!(self.ver(), Some(c) if c.is_alphanumeric() || c == '_') { self.i += 1; } let p: String = self.src[ini..self.i].iter().collect(); if CLAVES.contains(&p.as_str()) { return Tok::PalabraClave(if p == "func" { "function".into() } else { p }); } // Llamada a función: el `(` va PEGADO. `f (x)` con espacio es concatenación, no llamada — // regla de POSIX que distingue a awk de casi todo lo demás. if self.ver() == Some('(') { Tok::Funcion(p) } else { Tok::Ident(p) } } } #[cfg(test)] mod pruebas { use super::*; fn toks(s: &str) -> Vec { let mut l = Lexer::nuevo(s); let mut v = Vec::new(); loop { let t = l.siguiente().unwrap(); if t == Tok::Fin { break; } v.push(t); } v } #[test] fn la_barra_es_division_tras_un_valor_y_ere_en_otro_sitio() { assert_eq!(toks("a / b"), vec![Tok::Ident("a".into()), Tok::Op("/".into()), Tok::Ident("b".into())]); assert_eq!(toks("$0 ~ /x/"), vec![ Tok::Op("$".into()), Tok::Num(0.0), Tok::Op("~".into()), Tok::Ere("x".into())]); } #[test] fn una_barra_dentro_de_una_clase_no_cierra_la_ere() { assert_eq!(toks("/[/]/"), vec![Tok::Ere("[/]".into())]); } #[test] fn llamada_a_funcion_solo_con_el_parentesis_pegado() { assert_eq!(toks("f(1)")[0], Tok::Funcion("f".into())); assert_eq!(toks("f (1)")[0], Tok::Ident("f".into())); } #[test] fn la_continuacion_de_linea_no_emite_nueva_linea() { assert_eq!(toks("a \\\n b"), vec![Tok::Ident("a".into()), Tok::Ident("b".into())]); assert_eq!(toks("a \n b"), vec![Tok::Ident("a".into()), Tok::Nueva, Tok::Ident("b".into())]); } #[test] fn escapes_de_cadena() { assert_eq!(toks(r#""a\tb\101""#), vec![Tok::Cad("a\tbA".into())]); } }