RPG Moderno

Desde sus orígenes como lenguaje de reportes hasta el RPG IV free-format actual: sintaxis moderna, tipos de datos, procedimientos, manejo de errores y operaciones con archivos.

Historia del RPG

RPG (Report Program Generator) nació en los años 60 como un lenguaje para generar reportes en los mainframes de IBM. A lo largo de las décadas, evolucionó hasta convertirse en un lenguaje de propósito general y sigue siendo el lenguaje más utilizado en IBM i.

Evolución del lenguaje RPG

RPG IV Free-Format (2001–presente)Sintaxis libre, procedimientos, ILE completo
RPG IV / ILE RPG (1994)Columnar pero con subprocedimientos y BIFs
RPG III / RPG/400 (1979)Ciclo RPG, indicadores, specs en columnas fijas
RPG II (1969)Reportes en System/3, totalmente columnar
Dato clave: Desde IBM i 7.1 TR7 (2013), RPG soporta completamente la sintaxis free-format, eliminando la necesidad de las columnas fijas tradicionales. Todo código nuevo debería escribirse en free-format.

Por qué free-format importa

RPG columnar (fixed-format)

  • Código atado a columnas específicas (6-80)
  • Especificaciones H, F, D, C, P con layout fijo
  • Difícil de leer para programadores de otros lenguajes
  • No soporta indentación libre
  • Limitaciones de longitud en nombres de variables
  • Herencia del formato de tarjetas perforadas

RPG IV free-format

  • Código libre, sin ataduras a columnas
  • Declaraciones DCL-S, DCL-DS, DCL-PR, DCL-PROC
  • Legible para cualquier programador moderno
  • Indentación libre para mayor claridad
  • Nombres de variables de hasta 4096 caracteres
  • Se siente como un lenguaje moderno convencional
Comparación visual: fixed vs free
// ============ FIXED FORMAT (antiguo) ============
     C     KEY           CHAIN     CLIENTES
     C                   IF        %FOUND
     C                   EVAL      NOMBRE = 'FERNANDO'
     C                   UPDATE    CLIREC
     C                   ENDIF

// ============ FREE FORMAT (moderno) ============
chain key clientes;
if %found(clientes);
  nombre = 'FERNANDO';
  update clirec;
endif;

Sintaxis básica

En free-format, las declaraciones se hacen con palabras clave que comienzan con DCL-. El programa se delimita con CTL-OPT para opciones de control y todas las instrucciones terminan en punto y coma.

RPG — Opciones de control (CTL-OPT)
**free
ctl-opt dftactgrp(*no) actgrp(*caller);
ctl-opt option(*srcstmt : *nodebugio);
ctl-opt datedit(*ymd) datfmt(*iso);
ctl-opt bnddir('MYBNDDIR');
RPG — Declaración de variables (DCL-S)
// Variables standalone
dcl-s nombre       varchar(100);
dcl-s edad         int(10);
dcl-s saldo        packed(11:2);
dcl-s fechaHoy     date(*iso);
dcl-s horaActual   time;
dcl-s timestamp    timestamp;
dcl-s activo       ind;             // Indicador (boolean)
dcl-s mensaje      char(50) inz('Hola Mundo');

// Constantes
dcl-c MAX_REGISTROS 9999;
dcl-c IVA           0.21;
dcl-c EMPRESA       'Sinaptrix SRL';
RPG — Prototipos e interfaces (DCL-PR / DCL-PI)
// Prototipo: declara la interfaz de un programa o procedimiento externo
dcl-pr enviarEmail extpgm('SNDMAIL');
  destinatario  varchar(200) const;
  asunto        varchar(100) const;
  cuerpo        varchar(5000) const;
end-pr;

// Interfaz del programa actual (parámetros que recibe)
dcl-pi *n;
  prmCliente   packed(7:0);
  prmAccion    char(10);
end-pi;

Tipos de datos

