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…
⎇ 6809ef4be28f606aae856890a74667187ff75b05
crates/simi/src/parser.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
simi: el /bin/sh propio del producto — 4448 líneas, sólo libc, y CERO divergencias contra el ash
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}