tawasuyu

tawasuyu/brahman

Rust 332TOML 127JSON 17Markdown 154710 Definitions

⎇ b3278bdb0cccadf52a05c09cf2975027e498cbc4 crates/modules/charka/charka-codegen/src/sym.rs Rust · 273 lines · 8.0 KB History Who touched what raw

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