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, las relee con READ ... AT END y las muestra. Verificado: el intérprete sombra y el crate compilado por scaffold dan la misma salida. Alcance v1: organización line sequential; sin ficheros indexados ni relativos, sin FILE STATUS. Tests: charka-parser 17, charka-runtime 19, charka-ir 30, charka-codegen 25, charka-shadow 23. fmt + clippy limpios. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
19 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): 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 parseaFILE-CONTROL(SELECT name ASSIGN TO "ruta"); del FILE SECTION se asocia cadaFDcon su registro01.Program::files.charka-runtime: tipoCobFile— un fichero «line sequential» (cada registro una línea). Lectura: carga a memoria. Escritura: acumula y vuelca al cerrar.charka-ir:Ir::filesy los statementsOpen/Close/Read/Write.READlleva sus bloquesAT END/NOT AT END.charka-codegen: un campoCobFilepor fichero en elstruct 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 conREAD ... AT ENDy 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:Symbolsregistra ahora los párrafos en orden con su nombre de método;Symbols::buildtoma elIrcompleto.paragraph_range(name, thru)da los métodos del rango.emit_performemite la llamada a cada uno.charka-shadow:run_paragraph_rangeejecuta los párrafos denameathruinclusive.- Corpus: programa nuevo
17-rangopar(PERFORM PASO-A THRU PASO-Csobre 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 deSET(índices,TO FALSE) caen aStmt::Unknown. - Codegen y shadow:
SET cond TO TRUEasigna a su dato padre el valor del 88 (unMOVEdel valor a la variable). - Corpus: programa nuevo
16-bandera(cambia banderas de texto y de número conSET). 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 }. Elmodeldecharka-irregistra 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
OCCURSresetea 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
WhenBranchpasa devalues: Vec<Operand>atests: Vec<WhenTest>, dondeWhenTestesValue(igualdad),Range(WHEN lo THRU hi) oCond(EVALUATE TRUE WHEN cond). - Parser: detecta
EVALUATE TRUEy entonces cadaWHENparsea una condición; en modo valor reconoceWHEN lo THRU hi. - Codegen y shadow: una prueba
Rangese traduce alo <= s <= hi; unaCond, a la condición directa. - Corpus: programa nuevo
14-clasifica(clasifica notas con rangosTHRUy unEVALUATE 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 }conInspectOp::TallyingForAll(cuenta apariciones y las suma a un contador) yInspectOp::ReplacingAll(reemplaza apariciones). - Parser:
INSPECT t TALLYING n FOR ALL lityINSPECT t REPLACING ALL a BY b. Una forma no soportada cae aStmt::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 ALLyREPLACING ALL; sinLEADING,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 }yStmt::Unstring { source, delimiter, into }. - Parser:
STRING a b DELIMITED BY SIZE INTO t END-STRINGyUNSTRING s DELIMITED BY d INTO a b c END-UNSTRING. - Codegen:
STRING→format!concatenado;UNSTRING→ un bloque que parte constr::splity 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:
STRINGconDELIMITED BY SIZE(los demás delimitadores se ignoran); sinWITH POINTERniON 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 enDataItem. - IR:
Operand::Indexed { name, index }— una referenciaELEM(I). El subíndice es 1-based, como COBOL. Los destinos de los statements pasan deVec<String>aVec<Operand>, así que se puede escribir a un elemento de tabla (MOVE x TO ELEM(I),COMPUTE ELEM(I) = ...).Fielddel modelo ganaoccurs: Option<u32>. - Codegen: un campo
OCCURSse emite comoVec<Num>/Vec<Text>, inicializado convec![..; 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 porscaffolddan ambosSUMA DE CUADRADOS = 000055. - Alcance v1:
OCCURSen datos elementales, una dimensión, subíndice de un solo operando. Fuera:OCCURSde 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-irgana un módulomodel:resolve_data(&[DataItem]) -> DataModelaplana el árbol de datos a campos elementales (FieldconFieldKind) y a nombres de condición (ConditionName). ElIrahora lleva un campomodel. Es la fuente única de verdad sobre la clasificación de PICTURE.charka-codegenycharka-shadowconsumenir.modelen vez de reimplementar cada uno la clasificación numérico/alfanumérico, el ancho de PICTURE y la normalización deVALUE.charka-codegenya no depende decharka-bcd.Cond::Named(un nivel 88) se resuelve apadre = 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, enIFeIF/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 }conWhenBranch { values, body }. VariosWHENapilados comparten cuerpo;WHEN OTHERes 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(unEVALUATEpor valor anidado en unPERFORM VARYING, conWHENapilados yWHEN OTHER). Verificado: intérprete sombra y crate compilado dan la misma salida. - Alcance v1:
EVALUATEpor igualdad de valor; no la formaEVALUATE TRUEcon condiciones ni los rangosTHRU.
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 conden 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 conPERFORM VARYING). Verificado: el intérprete sombra y el crate compilado porscaffolddan ambosSUMA 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 decharka-runtimey 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-shadowcertifica que el transpilador preserva la semántica del COBOL original con una ejecución sombra: un intérprete que corre elIrdirectamente sobrecharka-runtime, sin compilar nada. Es una segunda ruta de ejecución, independiente del código que emitecharka-codegen.interpret(&Ir) -> Outcomeejecuta el IR y captura las líneas deDISPLAY;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(unDISPLAY),02-aritmetica(ADD/SUBTRACT/MULTIPLY),03-condicional(IF/ELSE),04-bucle(PERFORM TIMES),05-factorial(PERFORM UNTIL),06-nomina(grupos,COMPUTEcon paréntesis,ROUNDED,V99),07-clasificar(IFanidado,AND). Cada uno con su.expectedverificada 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 Programcon un campo por cada dato elemental —Numpara los numéricos,Textpara los alfanuméricos;Program::new()los inicializa desde sus cláusulasVALUE. - 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 elProgramy lo corre. - Cada
Stmt→ código Rust:MOVE→.store/.fill,DISPLAY→println!,COMPUTEy la aritmética → expresionesDecimal,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-runtimey produce la salida esperada (COMPUTE,IF,PERFORM,DISPLAY). - 14 tests del fuente emitido; fuera de alcance v1: grupos como campo,
REDEFINES,OCCURS,PERFORM ... THRUcomo 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): unDecimalconformado a suPicture.storetrunca a la escala declarada;store_roundedredondea; al desbordar la parte entera conserva los dígitos de bajo orden (elON SIZE ERRORde COBOL sin cláusula).displayda los dígitos con relleno de ceros y signo si corresponde.Text— campo alfanumérico (PIC X(n)) de longitud fija:storejustifica a la izquierda y rellena con espacios o trunca;fillmueve las constantes figurativas (SPACES,ZEROS).cobol_text_cmp— comparación alfanumérica que rellena el más corto con espacios.- Reexporta
Decimal/Picture/Roundingdecharka-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,
VALUEinicial, 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.StmtcubreMOVE,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
COMPUTEcon precedencia y paréntesis (+ -<* /<**asociativo a derecha). Condiciones deIF/UNTILcon comparadores en forma símbolo (= < > <= >= <>) o palabra (EQUAL TO,GREATER THAN...),AND/OR/NOTy 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, conectoresTO/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 elPROGRAM-ID. - DATA division → árbol de
DataItem: número de nivel, nombre, cláusulaPICTUREreensamblada (S9(5)V99→S9(5)V99) yVALUE. 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 susSentence(tokens crudos; sin parseo a nivel de statement, eso escharka-ir). Las sentencias previas al primer encabezado van a un párrafo implícito de nombre "". - Parser tolerante: salta encabezados de
SECTION, entradasFD/SDy cláusulas de datos que no seanPICTURE/VALUE.ParseErrorsólo ante un número de nivel inválido o un dato sin nombre. - 15 tests: PROGRAM-ID (forma larga y
IDcorta), í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; emiteWordpara 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
Tokenlleva línea y columna 1-based;LexErrortipado (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.