//! `charka-codegen` — emisión de Rust desde el IR del transpilador. //! //! Etapa final del pipeline COBOL→Rust: toma el [`Ir`] de `charka-ir` //! y produce un fuente Rust (un `String`) que, compilado contra //! `charka-runtime`, ejecuta la lógica del programa COBOL original. //! //! La forma del código emitido: //! //! - Un `struct Program` con un campo por cada dato elemental — `Num` //! para los numéricos, `Text` para los alfanuméricos. //! - `Program::new()` inicializa los campos desde sus cláusulas `VALUE`. //! - Un método `p_
(&mut self)` por cada párrafo del PROCEDURE.
//! - `run()` los encadena en orden (el «caer» de COBOL); `main()`
//! construye el `Program` y lo corre.
//!
//! Es **tolerante**: lo que no sabe transpilar (un `Stmt::Unknown`, un
//! dato sin resolver, `**`) se emite como un comentario `// charka:` —
//! el código generado siempre compila.
//!
//! Alcance v1 — fuera: grupos como campo propio, `REDEFINES`,
//! `OCCURS`/tablas, `PERFORM ... THRU` como rango, E/S de ficheros,
//! `EVALUATE`, CICS y SQL embebido.
#![forbid(unsafe_code)]
mod emit;
mod expr;
mod stmt;
mod sym;
use std::collections::HashMap;
use charka_ir::{Ir, Procedure};
use emit::Emitter;
use expr::rust_str;
use stmt::emit_stmt;
use sym::{paragraph_method, Field, FieldKind, Symbols};
/// Transpila un [`Ir`] a un fuente Rust completo (un `main.rs`).
pub fn generate(ir: &Ir) -> String {
let sym = Symbols::build(&ir.model);
let mut em = Emitter::new();
emit_header(&mut em);
emit_struct(&mut em, &sym);
emit_impl(&mut em, &sym, ir);
emit_main(&mut em);
em.finish()
}
/// El preámbulo: doc, `allow`s, el `use` del runtime y el helper `dec`.
fn emit_header(em: &mut Emitter) {
em.line("//! Generado por charka — transpilador COBOL → Rust.");
em.line("//! No editar a mano: regenerar desde el fuente COBOL.");
em.blank();
em.line(
"#![allow(dead_code, unused_mut, unused_variables, unused_parens, \
unreachable_code, clippy::all)]",
);
em.blank();
em.line("use charka_runtime::*;");
em.blank();
em.line("/// Construye un `Decimal` desde un literal numérico COBOL.");
em.line("fn dec(s: &str) -> Decimal {");
em.line(" Decimal::parse(s).expect(\"charka: literal numérico inválido\")");
em.line("}");
em.blank();
}
/// El `struct Program` con un campo por dato elemental.
fn emit_struct(em: &mut Emitter, sym: &Symbols) {
em.line("/// El estado del programa: un campo por cada dato elemental.");
em.line("struct Program {");
em.indent();
for f in &sym.fields {
let elem = match f.kind {
FieldKind::Num { .. } => "Num",
FieldKind::Text { .. } => "Text",
};
let ty = match f.occurs {
None => elem.to_string(),
Some(_) => format!("Vec<{elem}>"),
};
em.line(&format!("{}: {ty},", f.ident));
}
em.dedent();
em.line("}");
em.blank();
}
/// El bloque `impl Program`: `new`, los párrafos y `run`.
fn emit_impl(em: &mut Emitter, sym: &Symbols, ir: &Ir) {
em.line("impl Program {");
em.indent();
// new()
em.line("fn new() -> Self {");
em.indent();
em.line("Self {");
em.indent();
for f in &sym.fields {
em.line(&format!("{}: {},", f.ident, field_init(f)));
}
em.dedent();
em.line("}");
em.dedent();
em.line("}");
em.blank();
// Un método por párrafo.
let methods = paragraph_methods(ir);
for (name, proc) in &methods {
em.line(&format!("fn {name}(&mut self) {{"));
em.indent();
for s in &proc.body {
emit_stmt(em, sym, s);
}
em.dedent();
em.line("}");
em.blank();
}
// run() — encadena los párrafos en orden.
em.line("fn run(&mut self) {");
em.indent();
if methods.is_empty() {
em.line("// programa sin PROCEDURE division");
}
for (name, _) in &methods {
em.line(&format!("self.{name}();"));
}
em.dedent();
em.line("}");
em.dedent();
em.line("}");
em.blank();
}
/// El `fn main`.
fn emit_main(em: &mut Emitter) {
em.line("fn main() {");
em.indent();
em.line("Program::new().run();");
em.dedent();
em.line("}");
}
/// El inicializador de un campo, a partir de su `VALUE` ya
/// normalizado por `charka-ir`. Una tabla (`OCCURS n`) se inicializa
/// como un `Vec` de `n` copias del valor inicial.
fn field_init(f: &Field) -> String {
let scalar = match &f.kind {
FieldKind::Num { int, frac, signed } => format!(
"Num::with_value(Picture::new({int}, {frac}, {signed}), {})",
rust_str(&f.init)
),
FieldKind::Text { len } => {
format!("Text::with_value({len}, {})", rust_str(&f.init))
}
};
match f.occurs {
None => scalar,
Some(n) => format!("vec![{scalar}; {n}]"),
}
}
/// Asigna a cada párrafo un nombre de método único.
fn paragraph_methods(ir: &Ir) -> Vec<(String, &Procedure)> {
let mut seen: HashMap