feat(charka): PICTURE de edición — Z, coma de millares y punto decimal

El formateo de informes de COBOL: supresión de ceros a la izquierda,
coma de millares e inserción del punto decimal. Rebanada vertical.

- charka-lexer: el punto separador exige un espacio detrás; un punto
  pegado a un carácter (ZZ9.99) ya no es terminador, sino símbolo —
  el parser lo reensambla dentro de la cláusula PICTURE.
- charka-runtime: format_edited(valor, pic) — 9, Z, coma, punto, B.
- charka-ir: Field::edit guarda la PICTURE; el campo es texto.
- charka-codegen / charka-shadow: MOVE a un campo de edición pasa por
  format_edited antes de almacenar.
- Corpus: 19-reporte. Sombra y crate compilado dan la misma salida.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-21 23:00:15 +00:00
parent b3278bdb0c
commit 634a43006a
15 changed files with 264 additions and 23 deletions
+26 -1
View File
@@ -149,8 +149,20 @@ fn lex_line(content: &str, line: u32, base_col: u32, out: &mut Vec<Token>) -> Re
});
i = next;
} else if c == '.' {
// El separador de sentencia COBOL siempre lleva un espacio
// (o el fin de línea) detrás. Un punto pegado a un carácter
// —`ZZ9.99`— no es separador: pertenece a una PICTURE de
// edición y se emite como símbolo para que el parser lo
// reensamble dentro de la cláusula.
let is_separator = chars
.get(i + 1)
.map_or(true, |n| n.is_whitespace());
out.push(Token {
kind: TokenKind::Period,
kind: if is_separator {
TokenKind::Period
} else {
TokenKind::Symbol
},
text: ".".into(),
line,
col,
@@ -329,6 +341,19 @@ mod tests {
);
}
#[test]
fn period_inside_an_edit_picture_is_not_a_separator() {
// El punto de `ZZ9.99` va pegado a un dígito: es símbolo, no
// terminador. El punto final, con espacio detrás, sí termina.
let toks = kinds("PIC Z,ZZ9.99 .", SourceFormat::Free);
let dots: Vec<TokenKind> = toks
.iter()
.filter(|(_, t)| t == ".")
.map(|(k, _)| *k)
.collect();
assert_eq!(dots, vec![TokenKind::Symbol, TokenKind::Period]);
}
#[test]
fn two_and_one_char_operators() {
let toks = kinds("A <= B >= C <> D ** E + F", SourceFormat::Free);