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:
@@ -183,6 +183,8 @@ pub enum Stmt {
|
||||
delimiter: Operand,
|
||||
into: Vec<Operand>,
|
||||
},
|
||||
/// `INSPECT target ...` — cuenta o reemplaza caracteres.
|
||||
Inspect { target: Operand, op: InspectOp },
|
||||
/// `PERFORM ...` — ver [`Perform`].
|
||||
Perform(Perform),
|
||||
/// `GO TO target`
|
||||
@@ -200,6 +202,16 @@ pub enum Stmt {
|
||||
Unknown { verb: String, tokens: Vec<Token> },
|
||||
}
|
||||
|
||||
/// La operación de un `INSPECT`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum InspectOp {
|
||||
/// `TALLYING counter FOR ALL search` — suma a `counter` la cantidad
|
||||
/// de apariciones de `search` en el destino.
|
||||
TallyingForAll { counter: Operand, search: Operand },
|
||||
/// `REPLACING ALL from BY to` — reemplaza las apariciones de `from`.
|
||||
ReplacingAll { from: Operand, to: Operand },
|
||||
}
|
||||
|
||||
/// Una rama `WHEN` de un `EVALUATE`: los valores que la disparan
|
||||
/// (varios `WHEN` apilados comparten cuerpo) y el cuerpo a ejecutar.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
//! `ACCEPT`, `COMPUTE` (con expresiones con precedencia), `ADD`,
|
||||
//! `SUBTRACT`, `MULTIPLY`, `DIVIDE`, `IF`/`ELSE`/`END-IF` (con
|
||||
//! condiciones `AND`/`OR`/`NOT`), `EVALUATE`/`WHEN`, `STRING`,
|
||||
//! `UNSTRING`, `PERFORM` (fuera de línea, en línea, `TIMES`, `UNTIL`,
|
||||
//! `VARYING`), `GO TO`, `STOP RUN`, `GOBACK`, `EXIT`, `CONTINUE`.
|
||||
//! Fuera de alcance: E/S de ficheros, CICS y SQL embebido.
|
||||
//! `UNSTRING`, `INSPECT`, `PERFORM` (fuera de línea, en línea,
|
||||
//! `TIMES`, `UNTIL`, `VARYING`), `GO TO`, `STOP RUN`, `GOBACK`,
|
||||
//! `EXIT`, `CONTINUE`. Fuera de alcance: E/S de ficheros, CICS y SQL.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
@@ -383,6 +383,33 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inspect_tallying_and_replacing_parse() {
|
||||
let b = body("INSPECT WS-T TALLYING WS-N FOR ALL 'A'.");
|
||||
match &b[0] {
|
||||
Stmt::Inspect {
|
||||
target,
|
||||
op: InspectOp::TallyingForAll { counter, search },
|
||||
} => {
|
||||
assert_eq!(target, &Operand::Data("WS-T".into()));
|
||||
assert_eq!(counter, &Operand::Data("WS-N".into()));
|
||||
assert_eq!(search, &Operand::Str("A".into()));
|
||||
}
|
||||
other => panic!("se esperaba INSPECT TALLYING, vino {other:?}"),
|
||||
}
|
||||
let b = body("INSPECT WS-T REPLACING ALL 'A' BY 'O'.");
|
||||
match &b[0] {
|
||||
Stmt::Inspect {
|
||||
op: InspectOp::ReplacingAll { from, to },
|
||||
..
|
||||
} => {
|
||||
assert_eq!(from, &Operand::Str("A".into()));
|
||||
assert_eq!(to, &Operand::Str("O".into()));
|
||||
}
|
||||
other => panic!("se esperaba INSPECT REPLACING, vino {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn several_statements_in_one_sentence() {
|
||||
let b = body("MOVE 1 TO X DISPLAY X STOP RUN.");
|
||||
@@ -394,10 +421,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn unrecognized_verb_becomes_unknown() {
|
||||
let b = body("INSPECT WS-X TALLYING WS-N FOR ALL ' '.");
|
||||
let b = body("INITIALIZE WS-X WS-Y.");
|
||||
match &b[0] {
|
||||
Stmt::Unknown { verb, tokens } => {
|
||||
assert_eq!(verb, "INSPECT");
|
||||
assert_eq!(verb, "INITIALIZE");
|
||||
assert!(!tokens.is_empty());
|
||||
}
|
||||
other => panic!("se esperaba Unknown, vino {other:?}"),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use charka_parser::TokenKind;
|
||||
|
||||
use crate::ast::{Operand, Perform, PerformControl, PerformTarget, Stmt, WhenBranch};
|
||||
use crate::ast::{InspectOp, Operand, Perform, PerformControl, PerformTarget, Stmt, WhenBranch};
|
||||
use crate::cursor::{parse_operand, Cursor};
|
||||
use crate::expr::{parse_cond, parse_expr};
|
||||
use crate::kw::{is_boundary, is_terminator, is_verb};
|
||||
@@ -41,6 +41,7 @@ fn parse_one_stmt(c: &mut Cursor, stops: &[&str]) -> Stmt {
|
||||
"EVALUATE" => parse_evaluate(c),
|
||||
"STRING" => parse_string(c),
|
||||
"UNSTRING" => parse_unstring(c),
|
||||
"INSPECT" => parse_inspect(c),
|
||||
"PERFORM" => parse_perform(c),
|
||||
"GO" => parse_goto(c),
|
||||
"STOP" => parse_stop(c),
|
||||
@@ -378,6 +379,39 @@ fn parse_unstring(c: &mut Cursor) -> Stmt {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_inspect(c: &mut Cursor) -> Stmt {
|
||||
c.bump(); // INSPECT
|
||||
let target = parse_operand(c);
|
||||
if c.eat_word("TALLYING") {
|
||||
let counter = parse_operand(c);
|
||||
c.eat_word("FOR");
|
||||
c.eat_word("ALL");
|
||||
let search = parse_operand(c);
|
||||
skip_to_stmt_boundary(c);
|
||||
Stmt::Inspect {
|
||||
target,
|
||||
op: InspectOp::TallyingForAll { counter, search },
|
||||
}
|
||||
} else if c.eat_word("REPLACING") {
|
||||
c.eat_word("ALL");
|
||||
let from = parse_operand(c);
|
||||
c.eat_word("BY");
|
||||
let to = parse_operand(c);
|
||||
skip_to_stmt_boundary(c);
|
||||
Stmt::Inspect {
|
||||
target,
|
||||
op: InspectOp::ReplacingAll { from, to },
|
||||
}
|
||||
} else {
|
||||
// Forma de INSPECT que la v1 no modela.
|
||||
skip_to_stmt_boundary(c);
|
||||
Stmt::Unknown {
|
||||
verb: "INSPECT".to_string(),
|
||||
tokens: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_perform(c: &mut Cursor) -> Stmt {
|
||||
c.bump(); // PERFORM
|
||||
|
||||
|
||||
Reference in New Issue
Block a user