feat(charka): STRING y UNSTRING — manejo de cadenas

Dos verbos comunes de COBOL para construir y partir cadenas.

- IR: Stmt::StringConcat { sources, into } y
  Stmt::Unstring { source, delimiter, into }.
- Parser: STRING a b DELIMITED BY SIZE INTO t END-STRING y
  UNSTRING s DELIMITED BY d INTO a b c END-UNSTRING.
- Codegen: STRING -> format! concatenado; UNSTRING -> un bloque que
  parte con str::split y reparte los trozos a los destinos.
- 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 por scaffold dan la misma salida.

Alcance v1: STRING con DELIMITED BY SIZE (otros delimitadores se
ignoran); sin WITH POINTER ni ON OVERFLOW.

Tests: charka-ir 25, charka-codegen 19, charka-shadow 17. fmt +
clippy limpios.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-21 22:09:10 +00:00
parent 3902763daa
commit 47c49acd47
12 changed files with 269 additions and 10 deletions
@@ -247,6 +247,29 @@ impl<'a> Machine<'a> {
}
self.exec_block(other)
}
Stmt::StringConcat { sources, into } => {
let s: String = sources.iter().map(|o| self.eval_text(o)).collect();
self.store_text(into, &s);
Flow::Normal
}
Stmt::Unstring {
source,
delimiter,
into,
} => {
let src = self.eval_text(source);
let delim = self.eval_text(delimiter);
let parts: Vec<String> = if delim.is_empty() {
vec![src]
} else {
src.split(delim.as_str()).map(|p| p.to_string()).collect()
};
for (i, target) in into.iter().enumerate() {
let piece = parts.get(i).cloned().unwrap_or_default();
self.store_text(target, &piece);
}
Flow::Normal
}
Stmt::Perform(p) => self.exec_perform(p),
Stmt::GoTo { target } => {
// Aproximación: ejecuta el destino y sale del párrafo.
@@ -410,6 +433,26 @@ impl<'a> Machine<'a> {
}
}
/// Almacena un texto en un destino, conformándolo a su tipo.
fn store_text(&mut self, target: &Operand, text: &str) {
let Some((key, idx)) = self.resolve(target) else {
return;
};
match self.fields.get_mut(&key) {
Some(Cell::Text(arr)) => {
if let Some(t) = arr.get_mut(idx) {
t.store(text);
}
}
Some(Cell::Num(arr)) => {
if let Some(n) = arr.get_mut(idx) {
n.store(Decimal::parse(text.trim()).unwrap_or_else(|_| Decimal::zero()));
}
}
None => {}
}
}
// ── Evaluación ────────────────────────────────────────────────
fn eval_decimal(&self, op: &Operand) -> Decimal {