tawasuyu

tawasuyu/brahman

Rust 332TOML 127JSON 17Markdown 154710 Sutichaykuna

⎇ e52b3fb572cec07d35c55d87519ecc8e0bd0505a crates/modules/charka/charka-codegen/src/sym.rs Rust · 233 siq'ikuna · 6.3 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat(charka): charka-codegen — emisión de Rust desde el IR e52b3fb57 · sergio ·

1//! Tabla de símbolos: el modelo de datos COBOL traducido a campos Rust.
2
3use std::collections::HashMap;
4
5use charka_ir::DataItem;
6
7/// Un campo del struct `Program` generado.
8pub(crate) struct Field {
9    /// Nombre COBOL en mayúsculas.
10    pub cobol: String,
11    /// Identificador Rust saneado y único.
12    pub ident: String,
13    /// Numérico o alfanumérico.
14    pub kind: FieldKind,
15    /// La cláusula `VALUE`, si la hay.
16    pub value: Option<String>,
17}
18
19/// El tipo de un campo elemental.
20pub(crate) enum FieldKind {
21    /// Campo numérico — se emite como `Num`.
22    Num { int: u8, frac: u8, signed: bool },
23    /// Campo alfanumérico — se emite como `Text`.
24    Text { len: usize },
25}
26
27/// El conjunto de campos del programa, indexado por nombre COBOL.
28pub(crate) struct Symbols {
29    pub fields: Vec<Field>,
30    by_name: HashMap<String, usize>,
31}
32
33impl Symbols {
34    /// Construye la tabla recorriendo el árbol de datos.
35    pub(crate) fn build(data: &[DataItem]) -> Self {
36        let mut fields = Vec::new();
37        collect(data, &mut fields);
38        dedup_idents(&mut fields);
39        let by_name = fields
40            .iter()
41            .enumerate()
42            .map(|(i, f)| (f.cobol.clone(), i))
43            .collect();
44        Self { fields, by_name }
45    }
46
47    /// Busca un campo por su nombre COBOL (sin distinguir mayúsculas).
48    pub(crate) fn lookup(&self, cobol: &str) -> Option<&Field> {
49        self.by_name
50            .get(&cobol.to_uppercase())
51            .map(|&i| &self.fields[i])
52    }
53}
54
55/// Recoge los datos elementales del árbol. Los grupos no son campos —
56/// se recurre en sus hijos. Se saltan niveles 88/66 y los `FILLER`.
57fn collect(items: &[DataItem], out: &mut Vec<Field>) {
58    for it in items {
59        if it.level == 88 || it.level == 66 {
60            continue;
61        }
62        if !it.children.is_empty() {
63            collect(&it.children, out);
64            continue;
65        }
66        if it.name == "FILLER" {
67            continue;
68        }
69        let Some(kind) = classify(it.picture.as_deref()) else {
70            continue;
71        };
72        out.push(Field {
73            cobol: it.name.clone(),
74            ident: sanitize_ident(&it.name),
75            kind,
76            value: it.value.clone(),
77        });
78    }
79}
80
81/// Dos datos pueden compartir nombre (COBOL los califica); aquí, como
82/// son campos de un struct, sus identificadores deben ser únicos.
83fn dedup_idents(fields: &mut [Field]) {
84    let mut seen: HashMap<String, u32> = HashMap::new();
85    for f in fields.iter_mut() {
86        let n = seen.entry(f.ident.clone()).or_insert(0);
87        if *n > 0 {
88            f.ident = format!("{}_{}", f.ident, n);
89        }
90        *n += 1;
91    }
92}
93
94/// Clasifica una cláusula PICTURE: alfanumérica si tiene `X`/`A`,
95/// numérica si `charka-bcd` la parsea; una PICTURE de edición se trata
96/// como texto de presentación.
97fn classify(pic: Option<&str>) -> Option<FieldKind> {
98    let up = pic?.to_uppercase();
99    if up.contains('X') || up.contains('A') {
100        return Some(FieldKind::Text {
101            len: pic_width(&up).max(1),
102        });
103    }
104    if let Ok(p) = charka_bcd::Picture::parse(&up) {
105        return Some(FieldKind::Num {
106            int: p.integer_digits,
107            frac: p.fraction_digits,
108            signed: p.signed,
109        });
110    }
111    Some(FieldKind::Text {
112        len: pic_width(&up).max(1),
113    })
114}
115
116/// Cuenta las posiciones de presentación de una PICTURE, expandiendo
117/// la repetición `C(n)`. `S` y `V` no ocupan posición.
118fn pic_width(up: &str) -> usize {
119    let chars: Vec<char> = up.chars().collect();
120    let mut i = 0;
121    let mut total = 0usize;
122    while i < chars.len() {
123        let c = chars[i];
124        i += 1;
125        if c == 'S' || c == 'V' {
126            continue;
127        }
128        let mut count = 1usize;
129        if chars.get(i) == Some(&'(') {
130            i += 1;
131            let start = i;
132            while i < chars.len() && chars[i].is_ascii_digit() {
133                i += 1;
134            }
135            if let Ok(n) = chars[start..i].iter().collect::<String>().parse::<usize>() {
136                count = n;
137            }
138            if chars.get(i) == Some(&')') {
139                i += 1;
140            }
141        }
142        total += count;
143    }
144    total
145}
146
147/// Convierte un nombre COBOL en un identificador Rust válido.
148fn sanitize_ident(name: &str) -> String {
149    let mut s: String = name
150        .chars()
151        .map(|c| {
152            if c == '-' {
153                '_'
154            } else {
155                c.to_ascii_lowercase()
156            }
157        })
158        .filter(|c| c.is_ascii_alphanumeric() || *c == '_')
159        .collect();
160    if s.is_empty() || s.starts_with(|c: char| c.is_ascii_digit()) {
161        s = format!("f_{s}");
162    }
163    if is_rust_keyword(&s) {
164        s.push('_');
165    }
166    s
167}
168
169/// El nombre del método de un párrafo. El párrafo implícito ("") es
170/// `p_start`; el resto lleva el prefijo `p_`.
171pub(crate) fn paragraph_method(name: &str) -> String {
172    if name.is_empty() {
173        return "p_start".to_string();
174    }
175    let body: String = name
176        .chars()
177        .map(|c| {
178            if c == '-' {
179                '_'
180            } else {
181                c.to_ascii_lowercase()
182            }
183        })
184        .filter(|c| c.is_ascii_alphanumeric() || *c == '_')
185        .collect();
186    format!("p_{body}")
187}
188
189/// ¿Es `s` una palabra reservada de Rust? (Para no chocar al nombrar
190/// campos — un dato COBOL `MOVE` se volvería el keyword `move`.)
191fn is_rust_keyword(s: &str) -> bool {
192    matches!(
193        s,
194        "as" | "break"
195            | "const"
196            | "continue"
197            | "crate"
198            | "dyn"
199            | "else"
200            | "enum"
201            | "extern"
202            | "false"
203            | "fn"
204            | "for"
205            | "if"
206            | "impl"
207            | "in"
208            | "let"
209            | "loop"
210            | "match"
211            | "mod"
212            | "move"
213            | "mut"
214            | "pub"
215            | "ref"
216            | "return"
217            | "self"
218            | "static"
219            | "struct"
220            | "super"
221            | "trait"
222            | "true"
223            | "type"
224            | "unsafe"
225            | "use"
226            | "where"
227            | "while"
228            | "async"
229            | "await"
230            | "box"
231            | "yield"
232    )
233}