feat(charka): PERFORM ... THRU como rango real de párrafos
PERFORM A THRU C ejecuta A, B y C; antes el transpilador sólo ejecutaba A (lo marcaba como aproximado). - charka-codegen: Symbols registra ahora los párrafos en orden con su nombre de método; Symbols::build toma el Ir completo. paragraph_range(name, thru) da los métodos del rango; emit_perform emite la llamada a cada uno. - charka-shadow: run_paragraph_range ejecuta los párrafos de name a thru inclusive. - Corpus: programa nuevo 17-rangopar (PERFORM PASO-A THRU PASO-C sobre tres párrafos). Verificado: el intérprete sombra y el crate compilado por scaffold dan la misma salida. Tests: charka-codegen 24, charka-shadow 22. fmt + clippy limpios. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -28,18 +28,16 @@ mod expr;
|
||||
mod stmt;
|
||||
mod sym;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use charka_ir::{Ir, Procedure};
|
||||
use charka_ir::Ir;
|
||||
|
||||
use emit::Emitter;
|
||||
use expr::rust_str;
|
||||
use stmt::emit_stmt;
|
||||
use sym::{paragraph_method, Field, FieldKind, Symbols};
|
||||
use sym::{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 sym = Symbols::build(ir);
|
||||
let mut em = Emitter::new();
|
||||
emit_header(&mut em);
|
||||
emit_struct(&mut em, &sym);
|
||||
@@ -107,10 +105,9 @@ fn emit_impl(em: &mut Emitter, sym: &Symbols, ir: &Ir) {
|
||||
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) {{"));
|
||||
// Un método por párrafo (en paralelo con `sym.paragraphs`).
|
||||
for (i, proc) in ir.procedures.iter().enumerate() {
|
||||
em.line(&format!("fn {}(&mut self) {{", sym.paragraphs[i].1));
|
||||
em.indent();
|
||||
for s in &proc.body {
|
||||
emit_stmt(em, sym, s);
|
||||
@@ -123,11 +120,11 @@ fn emit_impl(em: &mut Emitter, sym: &Symbols, ir: &Ir) {
|
||||
// run() — encadena los párrafos en orden.
|
||||
em.line("fn run(&mut self) {");
|
||||
em.indent();
|
||||
if methods.is_empty() {
|
||||
if sym.paragraphs.is_empty() {
|
||||
em.line("// programa sin PROCEDURE division");
|
||||
}
|
||||
for (name, _) in &methods {
|
||||
em.line(&format!("self.{name}();"));
|
||||
for (_, method) in &sym.paragraphs {
|
||||
em.line(&format!("self.{method}();"));
|
||||
}
|
||||
em.dedent();
|
||||
em.line("}");
|
||||
@@ -165,20 +162,6 @@ fn field_init(f: &Field) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Asigna a cada párrafo un nombre de método único.
|
||||
fn paragraph_methods(ir: &Ir) -> Vec<(String, &Procedure)> {
|
||||
let mut seen: HashMap<String, u32> = HashMap::new();
|
||||
let mut out = Vec::new();
|
||||
for proc in &ir.procedures {
|
||||
let base = paragraph_method(&proc.name);
|
||||
let n = seen.entry(base.clone()).or_insert(0);
|
||||
let name = if *n > 0 { format!("{base}_{n}") } else { base };
|
||||
*n += 1;
|
||||
out.push((name, proc));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -439,6 +422,22 @@ mod tests {
|
||||
assert!(out.contains("self.ws_f.store(\"S\");"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn perform_thru_calls_the_paragraph_range() {
|
||||
let out = gen("PROCEDURE DIVISION.\n\
|
||||
MAIN.\n\
|
||||
PERFORM A THRU C.\n\
|
||||
A.\n\
|
||||
DISPLAY 'A'.\n\
|
||||
B.\n\
|
||||
DISPLAY 'B'.\n\
|
||||
C.\n\
|
||||
DISPLAY 'C'.\n");
|
||||
// `PERFORM A THRU C` emite la llamada a p_b dentro de p_main,
|
||||
// además de la que hace run() — de ahí >= 2 apariciones.
|
||||
assert!(out.matches("self.p_b();").count() >= 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_program_still_compiles_shape() {
|
||||
let out = gen("");
|
||||
|
||||
@@ -543,11 +543,9 @@ fn emit_perform(em: &mut Emitter, sym: &Symbols, p: &Perform) {
|
||||
// Emite el "cuerpo": la llamada al párrafo o el bloque en línea.
|
||||
let emit_body = |em: &mut Emitter, sym: &Symbols| match &p.target {
|
||||
PerformTarget::Paragraph { name, thru } => {
|
||||
let note = thru
|
||||
.as_ref()
|
||||
.map(|t| format!(" // charka: THRU {t} — rango no soportado"))
|
||||
.unwrap_or_default();
|
||||
em.line(&format!("self.{}();{note}", paragraph_method(name)));
|
||||
for m in sym.paragraph_range(name, thru.as_deref()) {
|
||||
em.line(&format!("self.{m}();"));
|
||||
}
|
||||
}
|
||||
PerformTarget::Inline(body) => emit_block(em, sym, body),
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use charka_ir::{ConditionName, DataModel};
|
||||
use charka_ir::{ConditionName, Ir};
|
||||
|
||||
/// El tipo de campo lo aporta `charka-ir`; se reexporta para que el
|
||||
/// resto del crate lo nombre como `crate::sym::FieldKind`.
|
||||
@@ -24,17 +24,21 @@ pub(crate) struct Field {
|
||||
pub occurs: Option<u32>,
|
||||
}
|
||||
|
||||
/// Los campos del programa, sus nombres de condición y sus grupos.
|
||||
/// Los campos del programa, sus nombres de condición, sus grupos y
|
||||
/// sus párrafos.
|
||||
pub(crate) struct Symbols {
|
||||
pub fields: Vec<Field>,
|
||||
by_name: HashMap<String, usize>,
|
||||
conditions: HashMap<String, ConditionName>,
|
||||
groups: HashMap<String, Vec<String>>,
|
||||
/// Los párrafos en orden: `(nombre COBOL, nombre de método Rust)`.
|
||||
pub paragraphs: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl Symbols {
|
||||
/// Construye la tabla desde el modelo de datos resuelto.
|
||||
pub(crate) fn build(model: &DataModel) -> Self {
|
||||
/// Construye la tabla desde el IR (su modelo de datos y párrafos).
|
||||
pub(crate) fn build(ir: &Ir) -> Self {
|
||||
let model = &ir.model;
|
||||
let mut fields: Vec<Field> = model
|
||||
.fields
|
||||
.iter()
|
||||
@@ -62,14 +66,56 @@ impl Symbols {
|
||||
.iter()
|
||||
.map(|g| (g.name.clone(), g.members.clone()))
|
||||
.collect();
|
||||
// Párrafos en orden, con su nombre de método único.
|
||||
let mut seen: HashMap<String, u32> = HashMap::new();
|
||||
let paragraphs = ir
|
||||
.procedures
|
||||
.iter()
|
||||
.map(|proc| {
|
||||
let base = paragraph_method(&proc.name);
|
||||
let n = seen.entry(base.clone()).or_insert(0);
|
||||
let method = if *n > 0 { format!("{base}_{n}") } else { base };
|
||||
*n += 1;
|
||||
(proc.name.to_uppercase(), method)
|
||||
})
|
||||
.collect();
|
||||
Self {
|
||||
fields,
|
||||
by_name,
|
||||
conditions,
|
||||
groups,
|
||||
paragraphs,
|
||||
}
|
||||
}
|
||||
|
||||
/// Los métodos a llamar para un `PERFORM name [THRU thru]`: el
|
||||
/// rango de párrafos desde `name` hasta `thru` inclusive.
|
||||
pub(crate) fn paragraph_range(&self, name: &str, thru: Option<&str>) -> Vec<String> {
|
||||
let up = name.to_uppercase();
|
||||
let Some(start) = self.paragraphs.iter().position(|(c, _)| *c == up) else {
|
||||
return vec![paragraph_method(name)];
|
||||
};
|
||||
let end = match thru {
|
||||
Some(t) => {
|
||||
let tu = t.to_uppercase();
|
||||
self.paragraphs
|
||||
.iter()
|
||||
.position(|(c, _)| *c == tu)
|
||||
.unwrap_or(start)
|
||||
}
|
||||
None => start,
|
||||
};
|
||||
let (lo, hi) = if start <= end {
|
||||
(start, end)
|
||||
} else {
|
||||
(end, start)
|
||||
};
|
||||
self.paragraphs[lo..=hi]
|
||||
.iter()
|
||||
.map(|(_, m)| m.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Los miembros de un grupo, si `name` es un grupo.
|
||||
pub(crate) fn group(&self, name: &str) -> Option<&[String]> {
|
||||
self.groups.get(&name.to_uppercase()).map(|v| v.as_slice())
|
||||
|
||||
Reference in New Issue
Block a user