gioser

tawasuyu/brahman

Rust 476TOML 190Markdown 50JSON 176602 Definitions

⎇ 4d9ce11b1e444430e6e2bcc0a72baa1d40a4527c crates/modules/charka/charka-shadow/src/field.rs Rust · 128 lines · 3.8 KB History Who touched what raw

feat(charka): charka-shadow — validador en sombra + corpus COBOL 4d9ce11b1 · sergio ·

1//! El estado de los datos durante la ejecución sombra: el árbol de
2//! `DataItem` del IR se aplana a un mapa de campos vivos.
3//!
4//! La clasificación de PICTURE refleja la de `charka-codegen` — un
5//! futuro refactor la unificaría en `charka-runtime`.
6
7use std::collections::HashMap;
8
9use charka_ir::DataItem;
10use charka_runtime::{Num, Picture, Text};
11
12/// Un campo vivo: numérico o alfanumérico.
13pub(crate) enum Cell {
14    Num(Num),
15    Text(Text),
16}
17
18/// Aplana el árbol de datos en un mapa `nombre COBOL → campo`.
19pub(crate) fn build_fields(data: &[DataItem]) -> HashMap<String, Cell> {
20    let mut map = HashMap::new();
21    collect(data, &mut map);
22    map
23}
24
25/// Recorre el árbol: los grupos no son campos (se recurre en sus
26/// hijos); se saltan los niveles 88/66 y los `FILLER`.
27fn collect(items: &[DataItem], map: &mut HashMap<String, Cell>) {
28    for it in items {
29        if it.level == 88 || it.level == 66 {
30            continue;
31        }
32        if !it.children.is_empty() {
33            collect(&it.children, map);
34            continue;
35        }
36        if it.name == "FILLER" {
37            continue;
38        }
39        if let Some(cell) = make_cell(it.picture.as_deref(), it.value.as_deref()) {
40            map.entry(it.name.to_uppercase()).or_insert(cell);
41        }
42    }
43}
44
45/// Construye un campo desde su PICTURE y su cláusula `VALUE`.
46fn make_cell(pic: Option<&str>, value: Option<&str>) -> Option<Cell> {
47    let up = pic?.to_uppercase();
48    if up.contains('X') || up.contains('A') {
49        return Some(Cell::Text(Text::with_value(
50            pic_width(&up).max(1),
51            &text_value(value),
52        )));
53    }
54    if let Ok(p) = Picture::parse(&up) {
55        return Some(Cell::Num(Num::with_value(p, &numeric_value(value))));
56    }
57    // PICTURE de edición → campo de texto de presentación.
58    Some(Cell::Text(Text::with_value(
59        pic_width(&up).max(1),
60        &text_value(value),
61    )))
62}
63
64/// Cuenta las posiciones de presentación de una PICTURE, expandiendo
65/// la repetición `C(n)`. `S` y `V` no ocupan posición.
66fn pic_width(up: &str) -> usize {
67    let chars: Vec<char> = up.chars().collect();
68    let mut i = 0;
69    let mut total = 0usize;
70    while i < chars.len() {
71        let c = chars[i];
72        i += 1;
73        if c == 'S' || c == 'V' {
74            continue;
75        }
76        let mut count = 1usize;
77        if chars.get(i) == Some(&'(') {
78            i += 1;
79            let start = i;
80            while i < chars.len() && chars[i].is_ascii_digit() {
81                i += 1;
82            }
83            if let Ok(n) = chars[start..i].iter().collect::<String>().parse::<usize>() {
84                count = n;
85            }
86            if chars.get(i) == Some(&')') {
87                i += 1;
88            }
89        }
90        total += count;
91    }
92    total
93}
94
95/// Normaliza el `VALUE` de un campo numérico a un literal parseable.
96fn numeric_value(v: Option<&str>) -> String {
97    let Some(raw) = v else {
98        return "0".to_string();
99    };
100    if matches!(raw.to_uppercase().as_str(), "ZERO" | "ZEROS" | "ZEROES") {
101        return "0".to_string();
102    }
103    if charka_runtime::Decimal::parse(raw).is_ok() {
104        raw.to_string()
105    } else {
106        "0".to_string()
107    }
108}
109
110/// Normaliza el `VALUE` de un campo de texto. El parser envuelve los
111/// literales de texto en comillas simples; aquí se desenvuelven.
112fn text_value(v: Option<&str>) -> String {
113    let Some(raw) = v else {
114        return String::new();
115    };
116    let up = raw.to_uppercase();
117    if matches!(up.as_str(), "SPACE" | "SPACES") {
118        return String::new();
119    }
120    if matches!(up.as_str(), "ZERO" | "ZEROS" | "ZEROES") {
121        return "0".to_string();
122    }
123    if raw.len() >= 2 && raw.starts_with('\'') && raw.ends_with('\'') {
124        raw[1..raw.len() - 1].to_string()
125    } else {
126        raw.to_string()
127    }
128}