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: 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>
This commit is contained in:
sergio
2026-05-21 22:36:53 +00:00
parent 82ba0b7a1a
commit f250fd0765
10 changed files with 155 additions and 41 deletions
@@ -391,7 +391,9 @@ impl<'a> Machine<'a> {
/// él termina esa pasada, no el programa.
fn run_target(&mut self, target: &'a PerformTarget) -> Flow {
let flow = match target {
PerformTarget::Paragraph { name, .. } => self.run_paragraph(name),
PerformTarget::Paragraph { name, thru } => {
self.run_paragraph_range(name, thru.as_deref())
}
PerformTarget::Inline(body) => self.exec_block(body),
};
match flow {
@@ -411,6 +413,34 @@ impl<'a> Machine<'a> {
}
}
/// Ejecuta el rango de párrafos de `name` a `thru` inclusive (el
/// `PERFORM name THRU thru`); sólo `name` si `thru` es `None`.
fn run_paragraph_range(&mut self, name: &str, thru: Option<&str>) -> Flow {
let Some(&start) = self.para_index.get(&name.to_uppercase()) else {
return Flow::Normal;
};
let end = match thru {
Some(t) => self
.para_index
.get(&t.to_uppercase())
.copied()
.unwrap_or(start),
None => start,
};
let (lo, hi) = if start <= end {
(start, end)
} else {
(end, start)
};
let ir = self.ir;
for i in lo..=hi {
if let Flow::Stop = self.exec_block(&ir.procedures[i].body) {
return Flow::Stop;
}
}
Flow::Normal
}
/// Resuelve una referencia a dato (escalar o elemento de tabla) a
/// su nombre y un índice 0-based. `None` si no es una referencia.
fn resolve(&self, op: &Operand) -> Option<(String, usize)> {