Tipo RPGPalabra claveEquivalente SQLEjemplo
CharacterCHAR(n)CHARdcl-s cod char(10);
VarcharVARCHAR(n)VARCHARdcl-s nom varchar(100);
IntegerINT(10)INTEGERdcl-s cnt int(10);
Small IntINT(5)SMALLINTdcl-s flag int(5);
Big IntINT(20)BIGINTdcl-s id int(20);
PackedPACKED(p:s)DECIMALdcl-s monto packed(11:2);
ZonedZONED(p:s)NUMERICdcl-s cod zoned(7:0);
FloatFLOAT(8)DOUBLEdcl-s ratio float(8);
DateDATEDATEdcl-s fec date(*iso);
TimeTIMETIMEdcl-s hora time;
TimestampTIMESTAMPTIMESTAMPdcl-s ts timestamp;
IndicatorINDBOOLEAN (1)dcl-s ok ind;
PointerPOINTERN/Adcl-s ptr pointer;
PACKED vs ZONED: Ambos son decimales de punto fijo. PACKED almacena dos dígitos por byte (más eficiente en disco); ZONED almacena un dígito por byte (más legible en dumps). PACKED es el estándar moderno en IBM i para valores numéricos.

Control de flujo

RPG — IF / ELSE / ELSEIF
if saldo > 10000;
  categoria = 'PREMIUM';
elseif saldo > 5000;
  categoria = 'GOLD';
elseif saldo > 0;
  categoria = 'STANDARD';
else;
  categoria = 'MOROSO';
endif;
RPG — SELECT / WHEN / OTHER
// SELECT es el equivalente a SWITCH/CASE
select;
  when accion = 'ALTA';
    agregarCliente(datos);
  when accion = 'BAJA';
    eliminarCliente(id);
  when accion = 'MODIF';
    modificarCliente(id : datos);
  other;
    enviarError('Acción no válida: ' + accion);
endsl;
RPG — Bucles: FOR, DOW, DOU
// FOR — bucle con contador (como for en C/Java)
for i = 1 to %elem(arreglo);
  total += arreglo(i);
endfor;

// FOR con incremento
for i = 10 downto 1 by 2;
  dsply %char(i);
endfor;

// DOW — Do While (evalúa al inicio)
dow not %eof(clientes);
  read clientes clienteDS;
  if not %eof(clientes);
    procesar(clienteDS);
  endif;
enddo;

// DOU — Do Until (evalúa al final, ejecuta al menos una vez)
dou respuesta = 'S' or respuesta = 'N';
  dsply 'Continuar? (S/N)' '' respuesta;
enddo;

// ITER y LEAVE
for i = 1 to 100;
  if esImpar(i);
    iter;            // salta a la siguiente iteración
  endif;
  if i > 50;
    leave;           // sale del bucle
  endif;
  procesar(i);
endfor;

Procedimientos y subprocedimientos

Los subprocedimientos son el equivalente a funciones en otros lenguajes. Se declaran con DCL-PROC y pueden tener variables locales, parámetros y valor de retorno.

RPG — Subprocedimiento con retorno
// Declaración del prototipo (en la sección global)
dcl-pr calcularIVA packed(11:2);
  monto packed(11:2) const;
  tasa  packed(5:4) const options(*nopass);
end-pr;

// Implementación del subprocedimiento
dcl-proc calcularIVA;
  dcl-pi *n packed(11:2);
    monto packed(11:2) const;
    tasa  packed(5:4) const options(*nopass);
  end-pi;

  dcl-s tasaReal packed(5:4);

  // Si no se pasó la tasa, usar 21%
  if %parms >= 2;
    tasaReal = tasa;
  else;
    tasaReal = 0.2100;
  endif;

  return monto * tasaReal;
end-proc;

// Uso:
dcl-s impuesto packed(11:2);
impuesto = calcularIVA(50000);          // Usa tasa default 21%
impuesto = calcularIVA(50000 : 0.105);  // Usa tasa 10.5%
RPG — Procedimiento con parámetros por referencia
dcl-proc formatearNombre;
  dcl-pi *n;
    nombre    varchar(100);      // Sin CONST = pasa por referencia
    apellido  varchar(100) const;
    resultado varchar(200);
  end-pi;

  nombre = %trim(nombre);  // Modifica el original
  resultado = %trim(apellido) + ', ' + %trim(nombre);
