1# Changelog — charka
2
3Transpilador COBOL → Rust. El módulo más grande del ecosistema (Fase D
4del plan macro) — el parser COBOL completo es un esfuerzo multi-mes.
5
6### feat(charka): PICTURE de edición — `Z`, coma de millares y punto decimal
7
8Las PICTURE de edición (`Z,ZZ9.99`) — el formateo de informes de
9COBOL: supresión de ceros a la izquierda, coma de millares e inserción
10del punto decimal. Una rebanada vertical por el pipeline.
11
12- `charka-lexer`: el punto separador de COBOL siempre lleva un espacio
13 detrás. Un punto pegado a un carácter —`ZZ9.99`— ya no se toma como
14 terminador de sentencia: se emite como símbolo, para que el parser
15 lo reensamble dentro de la cláusula PICTURE.
16- `charka-runtime`: `format_edited(valor, pic)` — formatea un decimal
17 según una PICTURE de edición: `9` (dígito), `Z` (dígito con
18 supresión de ceros), `,` (coma, en blanco en la zona suprimida),
19 `.` (punto decimal) y `B` (espacio).
20- `charka-ir`: el modelo de datos distingue una PICTURE de edición —
21 `Field::edit` guarda la PICTURE; el campo se materializa como texto
22 de presentación.
23- `charka-codegen` y `charka-shadow`: un `MOVE` a un campo de edición
24 pasa el valor por `format_edited` antes de almacenarlo.
25- Corpus: programa nuevo `19-reporte` — formatea importes y contadores
26 con `Z,ZZZ,ZZ9.99` y `ZZZ,ZZ9`. Verificado: el intérprete sombra y
27 el crate compilado dan la misma salida.
28
29### feat(charka): E/S de ficheros — SELECT / FD / OPEN / READ / WRITE / CLOSE
30
31El gran hueco que faltaba para el COBOL real: el procesamiento de
32ficheros secuenciales. Una rebanada vertical por los seis crates.
33
34- `charka-parser`: la ENVIRONMENT division ya no se ignora — se
35 parsea `FILE-CONTROL` (`SELECT name ASSIGN TO "ruta"`); del FILE
36 SECTION se asocia cada `FD` con su registro `01`. `Program::files`.
37- `charka-runtime`: tipo `CobFile` — un fichero «line sequential»
38 (cada registro una línea). Lectura: carga a memoria. Escritura:
39 acumula y vuelca al cerrar.
40- `charka-ir`: `Ir::files` y los statements `Open`/`Close`/`Read`/
41 `Write`. `READ` lleva sus bloques `AT END` / `NOT AT END`.
42- `charka-codegen`: un campo `CobFile` por fichero en el `struct
43 Program`; los verbos emiten llamadas al runtime.
44- `charka-shadow`: el intérprete hace E/S de ficheros real.
45- Corpus: programa nuevo `18-fichero` — escribe tres líneas a un
46 fichero, lo cierra, lo relee con `READ ... AT END` y las muestra.
47 Verificado: el intérprete sombra y el crate compilado dan la misma
48 salida.
49- Alcance v1: organización «line sequential»; sin ficheros indexados
50 ni relativos, sin `FILE STATUS`.
51
52### feat(charka): PERFORM ... THRU como rango real de párrafos
53
54`PERFORM A THRU C` ejecuta A, B y C; antes el transpilador sólo
55ejecutaba A (lo marcaba como aproximado).
56
57- `charka-codegen`: `Symbols` registra ahora los párrafos en orden con
58 su nombre de método; `Symbols::build` toma el `Ir` completo.
59 `paragraph_range(name, thru)` da los métodos del rango. `emit_perform`
60 emite la llamada a cada uno.
61- `charka-shadow`: `run_paragraph_range` ejecuta los párrafos de
62 `name` a `thru` inclusive.
63- Corpus: programa nuevo `17-rangopar` (`PERFORM PASO-A THRU PASO-C`
64 sobre tres párrafos). Verificado en ambas rutas.
65
66### feat(charka): SET ... TO TRUE — escribir nombres de condición (88)
67
68La cara de escritura de los nombres de condición de COBOL: si `IF
69ES-VALIDO` los lee, `SET ES-VALIDO TO TRUE` los escribe.
70
71- IR: `Stmt::SetTrue { conditions }`.
72- Parser: `SET cond-1 cond-2 ... TO TRUE`. Otras formas de `SET`
73 (índices, `TO FALSE`) caen a `Stmt::Unknown`.
74- Codegen y shadow: `SET cond TO TRUE` asigna a su dato padre el valor
75 del 88 (un `MOVE` del valor a la variable).
76- Corpus: programa nuevo `16-bandera` (cambia banderas de texto y de
77 número con `SET`). Verificado en ambas rutas.
78
79### feat(charka): INITIALIZE — resetear datos y grupos
80
81El verbo de COBOL para volver un dato (o un registro entero) a su
82valor por defecto.
83
84- IR: `Stmt::Initialize { targets }`. El `model` de `charka-ir`
85 registra ahora los grupos y sus datos elementales
86 (`DataModel::groups`, `GroupInfo`).
87- Parser: `INITIALIZE name-1 name-2 ...`.
88- Codegen y shadow: cada destino, si es un grupo, se expande a sus
89 miembros; cada dato elemental se pone a 0 (numérico) o a espacios
90 (alfanumérico); una tabla `OCCURS` resetea todos sus elementos.
91- Corpus: programa nuevo `15-resetear` (resetea un grupo y un escalar).
92 Verificado en ambas rutas.
93
94### feat(charka): EVALUATE TRUE y rangos WHEN ... THRU
95
96Completa el `EVALUATE` con sus dos formas que faltaban.
97
98- IR: la rama `WhenBranch` pasa de `values: Vec<Operand>` a
99 `tests: Vec<WhenTest>`, donde `WhenTest` es `Value` (igualdad),
100 `Range` (`WHEN lo THRU hi`) o `Cond` (`EVALUATE TRUE WHEN cond`).
101- Parser: detecta `EVALUATE TRUE` y entonces cada `WHEN` parsea una
102 condición; en modo valor reconoce `WHEN lo THRU hi`.
103- Codegen y shadow: una prueba `Range` se traduce a `lo <= s <= hi`;
104 una `Cond`, a la condición directa.
105- Corpus: programa nuevo `14-clasifica` (clasifica notas con rangos
106 `THRU` y un `EVALUATE TRUE`). Verificado en ambas rutas.
107
108### feat(charka): INSPECT — contar y reemplazar caracteres
109
110El verbo de COBOL para analizar y limpiar campos de texto.
111
112- IR: `Stmt::Inspect { target, op }` con `InspectOp::TallyingForAll`
113 (cuenta apariciones y las suma a un contador) y
114 `InspectOp::ReplacingAll` (reemplaza apariciones).
115- Parser: `INSPECT t TALLYING n FOR ALL lit` y
116 `INSPECT t REPLACING ALL a BY b`. Una forma no soportada cae a
117 `Stmt::Unknown`.
118- Codegen: `TALLYING` → `str::matches(..).count()`; `REPLACING` →
119 `str::replace`.
120- Shadow: el intérprete cuenta / reemplaza el texto.
121- Corpus: programa nuevo `13-inspeccion`. Verificado: el intérprete
122 sombra y el crate compilado dan la misma salida.
123- Alcance v1: `TALLYING FOR ALL` y `REPLACING ALL`; sin `LEADING`,
124 `FIRST`, `CHARACTERS`, `BEFORE`/`AFTER`.
125
126### feat(charka): STRING y UNSTRING — manejo de cadenas
127
128Dos verbos comunes de COBOL para construir y partir cadenas.
129
130- IR: `Stmt::StringConcat { sources, into }` y
131 `Stmt::Unstring { source, delimiter, into }`.
132- Parser: `STRING a b DELIMITED BY SIZE INTO t END-STRING` y
133 `UNSTRING s DELIMITED BY d INTO a b c END-UNSTRING`.
134- Codegen: `STRING` → `format!` concatenado; `UNSTRING` → un bloque
135 que parte con `str::split` y reparte los trozos.
136- Shadow: el intérprete concatena / parte el texto y lo reparte.
137- Corpus: programa nuevo `12-cadenas`. Verificado: el intérprete
138 sombra y el crate compilado dan la misma salida.
139- Alcance v1: `STRING` con `DELIMITED BY SIZE` (los demás
140 delimitadores se ignoran); sin `WITH POINTER` ni `ON OVERFLOW`.
141
142### feat(charka): OCCURS — tablas y referencias con subíndice
143
144Los arrays de COBOL, que antes el transpilador descartaba en silencio.
145Atraviesa el pipeline entero.
146
147- Parser: la cláusula `OCCURS n [TIMES]` se captura en `DataItem`.
148- IR: `Operand::Indexed { name, index }` — una referencia `ELEM(I)`.
149 El subíndice es 1-based, como COBOL. Los destinos de los statements
150 pasan de `Vec<String>` a `Vec<Operand>`, así que se puede escribir a
151 un elemento de tabla (`MOVE x TO ELEM(I)`, `COMPUTE ELEM(I) = ...`).
152 `Field` del modelo gana `occurs: Option<u32>`.
153- Codegen: un campo `OCCURS` se emite como `Vec<Num>`/`Vec<Text>`,
154 inicializado con `vec![..; n]`; una referencia con subíndice indexa
155 el vector (1-based → 0-based).
156- Shadow: en el intérprete todo campo es un vector — un escalar es de
157 longitud 1, una tabla de `n`; las referencias se resuelven a
158 `(nombre, índice)`.
159- Corpus: programa nuevo `11-tabla` (llena una tabla con cuadrados y
160 los suma). Verificado: el intérprete sombra y el crate compilado por
161 `scaffold` dan ambos `SUMA DE CUADRADOS = 000055`.
162- Alcance v1: `OCCURS` en datos elementales, una dimensión, subíndice
163 de un solo operando. Fuera: `OCCURS` de grupo, multidimensional,
164 `OCCURS DEPENDING ON`.
165
166### feat(charka): nombres de condición (nivel 88) + modelo de datos compartido
167
168`IF ES-VALIDO` — los nombres de condición de COBOL, que antes el
169transpilador evaluaba siempre como `false`. Y, de paso, se elimina una
170duplicación: la resolución del modelo de datos.
171
172- `charka-ir` gana un módulo `model`: `resolve_data(&[DataItem]) ->
173 DataModel` aplana el árbol de datos a campos elementales (`Field` con
174 `FieldKind`) y a nombres de condición (`ConditionName`). El `Ir`
175 ahora lleva un campo `model`. Es la fuente única de verdad sobre la
176 clasificación de PICTURE.
177- `charka-codegen` y `charka-shadow` consumen `ir.model` en vez de
178 reimplementar cada uno la clasificación numérico/alfanumérico, el
179 ancho de PICTURE y la normalización de `VALUE`. `charka-codegen` ya
180 no depende de `charka-bcd`.
181- `Cond::Named` (un nivel 88) se resuelve a `padre = valor`: el codegen
182 emite la comparación, el intérprete sombra la evalúa.
183- Corregido de paso: un dato con hijos de nivel 88 (p. ej.
184 `01 WS-FLAG PIC X. 88 ES-SI VALUE 'Y'.`) antes se perdía como si
185 fuera un grupo; ahora se reconoce como campo elemental.
186- Corpus: programa nuevo `10-condicion` (un semáforo con 88 de texto y
187 de número, en `IF` e `IF/ELSE`). Verificado: intérprete y crate
188 compilado dan la misma salida.
189
190### feat(charka): EVALUATE — el case de COBOL
191
192`EVALUATE` atraviesa el pipeline entero — antes el parser lo guardaba
193crudo como `Stmt::Unknown`.
194
195- IR: `Stmt::Evaluate { subject, whens, other }` con
196 `WhenBranch { values, body }`. Varios `WHEN` apilados comparten
197 cuerpo; `WHEN OTHER` es el caso por defecto.
198- Parser: `EVALUATE subject WHEN v1 WHEN v2 ... [WHEN OTHER ...]
199 END-EVALUATE`.
200- Codegen: lo baja a una cadena `if / else if / else` — una rama se
201 elige si el sujeto es igual a alguno de sus valores; sin caída.
202- Shadow: el intérprete evalúa el sujeto y ejecuta la primera rama
203 cuyos valores casen, o el `WHEN OTHER`.
204- Corpus: programa nuevo `09-evaluar` (un `EVALUATE` por valor anidado
205 en un `PERFORM VARYING`, con `WHEN` apilados y `WHEN OTHER`).
206 Verificado: intérprete sombra y crate compilado dan la misma salida.
207- Alcance v1: `EVALUATE` por igualdad de valor; no la forma
208 `EVALUATE TRUE` con condiciones ni los rangos `THRU`.
209
210### feat(charka): PERFORM VARYING — el bucle con variable de control
211
212El bucle más usado de COBOL, que antes el parser degradaba a un
213`PERFORM` vacío. Ahora atraviesa el pipeline entero.
214
215- IR: `PerformControl::Varying { var, from, by, until }`.
216- Parser: reconoce `PERFORM VARYING var FROM x BY y UNTIL cond` en
217 línea (`END-PERFORM`) y fuera de línea (`PERFORM párrafo VARYING …`).
218- Codegen: emite `var = from; while !(until) { cuerpo; var += by; }`.
219- Shadow: el intérprete inicializa la variable, evalúa la condición
220 antes de cada vuelta e incrementa al final.
221- Corpus: programa nuevo `08-varying` (suma 1..10 con `PERFORM
222 VARYING`). Verificado: el intérprete sombra y el crate compilado por
223 `scaffold` dan ambos `SUMA 1 A 10 = 00055`.
224
225### feat(charka): CLI del transpilador — transpile / scaffold / run / check
226
227App nueva `crates/apps/charka` — el binario `charka`, que vuelve usable
228el pipeline desde la terminal. Cuatro comandos:
229
230- `transpile <in.cob> [-o out.rs]` — emite el código Rust (a un archivo
231 o a la salida estándar).
232- `scaffold <in.cob> -o <dir>` — genera un crate Rust completo
233 (`Cargo.toml` + `src/main.rs`) que depende de `charka-runtime` y
234 compila tal cual.
235- `run <in.cob>` — ejecuta el programa con el intérprete sombra y
236 muestra su salida, sin compilar nada.
237- `check <in.cob> -e <esperado>` — ejecuta y diferencia la salida
238 contra un archivo esperado; reporta las líneas que difieren.
239
240Avisa de los verbos COBOL que aún no se transpilan. Verificado de
241punta a punta contra el corpus: `scaffold` de `06-nomina` genera un
242crate que compila y produce la misma salida que el intérprete sombra —
243las dos rutas de ejecución concuerdan. 4 tests; fmt + clippy limpios.
244
245### feat(charka-shadow): validador en sombra + corpus COBOL
246
247Crate nuevo `crates/modules/charka/charka-shadow` y un corpus de prueba
248`crates/modules/charka/corpus/` — el pipeline COBOL→Rust queda
249completo y validado de punta a punta.
250
251- `charka-shadow` certifica que el transpilador preserva la semántica
252 del COBOL original con una **ejecución sombra**: un intérprete que
253 corre el `Ir` directamente sobre `charka-runtime`, sin compilar nada.
254 Es una segunda ruta de ejecución, independiente del código que emite
255 `charka-codegen`.
256- `interpret(&Ir) -> Outcome` ejecuta el IR y captura las líneas de
257 `DISPLAY`; `run_source(&str)` corre el pipeline completo (lexer →
258 parser → IR → intérprete).
259- Tope de pasos (`Halt::StepLimit`): un bucle que no termina se corta
260 en vez de colgar la ejecución.
261- **Corpus**: 7 programas COBOL de complejidad graduada — `01-hola`
262 (un `DISPLAY`), `02-aritmetica` (`ADD`/`SUBTRACT`/`MULTIPLY`),
263 `03-condicional` (`IF`/`ELSE`), `04-bucle` (`PERFORM TIMES`),
264 `05-factorial` (`PERFORM UNTIL`), `06-nomina` (grupos, `COMPUTE` con
265 paréntesis, `ROUNDED`, `V99`), `07-clasificar` (`IF` anidado, `AND`).
266 Cada uno con su `.expected` verificada a mano.
267- 11 tests: los 7 programas del corpus + fuente vacío, `STOP RUN`,
268 corte por tope de pasos y propagación de error de léxico.
269
270### feat(charka-codegen): emisión de Rust desde el IR
271
272Crate nuevo `crates/modules/charka/charka-codegen` — la etapa final del
273pipeline. `generate(&Ir) -> String` produce un fuente Rust (un
274`main.rs`) que, compilado contra `charka-runtime`, ejecuta la lógica
275del programa COBOL.
276
277- Un `struct Program` con un campo por cada dato elemental — `Num`
278 para los numéricos, `Text` para los alfanuméricos; `Program::new()`
279 los inicializa desde sus cláusulas `VALUE`.
280- Un método `p_<párrafo>(&mut self)` por cada párrafo del PROCEDURE;
281 `run()` los encadena en orden (el «caer» de COBOL); `main()`
282 construye el `Program` y lo corre.
283- Cada `Stmt` → código Rust: `MOVE`→`.store`/`.fill`,
284 `DISPLAY`→`println!`, `COMPUTE` y la aritmética → expresiones
285 `Decimal`, `IF`→`if`/`else`, `PERFORM`→ llamada de método / `for` /
286 `while`, `GO TO`→ llamada + `return`, `STOP RUN`→`process::exit`.
287- Tolerante: lo no transpilable (`Stmt::Unknown`, un dato sin resolver,
288 el operador `**`) se emite como comentario `// charka:`; el código
289 generado siempre compila.
290- Saneado de identificadores COBOL→Rust (incl. choques con keywords).
291- Verificado de punta a punta: un programa COBOL de demostración
292 transpila a Rust que compila contra `charka-runtime` y produce la
293 salida esperada (`COMPUTE`, `IF`, `PERFORM`, `DISPLAY`).
294- 14 tests del fuente emitido; fuera de alcance v1: grupos como campo,
295 `REDEFINES`, `OCCURS`, `PERFORM ... THRU` como rango, E/S.
296
297### feat(charka-runtime): soporte de ejecución — campos Num y Text
298
299Crate nuevo `crates/modules/charka/charka-runtime` — el soporte que los
300programas COBOL transpilados enlazan. `charka-codegen` no emitirá Rust
301autónomo: emitirá Rust que llama a esta biblioteca.
302
303- `Num` — campo numérico (`PIC 9(5)V99`): un `Decimal` conformado a su
304 `Picture`. `store` trunca a la escala declarada; `store_rounded`
305 redondea; al desbordar la parte entera conserva los dígitos de bajo
306 orden (el `ON SIZE ERROR` de COBOL sin cláusula). `display` da los
307 dígitos con relleno de ceros y signo si corresponde.
308- `Text` — campo alfanumérico (`PIC X(n)`) de longitud fija: `store`
309 justifica a la izquierda y rellena con espacios o trunca; `fill`
310 mueve las constantes figurativas (`SPACES`, `ZEROS`).
311- `cobol_text_cmp` — comparación alfanumérica que rellena el más corto
312 con espacios.
313- Reexporta `Decimal`/`Picture`/`Rounding` de `charka-bcd`.
314- Construido antes que `charka-codegen` (la nota de orden del plan los
315 listaba al revés): el codegen emite contra esta API, así que el
316 runtime debe existir primero — y se verifica solo, sin el codegen.
317- 17 tests: campo en cero, `VALUE` inicial, truncado y redondeo,
318 desbordamiento que conserva bajo orden, magnitud sin signo y signo
319 con signo, justificación y relleno de texto, `fill`, comparación.
320
321### feat(charka-ir): representación intermedia — statements tipados
322
323Crate nuevo `crates/modules/charka/charka-ir` — la tercera etapa del
324pipeline: `Program` → `Ir`. El PROCEDURE division pasa de sentencias
325con tokens crudos a un árbol de instrucciones tipadas.
326
327- `lower(&Program) -> Ir` — total y tolerante: nunca falla.
328- `Ir { program_id, data, procedures }`. El modelo de datos (la DATA
329 division) pasa tal cual — sirve de tabla de símbolos.
330- `Stmt` cubre `MOVE`, `DISPLAY`, `ACCEPT`, `COMPUTE`, `ADD`,
331 `SUBTRACT`, `MULTIPLY`, `DIVIDE`, `IF`/`ELSE`/`END-IF`, `PERFORM`
332 (fuera de línea, en línea, `TIMES`, `UNTIL`), `GO TO`, `STOP RUN`,
333 `GOBACK`, `EXIT`, `CONTINUE`.
334- Expresiones de `COMPUTE` con precedencia y paréntesis (`+ -` <
335 `* /` < `**` asociativo a derecha). Condiciones de `IF`/`UNTIL` con
336 comparadores en forma símbolo (`= < > <= >= <>`) o palabra
337 (`EQUAL TO`, `GREATER THAN`...), `AND`/`OR`/`NOT` y nombres de
338 condición (datos de nivel 88).
339- COBOL no termina los statements con un símbolo: el parser delimita
340 las listas de operandos con palabras "frontera" (verbos,
341 terminadores `END-*`/`ELSE`, conectores `TO`/`GIVING`/`BY`...).
342- Un verbo no soportado se conserva como `Stmt::Unknown { verb,
343 tokens }` — el lowering nunca aborta.
344- Fuera de alcance v1: `EVALUATE`, `STRING`/`UNSTRING`, E/S de
345 ficheros, `PERFORM VARYING`, CICS, SQL embebido.
346- 17 tests: MOVE simple y multi-destino, DISPLAY con figurativas,
347 precedencia de COMPUTE, flag ROUNDED, ADD in-place vs GIVING,
348 SUBTRACT, DIVIDE BY/INTO, IF/ELSE, condiciones con AND, nombre de
349 condición, PERFORM párrafo/TIMES/UNTIL en línea, varios statements
350 en una sentencia, verbo desconocido, programa completo.
351
352### feat(charka-parser): parser COBOL'85 → AST
353
354Crate nuevo `crates/modules/charka/charka-parser` — la segunda etapa del
355pipeline: `Vec<Token>` → `Program` (AST). Alcance v1: el esqueleto del
356programa.
357
358- `parse(&[Token]) -> Result<Program, ParseError>`. El AST: `Program`
359 (`program_id`, `data`, `paragraphs`), `DataItem`, `Paragraph`,
360 `Sentence`.
361- Particiona el flujo de tokens en las cuatro divisiones por sus
362 encabezados (`X DIVISION`); de la IDENTIFICATION extrae el
363 `PROGRAM-ID`.
364- **DATA division** → árbol de `DataItem`: número de nivel, nombre,
365 cláusula `PICTURE` reensamblada (`S9` `(` `5` `)` `V99` →
366 `S9(5)V99`) y `VALUE`. Anida por número de nivel — 01 y 77 son
367 raíces, 88 cuelga del ítem precedente.
368- **PROCEDURE division** → `Vec<Paragraph>`, cada párrafo con sus
369 `Sentence` (tokens crudos; sin parseo a nivel de statement, eso es
370 `charka-ir`). Las sentencias previas al primer encabezado van a un
371 párrafo implícito de nombre "".
372- Parser tolerante: salta encabezados de `SECTION`, entradas `FD`/`SD`
373 y cláusulas de datos que no sean `PICTURE`/`VALUE`. `ParseError` sólo
374 ante un número de nivel inválido o un dato sin nombre.
375- 15 tests: PROGRAM-ID (forma larga y `ID` corta), ítems planos y
376 anidados, reensamblado de PICTURE, variantes de VALUE, niveles 88,
377 `FILLER`, párrafos y párrafo implícito, encabezado de sección,
378 programa completo de punta a punta, nivel inválido.
379
380### feat(charka-lexer): tokenizador de COBOL
381
382Crate nuevo `crates/modules/charka/charka-lexer` — la primera etapa del
383pipeline del transpilador: texto COBOL → secuencia de `Token`.
384
385- **Lexer deliberadamente tonto**: no conoce keywords ni la cláusula
386 `PICTURE`; emite `Word` para todo identificador y deja la
387 clasificación al parser.
388- Tokens: `Word` (palabras COBOL con guiones internos), `Number`
389 (literal sin signo), `String` (comillas dobladas colapsadas),
390 `Period` (el `.` terminador), `Symbol` (paréntesis, separadores y
391 operadores `+ - * / ** = < > <= >= <>`).
392- Dos formatos de fuente: **fijo** (la tarjeta de 80 columnas — cols
393 1-6 secuencia, 7 indicadora, 8-72 código, 73-80 identificación) y
394 **libre**. Comentarios por la columna indicadora (`*`/`/`) o por `*`
395 inicial en formato libre.
396- Cada `Token` lleva línea y columna 1-based; `LexError` tipado
397 (literal sin cerrar, carácter inesperado).
398- Limitación v1 documentada: no soporta continuación de literales entre
399 líneas (indicador `-`). Subconjunto COBOL'85, el hito intermedio.
400- 17 tests: sentencias, palabras con guiones, literales y comillas
401 dobladas, números vs terminador, operadores, ambos formatos,
402 tracking de posición, errores.
403
404### feat(charka-bcd): aritmética decimal con semántica COBOL
405
406(Pre-existente.) `Picture` + `Decimal` de punto fijo exacto — ver el
407SDD del módulo. 22 tests.
charka.md
Changelog — charka
Transpilador COBOL → Rust. El módulo más grande del ecosistema (Fase D
del plan macro) — el parser COBOL completo es un esfuerzo multi-mes.
feat(charka): PICTURE de edición — Z, coma de millares y punto decimal
Las PICTURE de edición (Z,ZZ9.99) — el formateo de informes de
COBOL: supresión de ceros a la izquierda, coma de millares e inserción
del punto decimal. Una rebanada vertical por el pipeline.
charka-lexer: el punto separador de COBOL siempre lleva un espacio
detrás. Un punto pegado a un carácter —ZZ9.99— ya no se toma como
terminador de sentencia: se emite como símbolo, para que el parser
lo reensamble dentro de la cláusula PICTURE.
charka-runtime: format_edited(valor, pic) — formatea un decimal
según una PICTURE de edición: 9 (dígito), Z (dígito con
supresión de ceros), , (coma, en blanco en la zona suprimida),
. (punto decimal) y B (espacio).
charka-ir: el modelo de datos distingue una PICTURE de edición —
Field::edit guarda la PICTURE; el campo se materializa como texto
de presentación.
charka-codegen y charka-shadow: un MOVE a un campo de edición
pasa el valor por format_edited antes de almacenarlo.
- Corpus: programa nuevo
19-reporte — formatea importes y contadores
con Z,ZZZ,ZZ9.99 y ZZZ,ZZ9. Verificado: el intérprete sombra y
el crate compilado dan la misma salida.
feat(charka): E/S de ficheros — SELECT / FD / OPEN / READ / WRITE / CLOSE
El gran hueco que faltaba para el COBOL real: el procesamiento de
ficheros secuenciales. Una rebanada vertical por los seis crates.
charka-parser: la ENVIRONMENT division ya no se ignora — se
parsea FILE-CONTROL (SELECT name ASSIGN TO "ruta"); del FILE
SECTION se asocia cada FD con su registro 01. Program::files.
charka-runtime: tipo CobFile — un fichero «line sequential»
(cada registro una línea). Lectura: carga a memoria. Escritura:
acumula y vuelca al cerrar.
charka-ir: Ir::files y los statements Open/Close/Read/
Write. READ lleva sus bloques AT END / NOT AT END.
charka-codegen: un campo CobFile por fichero en el struct Program; los verbos emiten llamadas al runtime.
charka-shadow: el intérprete hace E/S de ficheros real.
- Corpus: programa nuevo
18-fichero — escribe tres líneas a un
fichero, lo cierra, lo relee con READ ... AT END y las muestra.
Verificado: el intérprete sombra y el crate compilado dan la misma
salida.
- Alcance v1: organización «line sequential»; sin ficheros indexados
ni relativos, sin
FILE STATUS.
feat(charka): PERFORM ... THRU como rango real de párrafos
PERFORM A THRU C ejecuta A, B y C; antes el transpilador sólo
ejecutaba A (lo marcaba como aproximado).
charka-codegen: Symbols registra ahora los párrafos en orden con
su nombre de método; Symbols::build toma el Ir completo.
paragraph_range(name, thru) da los métodos del rango. emit_perform
emite la llamada a cada uno.
charka-shadow: run_paragraph_range ejecuta los párrafos de
name a thru inclusive.
- Corpus: programa nuevo
17-rangopar (PERFORM PASO-A THRU PASO-C
sobre tres párrafos). Verificado en ambas rutas.
feat(charka): SET ... TO TRUE — escribir nombres de condición (88)
La cara de escritura de los nombres de condición de COBOL: si IF ES-VALIDO los lee, SET ES-VALIDO TO TRUE los escribe.
- IR:
Stmt::SetTrue { conditions }.
- Parser:
SET cond-1 cond-2 ... TO TRUE. Otras formas de SET
(índices, TO FALSE) caen a Stmt::Unknown.
- Codegen y shadow:
SET cond TO TRUE asigna a su dato padre el valor
del 88 (un MOVE del valor a la variable).
- Corpus: programa nuevo
16-bandera (cambia banderas de texto y de
número con SET). Verificado en ambas rutas.
feat(charka): INITIALIZE — resetear datos y grupos
El verbo de COBOL para volver un dato (o un registro entero) a su
valor por defecto.
- IR:
Stmt::Initialize { targets }. El model de charka-ir
registra ahora los grupos y sus datos elementales
(DataModel::groups, GroupInfo).
- Parser:
INITIALIZE name-1 name-2 ....
- Codegen y shadow: cada destino, si es un grupo, se expande a sus
miembros; cada dato elemental se pone a 0 (numérico) o a espacios
(alfanumérico); una tabla
OCCURS resetea todos sus elementos.
- Corpus: programa nuevo
15-resetear (resetea un grupo y un escalar).
Verificado en ambas rutas.
feat(charka): EVALUATE TRUE y rangos WHEN ... THRU
Completa el EVALUATE con sus dos formas que faltaban.
- IR: la rama
WhenBranch pasa de values: Vec<Operand> a
tests: Vec<WhenTest>, donde WhenTest es Value (igualdad),
Range (WHEN lo THRU hi) o Cond (EVALUATE TRUE WHEN cond).
- Parser: detecta
EVALUATE TRUE y entonces cada WHEN parsea una
condición; en modo valor reconoce WHEN lo THRU hi.
- Codegen y shadow: una prueba
Range se traduce a lo <= s <= hi;
una Cond, a la condición directa.
- Corpus: programa nuevo
14-clasifica (clasifica notas con rangos
THRU y un EVALUATE TRUE). Verificado en ambas rutas.
feat(charka): INSPECT — contar y reemplazar caracteres
El verbo de COBOL para analizar y limpiar campos de texto.
- IR:
Stmt::Inspect { target, op } con InspectOp::TallyingForAll
(cuenta apariciones y las suma a un contador) y
InspectOp::ReplacingAll (reemplaza apariciones).
- Parser:
INSPECT t TALLYING n FOR ALL lit y
INSPECT t REPLACING ALL a BY b. Una forma no soportada cae a
Stmt::Unknown.
- Codegen:
TALLYING → str::matches(..).count(); REPLACING →
str::replace.
- Shadow: el intérprete cuenta / reemplaza el texto.
- Corpus: programa nuevo
13-inspeccion. Verificado: el intérprete
sombra y el crate compilado dan la misma salida.
- Alcance v1:
TALLYING FOR ALL y REPLACING ALL; sin LEADING,
FIRST, CHARACTERS, BEFORE/AFTER.
feat(charka): STRING y UNSTRING — manejo de cadenas
Dos verbos comunes de COBOL para construir y partir cadenas.
- IR:
Stmt::StringConcat { sources, into } y
Stmt::Unstring { source, delimiter, into }.
- Parser:
STRING a b DELIMITED BY SIZE INTO t END-STRING y
UNSTRING s DELIMITED BY d INTO a b c END-UNSTRING.
- Codegen:
STRING → format! concatenado; UNSTRING → un bloque
que parte con str::split y reparte los trozos.
- Shadow: el intérprete concatena / parte el texto y lo reparte.
- Corpus: programa nuevo
12-cadenas. Verificado: el intérprete
sombra y el crate compilado dan la misma salida.
- Alcance v1:
STRING con DELIMITED BY SIZE (los demás
delimitadores se ignoran); sin WITH POINTER ni ON OVERFLOW.
feat(charka): OCCURS — tablas y referencias con subíndice
Los arrays de COBOL, que antes el transpilador descartaba en silencio.
Atraviesa el pipeline entero.
- Parser: la cláusula
OCCURS n [TIMES] se captura en DataItem.
- IR:
Operand::Indexed { name, index } — una referencia ELEM(I).
El subíndice es 1-based, como COBOL. Los destinos de los statements
pasan de Vec<String> a Vec<Operand>, así que se puede escribir a
un elemento de tabla (MOVE x TO ELEM(I), COMPUTE ELEM(I) = ...).
Field del modelo gana occurs: Option<u32>.
- Codegen: un campo
OCCURS se emite como Vec<Num>/Vec<Text>,
inicializado con vec![..; n]; una referencia con subíndice indexa
el vector (1-based → 0-based).
- Shadow: en el intérprete todo campo es un vector — un escalar es de
longitud 1, una tabla de
n; las referencias se resuelven a
(nombre, índice).
- Corpus: programa nuevo
11-tabla (llena una tabla con cuadrados y
los suma). Verificado: el intérprete sombra y el crate compilado por
scaffold dan ambos SUMA DE CUADRADOS = 000055.
- Alcance v1:
OCCURS en datos elementales, una dimensión, subíndice
de un solo operando. Fuera: OCCURS de grupo, multidimensional,
OCCURS DEPENDING ON.
feat(charka): nombres de condición (nivel 88) + modelo de datos compartido
IF ES-VALIDO — los nombres de condición de COBOL, que antes el
transpilador evaluaba siempre como false. Y, de paso, se elimina una
duplicación: la resolución del modelo de datos.
charka-ir gana un módulo model: resolve_data(&[DataItem]) -> DataModel aplana el árbol de datos a campos elementales (Field con
FieldKind) y a nombres de condición (ConditionName). El Ir
ahora lleva un campo model. Es la fuente única de verdad sobre la
clasificación de PICTURE.
charka-codegen y charka-shadow consumen ir.model en vez de
reimplementar cada uno la clasificación numérico/alfanumérico, el
ancho de PICTURE y la normalización de VALUE. charka-codegen ya
no depende de charka-bcd.
Cond::Named (un nivel 88) se resuelve a padre = valor: el codegen
emite la comparación, el intérprete sombra la evalúa.
- Corregido de paso: un dato con hijos de nivel 88 (p. ej.
01 WS-FLAG PIC X. 88 ES-SI VALUE 'Y'.) antes se perdía como si
fuera un grupo; ahora se reconoce como campo elemental.
- Corpus: programa nuevo
10-condicion (un semáforo con 88 de texto y
de número, en IF e IF/ELSE). Verificado: intérprete y crate
compilado dan la misma salida.
feat(charka): EVALUATE — el case de COBOL
EVALUATE atraviesa el pipeline entero — antes el parser lo guardaba
crudo como Stmt::Unknown.
- IR:
Stmt::Evaluate { subject, whens, other } con
WhenBranch { values, body }. Varios WHEN apilados comparten
cuerpo; WHEN OTHER es el caso por defecto.
- Parser:
EVALUATE subject WHEN v1 WHEN v2 ... [WHEN OTHER ...] END-EVALUATE.
- Codegen: lo baja a una cadena
if / else if / else — una rama se
elige si el sujeto es igual a alguno de sus valores; sin caída.
- Shadow: el intérprete evalúa el sujeto y ejecuta la primera rama
cuyos valores casen, o el
WHEN OTHER.
- Corpus: programa nuevo
09-evaluar (un EVALUATE por valor anidado
en un PERFORM VARYING, con WHEN apilados y WHEN OTHER).
Verificado: intérprete sombra y crate compilado dan la misma salida.
- Alcance v1:
EVALUATE por igualdad de valor; no la forma
EVALUATE TRUE con condiciones ni los rangos THRU.
feat(charka): PERFORM VARYING — el bucle con variable de control
El bucle más usado de COBOL, que antes el parser degradaba a un
PERFORM vacío. Ahora atraviesa el pipeline entero.
- IR:
PerformControl::Varying { var, from, by, until }.
- Parser: reconoce
PERFORM VARYING var FROM x BY y UNTIL cond en
línea (END-PERFORM) y fuera de línea (PERFORM párrafo VARYING …).
- Codegen: emite
var = from; while !(until) { cuerpo; var += by; }.
- Shadow: el intérprete inicializa la variable, evalúa la condición
antes de cada vuelta e incrementa al final.
- Corpus: programa nuevo
08-varying (suma 1..10 con PERFORM VARYING). Verificado: el intérprete sombra y el crate compilado por
scaffold dan ambos SUMA 1 A 10 = 00055.
feat(charka): CLI del transpilador — transpile / scaffold / run / check
App nueva crates/apps/charka — el binario charka, que vuelve usable
el pipeline desde la terminal. Cuatro comandos:
transpile <in.cob> [-o out.rs] — emite el código Rust (a un archivo
o a la salida estándar).
scaffold <in.cob> -o <dir> — genera un crate Rust completo
(Cargo.toml + src/main.rs) que depende de charka-runtime y
compila tal cual.
run <in.cob> — ejecuta el programa con el intérprete sombra y
muestra su salida, sin compilar nada.
check <in.cob> -e <esperado> — ejecuta y diferencia la salida
contra un archivo esperado; reporta las líneas que difieren.
Avisa de los verbos COBOL que aún no se transpilan. Verificado de
punta a punta contra el corpus: scaffold de 06-nomina genera un
crate que compila y produce la misma salida que el intérprete sombra —
las dos rutas de ejecución concuerdan. 4 tests; fmt + clippy limpios.
feat(charka-shadow): validador en sombra + corpus COBOL
Crate nuevo crates/modules/charka/charka-shadow y un corpus de prueba
crates/modules/charka/corpus/ — el pipeline COBOL→Rust queda
completo y validado de punta a punta.
charka-shadow certifica que el transpilador preserva la semántica
del COBOL original con una ejecución sombra: un intérprete que
corre el Ir directamente sobre charka-runtime, sin compilar nada.
Es una segunda ruta de ejecución, independiente del código que emite
charka-codegen.
interpret(&Ir) -> Outcome ejecuta el IR y captura las líneas de
DISPLAY; run_source(&str) corre el pipeline completo (lexer →
parser → IR → intérprete).
- Tope de pasos (
Halt::StepLimit): un bucle que no termina se corta
en vez de colgar la ejecución.
- Corpus: 7 programas COBOL de complejidad graduada —
01-hola
(un DISPLAY), 02-aritmetica (ADD/SUBTRACT/MULTIPLY),
03-condicional (IF/ELSE), 04-bucle (PERFORM TIMES),
05-factorial (PERFORM UNTIL), 06-nomina (grupos, COMPUTE con
paréntesis, ROUNDED, V99), 07-clasificar (IF anidado, AND).
Cada uno con su .expected verificada a mano.
- 11 tests: los 7 programas del corpus + fuente vacío,
STOP RUN,
corte por tope de pasos y propagación de error de léxico.
feat(charka-codegen): emisión de Rust desde el IR
Crate nuevo crates/modules/charka/charka-codegen — la etapa final del
pipeline. generate(&Ir) -> String produce un fuente Rust (un
main.rs) que, compilado contra charka-runtime, ejecuta la lógica
del programa COBOL.
- Un
struct Program con un campo por cada dato elemental — Num
para los numéricos, Text para los alfanuméricos; Program::new()
los inicializa desde sus cláusulas VALUE.
- Un método
p_<párrafo>(&mut self) por cada párrafo del PROCEDURE;
run() los encadena en orden (el «caer» de COBOL); main()
construye el Program y lo corre.
- Cada
Stmt → código Rust: MOVE→.store/.fill,
DISPLAY→println!, COMPUTE y la aritmética → expresiones
Decimal, IF→if/else, PERFORM→ llamada de método / for /
while, GO TO→ llamada + return, STOP RUN→process::exit.
- Tolerante: lo no transpilable (
Stmt::Unknown, un dato sin resolver,
el operador **) se emite como comentario // charka:; el código
generado siempre compila.
- Saneado de identificadores COBOL→Rust (incl. choques con keywords).
- Verificado de punta a punta: un programa COBOL de demostración
transpila a Rust que compila contra
charka-runtime y produce la
salida esperada (COMPUTE, IF, PERFORM, DISPLAY).
- 14 tests del fuente emitido; fuera de alcance v1: grupos como campo,
REDEFINES, OCCURS, PERFORM ... THRU como rango, E/S.
feat(charka-runtime): soporte de ejecución — campos Num y Text
Crate nuevo crates/modules/charka/charka-runtime — el soporte que los
programas COBOL transpilados enlazan. charka-codegen no emitirá Rust
autónomo: emitirá Rust que llama a esta biblioteca.
Num — campo numérico (PIC 9(5)V99): un Decimal conformado a su
Picture. store trunca a la escala declarada; store_rounded
redondea; al desbordar la parte entera conserva los dígitos de bajo
orden (el ON SIZE ERROR de COBOL sin cláusula). display da los
dígitos con relleno de ceros y signo si corresponde.
Text — campo alfanumérico (PIC X(n)) de longitud fija: store
justifica a la izquierda y rellena con espacios o trunca; fill
mueve las constantes figurativas (SPACES, ZEROS).
cobol_text_cmp — comparación alfanumérica que rellena el más corto
con espacios.
- Reexporta
Decimal/Picture/Rounding de charka-bcd.
- Construido antes que
charka-codegen (la nota de orden del plan los
listaba al revés): el codegen emite contra esta API, así que el
runtime debe existir primero — y se verifica solo, sin el codegen.
- 17 tests: campo en cero,
VALUE inicial, truncado y redondeo,
desbordamiento que conserva bajo orden, magnitud sin signo y signo
con signo, justificación y relleno de texto, fill, comparación.
feat(charka-ir): representación intermedia — statements tipados
Crate nuevo crates/modules/charka/charka-ir — la tercera etapa del
pipeline: Program → Ir. El PROCEDURE division pasa de sentencias
con tokens crudos a un árbol de instrucciones tipadas.
lower(&Program) -> Ir — total y tolerante: nunca falla.
Ir { program_id, data, procedures }. El modelo de datos (la DATA
division) pasa tal cual — sirve de tabla de símbolos.
Stmt cubre MOVE, DISPLAY, ACCEPT, COMPUTE, ADD,
SUBTRACT, MULTIPLY, DIVIDE, IF/ELSE/END-IF, PERFORM
(fuera de línea, en línea, TIMES, UNTIL), GO TO, STOP RUN,
GOBACK, EXIT, CONTINUE.
- Expresiones de
COMPUTE con precedencia y paréntesis (+ - <
* / < ** asociativo a derecha). Condiciones de IF/UNTIL con
comparadores en forma símbolo (= < > <= >= <>) o palabra
(EQUAL TO, GREATER THAN...), AND/OR/NOT y nombres de
condición (datos de nivel 88).
- COBOL no termina los statements con un símbolo: el parser delimita
las listas de operandos con palabras "frontera" (verbos,
terminadores
END-*/ELSE, conectores TO/GIVING/BY...).
- Un verbo no soportado se conserva como
Stmt::Unknown { verb, tokens } — el lowering nunca aborta.
- Fuera de alcance v1:
EVALUATE, STRING/UNSTRING, E/S de
ficheros, PERFORM VARYING, CICS, SQL embebido.
- 17 tests: MOVE simple y multi-destino, DISPLAY con figurativas,
precedencia de COMPUTE, flag ROUNDED, ADD in-place vs GIVING,
SUBTRACT, DIVIDE BY/INTO, IF/ELSE, condiciones con AND, nombre de
condición, PERFORM párrafo/TIMES/UNTIL en línea, varios statements
en una sentencia, verbo desconocido, programa completo.
feat(charka-parser): parser COBOL'85 → AST
Crate nuevo crates/modules/charka/charka-parser — la segunda etapa del
pipeline: Vec<Token> → Program (AST). Alcance v1: el esqueleto del
programa.
parse(&[Token]) -> Result<Program, ParseError>. El AST: Program
(program_id, data, paragraphs), DataItem, Paragraph,
Sentence.
- Particiona el flujo de tokens en las cuatro divisiones por sus
encabezados (
X DIVISION); de la IDENTIFICATION extrae el
PROGRAM-ID.
- DATA division → árbol de
DataItem: número de nivel, nombre,
cláusula PICTURE reensamblada (S9 ( 5 ) V99 →
S9(5)V99) y VALUE. Anida por número de nivel — 01 y 77 son
raíces, 88 cuelga del ítem precedente.
- PROCEDURE division →
Vec<Paragraph>, cada párrafo con sus
Sentence (tokens crudos; sin parseo a nivel de statement, eso es
charka-ir). Las sentencias previas al primer encabezado van a un
párrafo implícito de nombre "".
- Parser tolerante: salta encabezados de
SECTION, entradas FD/SD
y cláusulas de datos que no sean PICTURE/VALUE. ParseError sólo
ante un número de nivel inválido o un dato sin nombre.
- 15 tests: PROGRAM-ID (forma larga y
ID corta), ítems planos y
anidados, reensamblado de PICTURE, variantes de VALUE, niveles 88,
FILLER, párrafos y párrafo implícito, encabezado de sección,
programa completo de punta a punta, nivel inválido.
feat(charka-lexer): tokenizador de COBOL
Crate nuevo crates/modules/charka/charka-lexer — la primera etapa del
pipeline del transpilador: texto COBOL → secuencia de Token.
- Lexer deliberadamente tonto: no conoce keywords ni la cláusula
PICTURE; emite Word para todo identificador y deja la
clasificación al parser.
- Tokens:
Word (palabras COBOL con guiones internos), Number
(literal sin signo), String (comillas dobladas colapsadas),
Period (el . terminador), Symbol (paréntesis, separadores y
operadores + - * / ** = < > <= >= <>).
- Dos formatos de fuente: fijo (la tarjeta de 80 columnas — cols
1-6 secuencia, 7 indicadora, 8-72 código, 73-80 identificación) y
libre. Comentarios por la columna indicadora (
*//) o por *
inicial en formato libre.
- Cada
Token lleva línea y columna 1-based; LexError tipado
(literal sin cerrar, carácter inesperado).
- Limitación v1 documentada: no soporta continuación de literales entre
líneas (indicador
-). Subconjunto COBOL'85, el hito intermedio.
- 17 tests: sentencias, palabras con guiones, literales y comillas
dobladas, números vs terminador, operadores, ambos formatos,
tracking de posición, errores.
feat(charka-bcd): aritmética decimal con semántica COBOL
(Pre-existente.) Picture + Decimal de punto fijo exacto — ver el
SDD del módulo. 22 tests.