//! El estado del shell y el flujo de control. Nada de sintaxis acá. use std::collections::HashMap; use std::sync::atomic::AtomicBool; use crate::ast::Orden; /// Señales atrapadas que llegaron y todavía no se ejecutaron. Un handler de señal sólo puede tocar /// cosas async-signal-safe, así que **lo único que hace es prender una bandera**; la acción del /// `trap` corre entre órdenes, que es donde POSIX dice que corre. pub static LLEGO_SENAL: [AtomicBool; 32] = [const { AtomicBool::new(false) }; 32]; #[derive(Debug, Clone, PartialEq)] pub enum Flujo { Normal, Break(u32), Continue(u32), /// `return` dentro de una función o de un `.` Return, /// `exit` — se propaga hasta arriba Exit(i32), } #[derive(Default, Clone)] pub struct Opciones { /// `set -e` pub errexit: bool, /// `set -u` pub nounset: bool, /// `set -x` pub xtrace: bool, /// `set -n`: parsear y no ejecutar pub noexec: bool, /// `set -f`: sin expansión de rutas pub noglob: bool, } pub struct Shell { /// Variables del shell. `exportada` decide si viaja en el `environ` de los hijos. pub vars: HashMap, pub funcs: HashMap, /// `$1`, `$2`… pub params: Vec, /// `$0` pub nombre: String, pub estado: i32, pub opts: Opciones, /// `trap` por señal: `"EXIT"` o el nombre/número de la señal. pub traps: HashMap, /// PID del último `&` pub ultimo_fondo: Option, /// profundidad de bucles, para `break`/`continue` pub bucles: u32, /// profundidad de funciones, para `return` y `local` pub funcion_honda: u32, /// pila de variables locales a restaurar al salir de cada función pub locales: Vec)>>, /// Profundidad de contextos donde un estado ≠ 0 NO dispara `set -e`: condiciones de `if` y /// `while`, operandos no finales de `&&`/`||`, y `!`. Es un contador DINÁMICO a propósito: así /// la suspensión entra sola en el cuerpo de una función llamada desde una condición, que es lo /// que POSIX manda y lo que el banco diferencial midió roto en la primera versión /// (`f() { false; echo sigo; }; f || true` con `set -e`: el ash sigue, simi salía). pub errexit_suspendido: u32, /// ¿este proceso es un subshell? Decide si el `trap EXIT` ya corrió. pub es_subshell: bool, pub trap_exit_corrido: bool, } impl Shell { pub fn nuevo() -> Self { let mut vars = HashMap::new(); for (k, v) in std::env::vars() { vars.insert(k, (v, true)); } vars.entry("IFS".into()).or_insert((" \t\n".into(), false)); if let Ok(d) = std::env::current_dir() { vars.insert("PWD".into(), (d.to_string_lossy().into_owned(), true)); } vars.insert("PPID".into(), (unsafe { libc::getppid() }.to_string(), false)); Shell { vars, funcs: HashMap::new(), params: Vec::new(), nombre: "simi".into(), estado: 0, opts: Opciones::default(), traps: HashMap::new(), ultimo_fondo: None, bucles: 0, funcion_honda: 0, locales: Vec::new(), errexit_suspendido: 0, es_subshell: false, trap_exit_corrido: false, } } pub fn leer(&self, nombre: &str) -> Option { match nombre { "?" => return Some(self.estado.to_string()), "#" => return Some(self.params.len().to_string()), "$" => return Some(unsafe { libc::getpid() }.to_string()), "!" => return Some(self.ultimo_fondo.unwrap_or(0).to_string()), "0" => return Some(self.nombre.clone()), "-" => { let mut s = String::new(); if self.opts.errexit { s.push('e'); } if self.opts.nounset { s.push('u'); } if self.opts.xtrace { s.push('x'); } return Some(s); } _ => {} } if let Ok(n) = nombre.parse::() { if n >= 1 { return self.params.get(n - 1).cloned(); } } self.vars.get(nombre).map(|(v, _)| v.clone()) } pub fn ifs(&self) -> String { self.leer("IFS").unwrap_or_else(|| " \t\n".to_string()) } pub fn asignar(&mut self, nombre: &str, valor: &str) { let exportada = self.vars.get(nombre).map(|(_, e)| *e).unwrap_or(false); self.vars.insert(nombre.to_string(), (valor.to_string(), exportada)); } pub fn exportar(&mut self, nombre: &str) { if let Some(e) = self.vars.get_mut(nombre) { e.1 = true; } else { self.vars.insert(nombre.to_string(), (String::new(), true)); } } /// Marca una variable como local a la función en curso, guardando su valor previo. pub fn declarar_local(&mut self, nombre: &str) { if let Some(marco) = self.locales.last_mut() { let previo = self.vars.get(nombre).cloned(); marco.push((nombre.to_string(), previo)); } } /// El `environ` para un hijo: sólo las exportadas. pub fn entorno(&self) -> Vec { let mut v: Vec = self .vars .iter() .filter(|(_, (_, e))| *e) .map(|(k, (val, _))| format!("{k}={val}")) .collect(); v.sort(); v } pub fn error(&self, msg: &str) { eprintln!("{}: {}", self.nombre, msg); } }