feat(charka): PICTURE de edición — Z, coma de millares y punto decimal

El formateo de informes de COBOL: supresión de ceros a la izquierda,
coma de millares e inserción del punto decimal. Rebanada vertical.

- charka-lexer: el punto separador exige un espacio detrás; un punto
  pegado a un carácter (ZZ9.99) ya no es terminador, sino símbolo —
  el parser lo reensambla dentro de la cláusula PICTURE.
- charka-runtime: format_edited(valor, pic) — 9, Z, coma, punto, B.
- charka-ir: Field::edit guarda la PICTURE; el campo es texto.
- charka-codegen / charka-shadow: MOVE a un campo de edición pasa por
  format_edited antes de almacenar.
- Corpus: 19-reporte. Sombra y crate compilado dan la misma salida.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-21 23:00:15 +00:00
parent b3278bdb0c
commit 634a43006a
15 changed files with 264 additions and 23 deletions
@@ -474,6 +474,21 @@ mod tests {
assert!(out.contains("self.file_arch.close();"));
}
#[test]
fn edited_field_is_text_and_move_formats_it() {
let out = gen("DATA DIVISION.\n\
WORKING-STORAGE SECTION.\n\
01 WS-N PIC 9(5).\n\
01 WS-E PIC Z,ZZ9.99.\n\
PROCEDURE DIVISION.\n\
MAIN.\n\
MOVE WS-N TO WS-E.\n");
// El campo de edición se materializa como texto de presentación.
assert!(out.contains("ws_e: Text,"));
// El MOVE pasa por `format_edited` con la PICTURE de edición.
assert!(out.contains("self.ws_e.store(&format_edited(self.ws_n.value(), \"Z,ZZ9.99\"));"));
}
#[test]
fn empty_program_still_compiles_shape() {
let out = gen("");
@@ -8,7 +8,8 @@ use charka_ir::{
use crate::emit::Emitter;
use crate::expr::{
emit_cond, emit_expr, field_ref, figurative_fill, operand_decimal, operand_display, operand_str,
emit_cond, emit_expr, field_ref, figurative_fill, operand_decimal, operand_display,
operand_str, rust_str,
};
use crate::sym::{paragraph_method, FieldKind, Symbols};
@@ -136,6 +137,21 @@ fn emit_store(em: &mut Emitter, sym: &Symbols, target: &Operand, value: &str, ro
fn emit_move(em: &mut Emitter, sym: &Symbols, from: &Operand, to: &[Operand]) {
for t in to {
// Un destino con PICTURE de edición formatea el valor numérico.
if let Operand::Data(name) = t {
if let Some(pic) = sym.lookup(name).and_then(|f| f.edit.clone()) {
let ident = sym
.lookup(name)
.map(|f| f.ident.clone())
.unwrap_or_default();
em.line(&format!(
"self.{ident}.store(&format_edited({}, {}));",
operand_decimal(sym, from),
rust_str(&pic)
));
continue;
}
}
match field_ref(sym, t) {
Some((lref, FieldKind::Num { .. })) => {
em.line(&format!("{lref}.store({});", operand_decimal(sym, from)));
@@ -22,6 +22,8 @@ pub(crate) struct Field {
pub init: String,
/// Si es una tabla (`OCCURS n`), su número de elementos.
pub occurs: Option<u32>,
/// Si es un campo de edición, su PICTURE.
pub edit: Option<String>,
}
/// Un fichero del programa generado.
@@ -62,6 +64,7 @@ impl Symbols {
kind: f.kind,
init: f.init.clone(),
occurs: f.occurs,
edit: f.edit.clone(),
})
.collect();
dedup_idents(&mut fields);