Files
brahman/crates/modules/charka/SDD.md
T
sergio 28ee1ae260 feat(charka): nivel 88 + modelo de datos compartido en charka-ir
Los nombres de condición de COBOL (IF ES-VALIDO), que antes el
transpilador evaluaba siempre como false. Y, de paso, se elimina la
duplicación de 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 lleva
  ahora un campo `model` — 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, 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: un dato con hijos de nivel 88 antes se perdía como si
  fuera un grupo; ahora se reconoce como campo elemental.
- Corpus: programa nuevo 10-condicion (semáforo con 88 de texto y de
  número). Verificado: intérprete y crate compilado dan igual salida.

Tests: charka-ir 23, charka-codegen 17, charka-shadow 15. fmt +
clippy limpios.

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

9.6 KiB

modules/charka/ — Transpilador COBOL → Rust

Propósito. Modernizar sistemas COBOL legados transpilándolos a Rust con un runtime determinista y un validador en sombra (shadow validator) que compara la salida del original y la del transpilado. Es el módulo más grande del ecosistema — el parser COBOL completo (CICS, SQL embebido, dialectos IBM Enterprise) es un esfuerzo multi-mes.

Crates

crate tipo rol
charka-bcd lib Aritmética decimal de punto fijo con semántica COBOL: Picture, Decimal, redondeo, ON SIZE ERROR
charka-lexer lib Tokenizador COBOL: formato fijo (tarjeta de 80 columnas) y libre
charka-parser lib Parser COBOL'85 (subconjunto): tokens → AST (Program)
charka-ir lib Representación intermedia: el AST con los statements del PROCEDURE ya tipados
charka-runtime lib Soporte de ejecución de los programas transpilados: campos Num y Text
charka-codegen lib Emisión de Rust: IR → fuente Rust sobre charka-runtime
charka-shadow lib Validador en sombra: intérprete del IR + corpus de prueba

charka-bcd

COBOL no calcula en binario flotante: opera sobre campos decimales de precisión fija (PIC S9(5)V99). Reproducir un programa COBOL exige reproducir esa aritmética dígito a dígito.

  • Picture — parsea la cláusula PICTURE numérica (9, V, S, 9(n)).
  • Decimal — punto fijo exacto (mantissa: i128 + scale); suma, resta y producto exactos; división con escala de resultado fija; redondeo Truncate/HalfUp; coerce a un Picture con detección de desbordamiento.
  • Determinista, sin dependencias de plataforma — mismo programa, mismos dígitos, en cualquier máquina.

charka-lexer

