feat(charka): charka-ir — representación intermedia con statements tipados

Tercera etapa del transpilador: Program -> Ir. El PROCEDURE division
pasa de sentencias con tokens crudos a un árbol de instrucciones
tipadas.

- lower(&Program) -> Ir: total y tolerante, nunca falla. La DATA
  division pasa tal cual y sirve de tabla de símbolos.
- Stmt cubre MOVE, DISPLAY, ACCEPT, COMPUTE, ADD, SUBTRACT, MULTIPLY,
  DIVIDE, IF/ELSE/END-IF, PERFORM (fuera de línea, en línea, TIMES,
  UNTIL), GO TO, STOP RUN, GOBACK, EXIT, CONTINUE.
- Expresiones de COMPUTE con precedencia y paréntesis (Pratt).
  Condiciones con comparadores símbolo/palabra, AND/OR/NOT y nombres
  de condición (nivel 88).
- Delimita statements por palabras frontera (COBOL no los separa con
  un símbolo). Verbo no soportado -> Stmt::Unknown con tokens crudos.
- Módulos: ast / kw / cursor / expr / stmt. 17 tests; fmt + clippy
  limpios.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-21 20:23:19 +00:00
parent b95383b01a
commit 71a4068d12
11 changed files with 1462 additions and 4 deletions
+200
View File
@@ -0,0 +1,200 @@
//! Los tipos del IR: el programa COBOL con su PROCEDURE division ya
//! parseada a instrucciones tipadas.
pub use charka_parser::{DataItem, Token};
/// Un programa COBOL en representación intermedia.
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Ir {
/// El `PROGRAM-ID` ("" si el programa no lo declara).
pub program_id: String,
/// El modelo de datos — el árbol de [`DataItem`] tal cual lo
/// produjo `charka-parser`. Sirve de tabla de símbolos.
pub data: Vec<DataItem>,
/// Los párrafos del PROCEDURE, con sus statements ya tipados.
pub procedures: Vec<Procedure>,
}
/// Un párrafo del PROCEDURE: un nombre y un cuerpo de statements.
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Procedure {
/// Nombre del párrafo en mayúsculas; "" para el párrafo implícito.
pub name: String,
/// Los statements del párrafo, en orden.
pub body: Vec<Stmt>,
}
/// Un operando: lo que puede ir donde se espera un valor.
#[derive(Debug, Clone, PartialEq)]
pub enum Operand {
/// Referencia a un dato, por nombre (en mayúsculas).
Data(String),
/// Literal numérico (texto, posiblemente con signo).
Num(String),
/// Literal de texto.
Str(String),
/// Constante figurativa (`ZERO`, `SPACE`...).
Figurative(Figurative),
}
/// Las constantes figurativas de COBOL.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Figurative {
Zero,
Space,
HighValue,
LowValue,
Quote,
Null,
}
/// Una expresión aritmética (la parte derecha de un `COMPUTE`).
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
/// Un operando hoja.
Operand(Operand),
/// Negación unaria.
Neg(Box<Expr>),
/// Operación binaria.
Binary {
op: BinOp,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
}
/// Operador aritmético binario.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
/// Exponenciación (`**`).
Pow,
}
/// Una condición (la guarda de un `IF` o de un `PERFORM UNTIL`).
#[derive(Debug, Clone, PartialEq)]
pub enum Cond {
/// Comparación relacional `lhs op rhs`.
Compare {
lhs: Operand,
op: CmpOp,
rhs: Operand,
},
/// Un nombre de condición (un dato de nivel 88) usado solo.
Named(String),
/// Negación lógica.
Not(Box<Cond>),
/// Conjunción lógica.
And(Box<Cond>, Box<Cond>),
/// Disyunción lógica.
Or(Box<Cond>, Box<Cond>),
}
/// Operador relacional.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CmpOp {
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
}
/// Un statement del PROCEDURE division.
#[derive(Debug, Clone, PartialEq)]
pub enum Stmt {
/// `MOVE from TO to...`
Move { from: Operand, to: Vec<String> },
/// `DISPLAY items...`
Display { items: Vec<Operand> },
/// `ACCEPT into`
Accept { into: String },
/// `COMPUTE targets... [ROUNDED] = expr`
Compute {
targets: Vec<String>,
rounded: bool,
expr: Expr,
},
/// `ADD addends... TO to... [GIVING giving...]`
Add {
addends: Vec<Operand>,
to: Vec<String>,
giving: Vec<String>,
rounded: bool,
},
/// `SUBTRACT amounts... FROM from... [GIVING giving...]`
Subtract {
amounts: Vec<Operand>,
from: Vec<String>,
giving: Vec<String>,
rounded: bool,
},
/// `MULTIPLY left BY by [GIVING giving...]`
Multiply {
left: Operand,
by: Operand,
giving: Vec<String>,
rounded: bool,
},
/// `DIVIDE left {BY|INTO} right [GIVING giving...]`. `by_form` es
/// true para `BY` (`left/right`), false para `INTO` (`right/left`).
Divide {
left: Operand,
right: Operand,
by_form: bool,
giving: Vec<String>,
rounded: bool,
},
/// `IF cond [THEN] then_branch [ELSE else_branch] [END-IF]`
If {
cond: Cond,
then_branch: Vec<Stmt>,
else_branch: Vec<Stmt>,
},
/// `PERFORM ...` — ver [`Perform`].
Perform(Perform),
/// `GO TO target`
GoTo { target: String },
/// `STOP RUN`
StopRun,
/// `GOBACK`
Goback,
/// `EXIT` (y sus variantes `EXIT PROGRAM`/`PARAGRAPH`...).
Exit,
/// `CONTINUE` — el no-op de COBOL.
Continue,
/// Un verbo que la v1 no parsea: se conserva crudo para que las
/// etapas siguientes (o un humano) lo revisen.
Unknown { verb: String, tokens: Vec<Token> },
}
/// Un statement `PERFORM`: a quién ejecuta y cuántas veces.
#[derive(Debug, Clone, PartialEq)]
pub struct Perform {
pub target: PerformTarget,
pub control: PerformControl,
}
/// El cuerpo que un `PERFORM` ejecuta.
#[derive(Debug, Clone, PartialEq)]
pub enum PerformTarget {
/// `PERFORM PARA [THRU PARA2]` — ejecuta uno o un rango de párrafos.
Paragraph { name: String, thru: Option<String> },
/// `PERFORM ... statements ... END-PERFORM` — cuerpo en línea.
Inline(Vec<Stmt>),
}
/// Cuántas veces se ejecuta el cuerpo de un `PERFORM`.
#[derive(Debug, Clone, PartialEq)]
pub enum PerformControl {
/// Una sola vez.
Once,
/// `n TIMES`.
Times(Operand),
/// `UNTIL cond`.
Until(Cond),
}