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: el intérprete sombra y el crate compilado por scaffold dan la misma salida. Tests: charka-codegen 24, charka-shadow 22. fmt + clippy limpios. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
10 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; redondeoTruncate/HalfUp;coercea unPicturecon 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— emiteWordpara 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.
LexErrortipado. - 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áusulaPICTUREdesde sus tokens (S9(5)V99→S9(5)V99); anida por número de nivel (01 y 77 son raíces; 88 cuelga del ítem precedente). - PROCEDURE division →
Vec<Paragraph>, cadaParagraphcon susSentence(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, entradasFD/SDy cláusulas de datos que no seanPICTURE/VALUE.ParseErrorsó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: Program → Ir. Aquí se parsea cada Sentence cruda
(tokens) a statements tipados — el PROCEDURE division pasa de tokens a
árbol de instrucciones.
lower(&Program) -> Ir— total y tolerante, nunca falla.Ir { program_id, data, model, procedures }.dataes el árbol deDataItemcon su estructura de grupos;modeles el modelo de datos resuelto (módulomodel): el árbol aplanado a campos elementales (FieldconFieldKindnumérico/alfanumérico y elVALUEnormalizado) más los nombres de condición (nivel 88). Es la fuente única de verdad sobre «qué tipo de campo describe una PICTURE» —charka-codegenycharka-shadowlo consumen en vez de reimplementar la clasificación.Procedure { name, body: Vec<Stmt> }.StmtcubreMove,Display,Accept,Compute,Add/Subtract/Multiply/Divide,If,Evaluate,StringConcat,Unstring,Inspect,Initialize,SetTrue,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 porAND/OR/NOT, más nombres de condición (nivel 88), que se resuelven contra elmodelapadre = 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+ conectoresTO/GIVING...) para delimitar listas de operandos. PERFORMcubre las cuatro formas: párrafo (incluido el rangoTHRU) / en línea,n TIMES,UNTIL condyVARYING var FROM x BY y UNTIL cond.EVALUATE— elcasede COBOL:WHEN valor,WHEN lo THRU hi(rango),WHEN OTHER, y la formaEVALUATE TRUE WHEN condición.STRING(concatenación) yUNSTRING(partición por delimitador) — el manejo de cadenas.INSPECT— contar (TALLYING FOR ALL) y reemplazar (REPLACING ALL).INITIALIZE— resetea un dato (o todos los elementales de un grupo) a su valor por defecto. Elmodelregistra los grupos y sus miembros (DataModel::groups).SET cond... TO TRUE— la cara de escritura de los nombres de condición (nivel 88): asigna a su dato padre el valor del 88.- Fuera de alcance v1: 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): unDecimalconformado a suPicture.store/store_roundedtruncan o redondean a la escala declarada; al desbordar conservan los dígitos de bajo orden (elON SIZE ERRORsin cláusula).displayda los dígitos con relleno de ceros.Text— campo alfanumérico (PIC X(20)) de longitud fija:storejustifica a la izquierda y rellena/trunca;fillmueve figurativas (SPACES,ZEROS).cobol_text_cmp— comparación alfanumérica con relleno de espacios.- Reexporta
Decimal/Picture/Roundingdecharka-bcdpara que el código generado sólo necesiteuse 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 Programcon un campo por cada dato elemental (Num/Text);Program::new()lo inicializa desde las cláusulasVALUE. - 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,DISPLAY→println!,COMPUTE/aritmética → expresionesDecimal,IF→if,PERFORM→ llamada /for/while,STOP RUN→process::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 campoOCCURSse emite comoVec<Num>/Vec<Text>; una referencia con subíndiceELEM(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-runtimey produce la salida correcta. - Fuera de alcance v1: grupos como campo propio,
REDEFINES,OCCURSde grupo, 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 deDISPLAY.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::StepLimiten 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 nde longitudn.
El corpus
crates/modules/charka/corpus/ — 17 programas COBOL graduados
(01-hola … 17-rangopar), 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 completo — charka-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).