3902763daa
Los arrays de COBOL, que antes el transpilador descartaba en silencio.
Una rebanada vertical amplia que atraviesa el pipeline entero.
- Parser: la cláusula OCCURS n [TIMES] se captura en DataItem.
- IR: Operand::Indexed { name, index } — una referencia ELEM(I), con
subíndice 1-based. Los destinos de los statements pasan de
Vec<String> a Vec<Operand>, así que se puede escribir a un elemento
de tabla (MOVE x TO ELEM(I), COMPUTE ELEM(I) = ...). model::Field
gana occurs: Option<u32>.
- Codegen: un campo OCCURS se emite como Vec<Num>/Vec<Text>,
inicializado con vec![..; n]; una referencia con subíndice indexa el
vector (1-based -> 0-based).
- Shadow: en el intérprete todo campo es un vector — un escalar es de
longitud 1, una tabla de n; las referencias se resuelven a
(nombre, índice).
- Corpus: programa nuevo 11-tabla (llena una tabla con cuadrados y los
suma). Verificado: el intérprete sombra y el crate compilado por
scaffold dan ambos SUMA DE CUADRADOS = 000055.
Alcance v1: OCCURS elemental, una dimensión, subíndice de un operando.
Fuera: OCCURS de grupo, multidimensional, DEPENDING ON.
Tests: charka-parser 16, charka-ir 24, charka-codegen 18,
charka-shadow 16. fmt + clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
205 lines
7.2 KiB
Rust
205 lines
7.2 KiB
Rust
//! 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,
|
|
}
|
|
}
|