end-proc;

Funciones incorporadas (BIFs)

Las Built-In Functions (BIFs) comienzan con %y proveen operaciones comunes sobre strings, números, fechas y archivos.

RPG — BIFs de strings
dcl-s texto varchar(200);
dcl-s pos   int(10);

texto = '  Hola Mundo  ';

// Limpieza de espacios
texto = %trim(texto);          // 'Hola Mundo'
texto = %triml(texto);         // trim izquierdo
texto = %trimr(texto);         // trim derecho

// Búsqueda
pos = %scan('Mundo' : texto);  // 6 (posición encontrada)
pos = %scan('xyz' : texto);    // 0 (no encontrado)

// Substring
texto = %subst(texto : 1 : 4); // 'Hola'

// Reemplazo
texto = %scanrpl('Mundo' : 'IBM i' : 'Hola Mundo'); // 'Hola IBM i'

// Largo
dcl-s len int(10);
len = %len(%trim(texto));      // largo real del contenido

// Concatenación (operador +)
texto = 'Hola' + ' ' + 'Mundo';
RPG — BIFs de conversión
dcl-s numTexto varchar(20);
dcl-s numVal   packed(11:2);
dcl-s fechaISO date(*iso);

// Número a texto
numTexto = %char(12345.67);     // '12345.67'
numTexto = %editc(numVal : '1');// Formato editado con comas
numTexto = %editc(numVal : 'J');// Con signo negativo

// Texto a número
numVal = %dec('12345.67' : 11 : 2);  // 12345.67
numVal = %int('42');                   // 42

// Fechas
fechaISO = %date();                    // Fecha actual
numTexto = %char(fechaISO : *iso);     // '2024-01-15'
fechaISO = %date('2024-06-15' : *iso); // Desde string

// Timestamp
dcl-s ts timestamp;
ts = %timestamp();             // Timestamp actual
%FOUND

Indica si la última operación de lectura encontró un registro

%EOF

Indica fin de archivo en operaciones de lectura secuencial

%ERROR

Indica si ocurrió un error en la última operación

%OPEN

Indica si un archivo está abierto

%ELEM

Número de elementos en un array o data structure

%PARMS

Número de parámetros pasados al procedimiento

%LOOKUP

Busca un valor en un array y retorna la posición

%XLATE

Traduce caracteres (ej: minúsculas a mayúsculas)

Data structures

Las data structures (DCL-DS) agrupan campos relacionados, similar a structs en C o clases simples en Java. Son fundamentales en RPG moderno.

RPG — Data structure básica
// DS qualified (recomendado: acceso con ds.campo)
dcl-ds cliente qualified;
  id       packed(7:0);
  nombre   varchar(100);
  email    varchar(200);
  saldo    packed(11:2);
  activo   ind;
end-ds;

// Uso con notación punto
cliente.id = 1001;
cliente.nombre = 'Fernando Secchi';
cliente.saldo = 50000.00;
cliente.activo = *on;

// DS con DIM (array de estructuras)
dcl-ds listaItems qualified dim(100);
  codigo     char(10);
  cantidad   packed(7:0);
  precio     packed(11:2);
end-ds;

// Acceso a elementos del array
listaItems(1).codigo = 'PROD001';
listaItems(1).cantidad = 5;
listaItems(1).precio = 1250.00;
RPG — LIKEDS y templates
// Definir una DS como template (no ocupa memoria)
dcl-ds clienteT qualified template;
  id       packed(7:0);
  nombre   varchar(100);
  email    varchar(200);
  saldo    packed(11:2);
end-ds;

// Crear variables basadas en el template
dcl-s clienteNuevo likeds(clienteT);
dcl-s clienteViejo likeds(clienteT);
dcl-ds listaClientes likeds(clienteT) dim(500);

