⎇ 7867d6830e128b8a2afe95cb752093e12a184988
crates/modules/charka/charka-codegen/src/sym.rs
Historia
Quién tocó qué
crudo
feat(charka): OCCURS — tablas y referencias con subíndice
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}