tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1181Shell 82Rust 62Markdown 521515 Definiciones

⎇ main crates/akllay/src/lexer.rs Rust · 249 líneas · 10.2 KB Historia Quién tocó qué crudo

el awk se llama akllay: `qillqa` estaba TOMADO en tawasuyu, y no lo comprobé 83720b303 · Sergio ·

1//! Analizador léxico de awk.
2//!
3//! Dos cosas hacen a este lexer distinto de uno corriente, y las dos vienen de la gramática de awk:
4//!
5//! 1. **`/` es ambiguo**: puede abrir una expresión regular (`/^foo/`) o ser una división (`a / b`).
6//!    No se puede decidir mirando sólo el carácter. La regla —la misma que usan awk, sed y los
7//!    lenguajes con literales de regex— es MIRAR EL TOKEN ANTERIOR: tras un valor (número, cadena,
8//!    identificador, `)`, `]`, `$x`, un incremento) una `/` es división; en cualquier otro sitio
9//!    abre una ERE. Por eso el lexer recuerda el último token emitido.
10//!
11//! 2. **El salto de línea es SIGNIFICATIVO**: termina una sentencia. Pero no siempre — tras `{`,
12//!    `&&`, `||`, `,`, `do`, `else` y un `)` de `if`/`for`/`while`, la línea continúa. El lexer
13//!    emite `Nueva` y es el parser quien decide cuándo ignorarla; mezclarlo acá haría falta un
14//!    lexer que entienda de sintaxis.
15
16#[derive(Debug, Clone, PartialEq)]
17pub enum Tok {
18    Num(f64),
19    Cad(String),
20    Ere(String),
21    Ident(String),
22    /// Nombre seguido de `(` SIN espacio: sólo así se llama a una función en awk.
23    Funcion(String),
24    PalabraClave(String),
25    /// Operador o signo de puntuación, tal cual.
26    Op(String),
27    Nueva,
28    Fin,
29}
30
31const CLAVES: &[&str] = &[
32    "BEGIN", "END", "function", "func", "if", "else", "while", "for", "do", "break", "continue",
33    "next", "nextfile", "exit", "return", "delete", "in", "getline", "print", "printf",
34];
35
36pub struct Lexer {
37    src: Vec<char>,
38    i: usize,
39    anterior: Option<Tok>,
40}
41
42impl Lexer {
43    pub fn nuevo(src: &str) -> Lexer {
44        Lexer { src: src.chars().collect(), i: 0, anterior: None }
45    }
46
47    fn ver(&self) -> Option<char> { self.src.get(self.i).copied() }
48    fn ver2(&self) -> Option<char> { self.src.get(self.i + 1).copied() }
49
50    /// ¿Una `/` aquí abre una ERE? Depende de lo último que se emitió.
51    fn toca_ere(&self) -> bool {
52        match &self.anterior {
53            None => true,
54            Some(Tok::Num(_)) | Some(Tok::Cad(_)) | Some(Tok::Ere(_)) | Some(Tok::Ident(_)) => false,
55            Some(Tok::Op(o)) => !matches!(o.as_str(), ")" | "]" | "++" | "--" | "$"),
56            Some(Tok::PalabraClave(k)) => !matches!(k.as_str(), "getline"),
57            _ => true,
58        }
59    }
60
61    fn espacios_y_comentarios(&mut self) {
62        loop {
63            match self.ver() {
64                Some(' ') | Some('\t') | Some('\r') => { self.i += 1; }
65                // `\` al final de línea: continuación explícita, no hay token de nueva línea.
66                Some('\\') if self.ver2() == Some('\n') => { self.i += 2; }
67                Some('\\') if self.ver2() == Some('\r') => {
68                    self.i += 2;
69                    if self.ver() == Some('\n') { self.i += 1; }
70                }
71                Some('#') => { while !matches!(self.ver(), None | Some('\n')) { self.i += 1; } }
72                _ => return,
73            }
74        }
75    }
76
77    pub fn siguiente(&mut self) -> Result<Tok, String> {
78        let t = self.siguiente_crudo()?;
79        self.anterior = Some(t.clone());
80        Ok(t)
81    }
82
83    fn siguiente_crudo(&mut self) -> Result<Tok, String> {
84        self.espacios_y_comentarios();
85        let c = match self.ver() { None => return Ok(Tok::Fin), Some(c) => c };
86        if c == '\n' { self.i += 1; return Ok(Tok::Nueva); }
87
88        if c.is_ascii_digit() || (c == '.' && matches!(self.ver2(), Some(d) if d.is_ascii_digit())) {
89            return self.numero();
90        }
91        if c == '"' { return self.cadena(); }
92        if c == '/' && self.toca_ere() { return self.ere(); }
93        if c.is_alphabetic() || c == '_' { return Ok(self.palabra()); }
94
95        // Operadores, los largos primero.
96        let tres = self.tramo(3);
97        if tres == "**=" { self.i += 3; return Ok(Tok::Op("^=".into())); } // `**` es el `^` histórico
98        let dos = self.tramo(2);
99        for op in ["==", "!=", "<=", ">=", "&&", "||", "++", "--", "+=", "-=", "*=", "/=", "%=", "^=", "!~", ">>"] {
100            if dos == *op { self.i += 2; return Ok(Tok::Op(op.to_string())); }
101        }
102        if dos == "**" { self.i += 2; return Ok(Tok::Op("^".into())); }
103        self.i += 1;
104        let uno = c.to_string();
105        if "{}()[];,+-*/%^<>=!?:~$|&".contains(c) { return Ok(Tok::Op(uno)); }
106        Err(format!("carácter inesperado: `{c}`"))
107    }
108
109    fn tramo(&self, n: usize) -> String {
110        self.src[self.i..(self.i + n).min(self.src.len())].iter().collect()
111    }
112
113    fn numero(&mut self) -> Result<Tok, String> {
114        let ini = self.i;
115        while matches!(self.ver(), Some(c) if c.is_ascii_digit()) { self.i += 1; }
116        if self.ver() == Some('.') {
117            self.i += 1;
118            while matches!(self.ver(), Some(c) if c.is_ascii_digit()) { self.i += 1; }
119        }
120        if matches!(self.ver(), Some('e') | Some('E')) {
121            let guardado = self.i;
122            self.i += 1;
123            if matches!(self.ver(), Some('+') | Some('-')) { self.i += 1; }
124            if matches!(self.ver(), Some(c) if c.is_ascii_digit()) {
125                while matches!(self.ver(), Some(c) if c.is_ascii_digit()) { self.i += 1; }
126            } else { self.i = guardado; }
127        }
128        let t: String = self.src[ini..self.i].iter().collect();
129        t.parse::<f64>().map(Tok::Num).map_err(|_| format!("número mal formado: `{t}`"))
130    }
131
132    fn cadena(&mut self) -> Result<Tok, String> {
133        self.i += 1; // la comilla de apertura
134        let mut s = String::new();
135        loop {
136            match self.ver() {
137                None | Some('\n') => return Err("cadena sin cerrar".into()),
138                Some('"') => { self.i += 1; return Ok(Tok::Cad(s)); }
139                Some('\\') => {
140                    self.i += 1;
141                    let e = self.ver().ok_or("`\\` al final de la cadena")?;
142                    self.i += 1;
143                    match e {
144                        'n' => s.push('\n'), 't' => s.push('\t'), 'r' => s.push('\r'),
145                        '\\' => s.push('\\'), '"' => s.push('"'), '/' => s.push('/'),
146                        'a' => s.push('\x07'), 'b' => s.push('\x08'), 'f' => s.push('\x0c'),
147                        'v' => s.push('\x0b'),
148                        // Octal: `\101` es `A`. Hasta tres dígitos.
149                        '0'..='7' => {
150                            let mut v = e.to_digit(8).unwrap();
151                            let mut n = 1;
152                            while n < 3 {
153                                match self.ver() {
154                                    Some(d) if d.is_digit(8) => { v = v * 8 + d.to_digit(8).unwrap(); self.i += 1; n += 1; }
155                                    _ => break,
156                                }
157                            }
158                            s.push(char::from_u32(v).unwrap_or('\0'));
159                        }
160                        // POSIX: una barra ante cualquier otro carácter queda INDEFINIDA. awk y gawk
161                        // conservan los dos caracteres, que es lo menos sorprendente.
162                        otro => { s.push('\\'); s.push(otro); }
163                    }
164                }
165                Some(c) => { s.push(c); self.i += 1; }
166            }
167        }
168    }
169
170    fn ere(&mut self) -> Result<Tok, String> {
171        self.i += 1; // la `/` de apertura
172        let mut s = String::new();
173        let mut en_clase = false;
174        loop {
175            match self.ver() {
176                None | Some('\n') => return Err("expresión regular sin cerrar".into()),
177                // Dentro de `[...]` una `/` no cierra: `/[/]/` es la ERE de una barra.
178                Some('[') if !en_clase => { en_clase = true; s.push('['); self.i += 1; }
179                Some(']') if en_clase => { en_clase = false; s.push(']'); self.i += 1; }
180                Some('/') if !en_clase => { self.i += 1; return Ok(Tok::Ere(s)); }
181                Some('\\') => {
182                    self.i += 1;
183                    let e = self.ver().ok_or("`\\` al final de la expresión regular")?;
184                    self.i += 1;
185                    // `\/` es una barra literal; el resto de escapes se los queda el motor ERE.
186                    if e == '/' { s.push('/'); } else { s.push('\\'); s.push(e); }
187                }
188                Some(c) => { s.push(c); self.i += 1; }
189            }
190        }
191    }
192
193    fn palabra(&mut self) -> Tok {
194        let ini = self.i;
195        while matches!(self.ver(), Some(c) if c.is_alphanumeric() || c == '_') { self.i += 1; }
196        let p: String = self.src[ini..self.i].iter().collect();
197        if CLAVES.contains(&p.as_str()) {
198            return Tok::PalabraClave(if p == "func" { "function".into() } else { p });
199        }
200        // Llamada a función: el `(` va PEGADO. `f (x)` con espacio es concatenación, no llamada —
201        // regla de POSIX que distingue a awk de casi todo lo demás.
202        if self.ver() == Some('(') { Tok::Funcion(p) } else { Tok::Ident(p) }
203    }
204}
205
206#[cfg(test)]
207mod pruebas {
208    use super::*;
209
210    fn toks(s: &str) -> Vec<Tok> {
211        let mut l = Lexer::nuevo(s);
212        let mut v = Vec::new();
213        loop {
214            let t = l.siguiente().unwrap();
215            if t == Tok::Fin { break; }
216            v.push(t);
217        }
218        v
219    }
220
221    #[test]
222    fn la_barra_es_division_tras_un_valor_y_ere_en_otro_sitio() {
223        assert_eq!(toks("a / b"), vec![Tok::Ident("a".into()), Tok::Op("/".into()), Tok::Ident("b".into())]);
224        assert_eq!(toks("$0 ~ /x/"), vec![
225            Tok::Op("$".into()), Tok::Num(0.0), Tok::Op("~".into()), Tok::Ere("x".into())]);
226    }
227
228    #[test]
229    fn una_barra_dentro_de_una_clase_no_cierra_la_ere() {
230        assert_eq!(toks("/[/]/"), vec![Tok::Ere("[/]".into())]);
231    }
232
233    #[test]
234    fn llamada_a_funcion_solo_con_el_parentesis_pegado() {
235        assert_eq!(toks("f(1)")[0], Tok::Funcion("f".into()));
236        assert_eq!(toks("f (1)")[0], Tok::Ident("f".into()));
237    }
238
239    #[test]
240    fn la_continuacion_de_linea_no_emite_nueva_linea() {
241        assert_eq!(toks("a \\\n b"), vec![Tok::Ident("a".into()), Tok::Ident("b".into())]);
242        assert_eq!(toks("a \n b"), vec![Tok::Ident("a".into()), Tok::Nueva, Tok::Ident("b".into())]);
243    }
244
245    #[test]
246    fn escapes_de_cadena() {
247        assert_eq!(toks(r#""a\tb\101""#), vec![Tok::Cad("a\tbA".into())]);
248    }
249}