//! El intérprete: recorre el árbol y ejecuta. //! //! Tres piezas merecen leerse antes de tocar nada: //! //! * **Los campos y `$0` son un mismo dato con dos caras.** Escribir `$2` reconstruye `$0` uniendo //! con OFS; escribir `$0` vuelve a partir en campos con FS; y asignar a `$5` cuando sólo hay tres //! campos CREA los que faltan vacíos y sube NF. Todo eso vive en `partir` / `rehacer_registro`. //! * **El separador FS tiene cuatro comportamientos**, no uno: `" "` (el de por defecto) parte por //! rachas de espacios/tabuladores/saltos y descarta los de los extremos; un carácter suelto parte //! por ese carácter exacto; `"\t"` es ese caso; y cualquier cosa de más de un carácter es una ERE. //! * **Los arrays se pasan POR REFERENCIA a las funciones y los escalares por valor**, y eso no se //! declara: se descubre al usar el parámetro. Por eso un parámetro puede empezar indefinido y //! convertirse en array dentro de la llamada. use crate::ast::*; use crate::formato::{self, Arg}; use crate::regex::Regex; use crate::valor::{self, Valor}; use std::cell::RefCell; use std::collections::HashMap; use std::io::{BufRead, BufReader, Read, Write}; use std::rc::Rc; /// Lo que puede haber en una variable: un escalar o un array. No hay un tercer caso, y confundirlos /// es un error en awk (`x[1]=1; x=2` es ilegal), pero no lo comprobamos: los awk reales tampoco lo /// hacen en todos los caminos y romper eso sólo rompe guiones que ya funcionaban. #[derive(Clone)] pub enum Celda { Escalar(Valor), Array(Rc>>), } /// Por qué se interrumpió la ejecución de un bloque. pub enum Flujo { Normal, Corta, Sigue, Siguiente, SiguienteFichero, Retorna(Valor), Sale(i32), } macro_rules! propaga { ($e:expr) => { match $e { Flujo::Normal => {}, otro => return Ok(otro) } }; } pub struct Salida { escritor: Box, /// Si es una tubería, hay un hijo al que esperar en `close`. hijo: Option, } pub struct Entrada { lector: Box, hijo: Option, } pub struct Interprete { pub prog: Programa, globales: HashMap, /// Marcos de las funciones. El último es el actual. marcos: Vec>, campos: Vec, registro: String, /// `$0` ya no refleja los campos (alguien escribió `$2`) y hay que rehacerlo antes de leerlo. sucio_registro: bool, /// Los campos ya no reflejan `$0`. sucio_campos: bool, rangos_activos: Vec, salidas: HashMap, entradas: HashMap, ere_cache: HashMap>, pub codigo_salida: i32, semilla: u64, semilla_previa: f64, /// Ficheros de la línea de órdenes pendientes, y el que se está leyendo. lector_actual: Option>, indice_argv: usize, pub stdout: Box, } fn ahora_ms() -> u64 { std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_millis() as u64).unwrap_or(0) } impl Interprete { pub fn nuevo(prog: Programa) -> Interprete { let rangos = prog.rangos; let mut it = Interprete { prog, globales: HashMap::new(), marcos: Vec::new(), campos: Vec::new(), registro: String::new(), sucio_registro: false, sucio_campos: false, rangos_activos: vec![false; rangos], salidas: HashMap::new(), entradas: HashMap::new(), ere_cache: HashMap::new(), codigo_salida: 0, semilla: 0, semilla_previa: 0.0, lector_actual: None, indice_argv: 1, stdout: Box::new(std::io::BufWriter::new(std::io::stdout())), }; for (k, v) in [("FS", " "), ("OFS", " "), ("ORS", "\n"), ("RS", "\n"), ("SUBSEP", "\u{1c}"), ("CONVFMT", "%.6g"), ("OFMT", "%.6g"), ("FILENAME", "")] { it.globales.insert(k.into(), Celda::Escalar(Valor::Cad(v.into()))); } for k in ["NR", "NF", "FNR", "RSTART", "RLENGTH"] { let v = if k == "RLENGTH" { -1.0 } else { 0.0 }; it.globales.insert(k.into(), Celda::Escalar(Valor::Num(v))); } it.semilla = 0; it } // ── Variables ──────────────────────────────────────────────────────────────────────────────── fn celda(&self, n: &str) -> Option<&Celda> { if let Some(m) = self.marcos.last() { if let Some(c) = m.get(n) { return Some(c); } } self.globales.get(n) } fn en_marco(&self, n: &str) -> bool { self.marcos.last().map_or(false, |m| m.contains_key(n)) } pub fn leer_var(&self, n: &str) -> Valor { match self.celda(n) { Some(Celda::Escalar(v)) => v.clone(), Some(Celda::Array(_)) => Valor::Vacio, None => Valor::Vacio, } } pub fn poner_var(&mut self, n: &str, v: Valor) { if self.en_marco(n) { self.marcos.last_mut().unwrap().insert(n.into(), Celda::Escalar(v)); return; } // Escribir NF recorta o extiende el registro; escribir los demás no tiene efecto colateral. if n == "NF" { let nuevo = v.numero().max(0.0) as usize; self.asegurar_campos(); self.campos.resize(nuevo, String::new()); self.globales.insert("NF".into(), Celda::Escalar(Valor::Num(nuevo as f64))); self.sucio_registro = true; return; } self.globales.insert(n.into(), Celda::Escalar(v)); } fn cadena_de(&self, v: &Valor) -> String { v.cadena(&self.convfmt()) } fn convfmt(&self) -> String { self.leer_var("CONVFMT").cadena("%.6g") } fn ofmt(&self) -> String { self.leer_var("OFMT").cadena("%.6g") } fn array(&mut self, n: &str) -> Rc>> { if let Some(Celda::Array(a)) = self.celda(n) { return a.clone(); } let a = Rc::new(RefCell::new(HashMap::new())); if self.en_marco(n) { self.marcos.last_mut().unwrap().insert(n.into(), Celda::Array(a.clone())); } else { self.globales.insert(n.into(), Celda::Array(a.clone())); } a } // ── Campos ─────────────────────────────────────────────────────────────────────────────────── /// ⚠ Los cuatro comportamientos de FS. fn partir(&mut self, linea: &str) -> Vec { let fs = self.leer_var("FS").cadena("%.6g"); Self::partir_con(linea, &fs, |p| self.ere(p)) } fn partir_con Rc>(linea: &str, fs: &str, mut ere: F) -> Vec { if fs == " " { return linea.split(|c: char| c == ' ' || c == '\t' || c == '\n') .filter(|s| !s.is_empty()).map(|s| s.to_string()).collect(); } if linea.is_empty() { return Vec::new(); } if fs.chars().count() == 1 && fs != "\\" { let c = fs.chars().next().unwrap(); // Un único carácter es literal, salvo que sea el espacio (ya tratado). En POSIX un // metacarácter suelto —`.` o `|`— también es literal acá, y ése es el punto. return linea.split(c).map(|s| s.to_string()).collect(); } let re = ere(fs); let cs: Vec = linea.chars().collect(); let mut trozos = Vec::new(); let mut ini = 0; let mut i = 0; while i <= cs.len() { match re.buscar(&cs, i) { Some((a, b)) if b > a => { trozos.push(cs[ini..a].iter().collect::()); ini = b; i = b; } // Una coincidencia VACÍA no parte: si no, `FS="x*"` haría un trozo por carácter y // se comería la cadena entera. Some((a, _)) => { i = a + 1; } None => break, } } trozos.push(cs[ini.min(cs.len())..].iter().collect::()); trozos } fn asegurar_campos(&mut self) { if !self.sucio_campos { return; } self.sucio_campos = false; let reg = self.registro.clone(); self.campos = self.partir(®); let nf = self.campos.len() as f64; self.globales.insert("NF".into(), Celda::Escalar(Valor::Num(nf))); } fn asegurar_registro(&mut self) { if !self.sucio_registro { return; } self.sucio_registro = false; let ofs = self.leer_var("OFS").cadena("%.6g"); self.registro = self.campos.join(&ofs); } pub fn poner_registro(&mut self, s: String) { self.registro = s; self.sucio_campos = true; self.sucio_registro = false; } fn leer_campo(&mut self, i: usize) -> Valor { if i == 0 { self.asegurar_registro(); return Valor::de_entrada(&self.registro.clone()); } self.asegurar_campos(); match self.campos.get(i - 1) { Some(s) => Valor::de_entrada(s), None => Valor::Vacio, } } fn poner_campo(&mut self, i: usize, v: String) { if i == 0 { self.poner_registro(v); self.asegurar_campos(); return; } self.asegurar_campos(); if i > self.campos.len() { self.campos.resize(i, String::new()); self.globales.insert("NF".into(), Celda::Escalar(Valor::Num(self.campos.len() as f64))); } self.campos[i - 1] = v; self.sucio_registro = true; } // ── Expresiones regulares ──────────────────────────────────────────────────────────────────── /// Compila y CACHEA. Sin la caché, un `gsub(/x/,"")` dentro de un bucle recompilaría el patrón /// en cada vuelta; con ella se compila una vez por texto distinto. fn ere(&self, patron: &str) -> Rc { // La caché es interior-mutable de hecho: se usa desde `&self` para poder llamarla desde // `partir`, que ya tiene prestado el intérprete. Se resuelve compilando sin cachear cuando // no se puede tomar prestado — es raro y el coste es de una compilación. if let Some(r) = self.ere_cache.get(patron) { return r.clone(); } Rc::new(Regex::nueva(patron).unwrap_or_else(|e| { eprintln!("akllay: expresión regular inválida /{patron}/: {e}"); std::process::exit(2); })) } fn ere_mut(&mut self, patron: &str) -> Rc { if let Some(r) = self.ere_cache.get(patron) { return r.clone(); } let r = self.ere(patron); self.ere_cache.insert(patron.to_string(), r.clone()); r } /// La ERE de una expresión: si es un literal `/re/`, la suya; si no, se compila su valor. fn ere_de(&mut self, e: &Expr) -> Result, String> { if let Expr::Ere(r) = e { return Ok(r.clone()); } let v = self.eval(e)?; let s = self.cadena_de(&v); Ok(self.ere_mut(&s)) } // ── Ejecución ──────────────────────────────────────────────────────────────────────────────── pub fn ejecutar_begin(&mut self) -> Result { let reglas = self.prog.reglas.clone(); for r in reglas.iter().filter(|r| matches!(r.patron, Patron::Begin)) { if let Some(a) = &r.accion { propaga!(self.bloque(a)?); } } Ok(Flujo::Normal) } pub fn ejecutar_end(&mut self) -> Result { let reglas = self.prog.reglas.clone(); for r in reglas.iter().filter(|r| matches!(r.patron, Patron::End)) { if let Some(a) = &r.accion { propaga!(self.bloque(a)?); } } Ok(Flujo::Normal) } pub fn hay_main(&self) -> bool { self.prog.reglas.iter().any(|r| !matches!(r.patron, Patron::Begin)) } pub fn hay_end(&self) -> bool { self.prog.reglas.iter().any(|r| matches!(r.patron, Patron::End)) } /// Corre las reglas normales para el registro actual. pub fn ejecutar_reglas(&mut self) -> Result { let reglas = self.prog.reglas.clone(); for r in ®las { let corre = match &r.patron { Patron::Begin | Patron::End => continue, Patron::Siempre => true, Patron::Expr(e) => self.eval(e)?.verdad(), Patron::Rango(a, b) => { let id = r.rango_id; if !self.rangos_activos[id] { if self.eval(a)?.verdad() { // Un rango cuyo final casa en la MISMA línea empieza y acaba ahí. self.rangos_activos[id] = !self.eval(b)?.verdad(); true } else { false } } else { if self.eval(b)?.verdad() { self.rangos_activos[id] = false; } true } } }; if !corre { continue; } match &r.accion { None => { let s = self.leer_campo(0); let t = self.cadena_de(&s); self.escribir_linea(&t, &None)?; } Some(a) => propaga!(self.bloque(a)?), } } Ok(Flujo::Normal) } fn bloque(&mut self, ss: &[Sent]) -> Result { for s in ss { propaga!(self.sentencia(s)?); } Ok(Flujo::Normal) } fn sentencia(&mut self, s: &Sent) -> Result { match s { Sent::Nada => {} Sent::Expr(e) => { self.eval(e)?; } Sent::Bloque(b) => propaga!(self.bloque(b)?), Sent::Print(args, redir) => { let ofs = self.leer_var("OFS").cadena("%.6g"); let ofmt = self.ofmt(); let mut partes = Vec::new(); if args.is_empty() { let v = self.leer_campo(0); partes.push(self.cadena_de(&v)); } else { let planos = Self::aplanar(args); for a in &planos { let v = self.eval(a)?; // `print` usa OFMT para los no-enteros, no CONVFMT. Es la única diferencia // entre las dos variables y se nota en cuanto alguien toca OFMT. partes.push(match &v { Valor::Num(n) => valor::numero_a_cadena(*n, &ofmt), otro => self.cadena_de(otro), }); } } let linea = partes.join(&ofs); self.escribir_linea(&linea, redir)?; } Sent::Printf(args, redir) => { let planos = Self::aplanar(args); if planos.is_empty() { return Err("printf sin formato".into()); } let fv = self.eval(&planos[0])?; let fmt = self.cadena_de(&fv); let mut vals = Vec::new(); for a in &planos[1..] { let v = self.eval(a)?; vals.push(match v { Valor::Num(n) => Arg::Numero(n), Valor::Cad(s) => Arg::Texto(s), Valor::StrNum(s, n) => Arg::Entrada(s, n), Valor::Vacio => Arg::Texto(String::new()), }); } let texto = formato::printf(&fmt, &vals, &self.convfmt()); self.escribir_crudo(&texto, redir)?; } Sent::Si(c, a, b) => { if self.eval(c)?.verdad() { propaga!(self.sentencia(a)?); } else if let Some(b) = b { propaga!(self.sentencia(b)?); } } Sent::Mientras(c, cuerpo) => { while self.eval(c)?.verdad() { match self.sentencia(cuerpo)? { Flujo::Corta => break, Flujo::Sigue | Flujo::Normal => {} otro => return Ok(otro), } } } Sent::HazMientras(cuerpo, c) => { loop { match self.sentencia(cuerpo)? { Flujo::Corta => break, Flujo::Sigue | Flujo::Normal => {} otro => return Ok(otro), } if !self.eval(c)?.verdad() { break; } } } Sent::Para(init, cond, paso, cuerpo) => { if let Some(i) = init { propaga!(self.sentencia(i)?); } loop { if let Some(c) = cond { if !self.eval(c)?.verdad() { break; } } match self.sentencia(cuerpo)? { Flujo::Corta => break, Flujo::Sigue | Flujo::Normal => {} otro => return Ok(otro), } if let Some(p) = paso { propaga!(self.sentencia(p)?); } } } Sent::ParaEn(k, arr, cuerpo) => { let a = self.array(arr); // Se copian las claves: el cuerpo puede borrar elementos, y recorrer el mapa // mientras se modifica es justo lo que rompe en los awk que no lo hacen. let claves: Vec = a.borrow().keys().cloned().collect(); for c in claves { if !a.borrow().contains_key(&c) { continue; } self.poner_var(k, Valor::de_entrada(&c)); match self.sentencia(cuerpo)? { Flujo::Corta => break, Flujo::Sigue | Flujo::Normal => {} otro => return Ok(otro), } } } Sent::Corta => return Ok(Flujo::Corta), Sent::Sigue => return Ok(Flujo::Sigue), Sent::Siguiente => return Ok(Flujo::Siguiente), Sent::SiguienteFichero => return Ok(Flujo::SiguienteFichero), Sent::Sale(e) => { let c = match e { Some(x) => self.eval(x)?.numero() as i32, None => self.codigo_salida }; return Ok(Flujo::Sale(c)); } Sent::Retorna(e) => { let v = match e { Some(x) => self.eval(x)?, None => Valor::Vacio }; return Ok(Flujo::Retorna(v)); } Sent::Borra(n, idx) => { let clave = self.clave(idx)?; let a = self.array(n); a.borrow_mut().remove(&clave); } Sent::BorraTodo(n) => { let a = self.array(n); a.borrow_mut().clear(); } } Ok(Flujo::Normal) } /// `print (a, b)` llega como una «llamada» sintética `@lista`: acá se desarma. fn aplanar(args: &[Expr]) -> Vec { if args.len() == 1 { if let Expr::Llamada(n, lista) = &args[0] { if n == "@lista" { return lista.clone(); } } } args.to_vec() } fn clave(&mut self, idx: &[Expr]) -> Result { let subsep = self.leer_var("SUBSEP").cadena("%.6g"); let mut partes = Vec::new(); for e in idx { let v = self.eval(e)?; partes.push(self.cadena_de(&v)); } Ok(partes.join(&subsep)) } // ── Salidas y entradas ─────────────────────────────────────────────────────────────────────── fn escribir_linea(&mut self, s: &str, r: &Option) -> Result<(), String> { let ors = self.leer_var("ORS").cadena("%.6g"); self.escribir_crudo(&format!("{s}{ors}"), r) } fn escribir_crudo(&mut self, s: &str, r: &Option) -> Result<(), String> { match r { None => { let _ = self.stdout.write_all(s.as_bytes()); } Some(red) => { let (destino, modo) = match red { Redir::Fichero(e) => (self.eval(e)?, 'w'), Redir::Anexa(e) => (self.eval(e)?, 'a'), Redir::Tuberia(e) => (self.eval(e)?, 'p'), }; let nombre = self.cadena_de(&destino); self.abrir_salida(&nombre, modo)?; if let Some(sal) = self.salidas.get_mut(&nombre) { let _ = sal.escritor.write_all(s.as_bytes()); } } } Ok(()) } fn abrir_salida(&mut self, nombre: &str, modo: char) -> Result<(), String> { if self.salidas.contains_key(nombre) { return Ok(()); } let sal = match modo { 'p' => { // La tubería la abre un `sh -c`, como manda POSIX: el destino es una LÍNEA DE // ÓRDENES, no un programa. Por eso `print | "sort -k2 | head"` funciona. let mut hijo = std::process::Command::new("/bin/sh") .arg("-c").arg(nombre) .stdin(std::process::Stdio::piped()) .spawn().map_err(|e| format!("no pude lanzar `{nombre}`: {e}"))?; let entrada = hijo.stdin.take().unwrap(); Salida { escritor: Box::new(entrada), hijo: Some(hijo) } } _ => { if nombre == "/dev/stdout" || nombre == "-" { Salida { escritor: Box::new(std::io::stdout()), hijo: None } } else if nombre == "/dev/stderr" { Salida { escritor: Box::new(std::io::stderr()), hijo: None } } else { let f = std::fs::OpenOptions::new() .write(true).create(true) .append(modo == 'a').truncate(modo == 'w') .open(nombre).map_err(|e| format!("no pude abrir `{nombre}`: {e}"))?; Salida { escritor: Box::new(std::io::BufWriter::new(f)), hijo: None } } } }; self.salidas.insert(nombre.to_string(), sal); Ok(()) } pub fn cerrar_todo(&mut self) { let _ = self.stdout.flush(); let nombres: Vec = self.salidas.keys().cloned().collect(); for n in nombres { self.cerrar(&n); } let nombres: Vec = self.entradas.keys().cloned().collect(); for n in nombres { self.cerrar(&n); } } fn cerrar(&mut self, nombre: &str) -> i32 { let mut r = -1; if let Some(mut s) = self.salidas.remove(nombre) { let _ = s.escritor.flush(); drop(s.escritor); if let Some(mut h) = s.hijo.take() { r = h.wait().ok().and_then(|e| e.code()).unwrap_or(0); } else { r = 0; } } if let Some(mut e) = self.entradas.remove(nombre) { drop(std::mem::replace(&mut e.lector, Box::new(std::io::empty()))); if let Some(mut h) = e.hijo.take() { r = h.wait().ok().and_then(|x| x.code()).unwrap_or(0); } else { r = 0; } } r } /// Lee un registro del lector dado, respetando RS. Devuelve `None` en fin de fichero. fn leer_registro(lector: &mut dyn BufRead, rs: &str) -> Option { if rs == "\n" { let mut buf = Vec::new(); match lector.read_until(b'\n', &mut buf) { Ok(0) | Err(_) => return None, Ok(_) => {} } if buf.last() == Some(&b'\n') { buf.pop(); } if buf.last() == Some(&b'\r') { buf.pop(); } return Some(String::from_utf8_lossy(&buf).into_owned()); } if rs.is_empty() { // RS="" es el MODO PÁRRAFO: los registros se separan por líneas en blanco, las de // delante se descartan, y `\n` pasa a ser separador de campos además de FS. let mut lineas: Vec = Vec::new(); loop { let mut buf = Vec::new(); match lector.read_until(b'\n', &mut buf) { Ok(0) | Err(_) => break, Ok(_) => {} } if buf.last() == Some(&b'\n') { buf.pop(); } let l = String::from_utf8_lossy(&buf).into_owned(); if l.is_empty() { if lineas.is_empty() { continue; } // blancos de delante break; } lineas.push(l); } if lineas.is_empty() { return None; } return Some(lineas.join("\n")); } // RS de un carácter. let sep = rs.as_bytes()[0]; let mut buf = Vec::new(); match lector.read_until(sep, &mut buf) { Ok(0) | Err(_) => return None, Ok(_) => {} } if buf.last() == Some(&sep) { buf.pop(); } Some(String::from_utf8_lossy(&buf).into_owned()) } /// Siguiente registro de la entrada principal (los ficheros de ARGV, o la entrada estándar). pub fn siguiente_registro(&mut self) -> Option { let rs = self.leer_var("RS").cadena("%.6g"); loop { if self.lector_actual.is_none() && !self.abrir_siguiente_fuente() { return None; } let lector = self.lector_actual.as_mut().unwrap(); match Self::leer_registro(lector.as_mut(), &rs) { Some(r) => { let nr = self.leer_var("NR").numero() + 1.0; let fnr = self.leer_var("FNR").numero() + 1.0; self.poner_var("NR", Valor::Num(nr)); self.poner_var("FNR", Valor::Num(fnr)); return Some(r); } None => { self.lector_actual = None; } } } } pub fn cerrar_fuente_actual(&mut self) { self.lector_actual = None; } /// Abre el siguiente operando de ARGV que no sea una asignación. Devuelve false si no queda. fn abrir_siguiente_fuente(&mut self) -> bool { let argc = self.leer_var("ARGC").numero() as usize; while self.indice_argv < argc { let i = self.indice_argv; self.indice_argv += 1; let arr = self.array("ARGV"); let v = arr.borrow().get(&i.to_string()).cloned().unwrap_or(Valor::Vacio); let s = self.cadena_de(&v); if s.is_empty() { continue; } // `var=valor` entre ficheros: asignación diferida, no un fichero. if let Some(p) = posicion_asignacion(&s) { let (n, v) = (s[..p].to_string(), desescapar(&s[p + 1..])); self.poner_var(&n, Valor::de_entrada(&v)); continue; } self.poner_var("FILENAME", Valor::Cad(s.clone())); self.poner_var("FNR", Valor::Num(0.0)); if s == "-" || s == "/dev/stdin" { self.lector_actual = Some(Box::new(BufReader::new(std::io::stdin()))); return true; } match std::fs::File::open(&s) { Ok(f) => { self.lector_actual = Some(Box::new(BufReader::new(f))); return true; } Err(e) => { eprintln!("akllay: no puedo leer `{s}`: {e}"); self.codigo_salida = 2; continue; } } } // Ningún operando de fichero: se lee la entrada estándar, una sola vez. if !self.uso_stdin() { self.marcar_stdin(); self.lector_actual = Some(Box::new(BufReader::new(std::io::stdin()))); return true; } false } fn uso_stdin(&self) -> bool { self.globales.contains_key("@stdin-usado") } fn marcar_stdin(&mut self) { self.globales.insert("@stdin-usado".into(), Celda::Escalar(Valor::Num(1.0))); } // ── Evaluación de expresiones ──────────────────────────────────────────────────────────────── pub fn eval(&mut self, e: &Expr) -> Result { Ok(match e { Expr::Num(n) => Valor::Num(*n), Expr::Cad(s) => Valor::Cad(s.clone()), Expr::Grupo(inner) => self.eval(inner)?, Expr::Ere(r) => { // Una ERE suelta vale `$0 ~ /re/`. let reg = self.leer_campo(0); let s = self.cadena_de(®); Valor::Num(if r.casa(&s) { 1.0 } else { 0.0 }) } // ⚠ Leer NF obliga a PARTIR el registro: si no, `{print NF}` da 0 hasta que alguien // toque un campo. El estado perezoso (`sucio_campos`) es lo que hace barato asignar a // `$0`, y el precio es acordarse de forzarlo en cada lectura que dependa de los campos. Expr::Var(n) if n == "NF" => { self.asegurar_campos(); self.leer_var(n) } Expr::Var(n) => self.leer_var(n), Expr::Campo(i) => { let idx = self.eval(i)?.numero(); if idx < 0.0 { return Err(format!("intento de acceder al campo {idx}")); } self.leer_campo(idx as usize) } Expr::Indice(n, idx) => { let clave = self.clave(idx)?; let a = self.array(n); // Leer un elemento que no existe lo CREA vacío. Es observable con `in`, y es lo que // manda POSIX: `if (a["x"] == "") ...` deja `"x"` dentro del array. let mut m = a.borrow_mut(); m.entry(clave).or_insert(Valor::Vacio).clone() } Expr::Asigna(l, v) => { let val = self.eval(v)?; self.asignar(l, val.clone())?; val } Expr::AsignaOp(l, op, v) => { let actual = self.leer_lugar(l)?; let d = self.eval(v)?; let r = Valor::Num(aritmetica(*op, actual.numero(), d.numero())); self.asignar(l, r.clone())?; r } Expr::Bin(op, a, b) => { let x = self.eval(a)?.numero(); let y = self.eval(b)?.numero(); Valor::Num(aritmetica(*op, x, y)) } Expr::Cmp(op, a, b) => { let x = self.eval(a)?; let y = self.eval(b)?; let o = x.comparar(&y, &self.convfmt()); let r = match op { CmpOp::Lt => o.is_lt(), CmpOp::Le => o.is_le(), CmpOp::Gt => o.is_gt(), CmpOp::Ge => o.is_ge(), CmpOp::Eq => o.is_eq(), CmpOp::Ne => o.is_ne(), }; Valor::Num(if r { 1.0 } else { 0.0 }) } Expr::Coincide(a, b, negado) => { let v = self.eval(a)?; let s = self.cadena_de(&v); let re = self.ere_de(b)?; let casa = re.casa(&s); Valor::Num(if casa != *negado { 1.0 } else { 0.0 }) } Expr::Y(a, b) => { if !self.eval(a)?.verdad() { Valor::Num(0.0) } else { Valor::Num(if self.eval(b)?.verdad() { 1.0 } else { 0.0 }) } } Expr::O(a, b) => { if self.eval(a)?.verdad() { Valor::Num(1.0) } else { Valor::Num(if self.eval(b)?.verdad() { 1.0 } else { 0.0 }) } } Expr::No(a) => Valor::Num(if self.eval(a)?.verdad() { 0.0 } else { 1.0 }), Expr::Neg(a) => Valor::Num(-self.eval(a)?.numero()), Expr::Pos(a) => Valor::Num(self.eval(a)?.numero()), Expr::Ternario(c, a, b) => if self.eval(c)?.verdad() { self.eval(a)? } else { self.eval(b)? }, Expr::Concat(a, b) => { let x = self.eval(a)?; let y = self.eval(b)?; Valor::Cad(format!("{}{}", self.cadena_de(&x), self.cadena_de(&y))) } Expr::Incr(l, previo, delta) => { let antes = self.leer_lugar(l)?.numero(); let despues = antes + delta; self.asignar(l, Valor::Num(despues))?; Valor::Num(if *previo { despues } else { antes }) } Expr::En(idx, arr) => { let clave = self.clave(idx)?; let a = self.array(arr); let hay = a.borrow().contains_key(&clave); Valor::Num(if hay { 1.0 } else { 0.0 }) } Expr::Builtin(n, args) => self.builtin(n, args)?, Expr::Llamada(n, args) => self.llamar(n, args)?, Expr::Getline(g) => self.getline(g)?, }) } fn leer_lugar(&mut self, l: &Lugar) -> Result { Ok(match l { Lugar::Var(n) => self.leer_var(n), Lugar::Campo(i) => { let idx = self.eval(i)?.numero() as usize; self.leer_campo(idx) } Lugar::Indice(n, idx) => { let clave = self.clave(idx)?; let a = self.array(n); let v = a.borrow().get(&clave).cloned(); v.unwrap_or(Valor::Vacio) } }) } fn asignar(&mut self, l: &Lugar, v: Valor) -> Result<(), String> { match l { Lugar::Var(n) => self.poner_var(n, v), Lugar::Campo(i) => { let idx = self.eval(i)?.numero() as usize; let s = self.cadena_de(&v); self.poner_campo(idx, s); } Lugar::Indice(n, idx) => { let clave = self.clave(idx)?; let a = self.array(n); a.borrow_mut().insert(clave, v); } } Ok(()) } // ── Funciones del programa ─────────────────────────────────────────────────────────────────── fn llamar(&mut self, nombre: &str, args: &[Expr]) -> Result { let f = self.prog.funciones.iter().find(|f| f.nombre == nombre).cloned() .ok_or_else(|| format!("función no definida: `{nombre}`"))?; let mut marco: HashMap = HashMap::new(); for (i, p) in f.params.iter().enumerate() { match args.get(i) { None => { marco.insert(p.clone(), Celda::Escalar(Valor::Vacio)); } Some(a) => { // ⚠ Un array se pasa POR REFERENCIA y un escalar por valor. Cuál es cada uno se // decide mirando qué hay en la variable: si ya es un array, se comparte el Rc. if let Expr::Var(vn) = a { let es_array = matches!(self.celda(vn), Some(Celda::Array(_))); if es_array { let arr = self.array(vn); marco.insert(p.clone(), Celda::Array(arr)); continue; } // Una variable nunca usada que se pasa a un parámetro que la función usa // como array tiene que compartir el array. Se resuelve creándolo ya. if self.celda(vn).is_none() && self.usa_como_array(&f, p) { let arr = self.array(vn); marco.insert(p.clone(), Celda::Array(arr)); continue; } } let v = self.eval(a)?; marco.insert(p.clone(), Celda::Escalar(v)); } } } self.marcos.push(marco); let r = self.bloque(&f.cuerpo); self.marcos.pop(); match r? { Flujo::Retorna(v) => Ok(v), Flujo::Sale(c) => Err(format!("@salir:{c}")), _ => Ok(Valor::Vacio), } } /// ¿La función usa este parámetro como array? Se mira el cuerpo: `p[...]`, `in p`, `delete p`. fn usa_como_array(&self, f: &Funcion, p: &str) -> bool { fn en_sent(s: &Sent, p: &str) -> bool { match s { Sent::Expr(e) => en_expr(e, p), Sent::Print(a, _) | Sent::Printf(a, _) => a.iter().any(|e| en_expr(e, p)), Sent::Bloque(b) => b.iter().any(|s| en_sent(s, p)), Sent::Si(c, a, b) => en_expr(c, p) || en_sent(a, p) || b.as_ref().map_or(false, |b| en_sent(b, p)), Sent::Mientras(c, b) => en_expr(c, p) || en_sent(b, p), Sent::HazMientras(b, c) => en_expr(c, p) || en_sent(b, p), Sent::Para(i, c, s2, b) => i.as_ref().map_or(false, |x| en_sent(x, p)) || c.as_ref().map_or(false, |x| en_expr(x, p)) || s2.as_ref().map_or(false, |x| en_sent(x, p)) || en_sent(b, p), Sent::ParaEn(_, a, b) => a == p || en_sent(b, p), Sent::Borra(n, _) | Sent::BorraTodo(n) => n == p, Sent::Sale(e) | Sent::Retorna(e) => e.as_ref().map_or(false, |x| en_expr(x, p)), _ => false, } } fn en_lugar(l: &Lugar, p: &str) -> bool { match l { Lugar::Indice(n, _) => n == p, Lugar::Campo(e) => en_expr(e, p), _ => false } } fn en_expr(e: &Expr, p: &str) -> bool { match e { Expr::Indice(n, _) => n == p, Expr::En(_, n) => n == p, Expr::Llamada(_, args) | Expr::Builtin(_, args) => args.iter().any(|a| en_expr(a, p)) || args.iter().any(|a| matches!(a, Expr::Var(v) if v == p)), Expr::Bin(_, a, b) | Expr::Cmp(_, a, b) | Expr::Y(a, b) | Expr::O(a, b) | Expr::Concat(a, b) => en_expr(a, p) || en_expr(b, p), Expr::Coincide(a, b, _) => en_expr(a, p) || en_expr(b, p), Expr::No(a) | Expr::Neg(a) | Expr::Pos(a) | Expr::Grupo(a) | Expr::Campo(a) => en_expr(a, p), Expr::Ternario(a, b, c) => en_expr(a, p) || en_expr(b, p) || en_expr(c, p), // ⚠ `a["k"] = 1` marca a `a` como array, y el nombre está en el LUGAR, no en el // valor. Mirar sólo el valor dejaba `funcion-array-ref` sin compartir el array. Expr::Asigna(l, v) => en_lugar(l, p) || en_expr(v, p), Expr::AsignaOp(l, _, v) => en_lugar(l, p) || en_expr(v, p), Expr::Incr(l, _, _) => en_lugar(l, p), _ => false, } } f.cuerpo.iter().any(|s| en_sent(s, p)) } // ── getline ────────────────────────────────────────────────────────────────────────────────── fn getline(&mut self, g: &Getline) -> Result { let rs = self.leer_var("RS").cadena("%.6g"); match g { Getline::Simple(l) => { match self.siguiente_registro() { None => Ok(Valor::Num(0.0)), Some(r) => { match l { None => { self.poner_registro(r); } Some(lug) => { let lug = (**lug).clone(); self.asignar(&lug, Valor::de_entrada(&r))?; } } Ok(Valor::Num(1.0)) } } } Getline::DeFichero(l, f) => { let fv = self.eval(f)?; let nombre = self.cadena_de(&fv); if !self.entradas.contains_key(&nombre) { let lector: Box = if nombre == "-" || nombre == "/dev/stdin" { Box::new(BufReader::new(std::io::stdin())) } else { match std::fs::File::open(&nombre) { Ok(f) => Box::new(BufReader::new(f)), // getline devuelve -1 cuando no puede abrir: es un valor, no un error. Err(_) => return Ok(Valor::Num(-1.0)), } }; self.entradas.insert(nombre.clone(), Entrada { lector, hijo: None }); } let ent = self.entradas.get_mut(&nombre).unwrap(); match Self::leer_registro(ent.lector.as_mut(), &rs) { None => Ok(Valor::Num(0.0)), Some(r) => { match l { // `getline < f` sin variable pone $0 y NF, pero NO toca NR. None => { self.poner_registro(r); } Some(lug) => { let lug = (**lug).clone(); self.asignar(&lug, Valor::de_entrada(&r))?; } } Ok(Valor::Num(1.0)) } } } Getline::DeTuberia(cmd, l) => { let cv = self.eval(cmd)?; let nombre = self.cadena_de(&cv); if !self.entradas.contains_key(&nombre) { let _ = self.stdout.flush(); // que lo ya impreso salga antes que lo del hijo let mut hijo = std::process::Command::new("/bin/sh") .arg("-c").arg(&nombre) .stdout(std::process::Stdio::piped()) .spawn().map_err(|e| format!("no pude lanzar `{nombre}`: {e}"))?; let salida = hijo.stdout.take().unwrap(); self.entradas.insert(nombre.clone(), Entrada { lector: Box::new(BufReader::new(salida)), hijo: Some(hijo), }); } let ent = self.entradas.get_mut(&nombre).unwrap(); match Self::leer_registro(ent.lector.as_mut(), &rs) { None => Ok(Valor::Num(0.0)), Some(r) => { // `"cmd" | getline` SÍ incrementa NR (POSIX lo distingue de `getline < f`). let nr = self.leer_var("NR").numero() + 1.0; self.poner_var("NR", Valor::Num(nr)); match l { None => { self.poner_registro(r); } Some(lug) => { let lug = (**lug).clone(); self.asignar(&lug, Valor::de_entrada(&r))?; } } Ok(Valor::Num(1.0)) } } } } } // ── Funciones incorporadas ─────────────────────────────────────────────────────────────────── fn builtin(&mut self, n: &str, args: &[Expr]) -> Result { match n { "length" => { if args.is_empty() { let v = self.leer_campo(0); return Ok(Valor::Num(self.cadena_de(&v).chars().count() as f64)); } // `length(a)` con un array da su número de elementos. if let Expr::Var(vn) = &args[0] { if let Some(Celda::Array(a)) = self.celda(vn) { return Ok(Valor::Num(a.borrow().len() as f64)); } } let v = self.eval(&args[0])?; Ok(Valor::Num(self.cadena_de(&v).chars().count() as f64)) } "substr" => { let v = self.eval(&args[0])?; let s: Vec = self.cadena_de(&v).chars().collect(); let m = self.eval(&args[1])?.numero(); // ⚠ Los índices empiezan en 1, y los dos rincones de esta función se resolvieron // MIDIENDO los dos oráculos, no leyendo el estándar — que acá es ambiguo: // · un inicio ≤ 0 se recorta a 1 y **el largo NO se ajusta**: // `substr("abcdef", -1, 3)` es `abc` en busybox y en gawk --posix, no `a`. // La lectura estricta de POSIX daría `a` (las posiciones -1,0,1); ningún awk // real hace eso, y un guión escrito contra ellos es lo que hay que ejecutar. // · los no enteros se TRUNCAN hacia cero: `substr("abcdef", 1.5, 2.5)` es `ab`. let inicio = m.trunc() as i64; let (desde, largo) = if args.len() > 2 { let l = self.eval(&args[2])?.numero().trunc() as i64; (inicio.max(1), l) } else { let d = inicio.max(1); (d, s.len() as i64 - d + 1) }; if largo <= 0 || desde > s.len() as i64 { return Ok(Valor::Cad(String::new())); } let a = (desde - 1) as usize; let b = ((desde - 1 + largo) as usize).min(s.len()); Ok(Valor::Cad(s[a..b].iter().collect())) } "index" => { let a = self.eval(&args[0])?; let sa = self.cadena_de(&a); let b = self.eval(&args[1])?; let sb = self.cadena_de(&b); let ha: Vec = sa.chars().collect(); let ag: Vec = sb.chars().collect(); if ag.is_empty() { return Ok(Valor::Num(if ha.is_empty() { 0.0 } else { 1.0 })); } for i in 0..ha.len().saturating_sub(ag.len() - 1) { if ha[i..i + ag.len()] == ag[..] { return Ok(Valor::Num((i + 1) as f64)); } } Ok(Valor::Num(0.0)) } "split" => { let v = self.eval(&args[0])?; let s = self.cadena_de(&v); let nombre = match &args[1] { Expr::Var(n) => n.clone(), Expr::Indice(n, _) => n.clone(), _ => return Err("split necesita un array como segundo argumento".into()), }; let fs = if args.len() > 2 { match &args[2] { Expr::Ere(r) => r.fuente().to_string(), e => { let v = self.eval(e)?; self.cadena_de(&v) } } } else { self.leer_var("FS").cadena("%.6g") }; let trozos = if s.is_empty() { Vec::new() } else { let mut cache: Vec<(String, Rc)> = Vec::new(); let t = Self::partir_con(&s, &fs, |p| { if let Some((_, r)) = cache.iter().find(|(k, _)| k == p) { return r.clone(); } let r = Rc::new(Regex::nueva(p).unwrap_or_else(|e| { eprintln!("akllay: expresión regular inválida /{p}/: {e}"); std::process::exit(2); })); cache.push((p.to_string(), r.clone())); r }); t }; let a = self.array(&nombre); a.borrow_mut().clear(); for (i, t) in trozos.iter().enumerate() { a.borrow_mut().insert((i + 1).to_string(), Valor::de_entrada(t)); } Ok(Valor::Num(trozos.len() as f64)) } "sub" | "gsub" => { let global = n == "gsub"; let re = self.ere_de(&args[0])?; let rv = self.eval(&args[1])?; let reemplazo = self.cadena_de(&rv); let destino = if args.len() > 2 { match &args[2] { Expr::Var(v) => Lugar::Var(v.clone()), Expr::Campo(i) => Lugar::Campo(i.clone()), Expr::Indice(v, i) => Lugar::Indice(v.clone(), i.clone()), Expr::Grupo(g) => match &**g { Expr::Var(v) => Lugar::Var(v.clone()), Expr::Campo(i) => Lugar::Campo(i.clone()), Expr::Indice(v, i) => Lugar::Indice(v.clone(), i.clone()), _ => return Err("sub/gsub necesita algo asignable".into()), }, _ => return Err("sub/gsub necesita algo asignable".into()), } } else { Lugar::Campo(Box::new(Expr::Num(0.0))) }; let actual = self.leer_lugar(&destino)?; let texto = self.cadena_de(&actual); let (nuevo, cuantas) = sustituir(&re, &texto, &reemplazo, global); if cuantas > 0 { self.asignar(&destino, Valor::Cad(nuevo))?; } Ok(Valor::Num(cuantas as f64)) } "match" => { let v = self.eval(&args[0])?; let s = self.cadena_de(&v); let re = self.ere_de(&args[1])?; let cs: Vec = s.chars().collect(); match re.buscar(&cs, 0) { Some((a, b)) => { self.poner_var("RSTART", Valor::Num((a + 1) as f64)); self.poner_var("RLENGTH", Valor::Num((b - a) as f64)); Ok(Valor::Num((a + 1) as f64)) } None => { self.poner_var("RSTART", Valor::Num(0.0)); self.poner_var("RLENGTH", Valor::Num(-1.0)); Ok(Valor::Num(0.0)) } } } "sprintf" => { if args.is_empty() { return Ok(Valor::Cad(String::new())); } let fv = self.eval(&args[0])?; let fmt = self.cadena_de(&fv); let mut vals = Vec::new(); for a in &args[1..] { let v = self.eval(a)?; vals.push(match v { Valor::Num(x) => Arg::Numero(x), Valor::Cad(s) => Arg::Texto(s), Valor::StrNum(s, x) => Arg::Entrada(s, x), Valor::Vacio => Arg::Texto(String::new()), }); } Ok(Valor::Cad(formato::printf(&fmt, &vals, &self.convfmt()))) } "sin" => Ok(Valor::Num(self.arg_num(args, 0)?.sin())), "cos" => Ok(Valor::Num(self.arg_num(args, 0)?.cos())), "atan2" => { let a = self.arg_num(args, 0)?; let b = self.arg_num(args, 1)?; Ok(Valor::Num(a.atan2(b))) } "exp" => Ok(Valor::Num(self.arg_num(args, 0)?.exp())), "log" => Ok(Valor::Num(self.arg_num(args, 0)?.ln())), "sqrt" => Ok(Valor::Num(self.arg_num(args, 0)?.sqrt())), "int" => Ok(Valor::Num(self.arg_num(args, 0)?.trunc())), "rand" => { // xorshift64*: determinista y suficiente. `rand` de awk NO promete calidad // criptográfica; lo que sí promete es repetirse con la misma semilla. self.semilla ^= self.semilla << 13; self.semilla ^= self.semilla >> 7; self.semilla ^= self.semilla << 17; let x = self.semilla.wrapping_mul(0x2545F4914F6CDD1D); Ok(Valor::Num((x >> 11) as f64 / (1u64 << 53) as f64)) } "srand" => { let previa = self.semilla_previa; let nueva = if args.is_empty() { ahora_ms() as f64 } else { self.arg_num(args, 0)? }; self.semilla_previa = nueva; self.semilla = (nueva as i64 as u64) | 1; // el 0 congelaría el xorshift Ok(Valor::Num(previa)) } "tolower" | "toupper" => { let v = self.eval(&args[0])?; let s = self.cadena_de(&v); Ok(Valor::Cad(if n == "tolower" { s.to_lowercase() } else { s.to_uppercase() })) } "system" => { let v = self.eval(&args[0])?; let cmd = self.cadena_de(&v); let _ = self.stdout.flush(); // POSIX: se vacía la salida ANTES de lanzar let r = std::process::Command::new("/bin/sh").arg("-c").arg(&cmd).status(); Ok(Valor::Num(r.ok().and_then(|s| s.code()).unwrap_or(-1) as f64)) } "close" => { let v = self.eval(&args[0])?; let nombre = self.cadena_de(&v); Ok(Valor::Num(self.cerrar(&nombre) as f64)) } "fflush" => { if args.is_empty() { let _ = self.stdout.flush(); return Ok(Valor::Num(0.0)); } let v = self.eval(&args[0])?; let nombre = self.cadena_de(&v); if let Some(s) = self.salidas.get_mut(&nombre) { let _ = s.escritor.flush(); } else { let _ = self.stdout.flush(); } Ok(Valor::Num(0.0)) } otro => Err(format!("función incorporada desconocida: `{otro}`")), } } fn arg_num(&mut self, args: &[Expr], i: usize) -> Result { match args.get(i) { Some(e) => Ok(self.eval(e)?.numero()), None => Ok(0.0) } } } fn redondear(x: f64) -> i64 { // POSIX dice «truncado al entero más cercano», que es redondeo a la mitad hacia arriba. (x + 0.5).floor() as i64 } fn aritmetica(op: BinOp, a: f64, b: f64) -> f64 { match op { BinOp::Suma => a + b, BinOp::Resta => a - b, BinOp::Mult => a * b, BinOp::Div => a / b, // `%` de awk es `fmod`, no el resto entero: `5.5 % 2` es 1.5. BinOp::Modulo => a % b, BinOp::Potencia => a.powf(b), } } /// `sub`/`gsub`. En el reemplazo, `&` es el texto que casó y `\&` es un `&` literal. /// /// ⚠ La regla que decide el caso raro: **una coincidencia VACÍA pegada al final de la anterior no /// sustituye**. Sin ella, `gsub(/a*/,"-")` sobre `"xax"` da `-x--x-` con 4 sustituciones —que es lo /// que hace el awk de busybox— en vez de `-x-x-` con 3, que es lo que hacen gawk y el awk original /// y lo que se deduce de POSIX. El banco diferencial lo destapó como una discrepancia ENTRE los /// oráculos, que es justo para lo que sirve tener dos. fn sustituir(re: &Regex, texto: &str, reemplazo: &str, global: bool) -> (String, usize) { let cs: Vec = texto.chars().collect(); let mut salida = String::new(); let mut i = 0; let mut cuantas = 0; let mut ultimo_vacio_en: Option = None; while i <= cs.len() { match re.buscar(&cs, i) { None => break, Some((a, b)) => { // Una coincidencia VACÍA justo donde terminó la anterior no cuenta: si no, // `gsub(/x*/,"-")` sobre "xax" produciría guiones de más. if a == b && ultimo_vacio_en == Some(a) { if a >= cs.len() { break; } salida.push(cs[a]); i = a + 1; continue; } salida.extend(&cs[i..a]); let casado: String = cs[a..b].iter().collect(); let mut rc = reemplazo.chars().peekable(); while let Some(c) = rc.next() { match c { '&' => salida.push_str(&casado), '\\' => match rc.peek() { Some('&') => { salida.push('&'); rc.next(); } Some('\\') => { salida.push('\\'); rc.next(); } _ => salida.push('\\'), }, otro => salida.push(otro), } } cuantas += 1; if !global { salida.extend(&cs[b..]); return (salida, cuantas); } if a == b { ultimo_vacio_en = Some(a); if a >= cs.len() { i = a; break; } salida.push(cs[a]); i = a + 1; } else { // Tras una coincidencia real, la posición donde termina queda VEDADA para una // vacía: es la regla de arriba, y es la única diferencia con busybox. ultimo_vacio_en = Some(b); i = b; } } } } if i <= cs.len() { salida.extend(&cs[i.min(cs.len())..]); } (salida, cuantas) } /// ¿`s` tiene forma `nombre=valor`? Devuelve la posición del `=`. pub fn posicion_asignacion(s: &str) -> Option { let cs: Vec = s.chars().collect(); if cs.is_empty() { return None; } if !(cs[0].is_alphabetic() || cs[0] == '_') { return None; } let mut i = 1; while i < cs.len() && (cs[i].is_alphanumeric() || cs[i] == '_') { i += 1; } if i < cs.len() && cs[i] == '=' { return Some(s.char_indices().nth(i).map(|(p, _)| p).unwrap_or(i)); } None } /// Los escapes de una asignación de la línea de órdenes se procesan como en una cadena. pub fn desescapar(s: &str) -> String { let mut out = String::new(); let mut it = s.chars().peekable(); while let Some(c) = it.next() { if c != '\\' { out.push(c); continue; } match it.next() { None => out.push('\\'), Some('n') => out.push('\n'), Some('t') => out.push('\t'), Some('r') => out.push('\r'), Some('\\') => out.push('\\'), Some('"') => out.push('"'), Some('/') => out.push('/'), Some('a') => out.push('\x07'), Some('b') => out.push('\x08'), Some('f') => out.push('\x0c'), Some('v') => out.push('\x0b'), Some(d @ '0'..='7') => { let mut v = d.to_digit(8).unwrap(); let mut n = 1; while n < 3 { match it.peek() { Some(x) if x.is_digit(8) => { v = v * 8 + x.to_digit(8).unwrap(); it.next(); n += 1; } _ => break, } } out.push(char::from_u32(v).unwrap_or('\0')); } Some(o) => { out.push('\\'); out.push(o); } } } out } impl Interprete { /// Acceso al array desde `main` (ENVIRON, ARGV), que es el único sitio de fuera que los toca. pub fn array_publico(&mut self, n: &str) -> Rc>> { self.array(n) } }