gioser

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 1393Shell 130Rust 79Markdown 732501 Definiciones

⎇ 13769aa7b41bf597cf8d0ff840d0ecfae76fdb5c crates/simi/src/parser.rs Rust · 515 líneas · 17.1 KB Historia Quién tocó qué crudo

simi: el /bin/sh propio del producto — 4448 líneas, sólo libc, y CERO divergencias contra el ash 6809ef4be · Sergio ·

1//! Parser de descenso recursivo sobre los tokens. La gramática es la de POSIX.1-2024 §2.10,
2//! recortada a lo que el producto usa (medido en `docs/plan-botar-busybox.md`): listas, tuberías,
3//! `&&`/`||`, `if`, `while`/`until`, `for`, `case`, funciones, grupos, subshells y redirecciones.
4//!
5//! Lo que NO hay, a propósito: alias (§2.3.1), `select`, `[[ ]]`, arrays. No son POSIX o no los usa
6//! nadie en el árbol — y un shell que es la raíz de confianza del arranque se mide por lo que NO
7//! trae.
8
9use crate::ast::*;
10use crate::lexer::{literal_desnudo, tokenizar, Tok};
11
12pub struct Parser {
13    ts: Vec<Tok>,
14    i: usize,
15    cuerpos: Vec<(String, bool)>,
16}
17
18const RESERVADAS: &[&str] = &[
19    "if", "then", "elif", "else", "fi", "while", "until", "do", "done", "for", "in", "case", "esac",
20    "{", "}", "!",
21];
22
23pub fn parsear(entrada: &str) -> Result<Lista, String> {
24    let (ts, cuerpos) = tokenizar(entrada)?;
25    let mut p = Parser { ts, i: 0, cuerpos };
26    let l = p.lista(&[])?;
27    p.saltos();
28    if p.i < p.ts.len() {
29        return Err(format!("sobra entrada en el token {:?}", p.ts[p.i]));
30    }
31    Ok(l)
32}
33
34impl Parser {
35    fn ver(&self) -> Option<&Tok> {
36        self.ts.get(self.i)
37    }
38
39    fn es_op(&self, op: &str) -> bool {
40        matches!(self.ver(), Some(Tok::Op(o)) if o == op)
41    }
42
43    fn es_palabra(&self, w: &str) -> bool {
44        matches!(self.ver(), Some(Tok::Pal(p)) if literal_desnudo(p) == Some(w))
45    }
46
47    fn comer_palabra(&mut self, w: &str) -> bool {
48        if self.es_palabra(w) {
49            self.i += 1;
50            true
51        } else {
52            false
53        }
54    }
55
56    fn exigir_palabra(&mut self, w: &str) -> Result<(), String> {
57        if self.comer_palabra(w) {
58            Ok(())
59        } else {
60            Err(format!("falta `{w}` (hay {:?})", self.ver()))
61        }
62    }
63
64    /// Salta saltos de línea (y los `;` sueltos donde la gramática los admite).
65    fn saltos(&mut self) {
66        while matches!(self.ver(), Some(Tok::Nueva)) {
67            self.i += 1;
68        }
69    }
70
71    // ───────────────────────────────────────────── listas
72
73    /// Una lista, parando ante cualquiera de las palabras reservadas de `hasta` (sin consumirla).
74    fn lista(&mut self, hasta: &[&str]) -> Result<Lista, String> {
75        let mut out: Lista = Vec::new();
76        loop {
77            self.saltos();
78            if self.ver().is_none() || self.es_op(";;") || self.es_op(")") {
79                break;
80            }
81            if hasta.iter().any(|w| self.es_palabra(w)) {
82                break;
83            }
84            let yo = self.yo()?;
85            // El separador se CONSUME acá, así que la decisión de seguir no puede volver a
86            // mirarlo: hay que recordar que estuvo. Con el `;` ya comido, mirar el token actual
87            // daba «falta `}`» en el idioma mismo de las cards (`…; exit 78; }`).
88            let mut hubo_separador = false;
89            let fin = if self.es_op("&") {
90                self.i += 1;
91                hubo_separador = true;
92                Fin::SegundoPlano
93            } else if self.es_op(";") {
94                self.i += 1;
95                hubo_separador = true;
96                Fin::Secuencial
97            } else {
98                if matches!(self.ver(), Some(Tok::Nueva)) {
99                    hubo_separador = true; // lo come el `saltos()` de la vuelta siguiente
100                }
101                Fin::Secuencial
102            };
103            out.push((yo, fin));
104            if !hubo_separador {
105                break;
106            }
107        }
108        if out.is_empty() {
109            return Err("lista vacía".into());
110        }
111        Ok(out)
112    }
113
114    fn yo(&mut self) -> Result<YO, String> {
115        let primera = self.tuberia()?;
116        let mut resto = Vec::new();
117        loop {
118            let enlace = if self.es_op("&&") {
119                Enlace::Y
120            } else if self.es_op("||") {
121                Enlace::O
122            } else {
123                break;
124            };
125            self.i += 1;
126            self.saltos();
127            resto.push((enlace, self.tuberia()?));
128        }
129        Ok(YO { primera, resto })
130    }
131
132    fn tuberia(&mut self) -> Result<Tuberia, String> {
133        let negada = self.comer_palabra("!");
134        let mut ordenes = vec![self.orden()?];
135        while self.es_op("|") {
136            self.i += 1;
137            self.saltos();
138            ordenes.push(self.orden()?);
139        }
140        Ok(Tuberia { negada, ordenes })
141    }
142
143    // ───────────────────────────────────────────── órdenes
144
145    fn orden(&mut self) -> Result<Orden, String> {
146        if self.es_palabra("if") {
147            return self.si();
148        }
149        if self.es_palabra("while") || self.es_palabra("until") {
150            return self.bucle();
151        }
152        if self.es_palabra("for") {
153            return self.para();
154        }
155        if self.es_palabra("case") {
156            return self.caso();
157        }
158        if self.es_palabra("{") {
159            self.i += 1;
160            let cuerpo = self.lista(&["}"])?;
161            self.exigir_palabra("}")?;
162            let redirs = self.redirs()?;
163            return Ok(Orden::Grupo { cuerpo, redirs });
164        }
165        if self.es_op("(") {
166            self.i += 1;
167            let cuerpo = self.lista(&[])?;
168            if !self.es_op(")") {
169                return Err(format!("falta `)` (hay {:?})", self.ver()));
170            }
171            self.i += 1;
172            let redirs = self.redirs()?;
173            return Ok(Orden::Subshell { cuerpo, redirs });
174        }
175        // ¿definición de función? `nombre ( )`
176        if let Some(Tok::Pal(p)) = self.ver() {
177            if let Some(nombre) = literal_desnudo(p) {
178                let nombre = nombre.to_string();
179                if !RESERVADAS.contains(&nombre.as_str())
180                    && matches!(self.ts.get(self.i + 1), Some(Tok::Op(o)) if o == "(")
181                    && matches!(self.ts.get(self.i + 2), Some(Tok::Op(o)) if o == ")")
182                {
183                    self.i += 3;
184                    self.saltos();
185                    let cuerpo = self.orden()?;
186                    return Ok(Orden::Funcion { nombre, cuerpo: Box::new(cuerpo) });
187                }
188            }
189        }
190        self.simple()
191    }
192
193    fn si(&mut self) -> Result<Orden, String> {
194        self.exigir_palabra("if")?;
195        let mut ramas = Vec::new();
196        let cond = self.lista(&["then"])?;
197        self.exigir_palabra("then")?;
198        let cuerpo = self.lista(&["elif", "else", "fi"])?;
199        ramas.push((cond, cuerpo));
200        let mut sino = None;
201        loop {
202            if self.comer_palabra("elif") {
203                let c = self.lista(&["then"])?;
204                self.exigir_palabra("then")?;
205                let b = self.lista(&["elif", "else", "fi"])?;
206                ramas.push((c, b));
207                continue;
208            }
209            if self.comer_palabra("else") {
210                sino = Some(self.lista(&["fi"])?);
211            }
212            break;
213        }
214        self.exigir_palabra("fi")?;
215        let redirs = self.redirs()?;
216        Ok(Orden::Si { ramas, sino, redirs })
217    }
218
219    fn bucle(&mut self) -> Result<Orden, String> {
220        let hasta = self.es_palabra("until");
221        self.i += 1;
222        let cond = self.lista(&["do"])?;
223        self.exigir_palabra("do")?;
224        let cuerpo = self.lista(&["done"])?;
225        self.exigir_palabra("done")?;
226        let redirs = self.redirs()?;
227        Ok(Orden::Bucle { hasta, cond, cuerpo, redirs })
228    }
229
230    fn para(&mut self) -> Result<Orden, String> {
231        self.exigir_palabra("for")?;
232        let var = match self.ver() {
233            Some(Tok::Pal(p)) => match literal_desnudo(p) {
234                Some(n) if !n.is_empty() => n.to_string(),
235                _ => return Err("`for` sin nombre de variable".into()),
236            },
237            _ => return Err("`for` sin nombre de variable".into()),
238        };
239        self.i += 1;
240        self.saltos();
241        let palabras = if self.comer_palabra("in") {
242            let mut ps = Vec::new();
243            while let Some(Tok::Pal(p)) = self.ver() {
244                if literal_desnudo(p) == Some("do") {
245                    break;
246                }
247                ps.push(p.clone());
248                self.i += 1;
249            }
250            if self.es_op(";") {
251                self.i += 1;
252            }
253            self.saltos();
254            Some(ps)
255        } else {
256            if self.es_op(";") {
257                self.i += 1;
258            }
259            self.saltos();
260            None
261        };
262        self.exigir_palabra("do")?;
263        let cuerpo = self.lista(&["done"])?;
264        self.exigir_palabra("done")?;
265        let redirs = self.redirs()?;
266        Ok(Orden::Para { var, palabras, cuerpo, redirs })
267    }
268
269    fn caso(&mut self) -> Result<Orden, String> {
270        self.exigir_palabra("case")?;
271        let sujeto = match self.ver() {
272            Some(Tok::Pal(p)) => p.clone(),
273            t => return Err(format!("`case` sin sujeto (hay {t:?})")),
274        };
275        self.i += 1;
276        self.saltos();
277        self.exigir_palabra("in")?;
278        let mut ramas = Vec::new();
279        loop {
280            self.saltos();
281            if self.comer_palabra("esac") {
282                break;
283            }
284            if self.ver().is_none() {
285                return Err("`case` sin `esac`".into());
286            }
287            // patrones: pal ('|' pal)* ')'
288            if self.es_op("(") {
289                self.i += 1;
290            }
291            let mut patrones = Vec::new();
292            loop {
293                match self.ver() {
294                    Some(Tok::Pal(p)) => {
295                        patrones.push(p.clone());
296                        self.i += 1;
297                    }
298                    t => return Err(format!("patrón de `case` inválido: {t:?}")),
299                }
300                if self.es_op("|") {
301                    self.i += 1;
302                    continue;
303                }
304                break;
305            }
306            if !self.es_op(")") {
307                return Err(format!("falta `)` en el patrón de `case` (hay {:?})", self.ver()));
308            }
309            self.i += 1;
310            self.saltos();
311            let cuerpo = if self.es_op(";;") || self.es_palabra("esac") {
312                Vec::new()
313            } else {
314                self.lista(&["esac"])?
315            };
316            ramas.push((patrones, cuerpo));
317            self.saltos();
318            if self.es_op(";;") {
319                self.i += 1;
320            }
321        }
322        let redirs = self.redirs()?;
323        Ok(Orden::Caso { sujeto, ramas, redirs })
324    }
325
326    fn simple(&mut self) -> Result<Orden, String> {
327        let mut asignaciones = Vec::new();
328        let mut palabras: Vec<Palabra> = Vec::new();
329        let mut redirs = Vec::new();
330        loop {
331            if let Some(r) = self.redir()? {
332                redirs.push(r);
333                continue;
334            }
335            let Some(Tok::Pal(p)) = self.ver() else { break };
336            let p = p.clone();
337            if palabras.is_empty() {
338                if let Some(a) = asignacion(&p) {
339                    asignaciones.push(a);
340                    self.i += 1;
341                    continue;
342                }
343                if let Some(w) = literal_desnudo(&p) {
344                    if RESERVADAS.contains(&w) {
345                        break;
346                    }
347                }
348            }
349            palabras.push(p);
350            self.i += 1;
351        }
352        if asignaciones.is_empty() && palabras.is_empty() && redirs.is_empty() {
353            return Err(format!("orden vacía en {:?}", self.ver()));
354        }
355        Ok(Orden::Simple { asignaciones, palabras, redirs })
356    }
357
358    fn redirs(&mut self) -> Result<Vec<Redir>, String> {
359        let mut out = Vec::new();
360        while let Some(r) = self.redir()? {
361            out.push(r);
362        }
363        Ok(out)
364    }
365
366    fn redir(&mut self) -> Result<Option<Redir>, String> {
367        let (fd_explicito, salto) = match self.ver() {
368            Some(Tok::IoNum(n)) => (Some(*n), 1),
369            _ => (None, 0),
370        };
371        let op = match self.ts.get(self.i + salto) {
372            Some(Tok::Op(o)) if matches!(o.as_str(), ">" | ">>" | "<" | "<<" | ">&" | "<&" | "<>") => {
373                o.clone()
374            }
375            _ => return Ok(None),
376        };
377        self.i += salto + 1;
378        if op == "<<" {
379            let id = match self.ver() {
380                Some(Tok::AquiDoc(id)) => *id,
381                t => return Err(format!("aquí-documento mal formado: {t:?}")),
382            };
383            self.i += 1;
384            let (texto, expande) = self.cuerpos[id].clone();
385            let cuerpo = if expande {
386                cuerpo_expandible(&texto)?
387            } else {
388                vec![crate::ast::Seg::Lit { texto, comillado: true }]
389            };
390            return Ok(Some(Redir::AquiDoc { fd: fd_explicito.unwrap_or(0), cuerpo, expande }));
391        }
392        let destino = match self.ver() {
393            Some(Tok::Pal(p)) => p.clone(),
394            t => return Err(format!("redirección `{op}` sin destino: {t:?}")),
395        };
396        self.i += 1;
397        let r = match op.as_str() {
398            ">" => Redir::Salida { fd: fd_explicito.unwrap_or(1), destino, append: false },
399            ">>" => Redir::Salida { fd: fd_explicito.unwrap_or(1), destino, append: true },
400            "<" => Redir::Entrada { fd: fd_explicito.unwrap_or(0), origen: destino },
401            "<>" => Redir::LecturaEscritura { fd: fd_explicito.unwrap_or(0), ruta: destino },
402            ">&" | "<&" => {
403                let escritura = op == ">&";
404                let dd = if literal_desnudo(&destino) == Some("-") {
405                    DupDestino::Cerrar
406                } else {
407                    DupDestino::Fd(destino)
408                };
409                Redir::Duplica {
410                    fd: fd_explicito.unwrap_or(if escritura { 1 } else { 0 }),
411                    otro: dd,
412                    escritura,
413                }
414            }
415            _ => unreachable!(),
416        };
417        Ok(Some(r))
418    }
419}
420
421/// El cuerpo de un aquí-documento que expande: se lexa como si estuviera entre comillas dobles
422/// (expansiones sí, `"` no especial).
423fn cuerpo_expandible(texto: &str) -> Result<Palabra, String> {
424    let escapado: String = texto.replace('"', "\\\"");
425    let (ts, _) = tokenizar(&format!("\"{escapado}\""))?;
426    for t in ts {
427        if let Tok::Pal(p) = t {
428            return Ok(p);
429        }
430    }
431    Ok(vec![crate::ast::Seg::Lit { texto: String::new(), comillado: true }])
432}
433
434/// `NOMBRE=valor` al principio de una orden simple.
435fn asignacion(p: &Palabra) -> Option<Asignacion> {
436    let Seg::Lit { texto, comillado: false } = &p[0] else { return None };
437    let pos = texto.find('=')?;
438    let nombre = &texto[..pos];
439    if nombre.is_empty() || !nombre.chars().next().map(|c| c == '_' || c.is_ascii_alphabetic())? {
440        return None;
441    }
442    if !nombre.chars().all(|c| c == '_' || c.is_ascii_alphanumeric()) {
443        return None;
444    }
445    let mut valor: Palabra = Vec::new();
446    let resto = &texto[pos + 1..];
447    if !resto.is_empty() {
448        valor.push(Seg::Lit { texto: resto.to_string(), comillado: false });
449    }
450    valor.extend(p[1..].iter().cloned());
451    if valor.is_empty() {
452        valor.push(Seg::Lit { texto: String::new(), comillado: true });
453    }
454    Some(Asignacion { nombre: nombre.to_string(), valor })
455}
456
457#[cfg(test)]
458mod pruebas {
459    use super::*;
460
461    #[test]
462    fn el_idioma_de_las_cards() {
463        // La forma exacta que usan 18 de las 23 cards con shell.
464        let l = parsear(
465            "test -f /etc/x.conf || { echo 'falta' >&2; exit 78; }; exec /usr/bin/x --flag",
466        )
467        .unwrap();
468        assert_eq!(l.len(), 2);
469    }
470
471    #[test]
472    fn bucle_de_espera_de_las_cards_de_compat() {
473        parsear(
474            "i=0; while [ ! -S /run/dbus/system_bus_socket ] && [ $i -lt 100 ]; \
475             do i=$((i+1)); sleep 0.2; done; exec /usr/bin/arje-polkit-compat",
476        )
477        .unwrap();
478    }
479
480    #[test]
481    fn el_for_y_el_case_del_montar_trabajo() {
482        parsear(
483            "for h in /home/*; do u=$(basename \"$h\"); uid=$(id -u \"$u\") || continue; \
484             mkdir -p \"/run/user/$uid\"; done; case $x in a|b) echo si ;; *) echo no ;; esac",
485        )
486        .unwrap();
487    }
488
489    #[test]
490    fn funcion_y_subshell_y_tuberia() {
491        parsear("f() { echo hola; }; ( f | cat ) > /dev/null 2>&1").unwrap();
492    }
493
494    #[test]
495    fn palabra_reservada_entrecomillada_no_es_reservada() {
496        // `"if"` es una orden, no un condicional.
497        let l = parsear("\"if\" x").unwrap();
498        match &l[0].0.primera.ordenes[0] {
499            Orden::Simple { palabras, .. } => assert_eq!(palabras.len(), 2),
500            o => panic!("{o:?}"),
501        }
502    }
503
504    #[test]
505    fn asignaciones_solo_antes_de_la_orden() {
506        let l = parsear("A=1 B=2 cmd C=3").unwrap();
507        match &l[0].0.primera.ordenes[0] {
508            Orden::Simple { asignaciones, palabras, .. } => {
509                assert_eq!(asignaciones.len(), 2);
510                assert_eq!(palabras.len(), 2); // `cmd` y `C=3` (que es un argumento)
511            }
512            o => panic!("{o:?}"),
513        }
514    }
515}