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…
simi: el /bin/sh propio del producto — 4448 líneas, sólo libc, y CERO divergencias contra el ash
1//! El estado del shell y el flujo de control. Nada de sintaxis acá.
2
3use std::collections::HashMap;
4use std::sync::atomic::AtomicBool;
5
6use crate::ast::Orden;
7
8/// Señales atrapadas que llegaron y todavía no se ejecutaron. Un handler de señal sólo puede tocar
9/// cosas async-signal-safe, así que **lo único que hace es prender una bandera**; la acción del
10/// `trap` corre entre órdenes, que es donde POSIX dice que corre.
11pub static LLEGO_SENAL: [AtomicBool; 32] = [const { AtomicBool::new(false) }; 32];
12
13#[derive(Debug, Clone, PartialEq)]
14pub enum Flujo {
15 Normal,
16 Break(u32),
17 Continue(u32),
18 /// `return` dentro de una función o de un `.`
19 Return,
20 /// `exit` — se propaga hasta arriba
21 Exit(i32),
22}
23
24#[derive(Default, Clone)]
25pub struct Opciones {
26 /// `set -e`
27 pub errexit: bool,
28 /// `set -u`
29 pub nounset: bool,
30 /// `set -x`
31 pub xtrace: bool,
32 /// `set -n`: parsear y no ejecutar
33 pub noexec: bool,
34 /// `set -f`: sin expansión de rutas
35 pub noglob: bool,
36}
37
38pub struct Shell {
39 /// Variables del shell. `exportada` decide si viaja en el `environ` de los hijos.
40 pub vars: HashMap<String, (String, bool)>,
41 pub funcs: HashMap<String, Orden>,
42 /// `$1`, `$2`…
43 pub params: Vec<String>,
44 /// `$0`
45 pub nombre: String,
46 pub estado: i32,
47 pub opts: Opciones,
48 /// `trap` por señal: `"EXIT"` o el nombre/número de la señal.
49 pub traps: HashMap<String, String>,
50 /// PID del último `&`
51 pub ultimo_fondo: Option<i32>,
52 /// profundidad de bucles, para `break`/`continue`
53 pub bucles: u32,
54 /// profundidad de funciones, para `return` y `local`
55 pub funcion_honda: u32,
56 /// pila de variables locales a restaurar al salir de cada función
57 pub locales: Vec<Vec<(String, Option<(String, bool)>)>>,
58 /// Profundidad de contextos donde un estado ≠ 0 NO dispara `set -e`: condiciones de `if` y
59 /// `while`, operandos no finales de `&&`/`||`, y `!`. Es un contador DINÁMICO a propósito: así
60 /// la suspensión entra sola en el cuerpo de una función llamada desde una condición, que es lo
61 /// que POSIX manda y lo que el banco diferencial midió roto en la primera versión
62 /// (`f() { false; echo sigo; }; f || true` con `set -e`: el ash sigue, simi salía).
63 pub errexit_suspendido: u32,
64 /// ¿este proceso es un subshell? Decide si el `trap EXIT` ya corrió.
65 pub es_subshell: bool,
66 pub trap_exit_corrido: bool,
67}
68
69impl Shell {
70 pub fn nuevo() -> Self {
71 let mut vars = HashMap::new();
72 for (k, v) in std::env::vars() {
73 vars.insert(k, (v, true));
74 }
75 vars.entry("IFS".into()).or_insert((" \t\n".into(), false));
76 if let Ok(d) = std::env::current_dir() {
77 vars.insert("PWD".into(), (d.to_string_lossy().into_owned(), true));
78 }
79 vars.insert("PPID".into(), (unsafe { libc::getppid() }.to_string(), false));
80 Shell {
81 vars,
82 funcs: HashMap::new(),
83 params: Vec::new(),
84 nombre: "simi".into(),
85 estado: 0,
86 opts: Opciones::default(),
87 traps: HashMap::new(),
88 ultimo_fondo: None,
89 bucles: 0,
90 funcion_honda: 0,
91 locales: Vec::new(),
92 errexit_suspendido: 0,
93 es_subshell: false,
94 trap_exit_corrido: false,
95 }
96 }
97
98 pub fn leer(&self, nombre: &str) -> Option<String> {
99 match nombre {
100 "?" => return Some(self.estado.to_string()),
101 "#" => return Some(self.params.len().to_string()),
102 "$" => return Some(unsafe { libc::getpid() }.to_string()),
103 "!" => return Some(self.ultimo_fondo.unwrap_or(0).to_string()),
104 "0" => return Some(self.nombre.clone()),
105 "-" => {
106 let mut s = String::new();
107 if self.opts.errexit {
108 s.push('e');
109 }
110 if self.opts.nounset {
111 s.push('u');
112 }
113 if self.opts.xtrace {
114 s.push('x');
115 }
116 return Some(s);
117 }
118 _ => {}
119 }
120 if let Ok(n) = nombre.parse::<usize>() {
121 if n >= 1 {
122 return self.params.get(n - 1).cloned();
123 }
124 }
125 self.vars.get(nombre).map(|(v, _)| v.clone())
126 }
127
128 pub fn ifs(&self) -> String {
129 self.leer("IFS").unwrap_or_else(|| " \t\n".to_string())
130 }
131
132 pub fn asignar(&mut self, nombre: &str, valor: &str) {
133 let exportada = self.vars.get(nombre).map(|(_, e)| *e).unwrap_or(false);
134 self.vars.insert(nombre.to_string(), (valor.to_string(), exportada));
135 }
136
137 pub fn exportar(&mut self, nombre: &str) {
138 if let Some(e) = self.vars.get_mut(nombre) {
139 e.1 = true;
140 } else {
141 self.vars.insert(nombre.to_string(), (String::new(), true));
142 }
143 }
144
145 /// Marca una variable como local a la función en curso, guardando su valor previo.
146 pub fn declarar_local(&mut self, nombre: &str) {
147 if let Some(marco) = self.locales.last_mut() {
148 let previo = self.vars.get(nombre).cloned();
149 marco.push((nombre.to_string(), previo));
150 }
151 }
152
153 /// El `environ` para un hijo: sólo las exportadas.
154 pub fn entorno(&self) -> Vec<String> {
155 let mut v: Vec<String> = self
156 .vars
157 .iter()
158 .filter(|(_, (_, e))| *e)
159 .map(|(k, (val, _))| format!("{k}={val}"))
160 .collect();
161 v.sort();
162 v
163 }
164
165 pub fn error(&self, msg: &str) {
166 eprintln!("{}: {}", self.nombre, msg);
167 }
168}