⎇ fa65f202065abca26e0fb28581427117b84af85c
crates/modules/charka/charka-codegen/src/sym.rs
History
Who touched what
raw
feat(charka): INITIALIZE — resetear datos y grupos
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}