// Copiar toda la estructura
clienteViejo = clienteNuevo;

// Limpiar estructura
reset clienteNuevo;
RPG — Data structure para I/O
// DS externa basada en el formato de un archivo
dcl-ds clienteRec extname('CLIENTES') qualified;
end-ds;

// DS con posiciones (para parsear buffers)
dcl-ds header qualified;
  tipo     char(2)   pos(1);
  fecha    char(8)   pos(3);
  secuenc  packed(5:0) pos(11);
  empresa  char(30)  pos(16);
end-ds;

Indicadores

Los indicadores (*IN01 a *IN99) son variables booleanas históricas de RPG. En código moderno, se reemplazan por BIFs y variables de tipo IND.

Estilo antiguo (indicadores)

  • *IN50 = *ON para señalar error
  • *IN90 usado como flag genérico
  • CHAIN(E) setea *IN99 si no encuentra
  • Difícil saber qué hace cada indicador
  • Limitados a 99 indicadores numéricos

Estilo moderno (BIFs + variables)

  • dcl-s hayError ind; con nombre descriptivo
  • dcl-s encontrado ind;
  • chain clave archivo; if %found(archivo);
  • El código se auto-documenta
  • Variables ilimitadas con nombres claros
RPG — Minimizar uso de indicadores
// ANTES: indicadores numéricos
// C     KEY      CHAIN(N)  CLIENTES              50
// C  N50         ...

// AHORA: BIFs descriptivas
chain key clientes;
if %found(clientes);
  // registro encontrado, procesar
endif;

// Para archivos de pantalla (DSPF) aún se usan indicadores
// pero se pueden mapear a nombres descriptivos:
dcl-ds indicadores qualified;
  salir      ind pos(3);    // F3 = Salir
  cancelar   ind pos(12);   // F12 = Cancelar
  confirmar  ind pos(6);    // F6 = Confirmar
  refrescar  ind pos(5);    // F5 = Refrescar
end-ds;

Operaciones con archivos

RPG accede a archivos (tablas Db2) de forma nativa a nivel de registro. Esta es una de sus mayores fortalezas: el acceso a datos es extremadamente eficiente sin necesidad de un driver intermedio.

RPG — Declaración de archivos (DCL-F)
// Archivo de entrada (lectura)
dcl-f clientes disk usage(*input) keyed;

// Archivo de actualización (lectura + escritura)
dcl-f facturas disk usage(*update : *delete : *output) keyed;

// Archivo de pantalla (display file)
dcl-f pantalla workstn;

// Archivo de impresión (printer file)
dcl-f reporte printer;

// Archivo con nombre largo referenciando PF
dcl-f cliMaestro disk extfile('MILIB/CLIENTES')
                      usage(*input) keyed;
RPG — Operaciones de lectura
// CHAIN — Lectura directa por clave (como SELECT WHERE key = ?)
dcl-s idBuscado packed(7:0) inz(1001);
chain idBuscado clientes;
if %found(clientes);
  dsply ('Encontrado: ' + %trim(nombre));
else;
  dsply 'No encontrado';
endif;

// READ — Lectura secuencial
read clientes;
dow not %eof(clientes);
  // procesar registro actual
  total += saldo;
  read clientes;
enddo;

// SETLL + READE — Lectura por rango de clave parcial
dcl-s codProv char(3) inz('BUE');
setll codProv facturas;
reade codProv facturas;
dow not %eof(facturas);
  procesarFactura();
  reade codProv facturas;
enddo;

// SETGT — Posicionar después de una clave
setgt idBuscado clientes;
readp clientes;  // Lee el registro anterior
RPG — Escritura, actualización, eliminación
// WRITE — Agregar un registro
nombre = 'Nuevo Cliente';
email = 'nuevo@email.com';
saldo = 0;
write clirec;

