Files
brahman/docs/changelog/charka.md
T
sergio 82ba0b7a1a 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: el intérprete sombra y el crate
  compilado por scaffold dan la misma salida.

Tests: charka-ir 29, charka-codegen 23, charka-shadow 21. fmt +
clippy limpios.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 22:32:08 +00:00

17 KiB

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): 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: TALLYINGstr::matches(..).count(); REPLACINGstr::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: STRINGformat! 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, DISPLAYprintln!, COMPUTE y la aritmética → expresiones Decimal, IFif/else, PERFORM→ llamada de método / for / while, GO TO→ llamada + return, STOP RUNprocess::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: ProgramIr. 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 ) V99S9(5)V99) y VALUE. Anida por número de nivel — 01 y 77 son raíces, 88 cuelga del ítem precedente.
  • PROCEDURE divisionVec<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.