//! Emisión de expresiones y condiciones: cada nodo del IR se convierte //! en un fragmento de código Rust (un `String`). use charka_ir::{BinOp, CmpOp, Cond, Expr, Figurative, Operand}; use crate::sym::{FieldKind, Symbols}; /// Un literal de texto Rust con las comillas y los escapes adecuados. pub(crate) fn rust_str(s: &str) -> String { let mut out = String::from("\""); for c in s.chars() { match c { '"' => out.push_str("\\\""), '\\' => out.push_str("\\\\"), '\n' => out.push_str("\\n"), '\r' => out.push_str("\\r"), '\t' => out.push_str("\\t"), _ => out.push(c), } } out.push('"'); out } /// El texto que representa una constante figurativa. pub(crate) fn figurative_text(f: Figurative) -> &'static str { match f { Figurative::Zero => "0", Figurative::Space => " ", Figurative::Quote => "\"", Figurative::HighValue | Figurative::LowValue | Figurative::Null => "", } } /// El carácter de relleno de una figurativa, para `Text::fill`. pub(crate) fn figurative_fill(f: Figurative) -> char { match f { Figurative::Zero => '0', Figurative::Quote => '"', _ => ' ', } } /// La referencia Rust a un campo (un dato escalar `self.x` o un /// elemento de tabla `self.x[idx]`) y el tipo del campo. `None` si el /// operando no es una referencia a dato. pub(crate) fn field_ref(sym: &Symbols, op: &Operand) -> Option<(String, FieldKind)> { match op { Operand::Data(name) => sym .lookup(name) .map(|f| (format!("self.{}", f.ident), f.kind)), Operand::Indexed { name, index } => sym.lookup(name).map(|f| { ( format!("self.{}[{}]", f.ident, subscript(sym, index)), f.kind, ) }), _ => None, } } /// Un subíndice de tabla como expresión `usize`. COBOL es 1-based; /// Rust 0-based — de ahí el `saturating_sub(1)`. fn subscript(sym: &Symbols, index: &Operand) -> String { format!( "(({}).rescale(0, Rounding::Truncate).mantissa() as usize).saturating_sub(1)", operand_decimal(sym, index) ) } /// Un operando como expresión de tipo `Decimal`. pub(crate) fn operand_decimal(sym: &Symbols, op: &Operand) -> String { match op { Operand::Num(n) => format!("dec({})", rust_str(n)), Operand::Str(s) => format!( "Decimal::parse({}).unwrap_or_else(|_| Decimal::zero())", rust_str(s) ), Operand::Figurative(_) => "Decimal::zero()".to_string(), Operand::Data(_) | Operand::Indexed { .. } => match field_ref(sym, op) { Some((lref, FieldKind::Num { .. })) => format!("{lref}.value()"), Some((lref, FieldKind::Text { .. })) => format!( "Decimal::parse({lref}.display().trim()).unwrap_or_else(|_| Decimal::zero())" ), None => "Decimal::zero() /* charka: dato no resuelto */".to_string(), }, } } /// Un operando como expresión de tipo `&str` (para texto). pub(crate) fn operand_str(sym: &Symbols, op: &Operand) -> String { match op { Operand::Str(s) => rust_str(s), Operand::Num(n) => rust_str(n), Operand::Figurative(f) => rust_str(figurative_text(*f)), Operand::Data(_) | Operand::Indexed { .. } => match field_ref(sym, op) { Some((lref, _)) => format!("{lref}.display().as_str()"), None => "\"\" /* charka: dato no resuelto */".to_string(), }, } } /// Un operando como expresión que implementa `Display` (para `DISPLAY`). pub(crate) fn operand_display(sym: &Symbols, op: &Operand) -> String { match op { Operand::Str(s) => rust_str(s), Operand::Num(n) => rust_str(n), Operand::Figurative(f) => rust_str(figurative_text(*f)), Operand::Data(_) | Operand::Indexed { .. } => match field_ref(sym, op) { Some((lref, _)) => format!("{lref}.display()"), None => "\"\" /* charka: dato no resuelto */".to_string(), }, } } /// Emite una expresión aritmética como código Rust de tipo `Decimal`. pub(crate) fn emit_expr(sym: &Symbols, e: &Expr) -> String { match e { Expr::Operand(op) => operand_decimal(sym, op), Expr::Neg(inner) => format!("Decimal::zero().sub(&({}))", emit_expr(sym, inner)), Expr::Binary { op, lhs, rhs } => { let l = emit_expr(sym, lhs); let r = emit_expr(sym, rhs); match op { BinOp::Add => format!("({l}).add(&({r}))"), BinOp::Sub => format!("({l}).sub(&({r}))"), BinOp::Mul => format!("({l}).mul(&({r}))"), BinOp::Div => format!( "({l}).div(&({r}), 9, Rounding::Truncate).unwrap_or_else(|_| Decimal::zero())" ), BinOp::Pow => "Decimal::zero() /* charka: ** no soportado */".to_string(), } } } } /// Emite una condición como código Rust de tipo `bool`. pub(crate) fn emit_cond(sym: &Symbols, c: &Cond) -> String { match c { Cond::Compare { lhs, op, rhs } => emit_compare(sym, lhs, *op, rhs), Cond::Named(name) => match sym.condition(name) { // Un nombre de condición (88) equivale a comparar su dato // padre con el valor que la hace verdadera. Some(cn) => emit_cond( sym, &Cond::Compare { lhs: Operand::Data(cn.parent.clone()), op: CmpOp::Eq, rhs: cn.value.clone(), }, ), None => format!("false /* charka: condición 88 no resuelta: {name} */"), }, Cond::Not(inner) => format!("!({})", emit_cond(sym, inner)), Cond::And(a, b) => format!("({}) && ({})", emit_cond(sym, a), emit_cond(sym, b)), Cond::Or(a, b) => format!("({}) || ({})", emit_cond(sym, a), emit_cond(sym, b)), } } /// Emite una comparación: numérica si ambos lados lo son, alfanumérica /// si alguno es texto. fn emit_compare(sym: &Symbols, lhs: &Operand, op: CmpOp, rhs: &Operand) -> String { if is_text_operand(sym, lhs) || is_text_operand(sym, rhs) { let method = match op { CmpOp::Eq => "is_eq", CmpOp::Ne => "is_ne", CmpOp::Lt => "is_lt", CmpOp::Gt => "is_gt", CmpOp::Le => "is_le", CmpOp::Ge => "is_ge", }; format!( "cobol_text_cmp({}, {}).{method}()", operand_str(sym, lhs), operand_str(sym, rhs) ) } else { let rust_op = match op { CmpOp::Eq => "==", CmpOp::Ne => "!=", CmpOp::Lt => "<", CmpOp::Gt => ">", CmpOp::Le => "<=", CmpOp::Ge => ">=", }; format!( "({}) {rust_op} ({})", operand_decimal(sym, lhs), operand_decimal(sym, rhs) ) } } /// ¿El operando es alfanumérico? fn is_text_operand(sym: &Symbols, op: &Operand) -> bool { match op { Operand::Str(_) => true, Operand::Data(_) | Operand::Indexed { .. } => matches!( field_ref(sym, op).map(|(_, k)| k), Some(FieldKind::Text { .. }) ), _ => false, } }