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…
el awk se llama akllay: `qillqa` estaba TOMADO en tawasuyu, y no lo comprobé
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}