Files
brahman/crates/modules/charka/SDD.md
T
sergio 3902763daa feat(charka): OCCURS — tablas y referencias con subíndice
Los arrays de COBOL, que antes el transpilador descartaba en silencio.
Una rebanada vertical amplia que atraviesa el pipeline entero.

- Parser: la cláusula OCCURS n [TIMES] se captura en DataItem.
- IR: Operand::Indexed { name, index } — una referencia ELEM(I), con
  subíndice 1-based. 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) = ...). model::Field
  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 elemental, una dimensión, subíndice de un operando.
Fuera: OCCURS de grupo, multidimensional, DEPENDING ON.

Tests: charka-parser 16, charka-ir 24, charka-codegen 18,
charka-shadow 16. fmt + clippy limpios.

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

9.9 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.
  • Tablas (OCCURS n): un campo OCCURS se emite como Vec<Num> / Vec<Text>; una referencia con subíndice ELEM(I) indexa el vector (subíndice 1-based de COBOL → 0-based de Rust).
  • 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 de grupo, 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/): programas COBOL de complejidad graduada con sus salidas esperadas verificadas a mano. Un modo futuro, con GnuCOBOL, diferenciará contra el compilador real.
  • En el intérprete todo campo es un vector — un escalar es de longitud 1, una tabla OCCURS n de longitud n.

El corpus

crates/modules/charka/corpus/ — 11 programas COBOL graduados (01-hola11-tabla), 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 como campo, REDEFINES, OCCURS de grupo, E/S de ficheros).