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 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>
This commit is contained in:
@@ -8,10 +8,10 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use charka_ir::{
|
||||
BinOp, CmpOp, Cond, ConditionName, Expr, Figurative, InspectOp, Ir, Operand, Perform,
|
||||
BinOp, CmpOp, Cond, ConditionName, Expr, Figurative, FileMode, InspectOp, Ir, Operand, Perform,
|
||||
PerformControl, PerformTarget, Stmt, WhenTest,
|
||||
};
|
||||
use charka_runtime::{cobol_text_cmp, Decimal, Num, Rounding, Text};
|
||||
use charka_runtime::{cobol_text_cmp, CobFile, Decimal, Num, Rounding, Text};
|
||||
|
||||
use crate::field::{build_fields, Cell};
|
||||
|
||||
@@ -39,6 +39,7 @@ pub(crate) struct Machine<'a> {
|
||||
fields: HashMap<String, Cell>,
|
||||
para_index: HashMap<String, usize>,
|
||||
conditions: HashMap<String, ConditionName>,
|
||||
files: HashMap<String, CobFile>,
|
||||
pub output: Vec<String>,
|
||||
budget: u64,
|
||||
pub step_limit_hit: bool,
|
||||
@@ -58,11 +59,17 @@ impl<'a> Machine<'a> {
|
||||
.iter()
|
||||
.map(|c| (c.name.clone(), c.clone()))
|
||||
.collect();
|
||||
let files = ir
|
||||
.files
|
||||
.iter()
|
||||
.map(|f| (f.name.to_uppercase(), CobFile::new(&f.path)))
|
||||
.collect();
|
||||
Self {
|
||||
ir,
|
||||
fields: build_fields(&ir.model),
|
||||
para_index,
|
||||
conditions,
|
||||
files,
|
||||
output: Vec::new(),
|
||||
budget: STEP_BUDGET,
|
||||
step_limit_hit: false,
|
||||
@@ -320,6 +327,69 @@ impl<'a> Machine<'a> {
|
||||
}
|
||||
Flow::Normal
|
||||
}
|
||||
Stmt::Open { mode, files } => {
|
||||
for f in files {
|
||||
if let Some(cf) = self.files.get_mut(&f.to_uppercase()) {
|
||||
match mode {
|
||||
FileMode::Input => cf.open_input(),
|
||||
FileMode::Output => cf.open_output(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Flow::Normal
|
||||
}
|
||||
Stmt::Close { files } => {
|
||||
for f in files {
|
||||
if let Some(cf) = self.files.get_mut(&f.to_uppercase()) {
|
||||
cf.close();
|
||||
}
|
||||
}
|
||||
Flow::Normal
|
||||
}
|
||||
Stmt::Read {
|
||||
file,
|
||||
at_end,
|
||||
not_at_end,
|
||||
} => {
|
||||
let line = self
|
||||
.files
|
||||
.get_mut(&file.to_uppercase())
|
||||
.and_then(|cf| cf.read());
|
||||
match line {
|
||||
Some(text) => {
|
||||
let record = self
|
||||
.ir
|
||||
.files
|
||||
.iter()
|
||||
.find(|f| f.name.eq_ignore_ascii_case(file))
|
||||
.map(|f| f.record.clone());
|
||||
if let Some(rec) = record {
|
||||
self.store_text(&Operand::Data(rec), &text);
|
||||
}
|
||||
self.exec_block(not_at_end)
|
||||
}
|
||||
None => self.exec_block(at_end),
|
||||
}
|
||||
}
|
||||
Stmt::Write { record, from } => {
|
||||
if let Some(src) = from {
|
||||
let text = self.eval_text(src);
|
||||
self.store_text(&Operand::Data(record.clone()), &text);
|
||||
}
|
||||
let file = self
|
||||
.ir
|
||||
.files
|
||||
.iter()
|
||||
.find(|f| f.record.eq_ignore_ascii_case(record))
|
||||
.map(|f| f.name.to_uppercase());
|
||||
if let Some(file) = file {
|
||||
let line = self.eval_text(&Operand::Data(record.clone()));
|
||||
if let Some(cf) = self.files.get_mut(&file) {
|
||||
cf.write(&line);
|
||||
}
|
||||
}
|
||||
Flow::Normal
|
||||
}
|
||||
Stmt::Perform(p) => self.exec_perform(p),
|
||||
Stmt::GoTo { target } => {
|
||||
// Aproximación: ejecuta el destino y sale del párrafo.
|
||||
|
||||
@@ -126,6 +126,7 @@ mod tests {
|
||||
corpus_test!(corpus_15_resetear, "15-resetear");
|
||||
corpus_test!(corpus_16_bandera, "16-bandera");
|
||||
corpus_test!(corpus_17_rangopar, "17-rangopar");
|
||||
corpus_test!(corpus_18_fichero, "18-fichero");
|
||||
|
||||
#[test]
|
||||
fn empty_source_runs_clean() {
|
||||
|
||||
Reference in New Issue
Block a user