// UPDATE — Actualizar registro actual (después de CHAIN o READ)
chain idBuscado clientes;
if %found(clientes);
  saldo += 5000;
  update clirec;
endif;

// DELETE — Eliminar registro
chain idBuscado clientes;
if %found(clientes);
  delete clirec;
endif;

// DELETE directo por clave (sin CHAIN previo)
delete idBuscado clientes;

Manejo de errores

RPG moderno usa MONITOR / ON-ERROR como bloque try/catch para capturar errores en tiempo de ejecución.

RPG — MONITOR / ON-ERROR
monitor;
  // Código que podría fallar
  chain clienteId clientes;
  if %found(clientes);
    saldo = saldo / divisor;   // podría ser división por cero
    update clirec;
  endif;

on-error 00102;
  // Error de string (MCH1202 - conversión inválida)
  enviarMensaje('Error de conversión de datos');

on-error 00101;
  // División por cero
  enviarMensaje('Error: división por cero');

on-error *file;
  // Cualquier error de archivo (locked, not found, etc.)
  enviarMensaje('Error accediendo al archivo');

on-error *all;
  // Cualquier otro error no capturado arriba
  enviarMensaje('Error inesperado: ' + %char(%status));

endmon;
*FILE

Cualquier error de archivo I/O

*PROGRAM

Errores de programa (llamadas, parámetros)

*ALL

Cualquier error (catch genérico)

00100

Error en conversión de cadena

00101

División por cero (MCH1211)

00102

Desbordamiento numérico

01211

Registro bloqueado en archivo

01218

Registro no encontrado en archivo

Ejemplo completo: programa CRUD

A continuación un programa RPG completo que implementa operaciones CRUD sobre una tabla de clientes. Incluye manejo de errores, procedimientos y buenas prácticas modernas.

GESTCLI.RPGLE — Gestión de Clientes
**free
// ---------------------------------------------------------------
// GESTCLI: Programa de gestión de clientes (CRUD)
// ---------------------------------------------------------------
ctl-opt dftactgrp(*no) actgrp(*caller);
ctl-opt option(*srcstmt : *nodebugio);

// ---------------------------------------------------------------
// Archivos
// ---------------------------------------------------------------
dcl-f clientes disk usage(*input : *update : *delete : *output)
                    keyed;

// ---------------------------------------------------------------
// Interfaz del programa
// ---------------------------------------------------------------
dcl-pi *n;
  prmAccion   char(10);       // ALTA, BAJA, MODIF, CONS
  prmId       packed(7:0);
  prmNombre   varchar(100);
  prmEmail    varchar(200);
  prmResultado char(1);       // S=ok, N=error
end-pi;

// ---------------------------------------------------------------
// Lógica principal
// ---------------------------------------------------------------
prmResultado = 'N';

select;
  when prmAccion = 'ALTA';
    prmResultado = altaCliente(prmId : prmNombre : prmEmail);

  when prmAccion = 'BAJA';
    prmResultado = bajaCliente(prmId);

  when prmAccion = 'MODIF';
    prmResultado = modifCliente(prmId : prmNombre : prmEmail);

  when prmAccion = 'CONS';
    prmResultado = consCliente(prmId : prmNombre : prmEmail);

  other;
    prmResultado = 'N';
endsl;

*inlr = *on;
return;

// ---------------------------------------------------------------
// Subprocedimiento: Alta de cliente
// ---------------------------------------------------------------
dcl-proc altaCliente;
  dcl-pi *n char(1);
    id      packed(7:0) const;
    nombre  varchar(100) const;
    email   varchar(200) const;
  end-pi;

  monitor;
    cliId  = id;
    cliNom = nombre;
    cliMail = email;
    cliSaldo = 0;
    cliActivo = 'A';
    write clirec;
    return 'S';
  on-error *all;
    return 'N';
  endmon;
end-proc;

// ---------------------------------------------------------------
// Subprocedimiento: Baja de cliente
// ---------------------------------------------------------------
dcl-proc bajaCliente;
  dcl-pi *n char(1);
    id packed(7:0) const;
  end-pi;

  chain id clientes;
  if %found(clientes);
    delete clirec;
    return 'S';
  endif;
  return 'N';