Primera etapa del pipeline: texto COBOL → secuencia de Token.

  • Lexer tonto: no conoce keywords ni la cláusula PICTURE — emite Word para todo identificador; la clasificación es del parser.
  • Tokens: Word (con guiones internos, WORKING-STORAGE), Number (sin signo), String (comillas dobladas colapsadas), Period, Symbol (( ) , ; : y operadores + - * / ** = < > <= >= <>).
  • Dos formatos: fijo (cols 1-6 secuencia, 7 indicadora, 8-72 código, 73-80 id) y libre. Comentarios por col 7 (*//) o * inicial en formato libre.
  • Cada token lleva línea/columna 1-based. LexError tipado.
  • Limitación v1: sin continuación de literales entre líneas (indicador -).

charka-parser

Segunda etapa: tokens → AST. Alcance v1 — el esqueleto del programa.

  • parse(&[Token]) -> Result<Program, ParseError>.
  • Program { program_id, data: Vec<DataItem>, paragraphs: Vec<Paragraph> }.
  • DATA division → árbol de DataItem (level, name, picture, value, children). Reensambla la cláusula PICTURE desde sus tokens (S9 ( 5 ) V99S9(5)V99); anida por número de nivel (01 y 77 son raíces; 88 cuelga del ítem precedente).
  • PROCEDURE divisionVec<Paragraph>, cada Paragraph con sus Sentence (tokens crudos — sin parseo a nivel de statement). Las sentencias previas al primer encabezado van a un párrafo implícito de nombre "".
  • Tolerante: salta encabezados de SECTION, entradas FD/SD y cláusulas de datos que no sean PICTURE/VALUE. ParseError sólo ante nivel inválido o dato sin nombre.
  • Limitación v1: no parsea statements, ni la ENVIRONMENT division, ni CICS / SQL embebido / dialectos IBM.

charka-ir

Tercera etapa: ProgramIr. Aquí se parsea cada Sentence cruda (tokens) a statements tipados — el PROCEDURE division pasa de tokens a árbol de instrucciones.

  • lower(&Program) -> Irtotal y tolerante, nunca falla.
  • Ir { program_id, data, model, procedures }. data es el árbol de DataItem con su estructura de grupos; model es el modelo de datos resuelto (módulo model): el árbol aplanado a campos elementales (Field con FieldKind numérico/alfanumérico y el VALUE normalizado) más los nombres de condición (nivel 88). Es la fuente única de verdad sobre «qué tipo de campo describe una PICTURE» — charka-codegen y charka-shadow lo consumen en vez de reimplementar la clasificación.
  • Procedure { name, body: Vec<Stmt> }. Stmt cubre Move, Display, Accept, Compute, Add/Subtract/Multiply/Divide, If, Evaluate, Perform, GoTo, StopRun, Goback, Exit, Continue.
  • Expr — expresiones aritméticas con precedencia y paréntesis (Pratt: + - < * / < ** der.). Cond — comparaciones (símbolo o forma palabra) unidas por AND/OR/NOT, más nombres de condición (nivel 88), que se resuelven contra el model a padre = valor.
  • Un verbo no soportado se conserva como Stmt::Unknown { verb, tokens } — el lowering jamás aborta.
  • COBOL no separa statements con un símbolo: cada uno corta donde empieza el verbo del siguiente. El parser usa palabras "frontera" (verbos + terminadores END-*/ELSE + conectores TO/GIVING...) para delimitar listas de operandos.
  • PERFORM cubre las cuatro formas: párrafo / en línea, n TIMES, UNTIL cond y VARYING var FROM x BY y UNTIL cond.
  • EVALUATE subject WHEN ... WHEN OTHER — el case de COBOL, por igualdad de valor (no la forma EVALUATE TRUE con condiciones).
  • Fuera de alcance v1: STRING/UNSTRING, E/S de ficheros, CICS, SQL embebido.

charka-runtime

El soporte de ejecución: lo que charka-codegen emite es Rust que enlaza contra este crate. Da a un programa transpilado la semántica de COBOL en tiempo de ejecución.

  • Num — campo numérico (PIC 9(5)V99): un Decimal conformado a su Picture. store/store_rounded truncan o redondean a la escala declarada; al desbordar conservan los dígitos de bajo orden (el ON SIZE ERROR sin cláusula). display da los dígitos con relleno de ceros.
  • Text — campo alfanumérico (PIC X(20)) de longitud fija: store justifica a la izquierda y rellena/trunca; fill mueve figurativas (SPACES, ZEROS).
  • cobol_text_cmp — comparación alfanumérica con relleno de espacios.
  • Reexporta Decimal/Picture/Rounding de charka-bcd para que el código generado sólo necesite use charka_runtime::*;.

Construido antes que charka-codegen (la nota de orden del plan los listaba al revés): el codegen emite llamadas contra esta API, así que el runtime debe existir primero — y es un crate autocontenido, verificable sin depender del código emitido.

charka-codegen

La etapa final: 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 / Text); Program::new() lo inicializa desde las cláusulas VALUE.
  • Un método p_<párrafo>(&mut self) por párrafo; run() los encadena en orden (el «caer» de COBOL); main() construye y corre.
  • Cada Stmt → líneas Rust: MOVE.store, DISPLAYprintln!, COMPUTE/aritmética → expresiones Decimal, IFif, PERFORM→ llamada / for / while, STOP RUNprocess::exit.
  • Tolerante: lo no transpilable (Stmt::Unknown, dato sin resolver, **) se emite como comentario // charka: — el código generado siempre compila.
  • Verificado de punta a punta: un programa COBOL de demostración transpila a Rust que compila contra charka-runtime y produce la salida correcta.
  • Fuera de alcance v1: grupos como campo propio, REDEFINES, OCCURS/tablas, PERFORM ... THRU como rango, E/S de ficheros.

charka-shadow

El validador: certifica que el pipeline preserva la semántica del COBOL original. Lo hace con una ejecución sombra — un intérprete que corre el Ir directamente sobre charka-runtime, sin compilar.

  • interpret(&Ir) -> Outcome — ejecuta el IR y captura las líneas de DISPLAY. run_source(&str) corre el pipeline completo.
  • El intérprete es una segunda ruta de ejecución, independiente del código que emite charka-codegen: si la sombra y el transpilado divergieran, eso delataría un bug.
  • Tope de pasos: un bucle que no termina se corta con Halt::StepLimit en vez de colgarse.
  • La referencia v1 es el corpus (corpus/): 7 programas COBOL de complejidad graduada con sus salidas esperadas verificadas a mano. Un modo futuro, con GnuCOBOL, diferenciará contra el compilador real.

El corpus

crates/modules/charka/corpus/ — 7 programas COBOL graduados (01-hola07-clasificar), cada uno con su .expected. Ejercita el pipeline completo de punta a punta. Ver su README.md.

La CLI

crates/apps/charka/ — el binario charka, que envuelve el pipeline en cuatro comandos: transpile (emite Rust), scaffold (genera un crate compilable), run (ejecuta vía el intérprete sombra) y check (ejecuta y diferencia contra una salida esperada). Avisa de los verbos no transpilados.

Estado

Pipeline completocharka-bcd (22 tests), charka-lexer (17), charka-parser (15), charka-ir (17), charka-runtime (17), charka-codegen (14), charka-shadow (11) y la CLI charka (4) implementados y verdes. COBOL → Rust corre de punta a punta, validado contra el corpus. El crate que genera scaffold compila y su salida coincide con la del intérprete sombra — las dos rutas de ejecución concuerdan.

Próximo hito mayor: salir del subconjunto COBOL'85 puro hacia CICS, SQL embebido y los dialectos IBM Enterprise; ampliar el codegen (grupos, REDEFINES, OCCURS/tablas, E/S de ficheros).