feat(charka): charka-runtime — soporte de ejecución (campos Num y Text)

El soporte que los programas COBOL transpilados enlazan. charka-codegen
emitirá Rust que llama a esta biblioteca, no Rust autónomo.

- Num: campo numérico (PIC 9(5)V99) — un Decimal conformado a su
  Picture. store trunca a la escala declarada, store_rounded redondea;
  al desbordar la parte entera conserva los dígitos de bajo orden (el
  ON SIZE ERROR de COBOL sin cláusula). display da los dígitos con
  relleno de ceros y signo.
- Text: campo alfanumérico (PIC X(n)) de longitud fija — store
  justifica a la izquierda y rellena/trunca; fill mueve figurativas.
- cobol_text_cmp: comparación alfanumérica con relleno de espacios.
- Reexporta Decimal/Picture/Rounding de charka-bcd.

Construido antes que charka-codegen (la nota de orden del plan los
listaba al revés): el codegen emite contra esta API. 17 tests; fmt +
clippy limpios.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-21 20:27:28 +00:00
parent 71a4068d12
commit 85156c1509
8 changed files with 414 additions and 6 deletions
@@ -0,0 +1,72 @@
//! `charka-runtime` — el soporte de ejecución de los programas COBOL
//! transpilados.
//!
//! Lo que `charka-codegen` emite no es Rust autónomo: es Rust que
//! enlaza contra esta biblioteca. Aquí viven los tipos que dan a un
//! programa transpilado la semántica de COBOL en tiempo de ejecución:
//!
//! - [`Num`] — un campo numérico (`PIC 9(5)V99`): un [`Decimal`] de
//! punto fijo conformado a su [`Picture`]. Toda asignación trunca a
//! la escala y al tamaño declarados, como el `MOVE` de COBOL.
//! - [`Text`] — un campo alfanumérico (`PIC X(20)`) de longitud fija:
//! toda asignación justifica a la izquierda y rellena o trunca.
//!
//! La aritmética decimal exacta la aporta `charka-bcd`, cuyos tipos
//! ([`Decimal`], [`Picture`], [`Rounding`]) se reexportan para que el
//! código generado sólo necesite `use charka_runtime::*;`.
#![forbid(unsafe_code)]
mod num;
mod text;
pub use charka_bcd::{Decimal, Picture, Rounding};
pub use num::Num;
pub use text::Text;
use std::cmp::Ordering;
/// Compara dos campos alfanuméricos con la semántica de COBOL: el más
/// corto se considera rellenado con espacios a la derecha, de modo que
/// `"AB"` y `"AB "` son iguales.
pub fn cobol_text_cmp(a: &str, b: &str) -> Ordering {
let n = a.chars().count().max(b.chars().count());
let padded = |s: &str| -> Vec<char> {
let mut v: Vec<char> = s.chars().collect();
while v.len() < n {
v.push(' ');
}
v
};
padded(a).cmp(&padded(b))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn text_cmp_orders_lexically() {
assert_eq!(cobol_text_cmp("ABC", "ABD"), Ordering::Less);
assert_eq!(cobol_text_cmp("ABD", "ABC"), Ordering::Greater);
assert_eq!(cobol_text_cmp("ABC", "ABC"), Ordering::Equal);
}
#[test]
fn text_cmp_pads_the_shorter_with_spaces() {
assert_eq!(cobol_text_cmp("AB", "AB "), Ordering::Equal);
assert_eq!(cobol_text_cmp("AB", "ABC"), Ordering::Less);
}
#[test]
fn fields_compose_for_generated_code() {
// Un mini-programa transpilado a mano: WS-CT crece, WS-MSG fijo.
let mut ws_ct = Num::with_value(Picture::new(3, 0, false), "0");
ws_ct.store(ws_ct.value().add(&Decimal::from_integer(5)));
assert_eq!(ws_ct.display(), "005");
let mut ws_msg = Text::new(10);
ws_msg.store("LISTO");
assert_eq!(ws_msg.as_str(), "LISTO ");
}
}