end-proc;

// ---------------------------------------------------------------
// Subprocedimiento: Modificación
// ---------------------------------------------------------------
dcl-proc modifCliente;
  dcl-pi *n char(1);
    id      packed(7:0) const;
    nombre  varchar(100) const;
    email   varchar(200) const;
  end-pi;

  chain id clientes;
  if %found(clientes);
    cliNom = nombre;
    cliMail = email;
    update clirec;
    return 'S';
  endif;
  return 'N';
end-proc;

// ---------------------------------------------------------------
// Subprocedimiento: Consulta
// ---------------------------------------------------------------
dcl-proc consCliente;
  dcl-pi *n char(1);
    id      packed(7:0) const;
    nombre  varchar(100);
    email   varchar(200);
  end-pi;

  chain id clientes;
  if %found(clientes);
    nombre = cliNom;
    email = cliMail;
    return 'S';
  endif;
  return 'N';
end-proc;

Compilación

Existen dos formas principales de compilar programas RPG, dependiendo de si necesitás un programa bound simple o un módulo para service programs.

CL — Compilar RPG
// Opción 1: CRTBNDRPG — Compilar y enlazar en un paso
// Crea un programa *PGM directamente
CRTBNDRPG PGM(MILIB/GESTCLI)
          SRCFILE(MILIB/QRPGLESRC)
          SRCMBR(GESTCLI)
          DBGVIEW(*SOURCE)
          OPTION(*SRCSTMT *NODEBUGIO)
          TGTRLS(*CURRENT)

// Opción 2: CRTRPGMOD + CRTPGM — Compilar en dos pasos
// Primero crear el módulo *MODULE
CRTRPGMOD MODULE(MILIB/GESTCLI)
          SRCFILE(MILIB/QRPGLESRC)
          SRCMBR(GESTCLI)
          DBGVIEW(*SOURCE)

// Luego enlazar en un programa *PGM
CRTPGM PGM(MILIB/GESTCLI)
       MODULE(MILIB/GESTCLI)
       ACTGRP(*CALLER)

// Opción 3: Desde IFS con CRTBNDRPG
CRTBNDRPG PGM(MILIB/GESTCLI)
          SRCSTMF('/home/dev/src/gestcli.rpgle')
          DBGVIEW(*SOURCE)
Recomendación: Para programas simples e independientes, usá CRTBNDRPG. Para programas que comparten lógica mediante service programs, usá la compilación en dos pasos con CRTRPGMOD + CRTPGM. Más detalle en la sección de ILE.

AI-enhanced RPG (IBM i 7.6)

IBM esta explorando el uso de inteligencia artificial para asistir en el desarrollo y modernizacion de codigo RPG. IBM Code Assistant es la herramienta que IBM desarrolla para este proposito.

Casos de uso

  • Modernizacion de codigo legacy: convertir RPG columnar (RPG III/IV fijo) a free-format moderno
  • Sugerencias de codigo: asistencia de IA al escribir RPG, similar a GitHub Copilot
  • Analisis de programas: entender la logica de programas RPG complejos y legacy
  • Generacion de documentacion: crear documentacion automatica a partir del codigo fuente
  • Generacion de tests: crear casos de prueba para programas RPG existentes

IBM Code Assistant

IBM Code Assistant for i es una herramienta basada en modelos de lenguaje entrenados especificamente en codigo RPG e IBM i. Se integra con VS Code y esta disenada para entender la semantica del RPG, incluyendo DDS, DSPF, archivos fisicos/logicos y la arquitectura ILE.

Estado actual: Estas capacidades de IA estan en diferentes estados de madurez. La conversion de RPG fijo a free-format es la mas avanzada. Las sugerencias de codigo y generacion de tests estan en desarrollo activo. Mantenete actualizado con los anuncios de IBM para conocer la disponibilidad.