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:
@@ -0,0 +1,103 @@
|
||||
//! `format_edited` — el formateo de un valor numérico según una
|
||||
//! PICTURE de edición (`ZZ,ZZ9.99`).
|
||||
|
||||
use charka_bcd::{Decimal, Rounding};
|
||||
|
||||
/// Formatea `value` según una PICTURE de edición. Soporta `9` (dígito),
|
||||
/// `Z` (dígito con supresión de ceros a la izquierda), `,` (coma de
|
||||
/// millares, en blanco dentro de la zona suprimida), `.` (punto
|
||||
/// decimal) y `B` (espacio). El signo se descarta.
|
||||
pub fn format_edited(value: Decimal, pic: &str) -> String {
|
||||
let up = pic.to_uppercase();
|
||||
let (int_pic, frac_pic) = match up.split_once('.') {
|
||||
Some((a, b)) => (a, b),
|
||||
None => (up.as_str(), ""),
|
||||
};
|
||||
let count_digits = |s: &str| s.chars().filter(|c| *c == '9' || *c == 'Z').count();
|
||||
let int_digits = count_digits(int_pic);
|
||||
let frac_digits = count_digits(frac_pic);
|
||||
let total = int_digits + frac_digits;
|
||||
|
||||
// Los dígitos del valor, con exactamente `frac_digits` decimales.
|
||||
let mantissa = value
|
||||
.rescale(frac_digits as u8, Rounding::Truncate)
|
||||
.mantissa()
|
||||
.unsigned_abs();
|
||||
let mut digits = mantissa.to_string();
|
||||
if digits.len() < total {
|
||||
digits = format!("{}{}", "0".repeat(total - digits.len()), digits);
|
||||
} else if digits.len() > total {
|
||||
digits = digits[digits.len() - total..].to_string();
|
||||
}
|
||||
let int_part = &digits.as_bytes()[..int_digits];
|
||||
let frac_part = &digits.as_bytes()[int_digits..];
|
||||
|
||||
let mut out = String::new();
|
||||
let mut di = 0;
|
||||
let mut seen = false;
|
||||
for ch in int_pic.chars() {
|
||||
match ch {
|
||||
'9' => {
|
||||
out.push(int_part[di] as char);
|
||||
di += 1;
|
||||
seen = true;
|
||||
}
|
||||
'Z' => {
|
||||
let d = int_part[di];
|
||||
di += 1;
|
||||
if seen || d != b'0' {
|
||||
out.push(d as char);
|
||||
seen = true;
|
||||
} else {
|
||||
out.push(' ');
|
||||
}
|
||||
}
|
||||
',' => out.push(if seen { ',' } else { ' ' }),
|
||||
'B' => out.push(' '),
|
||||
other => out.push(other),
|
||||
}
|
||||
}
|
||||
if !frac_pic.is_empty() {
|
||||
out.push('.');
|
||||
let mut fi = 0;
|
||||
for ch in frac_pic.chars() {
|
||||
match ch {
|
||||
'9' | 'Z' => {
|
||||
out.push(frac_part[fi] as char);
|
||||
fi += 1;
|
||||
}
|
||||
other => out.push(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn dec(s: &str) -> Decimal {
|
||||
Decimal::parse(s).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suppresses_leading_zeros_and_inserts_commas() {
|
||||
assert_eq!(format_edited(dec("1234.5"), "Z,ZZ9.99"), "1,234.50");
|
||||
assert_eq!(format_edited(dec("7"), "Z,ZZ9.99"), " 7.00");
|
||||
assert_eq!(format_edited(dec("0"), "Z,ZZ9.99"), " 0.00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comma_in_suppressed_zone_is_blank() {
|
||||
// El millar va en blanco si no hay dígito significativo antes.
|
||||
assert_eq!(format_edited(dec("42"), "ZZ,ZZ9"), " 42");
|
||||
assert_eq!(format_edited(dec("12345"), "ZZ,ZZ9"), "12,345");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nine_positions_always_show() {
|
||||
assert_eq!(format_edited(dec("0"), "999"), "000");
|
||||
assert_eq!(format_edited(dec("0"), "ZZZ"), " ");
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,13 @@
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod edited;
|
||||
mod file;
|
||||
mod num;
|
||||
mod text;
|
||||
|
||||
pub use charka_bcd::{Decimal, Picture, Rounding};
|
||||
pub use edited::format_edited;
|
||||
pub use file::CobFile;
|
||||
pub use num::Num;
|
||||
pub use text::Text;
|
||||
|
||||
Reference in New Issue
Block a user