//! `$(( … ))`. Aritmética entera con signo (POSIX §2.6.4: la misma que el C de `long`), con //! precedencia completa, ternario y asignación. Propio: es un descenso recursivo de 150 líneas. //! //! La división por cero es un ERROR, no un 0 silencioso — un `$((x/0))` que devuelve 0 es //! exactamente la clase de fallo que llega hasta el final diciendo que todo fue bien. type Num = i64; pub struct Contexto<'a> { /// Lee una variable (vacía si no existe), como manda POSIX para la aritmética. pub leer: &'a dyn Fn(&str) -> String, /// Asigna (para `x = …`, `x += …`). pub asignar: &'a mut dyn FnMut(&str, &str), } pub fn evaluar(expr: &str, ctx: &mut Contexto) -> Result { let mut p = P { cs: expr.chars().collect(), i: 0, ctx }; p.espacios(); let v = p.ternario()?; p.espacios(); if p.i < p.cs.len() { return Err(format!("aritmética: sobra `{}`", p.cs[p.i..].iter().collect::())); } Ok(v) } struct P<'a, 'b> { cs: Vec, i: usize, ctx: &'a mut Contexto<'b>, } impl P<'_, '_> { fn espacios(&mut self) { while matches!(self.cs.get(self.i), Some(c) if c.is_whitespace()) { self.i += 1; } } fn come(&mut self, s: &str) -> bool { let n = s.chars().count(); if self.cs[self.i..].starts_with(&s.chars().collect::>()[..]) { // no confundir `=` con `==`, ni `<` con `<<` self.i += n; self.espacios(); true } else { false } } fn mira(&self, s: &str) -> bool { self.cs[self.i..].starts_with(&s.chars().collect::>()[..]) } fn ternario(&mut self) -> Result { let c = self.o_logico()?; if self.come("?") { let a = self.ternario()?; if !self.come(":") { return Err("aritmética: falta `:` del ternario".into()); } let b = self.ternario()?; return Ok(if c != 0 { a } else { b }); } Ok(c) } fn o_logico(&mut self) -> Result { let mut a = self.y_logico()?; while self.mira("||") { self.come("||"); let b = self.y_logico()?; a = ((a != 0) || (b != 0)) as Num; } Ok(a) } fn y_logico(&mut self) -> Result { let mut a = self.o_bits()?; while self.mira("&&") { self.come("&&"); let b = self.o_bits()?; a = ((a != 0) && (b != 0)) as Num; } Ok(a) } fn o_bits(&mut self) -> Result { let mut a = self.xor_bits()?; while self.mira("|") && !self.mira("||") { self.come("|"); a |= self.xor_bits()?; } Ok(a) } fn xor_bits(&mut self) -> Result { let mut a = self.y_bits()?; while self.mira("^") { self.come("^"); a ^= self.y_bits()?; } Ok(a) } fn y_bits(&mut self) -> Result { let mut a = self.igualdad()?; while self.mira("&") && !self.mira("&&") { self.come("&"); a &= self.igualdad()?; } Ok(a) } fn igualdad(&mut self) -> Result { let mut a = self.relacional()?; loop { if self.mira("==") { self.come("=="); a = (a == self.relacional()?) as Num; } else if self.mira("!=") { self.come("!="); a = (a != self.relacional()?) as Num; } else { return Ok(a); } } } fn relacional(&mut self) -> Result { let mut a = self.desplazamiento()?; loop { if self.mira("<=") { self.come("<="); a = (a <= self.desplazamiento()?) as Num; } else if self.mira(">=") { self.come(">="); a = (a >= self.desplazamiento()?) as Num; } else if self.mira("<") && !self.mira("<<") { self.come("<"); a = (a < self.desplazamiento()?) as Num; } else if self.mira(">") && !self.mira(">>") { self.come(">"); a = (a > self.desplazamiento()?) as Num; } else { return Ok(a); } } } fn desplazamiento(&mut self) -> Result { let mut a = self.aditivo()?; loop { if self.mira("<<") { self.come("<<"); a = a.wrapping_shl(self.aditivo()? as u32); } else if self.mira(">>") { self.come(">>"); a = a.wrapping_shr(self.aditivo()? as u32); } else { return Ok(a); } } } fn aditivo(&mut self) -> Result { let mut a = self.multiplicativo()?; loop { self.espacios(); if self.mira("+") && !self.mira("++") { self.come("+"); a = a.wrapping_add(self.multiplicativo()?); } else if self.mira("-") && !self.mira("--") { self.come("-"); a = a.wrapping_sub(self.multiplicativo()?); } else { return Ok(a); } } } fn multiplicativo(&mut self) -> Result { let mut a = self.unario()?; loop { self.espacios(); if self.mira("*") { self.come("*"); a = a.wrapping_mul(self.unario()?); } else if self.mira("/") { self.come("/"); let b = self.unario()?; if b == 0 { return Err("aritmética: división por cero".into()); } a = a.wrapping_div(b); } else if self.mira("%") { self.come("%"); let b = self.unario()?; if b == 0 { return Err("aritmética: módulo por cero".into()); } a = a.wrapping_rem(b); } else { return Ok(a); } } } fn unario(&mut self) -> Result { self.espacios(); if self.come("!") { return Ok((self.unario()? == 0) as Num); } if self.come("~") { return Ok(!self.unario()?); } if self.mira("-") && !self.mira("--") { self.come("-"); return Ok(self.unario()?.wrapping_neg()); } if self.mira("+") && !self.mira("++") { self.come("+"); return self.unario(); } self.primario() } fn primario(&mut self) -> Result { self.espacios(); if self.come("(") { let v = self.ternario()?; if !self.come(")") { return Err("aritmética: falta `)`".into()); } return Ok(v); } match self.cs.get(self.i) { Some(c) if c.is_ascii_digit() => self.numero(), Some(c) if *c == '_' || c.is_ascii_alphabetic() => { let mut n = String::new(); while let Some(d) = self.cs.get(self.i) { if *d == '_' || d.is_ascii_alphanumeric() { n.push(*d); self.i += 1; } else { break; } } self.espacios(); // asignaciones for (op, f) in [ ("+=", 1i32), ("-=", 2), ("*=", 3), ("/=", 4), ("%=", 5), ] { if self.mira(op) { self.come(op); let b = self.ternario()?; let a = self.valor_de(&n)?; let v = match f { 1 => a.wrapping_add(b), 2 => a.wrapping_sub(b), 3 => a.wrapping_mul(b), 4 => { if b == 0 { return Err("aritmética: división por cero".into()); } a.wrapping_div(b) } _ => { if b == 0 { return Err("aritmética: módulo por cero".into()); } a.wrapping_rem(b) } }; (self.ctx.asignar)(&n, &v.to_string()); return Ok(v); } } if self.mira("=") && !self.mira("==") { self.come("="); let v = self.ternario()?; (self.ctx.asignar)(&n, &v.to_string()); return Ok(v); } self.valor_de(&n) } Some(c) => Err(format!("aritmética: no esperaba `{c}`")), None => Err("aritmética: expresión incompleta".into()), } } fn valor_de(&self, n: &str) -> Result { let s = (self.ctx.leer)(n); let s = s.trim(); if s.is_empty() { return Ok(0); } interpretar(s).ok_or_else(|| format!("aritmética: `{n}` no es un número (`{s}`)")) } fn numero(&mut self) -> Result { let inicio = self.i; while matches!(self.cs.get(self.i), Some(c) if c.is_ascii_alphanumeric()) { self.i += 1; } let s: String = self.cs[inicio..self.i].iter().collect(); self.espacios(); interpretar(&s).ok_or_else(|| format!("aritmética: `{s}` no es un número")) } } /// POSIX: decimal, octal con `0` delante, hexadecimal con `0x`. fn interpretar(s: &str) -> Option { let (negativo, s) = match s.strip_prefix('-') { Some(r) => (true, r), None => (false, s), }; let v = if let Some(h) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { Num::from_str_radix(h, 16).ok()? } else if s.len() > 1 && s.starts_with('0') { Num::from_str_radix(&s[1..], 8).ok()? } else { s.parse().ok()? }; Some(if negativo { -v } else { v }) } #[cfg(test)] mod pruebas { use super::*; use std::cell::RefCell; use std::collections::HashMap; fn ev(expr: &str, vars: &[(&str, &str)]) -> Result { let mapa: RefCell> = RefCell::new(vars.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()); let leer = |n: &str| mapa.borrow().get(n).cloned().unwrap_or_default(); let mut asignar = |n: &str, v: &str| { mapa.borrow_mut().insert(n.to_string(), v.to_string()); }; let mut ctx = Contexto { leer: &leer, asignar: &mut asignar }; evaluar(expr, &mut ctx) } #[test] fn lo_que_usan_las_cards() { // `i=$((i+1))` es la única aritmética de las cards medidas assert_eq!(ev("i+1", &[("i", "0")]).unwrap(), 1); assert_eq!(ev("i+1", &[("i", "99")]).unwrap(), 100); // una variable sin definir vale 0 assert_eq!(ev("n+1", &[]).unwrap(), 1); } #[test] fn precedencia_y_parentesis() { assert_eq!(ev("2+3*4", &[]).unwrap(), 14); assert_eq!(ev("(2+3)*4", &[]).unwrap(), 20); assert_eq!(ev("1 << 4", &[]).unwrap(), 16); assert_eq!(ev("7 % 4", &[]).unwrap(), 3); assert_eq!(ev("-3 + 1", &[]).unwrap(), -2); assert_eq!(ev("!0", &[]).unwrap(), 1); assert_eq!(ev("1 < 2 && 3 >= 3", &[]).unwrap(), 1); assert_eq!(ev("0 ? 10 : 20", &[]).unwrap(), 20); } #[test] fn bases() { assert_eq!(ev("0x10", &[]).unwrap(), 16); assert_eq!(ev("010", &[]).unwrap(), 8); assert_eq!(ev("10", &[]).unwrap(), 10); } #[test] fn division_por_cero_es_error_no_cero() { assert!(ev("1/0", &[]).is_err()); assert!(ev("1%0", &[]).is_err()); } #[test] fn asigna() { assert_eq!(ev("x = 3 + 4", &[]).unwrap(), 7); assert_eq!(ev("x += 2", &[("x", "5")]).unwrap(), 7); } }