gioser

tawasuyu/brahman

Rust 534TOML 203Markdown 58JSON 197690 Sutichaykuna

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

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