tawasuyu

tawasuyu/brahman

Rust 563TOML 212Markdown 66JSON 198014 Definiciones

⎇ 7867d6830e128b8a2afe95cb752093e12a184988 crates/modules/charka/charka-codegen/src/sym.rs Rust · 178 líneas · 4.8 KB Historia Quién tocó qué crudo

feat(charka): OCCURS — tablas y referencias con subíndice 3902763da · 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, DataModel};
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/// Los campos del programa y sus nombres de condición, indexados.
28pub(crate) struct Symbols {
29    pub fields: Vec<Field>,
30    by_name: HashMap<String, usize>,
31    conditions: HashMap<String, ConditionName>,
32}
33
34impl Symbols {
35    /// Construye la tabla desde el modelo de datos resuelto.
36    pub(crate) fn build(model: &DataModel) -> Self {
37        let mut fields: Vec<Field> = model
38            .fields
39            .iter()
40            .map(|f| Field {
41                cobol: f.name.clone(),
42                ident: sanitize_ident(&f.name),
43                kind: f.kind,
44                init: f.init.clone(),
45                occurs: f.occurs,
46            })
47            .collect();
48        dedup_idents(&mut fields);
49        let by_name = fields
50            .iter()
51            .enumerate()
52            .map(|(i, f)| (f.cobol.clone(), i))
53            .collect();
54        let conditions = model
55            .conditions
56            .iter()
57            .map(|c| (c.name.clone(), c.clone()))
58            .collect();
59        Self {
60            fields,
61            by_name,
62            conditions,
63        }
64    }
65
66    /// Busca un campo por su nombre COBOL (sin distinguir mayúsculas).
67    pub(crate) fn lookup(&self, cobol: &str) -> Option<&Field> {
68        self.by_name
69            .get(&cobol.to_uppercase())
70            .map(|&i| &self.fields[i])
71    }
72
73    /// Busca un nombre de condición (un dato de nivel 88).
74    pub(crate) fn condition(&self, name: &str) -> Option<&ConditionName> {
75        self.conditions.get(&name.to_uppercase())
76    }
77}
78
79/// Dos datos pueden compartir nombre (COBOL los califica); aquí, como
80/// son campos de un struct, sus identificadores deben ser únicos.
81fn dedup_idents(fields: &mut [Field]) {
82    let mut seen: HashMap<String, u32> = HashMap::new();
83    for f in fields.iter_mut() {
84        let n = seen.entry(f.ident.clone()).or_insert(0);
85        if *n > 0 {
86            f.ident = format!("{}_{}", f.ident, n);
87        }
88        *n += 1;
89    }
90}
91
92/// Convierte un nombre COBOL en un identificador Rust válido.
93fn sanitize_ident(name: &str) -> String {
94    let mut s: String = name
95        .chars()
96        .map(|c| {
97            if c == '-' {
98                '_'
99            } else {
100                c.to_ascii_lowercase()
101            }
102        })
103        .filter(|c| c.is_ascii_alphanumeric() || *c == '_')
104        .collect();
105    if s.is_empty() || s.starts_with(|c: char| c.is_ascii_digit()) {
106        s = format!("f_{s}");
107    }
108    if is_rust_keyword(&s) {
109        s.push('_');
110    }
111    s
112}
113
114/// El nombre del método de un párrafo. El párrafo implícito ("") es
115/// `p_start`; el resto lleva el prefijo `p_`.
116pub(crate) fn paragraph_method(name: &str) -> String {
117    if name.is_empty() {
118        return "p_start".to_string();
119    }
120    let body: String = name
121        .chars()
122        .map(|c| {
123            if c == '-' {
124                '_'
125            } else {
126                c.to_ascii_lowercase()
127            }
128        })
129        .filter(|c| c.is_ascii_alphanumeric() || *c == '_')
130        .collect();
131    format!("p_{body}")
132}
133
134/// ¿Es `s` una palabra reservada de Rust? (Para no chocar al nombrar
135/// campos — un dato COBOL `MOVE` se volvería el keyword `move`.)
136fn is_rust_keyword(s: &str) -> bool {
137    matches!(
138        s,
139        "as" | "break"
140            | "const"
141            | "continue"
142            | "crate"
143            | "dyn"
144            | "else"
145            | "enum"
146            | "extern"
147            | "false"
148            | "fn"
149            | "for"
150            | "if"
151            | "impl"
152            | "in"
153            | "let"
154            | "loop"
155            | "match"
156            | "mod"
157            | "move"
158            | "mut"
159            | "pub"
160            | "ref"
161            | "return"
162            | "self"
163            | "static"
164            | "struct"
165            | "super"
166            | "trait"
167            | "true"
168            | "type"
169            | "unsafe"
170            | "use"
171            | "where"
172            | "while"
173            | "async"
174            | "await"
175            | "box"
176            | "yield"
177    )
178}