tawasuyu

tawasuyu/brahman

Rust 572TOML 217Markdown 68JSON 198326 Sutichaykuna

⎇ 634a43006ac4257288af40320b5360e9f157ab09 crates/modules/charka/charka-codegen/src/sym.rs Rust · 276 siq'ikuna · 8.2 KB Ñawpa willay Pi imata llamkarqan ch'uya

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

1//! Tabla de símbolos del código generado: los campos del `struct
2//! Program` y los nombres de condición, derivados del modelo de datos
3//! resuelto que entrega `charka-ir`.
4
5use std::collections::HashMap;
6
7use charka_ir::{ConditionName, Ir};
8
9/// El tipo de campo lo aporta `charka-ir`; se reexporta para que el
10/// resto del crate lo nombre como `crate::sym::FieldKind`.
11pub(crate) use charka_ir::FieldKind;
12
13/// Un campo del struct `Program` generado.
14pub(crate) struct Field {
15    /// Nombre COBOL en mayúsculas.
16    pub cobol: String,
17    /// Identificador Rust saneado y único.
18    pub ident: String,
19    /// Numérico o alfanumérico.
20    pub kind: FieldKind,
21    /// Valor inicial normalizado (de la cláusula `VALUE`).
22    pub init: String,
23    /// Si es una tabla (`OCCURS n`), su número de elementos.
24    pub occurs: Option<u32>,
25    /// Si es un campo de edición, su PICTURE.
26    pub edit: Option<String>,
27}
28
29/// Un fichero del programa generado.
30pub(crate) struct FileSym {
31    /// Nombre COBOL del fichero.
32    pub cobol: String,
33    /// Identificador Rust del campo `CobFile` (prefijo `file_`).
34    pub ident: String,
35    /// Ruta a la que está asignado.
36    pub path: String,
37    /// Nombre COBOL del registro asociado (su `FD`).
38    pub record: String,
39}
40
41/// Los campos del programa, sus nombres de condición, sus grupos, sus
42/// párrafos y sus ficheros.
43pub(crate) struct Symbols {
44    pub fields: Vec<Field>,
45    by_name: HashMap<String, usize>,
46    conditions: HashMap<String, ConditionName>,
47    groups: HashMap<String, Vec<String>>,
48    /// Los párrafos en orden: `(nombre COBOL, nombre de método Rust)`.
49    pub paragraphs: Vec<(String, String)>,
50    /// Los ficheros declarados.
51    pub files: Vec<FileSym>,
52}
53
54impl Symbols {
55    /// Construye la tabla desde el IR (su modelo de datos y párrafos).
56    pub(crate) fn build(ir: &Ir) -> Self {
57        let model = &ir.model;
58        let mut fields: Vec<Field> = model
59            .fields
60            .iter()
61            .map(|f| Field {
62                cobol: f.name.clone(),
63                ident: sanitize_ident(&f.name),
64                kind: f.kind,
65                init: f.init.clone(),
66                occurs: f.occurs,
67                edit: f.edit.clone(),
68            })
69            .collect();
70        dedup_idents(&mut fields);
71        let by_name = fields
72            .iter()
73            .enumerate()
74            .map(|(i, f)| (f.cobol.clone(), i))
75            .collect();
76        let conditions = model
77            .conditions
78            .iter()
79            .map(|c| (c.name.clone(), c.clone()))
80            .collect();
81        let groups = model
82            .groups
83            .iter()
84            .map(|g| (g.name.clone(), g.members.clone()))
85            .collect();
86        // Párrafos en orden, con su nombre de método único.
87        let mut seen: HashMap<String, u32> = HashMap::new();
88        let paragraphs = ir
89            .procedures
90            .iter()
91            .map(|proc| {
92                let base = paragraph_method(&proc.name);
93                let n = seen.entry(base.clone()).or_insert(0);
94                let method = if *n > 0 { format!("{base}_{n}") } else { base };
95                *n += 1;
96                (proc.name.to_uppercase(), method)
97            })
98            .collect();
99        let files = ir
100            .files
101            .iter()
102            .map(|f| FileSym {
103                cobol: f.name.clone(),
104                ident: format!("file_{}", sanitize_ident(&f.name)),
105                path: f.path.clone(),
106                record: f.record.clone(),
107            })
108            .collect();
109        Self {
110            fields,
111            by_name,
112            conditions,
113            groups,
114            paragraphs,
115            files,
116        }
117    }
118
119    /// Busca un fichero por su nombre COBOL.
120    pub(crate) fn file(&self, name: &str) -> Option<&FileSym> {
121        let up = name.to_uppercase();
122        self.files.iter().find(|f| f.cobol == up)
123    }
124
125    /// Busca el fichero cuyo registro `FD` es `record`.
126    pub(crate) fn file_of_record(&self, record: &str) -> Option<&FileSym> {
127        let up = record.to_uppercase();
128        self.files.iter().find(|f| f.record == up)
129    }
130
131    /// Los métodos a llamar para un `PERFORM name [THRU thru]`: el
132    /// rango de párrafos desde `name` hasta `thru` inclusive.
133    pub(crate) fn paragraph_range(&self, name: &str, thru: Option<&str>) -> Vec<String> {
134        let up = name.to_uppercase();
135        let Some(start) = self.paragraphs.iter().position(|(c, _)| *c == up) else {
136            return vec![paragraph_method(name)];
137        };
138        let end = match thru {
139            Some(t) => {
140                let tu = t.to_uppercase();
141                self.paragraphs
142                    .iter()
143                    .position(|(c, _)| *c == tu)
144                    .unwrap_or(start)
145            }
146            None => start,
147        };
148        let (lo, hi) = if start <= end {
149            (start, end)
150        } else {
151            (end, start)
152        };
153        self.paragraphs[lo..=hi]
154            .iter()
155            .map(|(_, m)| m.clone())
156            .collect()
157    }
158
159    /// Los miembros de un grupo, si `name` es un grupo.
160    pub(crate) fn group(&self, name: &str) -> Option<&[String]> {
161        self.groups.get(&name.to_uppercase()).map(|v| v.as_slice())
162    }
163
164    /// Busca un campo por su nombre COBOL (sin distinguir mayúsculas).
165    pub(crate) fn lookup(&self, cobol: &str) -> Option<&Field> {
166        self.by_name
167            .get(&cobol.to_uppercase())
168            .map(|&i| &self.fields[i])
169    }
170
171    /// Busca un nombre de condición (un dato de nivel 88).
172    pub(crate) fn condition(&self, name: &str) -> Option<&ConditionName> {
173        self.conditions.get(&name.to_uppercase())
174    }
175}
176
177/// Dos datos pueden compartir nombre (COBOL los califica); aquí, como
178/// son campos de un struct, sus identificadores deben ser únicos.
179fn dedup_idents(fields: &mut [Field]) {
180    let mut seen: HashMap<String, u32> = HashMap::new();
181    for f in fields.iter_mut() {
182        let n = seen.entry(f.ident.clone()).or_insert(0);
183        if *n > 0 {
184            f.ident = format!("{}_{}", f.ident, n);
185        }
186        *n += 1;
187    }
188}
189
190/// Convierte un nombre COBOL en un identificador Rust válido.
191fn sanitize_ident(name: &str) -> String {
192    let mut s: String = name
193        .chars()
194        .map(|c| {
195            if c == '-' {
196                '_'
197            } else {
198                c.to_ascii_lowercase()
199            }
200        })
201        .filter(|c| c.is_ascii_alphanumeric() || *c == '_')
202        .collect();
203    if s.is_empty() || s.starts_with(|c: char| c.is_ascii_digit()) {
204        s = format!("f_{s}");
205    }
206    if is_rust_keyword(&s) {
207        s.push('_');
208    }
209    s
210}
211
212/// El nombre del método de un párrafo. El párrafo implícito ("") es
213/// `p_start`; el resto lleva el prefijo `p_`.
214pub(crate) fn paragraph_method(name: &str) -> String {
215    if name.is_empty() {
216        return "p_start".to_string();
217    }
218    let body: String = name
219        .chars()
220        .map(|c| {
221            if c == '-' {
222                '_'
223            } else {
224                c.to_ascii_lowercase()
225            }
226        })
227        .filter(|c| c.is_ascii_alphanumeric() || *c == '_')
228        .collect();
229    format!("p_{body}")
230}
231
232/// ¿Es `s` una palabra reservada de Rust? (Para no chocar al nombrar
233/// campos — un dato COBOL `MOVE` se volvería el keyword `move`.)
234fn is_rust_keyword(s: &str) -> bool {
235    matches!(
236        s,
237        "as" | "break"
238            | "const"
239            | "continue"
240            | "crate"
241            | "dyn"
242            | "else"
243            | "enum"
244            | "extern"
245            | "false"
246            | "fn"
247            | "for"
248            | "if"
249            | "impl"
250            | "in"
251            | "let"
252            | "loop"
253            | "match"
254            | "mod"
255            | "move"
256            | "mut"
257            | "pub"
258            | "ref"
259            | "return"
260            | "self"
261            | "static"
262            | "struct"
263            | "super"
264            | "trait"
265            | "true"
266            | "type"
267            | "unsafe"
268            | "use"
269            | "where"
270            | "while"
271            | "async"
272            | "await"
273            | "box"
274            | "yield"
275    )
276}