feat(charka): INSPECT — contar y reemplazar caracteres

El verbo de COBOL para analizar y limpiar campos de texto.

- IR: Stmt::Inspect { target, op } con InspectOp::TallyingForAll
  (cuenta apariciones y las suma a un contador) y
  InspectOp::ReplacingAll (reemplaza apariciones).
- Parser: INSPECT t TALLYING n FOR ALL lit y
  INSPECT t REPLACING ALL a BY b. Una forma no soportada cae a
  Stmt::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 por scaffold dan la misma salida.

Alcance v1: TALLYING FOR ALL y REPLACING ALL; sin LEADING, FIRST,
CHARACTERS, BEFORE/AFTER.

Tests: charka-ir 26, charka-codegen 20, charka-shadow 18. fmt +
clippy limpios.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-21 22:17:47 +00:00
parent 47c49acd47
commit 2728698f5e
13 changed files with 205 additions and 18 deletions
@@ -8,8 +8,8 @@
use std::collections::HashMap;
use charka_ir::{
BinOp, CmpOp, Cond, ConditionName, Expr, Figurative, Ir, Operand, Perform, PerformControl,
PerformTarget, Stmt,
BinOp, CmpOp, Cond, ConditionName, Expr, Figurative, InspectOp, Ir, Operand, Perform,
PerformControl, PerformTarget, Stmt,
};
use charka_runtime::{cobol_text_cmp, Decimal, Rounding};
@@ -270,6 +270,33 @@ impl<'a> Machine<'a> {
}
Flow::Normal
}
Stmt::Inspect { target, op } => {
match op {
InspectOp::TallyingForAll { counter, search } => {
let hay = self.eval_text(target);
let needle = self.eval_text(search);
let n = if needle.is_empty() {
0
} else {
hay.matches(needle.as_str()).count()
};
let cur = self.eval_decimal(counter);
self.store(counter, cur.add(&Decimal::from_integer(n as i128)), false);
}
InspectOp::ReplacingAll { from, to } => {
let hay = self.eval_text(target);
let f = self.eval_text(from);
let t = self.eval_text(to);
let new = if f.is_empty() {
hay
} else {
hay.replace(f.as_str(), t.as_str())
};
self.store_text(target, &new);
}
}
Flow::Normal
}
Stmt::Perform(p) => self.exec_perform(p),
Stmt::GoTo { target } => {
// Aproximación: ejecuta el destino y sale del párrafo.