WARP & Warpgate: la evolución en el desarrollo para IBM i
WARP es un lenguaje de programación de alto nivel con sintaxis declarativa, diseñado para optimizar y acelerar el desarrollo en entornos IBM i (AS/400). En lugar de escribir manualmente extenso código RPGLE o definiciones DDS, WARP permite definir programas, pantallas interactivas y tablas en archivos .warp limpios y expresivos.
A través del compilador Warpgate, el código WARP se transforma automáticamente en fuente RPGLE y DDS altamente optimizado, listo para ejecutarse en el sistema.
Características principales
- Ingeniería de alto rendimiento: el compilador Warpgate está desarrollado 100% en Rust, garantizando una velocidad de compilación ultra rápida, seguridad de memoria y portabilidad nativa en Windows, GNU/Linux y macOS.
- Integración en pipelines CI/CD: al ser un ejecutable nativo multiplataforma capaz de realizar builds desde la línea de comandos, se integra fácilmente en pipelines de integración y despliegue continuo (GitHub Actions, GitLab CI, Azure DevOps, etc.), llevando prácticas DevOps modernas a la plataforma IBM i.
- Despliegue remoto multi-protocolo: permite la transferencia y despliegue directo hacia servidores IBM i remotos mediante protocolos seguros como SSH, SFTP y FTP, configurables desde el archivo de proyecto (ver
@Connection/Protocol). - Modernización y productividad: reduce drásticamente las líneas de código necesarias y elimina la verbosidad del RPG tradicional, agilizando el flujo de trabajo de los equipos de desarrollo.
- Evolución continua: mejora e incorpora constantemente nuevas capacidades de lenguaje y generación de código.
Instalación
Warpgate se distribuye como una extensión de VSCode, disponible en el Visual Studio Marketplace:
- Abrir VSCode → pestaña Extensiones (
Ctrl+Shift+X). - Buscar “WaRPGate for IBM i” (publisher Software House) e instalar.
La extensión ya incluye el compilador para Windows, GNU/Linux y macOS — no hace falta instalar nada aparte. Para generar y desplegar código (generate/build/reverse) hace falta una licencia — ver Licenciamiento.
Nota para macOS: el compilador no está firmado con un certificado de Apple, así que macOS puede bloquear su primera ejecución con un aviso de que no se puede verificar el desarrollador. Para permitirlo, ejecuta una vez en Terminal, reemplazando la ruta por la del binario:
xattr -d com.apple.quarantine /ruta/a/warpgate
Desarrollado por Software House
WARP y Warpgate son diseñados, desarrollados y mantenidos por Giuliano Gonzales Zeballos, creador y arquitecto principal en Software House, firma dedicada a la ingeniería de software, arquitectura de sistemas y soluciones de modernización para entornos enterprise.
Sobre este libro
Este libro documenta:
- Las guías: cómo armar, paso a paso, cada tipo de objeto (
Program,Screen,Table) y las funcionalidades más grandes (DataGridView, reportes conMember/Export). - La referencia del lenguaje: cada sección (
@Properties,@Variables,@Source, …), cada tipo de dato, cada sentencia de control y cada función builtin, con su sintaxis exacta y sus reglas de validación.
¿Qué es un archivo .warp?
Un archivo .warp describe un objeto: un Program (una lógica de negocio, con o sin salida impresa), un Screen (una pantalla interactiva 5250) o una tabla (un físico DDS). El tipo se declara en @Properties/Type.
Un .warp se organiza en secciones de nivel superior, marcadas con @Nombre { ... }:
@Documentation {
Author Name : "Nombre Programador"
}
@Properties {
Type : Program
Name : PTESTFN
Description : "Ejemplo mínimo"
}
@References {
Programs = []
Screens = []
Tables = []
}
@Variables {
global {
Usuario char(10)
}
}
@Parameters {
}
@Source {
&Usuario = USERID()
}
Qué secciones son válidas y en qué orden depende del Type — un Program tiene @Layout/@Source, una tabla tiene @Structure/@Fields/@Indexes, un Screen tiene @Layout/@Source pero no @Structure. Cada capítulo de la referencia detalla en qué tipo de archivo aplica cada sección.
Cómo compilar
El compilador es un binario CLI (cli, distribuido como warpgate/warpgate.exe dentro de la extensión de VSCode) que recibe un proyecto (.warproj) y un archivo fuente:
warpgate --project mi-proyecto.warproj --source programs/PMIPROG.warp --action validate
warpgate --project mi-proyecto.warproj --source programs/PMIPROG.warp --action generate
warpgate --project mi-proyecto.warproj --source programs/PMIPROG.warp --action build --profile Prod
validate: sólo corre el análisis semántico (lo que también dispara la extensión de VSCode al guardar) — no toca IBM i.generate: genera el RPGLE/DDS/CL y los escribe enOutput— puramente local, nunca se conecta al IBM i (aunque el CL generado ya referencia rutas remotas, según@Connection/@Deployment).build: hace lo mismo quegenerate, y además sube lo generado y lo compila en el IBM i configurado en@Connection(CRTPF/CRTLF/CRTBNDRPG/CRTDSPF, según el tipo).reverse: reconstruye un.warpde tabla a partir de un DDS ya existente en el IBM i (ingeniería inversa).
Cuando el .warpcfg declara más de un @Profile (Dev/UAT/Prod, ver Archivo de proyecto), generate/build/reverse y license-status necesitan saber cuál usar — con el flag --profile <Nombre> o, si se omite, con Default Profile de @Project. validate ignora este flag por completo.
Ver Archivo de proyecto para el formato de .warproj/.warpcfg.
Primeros pasos: tu primer Program
Un Program es la unidad básica de lógica de negocio: recibe parámetros, hace algo (leer/escribir tablas, calcular, invocar otros programas) y termina. No tiene por qué mostrar pantalla ni imprimir nada.
1. Archivo mínimo
@Documentation {
Author Name : "Tu Nombre"
}
@Properties {
Type : Program
Name : PSALUDO
Description : "Programa de saludo mínimo"
}
@References {
Programs = []
Screens = []
Tables = []
}
@Variables {
global {
Usuario char(10)
Saludo char(30)
}
}
@Parameters {
}
@Source {
&Usuario = USERID()
&Saludo = "Hola, " + &Usuario
}
Esto ya es un .warp válido: declara el objeto (@Properties), sus variables (@Variables) y su lógica (@Source). @References/@Parameters quedan vacíos porque este programa no depende de otros objetos ni recibe parámetros.
2. Agregar parámetros
Un Program recibe parámetros declarados en @Parameters, referenciando variables ya declaradas en @Variables/global:
@Variables {
global {
Fecha date
Hora char(8)
}
}
@Parameters {
inout:&Fecha,
inout:&Hora
}
Los modos son in (sólo entra), inout (entra y puede modificarse) y out (sólo sale) — ver @Parameters.
3. Invocar otro programa
Para llamar a un programa externo, agregalo a @References/Programs y usá Call(...):
@References {
Programs = ["../programs/POTRO.warp"]
}
@Source {
Call(POTRO, &Fecha, &Hora)
}
4. Procedimientos y funciones locales
Procedure/Function viven dentro de @Source, con sus propias variables en @Variables (una entrada por nombre de procedimiento/función, además de global):
@Variables {
global {
Usuario char(10)
}
Saluda {
nombre char(10)
}
}
@Source {
Function Saluda(&nombre) char(20)
Return "Hola " + &nombre
EndFunc
&Usuario = USERID()
Message("Bienvenido", Info) // sólo válido en Screen; en Program usar Print()
}
Para invocar un Procedure (sin retorno) usá Do Nombre(...); una Function (con retorno) se invoca como expresión: &variable = Nombre(...).
5. Siguiente paso
- Si tu programa necesita leer/escribir una tabla: Tablas.
- Si necesita mostrar una pantalla interactiva: Pantallas.
- Si necesita generar un reporte a un archivo de texto: Reportes con Member()/Export().
Para la sintaxis completa de cada sección, ver la Referencia del lenguaje.
Tablas: @Structure, @Fields, @Indexes
Una tabla .warp describe un físico DDS: sus columnas y sus accesos por clave. Se referencia desde un Program/Screen vía @References/Tables.
1. Archivo mínimo
@Properties {
Type : "Table"
}
@Structure {
Name : "GRIDDEMO"
Description : "Clientes de ejemplo"
@Fields {
CodCli char(6) Description("Código de Cliente")
Nombre char(20) Description("Nombre")
Ciudad char(15) Description("Ciudad")
}
@Indexes {
PrimaryKey ( GRIDDEMOPK, [CodCli] )
Index ( GRIDDMNOM, [Nombre] )
}
}
@Fields: una líneaNombre tipo(...)por columna. Sólochar/number/date— ver Tipos de dato. Modificadores disponibles:Description(...),Default(...),AllowNull.@Indexes:PrimaryKey(obligatoria, sus campos no pueden serAllowNull),Unique,Index— cada uno comoKind(Nombre, [Campo, ...], "Descripción opcional").
2. Referenciarla desde un Program
@References {
Tables = ["../tables/TLGRIDDEMO.warp"]
}
La ruta es relativa al archivo que la referencia. Una vez referenciada, sus campos quedan disponibles como identificadores sueltos dentro de un For Each/New (ver abajo), y como field(NombreCampo) en @Variables para heredar su tipo.
3. Leer filas: For Each In
@Source {
For Each In GRIDDEMO Index GRIDDMNOM
Where CodCli = &VCodCli
&VNombre = Nombre
&VCiudad = Ciudad
When None
Message("Cliente no encontrado", Error)
EndFor
}
Index selecciona el acceso (por defecto el de acceso secuencial si se omite); Where filtra por igualdad; When None corre si no hubo ninguna fila.
Unir tablas: Join In
Para leer datos de una tabla relacionada, agregue Join In bajo el For Each. La tabla unida se lee por su PrimaryKey por cada fila recorrida:
For Each In Pedidos
Join In Clientes On CodCli = CodCli
&VNombre = NomCli
EndFor
Sin On, las columnas de la llave de la tabla unida deben existir con el mismo nombre en la tabla recorrida. Si no hay pareja, la fila no se omite y los campos unidos quedan en blanco o cero. Son de sólo lectura. Detalle completo en @Source: sentencias de control.
4. Insertar filas: New
@Source {
New In GRIDDEMO
CodCli = &VCodCli
Nombre = &VNombre
Ciudad = &VCiudad
When Duplicate
Message("Ya existe ese código", Error)
EndNew
}
When Duplicate corre si la fila ya existe: el New la busca por la PrimaryKey de la tabla (que debe estar en @Indexes) y, si la encuentra, ejecuta el bloque sobre esa fila y la actualiza si el bloque asignó campos. El bloque admite cualquier sentencia (If, While, Do Case, etc.). Detalle en @Source: sentencias de control.
5. Documentación extendida
Una tabla puede llevar además @Documentation (anidada dentro de @Structure, a diferencia de un Program donde va al nivel superior) con @Author, @Application, @Module, @Repository e @History (@Creation + @Change por cada entrada del changelog) — ver @Structure, @Fields, @Indexes para el detalle completo de cada sub-sección.
6. Ingeniería inversa desde un DDS existente
Si la tabla ya existe en el IBM i, no hace falta escribirla a mano: el comando WaRPGate: Reverse Engineer de la extensión de VSCode se conecta al IBM i, lee el DDS (y opcionalmente sus LF asociadas como @Indexes) y genera el .warp correspondiente. Si el formato de registro del DDS descargado difiere del nombre de la tabla, se escribe Record Format : "X"; si es igual, no se escribe.
Los comentarios del DDS (líneas con * en la columna 7) también se conservan: los del encabezado del archivo quedan como líneas // arriba de @Structure, y los que preceden a un grupo de campos quedan como líneas // justo antes del primer campo del grupo. Los bordes decorativos de asteriscos se omiten.
Pantallas: @Layout y Screen
Un Screen es una pantalla interactiva 5250: define su distribución (@Layout) y reacciona a eventos del usuario (@Source).
1. Estructura mínima
@Properties {
Type : Screen
Name : SFORM01
Description : "Pantalla de ejemplo"
Path : Screens
}
@References {
Tables = ["../tables/TLADD50.warp"]
}
@Variables {
Global {
Usuario char(10)
Fecha Date
}
}
@Parameters {
}
@Layout {
@Label(1,2,&Usuario)
@Label(1,70,&Fecha)
@Input("txtNroSolicitud", 3, 1, "Nro. Solicitud: ", &NroSolicitud, true)
}
@Source {
Event Init
&Usuario = UserId()
&Fecha = Today()
EndEvent
}
A diferencia de un Program, un Screen no tiene @Structure propia — sólo referencia tablas para leer/escribir.
Parámetros del Screen
El bloque @Parameters del ejemplo está vacío, pero un Screen (pantalla completa o Popup) puede declararlo igual que un Program: @Parameters { in:&X, inout:&Y, out:&Z }. Los valores están disponibles desde Event Init y se invoca con Call(Nombre, ...). En un Popup sirve para recibir datos y devolver la selección del usuario. Ver @Parameters.
2. Controles de @Layout
-
Label(x, y, texto): texto/variable estático.x,y = 1,1(esquina exacta) está prohibido — DDS lo rechaza. -
Input("Nombre", x, y, texto, &Variable, habilitado): campo editable."Nombre"es lo que devuelveCurrentInput()cuando el cursor cae ahí.habilitadoes opcional (defaulttrue): literal,&Variable,Funcion()de retornonumbero una condición completa (Not &Bloqueado = 1 And &Estado <> "9").Si la variable es
char/varchar, el campo admite minúsculas y el programa recibe lo tipeado tal cual (Lima, noLIMA); para ignorar la diferencia al comparar useUpper(&campo). LosInputnuméricos, de fecha y de hora no cambian. -
DataGridView("Nombre", x, y, filas)[...]: grilla — ver DataGridView.
Los nombres de control pueden prefijarse con @ (@Label, @Input) o no — es sólo estilo, mismo significado.
Ancho disponible y error E00353
Ningún elemento puede terminar más allá de la última columna de la pantalla: 80 por defecto, 132 si el proyecto declara ese tamaño, o el ancho de la ventana en un popup. Terminar justo en la última columna sí cabe. Si no cabe, el compilador reporta E00353 (IBM i rechazaría el campo o lo partiría en dos filas). El ancho de cada elemento se calcula así:
-
Label: el largo del texto.
-
Input: la etiqueta + 1 espacio + el ancho de la variable.
-
DataGridView: la suma de los anchos de sus columnas visibles, más la separación entre cada par de columnas contiguas. El ancho de una columna es el mayor entre el encabezado y el dato. La separación es el separador declarado o, si no hay separador o es más corto, al menos 1 posición (un espacio). Las columnas
Hiddenno cuentan: se declaran como campos ocultos del subfile, sin línea ni posición en pantalla.En 5250 cada campo lleva un byte de atributo justo antes de su primer carácter; sin ese espacio libre, el de una columna se solaparía con el último carácter de la anterior (
CPD7866) y la columna no se mostraría. Por eso, sin separador, cada columna queda una posición más a la derecha que la anterior.Ejemplo: tres columnas de 6, 20 y 15 posiciones, sin separador, ocupan 6 + 1 + 20 + 1 + 15 = 43 posiciones; con una grilla en la columna 2 terminan en la 44.
La columna donde termina el elemento es x + ancho - 1; si supera el máximo, acorte el texto o los anchos, o mueva el elemento a la izquierda.
Color y atributos con Style
Label, Input y Column (de un DataGridView) aceptan al final uno o varios Style(...) con un color (Green, White, Red, Blue, Yellow, Pink, Turquoise) y atributos (Reverse, High, Underline, Blink, Hidden). Un Style puede llevar al final una condición, y entonces se aplica sólo mientras se cumpla:
Label(1,2,&Titulo,Style(Reverse,High))
Label(5,2,&Desc,Style(Red,&Estado = "9"),Style(Green))
Input("TxtOp",7,2,"Operacion:",&Op,Not &Bloqueado = 1,Style(Yellow,Underline))
Por elemento se admite a lo sumo un Style sin condición y hasta tres con condición. En una Column la condición se evalúa por fila, en el momento de LoadRow(), con los valores de las variables en ese instante (asígnalas antes de LoadRow()); el color queda guardado con la fila:
Column("Cantidad",&VCant,Style(Red,High,&VCant > 100),Style(Green))
La habilitación dinámica por fila (Column(..., Input) condicionado) aún no existe. Detalle completo en @Layout: Style.
3. Eventos estándar
| Evento | Cuándo dispara |
|---|---|
Init | Una sola vez, antes del primer despliegue. |
Load | Una sola vez, antes del primer despliegue — típicamente llena el DataGridView con LoadRow() (la grilla se reinicia al entrar). |
Enter | Al presionar Enter (sin tecla de función activa). |
Refresh | Al presionar F5, si está declarada en @FunctionKeys del proyecto. Después recarga el DataGridView, si lo hay. |
Close | Fijo a F3 — siempre corre y cierra la pantalla, sin declaración necesaria. |
@Source {
Event Init
&Usuario = UserId()
EndEvent
Event Load
For Each In LADD50 Index LADD5010
Where SPT25PAIS = &nPais
&cEstado = ADSTS50
LoadRow()
EndFor
EndEvent
Event Enter
&Control = CurrentInput()
Message("Control activo: " + &Control, Info)
EndEvent
}
Dos sentencias ayudan a controlar la pantalla desde cualquier evento: &Variable.SetFocus() ubica el cursor en un Input, y Refresh / Refresh Keep recargan el DataGridView. Ver @Source: Event y Screen.
4. Teclas de función personalizadas
Además de los eventos estándar, se puede atar un evento a cualquier tecla F1-F24 (F13-F24 vía Shift):
@Source {
Event 'BuscarCliente' 4
Message("Se presionó F4", Info)
EndEvent
}
Por defecto, la fila de teclas de función muestra el nombre del evento (F4=BuscarCliente). Para mostrar otro texto, agrégalo entre comillas después de la tecla (máximo 20 caracteres):
Event 'BuscarCliente' 4 "Buscar"
El nombre del evento ('BuscarCliente') se convierte en el nombre de un subprocedimiento RPG, por eso debe ser un identificador: letras, dígitos y _, empezando por letra (E00357). El texto con espacios va como tercer valor: Event 'Borrar' 7 "Borrar fila".
Este texto tiene prioridad sobre el declarado en @FunctionKeys del .warpcfg del proyecto — ver Proyecto.
Posición y color de la fila de teclas
Por defecto la fila se dibuja en Rows - 1 (la fila 23 en una pantalla de 24x80; en un popup, la última fila de la ventana menos una). Con Function Keys Row se elige otra fila, y con Function Keys Color se colorea toda la fila. Ambas propiedades se declaran en @GeneratorConfig/@Screen (para todo el proyecto) o en el @Properties de un Screen (solo ese objeto, y manda sobre el proyecto):
@Properties {
Type : Screen
Name : SCLIENTES
Function Keys Row : 22
Function Keys Color: Blue
}
La fila debe estar entre 1 y Rows - 1 (la última queda para los mensajes); si no, se reporta E00346. El compilador no avisa si la fila invade el @Layout: déjala libre. Un color inválido da E00347.
Cada Event puede llevar su propio color al final, que manda sobre el de la fila:
Event 'Borrar' 7 "Borrar" Red
Los colores son Green, White, Red, Blue, Yellow, Pink y Turquoise.
Rotación de teclas con F24
Si F24 está declarada en @FunctionKeys (por ejemplo F24 : "Mas teclas") y ningún Event la usa, las teclas que no caben en una fila se reparten en juegos rotativos. Con F3 : "Salir", F5 : "Refrescar" y los eventos Event 'Inicio' 6, Event 'Salida' 7, Event 'Cursor' 8 y Event 'Despues' 9 "Tras Refresh":
Juego 1: F3=Salir F5=Refrescar F6=Inicio F7=Salida F8=Cursor F24=Mas teclas
Juego 2: F9=Tras Refresh F24=Mas teclas
Cada juego cabe en una sola fila (ancho de la pantalla menos 2 columnas; el de la ventana en un popup), las teclas van ordenadas por número y cada juego termina con F24. Al pulsar F24 se muestra el juego siguiente y tras el último se vuelve al primero; la pulsación solo cambia la fila de teclas y no ejecuta ningún evento. Si todas las teclas caben en una fila no hay rotación. Si no se declara F24, o si un Event la usa, la fila muestra todas las teclas, en una o dos filas.
5. Mensajes al usuario
Message("Filas encontradas: " + String(&nFilas, 5), Info)
Error (rojo), Warning (amarillo), Info (color normal). Se muestra en el siguiente refresco y se limpia después de un ciclo.
6. Siguiente paso
Si la pantalla necesita mostrar una lista de filas con selección/paginación, ver DataGridView.
DataGridView: grillas paginadas
DataGridView muestra una lista de filas de una tabla en un Screen, con paginación nativa (Page Up/Down) sobre un subfile DDS. Sólo puede haber uno por Screen.
1. Ejemplo completo
@Properties {
Type : Screen
Name : SGRIDDEMO
}
@References {
Tables = ["tables/TLGRIDDEMO.warp"]
}
@Variables {
global {
VCodCli char(6)
VNombre char(20)
VCiudad char(15)
VTotal number(5)
VUltimo char(6)
}
}
@Layout {
DataGridView("GridDemo", 2, 2, 5)
[
Column("Codigo", &VCodCli)
Column("Nombre", &VNombre)
Column("Ciudad", &VCiudad)
]
}
@Source {
Event Load
For Each In GRIDDEMO Index GRIDDMNOM
&VCodCli = CodCli
&VNombre = Nombre
&VCiudad = Ciudad
LoadRow()
EndFor
EndEvent
Event Enter
&VTotal = 0
For Each Row
&VTotal = &VTotal + 1
&VUltimo = &VCodCli
EndFor
Message("Filas: " + String(&VTotal, 5) + " Ultimo: " + &VUltimo, Info)
EndEvent
}
Entre dos columnas visibles contiguas siempre queda al menos una posición libre (el separador, si se declara; si no, un espacio). Esto cuenta para el cálculo de ancho de E00353: ver Ancho disponible y error E00353.
2. Cargar la grilla: Event Load + LoadRow()
LoadRow() toma los valores actuales de las variables atadas a cada Column y agrega una fila a la grilla. Es válido en cualquier lugar del cuerpo de cualquier Event de un Screen —directo, dentro de If, Case, While, For o For Each—, de modo que también sirve para cargar la grilla con datos que no vienen de una tabla. Un For Each que cargue la grilla debe declarar Index (E00201). Puede haber varias cargas en el mismo evento: todas agregan filas a la misma grilla. En Event Load la grilla se reinicia una sola vez, al entrar al evento; en cualquier otro Event o Procedure las filas se agregan a las que ya hay. Carga todo el resultado en una sola pasada — Page Up/Down son scroll nativo del terminal sobre lo ya cargado; Load nunca vuelve a correr.
LoadRow() también se puede usar dentro de cualquier Procedure del Screen, sin importar desde dónde se invoque (con Do). Una Function no puede usar LoadRow(), ni tampoco puede usarse fuera de un Event o Procedure (E00198).
Event Load
Do Carga()
EndEvent
Procedure Carga()
For &Indice = 1 To 5
&VCod = &Indice
&VNombre = "Item " + String(&Indice, 3)
LoadRow()
EndFor
EndProc
Una Procedure con LoadRow() también puede llamarse desde otro evento. Aquí Enter agrega filas a las que Load ya cargó (y F5/Refresh vuelve a correr Load, que reinicia la grilla):
Event Enter
Do AgregaFilas()
EndEvent
Procedure AgregaFilas()
&VCod = 99
&VNombre = "Fila agregada"
LoadRow()
EndProc
Y directo en el evento, sin procedimiento ni tabla:
Event Load
&VCod = 1
&VNombre = "Primero"
LoadRow()
&VCod = 2
&VNombre = "Segundo"
LoadRow()
EndEvent
Regla relacionada: una variable local con el mismo nombre que la variable de una Column tapa a la global (LoadRow() copiaría el valor local), error E00356. Ver Diagnósticos.
3. Leer la selección: RefreshSelectRow() y CurrentInput()
Cuando el cursor cae dentro del rectángulo de la grilla (encabezado, fila de atención o cualquier fila de datos), CurrentInput() devuelve el nombre de la grilla. RefreshSelectRow() relee la fila bajo el cursor y escribe cada columna (incluidas las Hidden) de vuelta en su variable atada — es la inversa de LoadRow(). Es la forma de recuperar el valor de una columna Hidden: esas columnas viajan en el registro del subfile como campos ocultos (uso H en el DDS), sin línea ni posición en pantalla:
Event Enter
&Control = CurrentInput()
If &Control = "GridDemo"
RefreshSelectRow()
Message("Seleccionaste: " + &VCodCli, Info)
EndIf
EndEvent
4. Recorrer todas las filas cargadas: For Each Row
For Each Row ... EndFor recorre todas las filas ya cargadas en el subfile (no la tabla de base de datos), escribiendo cada Column en su variable atada antes de cada iteración — misma dirección que RefreshSelectRow(). No admite Index/Where/When None porque no hay tabla que recorrer:
Event Enter
&VTotal = 0
For Each Row
&VTotal = &VTotal + 1
EndFor
EndEvent
5. Columnas ocultas (Hidden)
Column("ID Interno", &VIdInterno, Hidden)
Una columna Hidden carga su dato (participa en LoadRow()/RefreshSelectRow()/For Each Row) pero no muestra la celda ni el título de su encabezado — útil para llevar una clave interna sin ocupar espacio en pantalla.
6. Columnas editables (Input)
Column("Cantidad", &VCantidad, Input)
Permite que el usuario edite el valor directamente en la grilla.
Recargar la grilla: Refresh y Refresh Keep
Para volver a ejecutar Event Load (por ejemplo, tras cambiar un filtro) usa Refresh (vuelve a la primera fila) o Refresh Keep (conserva la fila donde estaba el usuario). La recarga ocurre al terminar el evento actual. F5, si está declarada en @FunctionKeys, ejecuta Event Refresh y luego recarga la grilla. Ver Refresh y Refresh Keep.
7. Reglas a tener en cuenta
- El nombre (
"GridDemo") es obligatorio y único entreInput/DataGridViewde la misma pantalla. - Sólo un
DataGridViewporScreen. - Sólo válido dentro de
@Layoutde unScreen, nunca de unProgram.
Reportes con Member() / Export()
Patrón para generar un reporte: acumular filas en una tabla física (un miembro de datos exclusivo del usuario/ejecución) y, al terminar, volcarlo a un archivo de texto en el IFS.
Esta funcionalidad usa
varcharen@Variables— ver Tipos de dato.
1. Ejemplo completo
@Properties {
Type : Program
Name : PTESTME
}
@References {
Tables = ["../tables/TLT3D01.warp"]
}
@Variables {
global {
Usuario char(10)
Destino varchar(200)
T301TIPLOT char(1)
T301NROTAR char(19)
}
}
@Source {
&Usuario = USERID()
&Destino = "/tmp/reporte.txt"
Member(LT3D01, &Usuario)
For Each In LT3D01 Index LT3D0101
Where T301TIPLOT = &T301TIPLOT
Where T301NROTAR = &T301NROTAR
// ... acumula filas en LT3D01, p.ej. con New In LT3D01 ...
EndFor
Export(LT3D01, &Destino)
}
2. Member(Tabla, expr): un miembro por usuario/ejecución
Member(LT3D01, &Usuario)
Liga un miembro de datos (parametrizado por expr, típicamente &Usuario para que cada usuario tenga el suyo) a Tabla para todo el programa:
- Lo crea con
ADDPFMsi no existe todavía (tolera el error si ya existe). - Lo deja activo con
OVRDBF— efectivo para el resto del programa, incluida cualquier apertura de esa tabla y cualquierExport(...)posterior sobre la misma.
Reglas de posición (a lo sumo una vez por tabla, en cualquier caso):
- En un
Program: sólo a nivel superior de@Source(no anidado dentro deIf/While/For/Event/etc.). - En un
Screen: sólo a nivel superior deEvent Init(no anidado, y no en ningún otroEvent) —Initcorre una única vez, antes del loop principal de la pantalla, el mismo lugar donde iría el prólogo de unProgram:
@Properties {
Type : Screen
Name : SGRIDDEMO
}
@Source {
Event Init
Member(LT3D01, &Usuario)
EndEvent
Event Load
For Each In LT3D01 Index LT3D0101
// ... llena el DataGridView, o lee/escribe filas ...
EndFor
EndEvent
}
Export(...) sigue sin estar permitido en Screen — no hay caso de uso para exportar un reporte desde una pantalla interactiva.
Importante:
Member(Tabla, expr)debe aparecer antes de cualquierFor Each/NewsobreTablaen el mismo cuerpo — usarlo después de haber leído/escrito la tabla es un error en tiempo de ejecución, no de compilación.
3. Export(Tabla, destino): volcar a IFS
Export(LT3D01, &Destino)
Copia el miembro activo de Tabla (el que dejó Member(...), si hay uno) a un stream file del IFS con CPYTOIMPF, formato delimitado por coma (RCDDLM(*CRLF) STRDLM(*NONE) FLDDLM(',')), reemplazando el destino si ya existe (MBROPT(*REPLACE)).
Reglas: sólo válido en Program; a diferencia de Member, sin restricción de posición — se usa donde convenga, típicamente después del For Each/New que llenó la tabla de reporte.
Proyecto: .warproj y .warpcfg
Todo .warp se compila en el contexto de un proyecto (.warproj), que a su vez apunta a un archivo de configuración del generador (.warpcfg) con las reglas de generación de código y el despliegue al IBM i.
1. .warproj
@Project {
Name : Example
Description : "Proyecto de ejemplo"
Version : 1.0
Default Profile : Dev
@Generator {
Name : "waRPGate generator"
Language : RPGLE
Description : "Generador RPGLE"
@Paths {
Config : ../example/generator/rpg/rpg.warpcfg
Output : output/rpg
}
}
}
@Paths/Config: ruta al.warpcfg(relativa al.warproj).@Paths/Output: carpeta donde se escribe el RPGLE/DDS generado.
2. .warpcfg
@GeneratorConfig {
Date Format : DMY
Date Separator : /
Time Format : 24H
Time Separator : :
Print Mode : File
Commitment : True
Commit on Exit : True
DDS Name : <ObjectName>
@Screen {
Rows : 24
Columns : 80
@FunctionKeys {
F3 : "Salir"
F5 : "Refrescar"
F24 : "Mas teclas"
}
}
@Profile "Dev" {
@Connection {
Host : DEVSERVER
User : usuario
Auth Method : key
Key File : ~/.ssh/id_rsa_ibmi
Protocol : ssh
Port : 22
Timeout : 30
}
@Deployment {
CL Source : QCLSRC
Cleanup : true
Target Release : *Current
Optimization : *FULL
Debug : false
Temp Path : /tmp
Data Library : DTALIB02
Objects Library : OBJLIB01
Source Tables : QDDSSRC
Source Programs : QRPGLESRC
Compile Library : DTALIB01,DTALIB02,DTALIB03,OBJLIB01
}
}
@Profile "Prod" {
@Connection {
Host : PRODSERVER
User : usuario
Auth Method : key
Key File : ~/.ssh/id_rsa_ibmi
Protocol : ssh
Port : 22
Timeout : 30
}
@Deployment {
CL Source : QCLSRC
Cleanup : true
Target Release : *Current
Optimization : *FULL
Debug : false
Temp Path : /tmp
Data Library : DTALIB02
Objects Library : OBJLIB01
Source Tables : QDDSSRC
Source Programs : QRPGLESRC
Compile Library : DTALIB01,DTALIB02,DTALIB03,OBJLIB01
}
}
}
Qué controla cada bloque
- Nivel superior (
Date Format,Print Mode,Commitment,Commit on Exit,DDS Name): reglas de generación de código, aplicadas a todo el proyecto.Commitment/Commit on Exitse pueden sobreescribir por objeto en el@Propertiesde un.warppuntual. @Screen: tamaño de pantalla por defecto (24x80 si se omite) y las teclas de función (@FunctionKeys) disponibles para losScreendel proyecto — la etiqueta que se muestra en la línea de atención. UnScreenliga una tecla a un evento propio conEvent 'Nombre' <n> ... EndEvent.@Profile "Nombre": un ambiente de despliegue (Dev,UAT,Prod, el nombre es libre). Cada perfil agrupa su propia@Connection,@Deploymenty, opcionalmente,@License. Un.warpcfgdebe declarar al menos un@Profile.@Connection: cómo llega el compilador al IBM i para--action build/reverse(no aplica avalidate/generate, que son puramente locales).@Deployment: bibliotecas y member de fuente donde se sube y compila (CRTPF/CRTLF/CRTCLPGM) el código generado.
Con más de un @Profile, hace falta indicar cuál usar: con --profile <Nombre> en el CLI, o dejando Default Profile en @Project (como en el ejemplo del .warproj de arriba). La extensión de VSCode tiene su propio selector — ver Comandos de VSCode.
Ver Archivo de proyecto para la lista completa de claves y sus valores permitidos.
Licenciamiento
Las acciones generate, build y reverse del compilador exigen una licencia. La acción validate no la exige, así que la validación de fuentes (y el IntelliSense de la extensión) funciona siempre.
La licencia se gestiona automáticamente: se toma al usar el compilador y se libera al terminar, sin intervención manual.
Modos de licencia
Organización
Un servicio de licencias administra un conjunto (pool) de seats. El compilador:
- Se conecta de forma segura al servicio de licencias de tu organización (identificando al servidor con
Fingerprint, en la configuración del proyecto). ElHost/Port/Fingerprintlos entrega quien administra ese servicio — ver Configurar el servicio de licencias si esa persona eres tú. - Cada usuario ocupa un solo puesto (seat) a la vez.
- Si el compilador se cierra o pierde la conexión, el puesto se libera automáticamente — nadie queda ocupando una licencia sin usarla.
- Compatible con autenticación Kerberos/Active Directory, para integrarse con las credenciales corporativas ya existentes.
- En macOS la autenticación Kerberos no está disponible: si el servicio de licencias de tu organización exige Kerberos, el compilador para macOS no podrá obtener licencia desde ese servicio. Windows y GNU/Linux no tienen esta limitación.
- Si el
.warpcfgtieneHost, manda el servicio: no hay respaldo automático al archivo de licencia individual. - Sin dominio (sin Kerberos),
license-statusen modo organización solo se acepta desde la propia máquina del servicio, salvo que el administrador habilite la consulta remota — ver Configurar el servicio de licencias.
Individual
Un archivo de licencia firmado, ligado al equipo, que se valida localmente y sin red. La ruta por defecto es:
- Linux/macOS:
~/.config/warpgate/license.json - Windows:
%APPDATA%\warpgate\license.json
Se puede usar otra ruta con File (en @License), --license-file o WARPGATE_LICENSE_FILE.
Configuración
Bloque @License en el .warpcfg
Bloque opcional dentro de cada @Profile (ver Archivo de proyecto) — cada perfil (Dev/UAT/Prod) puede tener su propia configuración de licencia, para consumir licencias o servicios de licenciamiento distintos según el ambiente. Las propiedades no distinguen mayúsculas de minúsculas.
@GeneratorConfig {
@Profile "Prod" {
@License {
Host : "servidor01.dominio.local"
Port : 7443
Fingerprint : "3f2a9c41d87b05e6a1c4f0937be2d5688a1f4c0d29e7b3a65c8d1f0e4b7a9c23"
}
}
}
Con --profile/Default Profile se elige qué perfil (y por tanto qué @License) usar en cada ejecución — ver Proyecto: .warproj y .warpcfg.
| Clave | Descripción |
|---|---|
Host | Host del servicio de licencias. Si está presente, el modo es organización y File se ignora. |
Port | Puerto TCP del servicio, de 1 a 65535. Por defecto 7443. |
Fingerprint | Huella SHA-256 del certificado del servicio: 64 caracteres hexadecimales (se aceptan mayúsculas y separadores :). |
SPN | Nombre de servicio Kerberos. Por defecto warpgate-license/<host>. |
File | Ruta del archivo de licencia individual. |
Sin Host, el modo es individual.
Flags y variables de entorno
| Flag | Variable de entorno | Equivale a |
|---|---|---|
--license-host | WARPGATE_LICENSE_HOST | Host |
--license-port | WARPGATE_LICENSE_PORT | Port |
--license-fingerprint | WARPGATE_LICENSE_FINGERPRINT | Fingerprint |
--license-spn | WARPGATE_LICENSE_SPN | SPN |
--license-file | WARPGATE_LICENSE_FILE | File |
Precedencia: flag > variable de entorno > .warpcfg.
Con varios @Profile declarados, license-status también respeta --profile/Default Profile para saber de cuál @License leer — ver Proyecto: .warproj y .warpcfg.
Códigos de salida
| Código | Significado |
|---|---|
3 | No hay licencia disponible. El compilador muestra un mensaje con lo que se debe hacer. |
4 | Se perdió la licencia a mitad de la ejecución. |
130 | Ejecución interrumpida (Ctrl-C/SIGTERM). |
Solicitar una licencia
Estas acciones de soporte no toman seat ni necesitan --source; --project es opcional.
warpgate --action license-request --license-id <ID> --license-out request.json
Escribe el archivo de solicitud (--license-out es opcional; por defecto request.json) y muestra el identificador del equipo. Con el archivo de licencia recibido, se copia a la ruta por defecto (o a la indicada con File). Instalar una licencia nueva reemplaza la anterior: la anterior deja de valer por su número de serie.
Cómo obtener una licencia (sin costo, por tiempo limitado)
Actualmente Warpgate se distribuye sin costo: Software House emite licencias individuales gratuitas, válidas por 90 días y renovables. Esta política puede cambiar más adelante, sin afectar las licencias ya emitidas.
- En el equipo donde se va a usar el compilador, generar la solicitud:
warpgate --action license-request --license-id <nombre o empresa> --license-out request.json - Enviar a warpgate@softwarehouse.pe:
- El archivo
request.jsongenerado. - El identificador del equipo que el comando imprime en pantalla (necesario para emitir la licencia).
- El archivo
- Software House responde con un archivo
license.jsonfirmado, ligado a ese equipo, válido por 90 días. - Copiar ese archivo a la ruta por defecto (o a la que indiquen
--license-file/File/WARPGATE_LICENSE_FILE):- Linux/macOS:
~/.config/warpgate/license.json - Windows:
%APPDATA%\warpgate\license.json
- Linux/macOS:
- Confirmar con
warpgate --action license-status(debe mostrar el estado y la fecha de vencimiento).
Al vencer los 90 días, generate/build/reverse dejan de funcionar hasta renovar — repetir este mismo procedimiento para obtener una licencia nueva. validate sigue funcionando siempre, incluso sin licencia. Para una licencia con seats para varios usuarios (modo organización), escribir también a warpgate@softwarehouse.pe.
Desde VSCode
Los mismos pasos se pueden hacer desde la paleta de comandos (Ctrl+Shift+P / F1), sin usar la consola — ver Comandos de VSCode:
WaRPGate: Request License— pide el nombre o empresa, guarda elrequest.jsony muestra el identificador del equipo (botones Copy Machine ID y Reveal File). Enviar ambos a warpgate@softwarehouse.pe.WaRPGate: Install License— selecciona ellicense.jsonrecibido y lo copia a la ruta por defecto (pide confirmación si ya existe una licencia, que dejaría de valer).WaRPGate: License Status— muestra el estado, el vencimiento y el producto.
Para consultar el estado actual:
warpgate --action license-status
Muestra el modo, la licencia, el vencimiento y, en modo organización, los seats en uso y disponibles.
Alcance de la licencia
Una licencia cubre un producto y una versión mayor (por ejemplo, la serie 0.x). Puede ser perpetua o tener vencimiento.
Desarrollo
Las compilaciones de desarrollo aceptan WARPGATE_LICENSE=off para omitir la exigencia. Los binarios de release no lo aceptan.
Diagnósticos relacionados
| Código | Situación |
|---|---|
E00284 | Port de @License es 0. |
E00285 | Host de @License no es un host válido. |
E00286 | Fingerprint no es una huella SHA-256 válida. |
E00287 | Aviso: hay Host sin Fingerprint. |
E00288 | Aviso: Host y File juntos (File se ignora). |
Configurar el servicio de licencias (modo organización)
Esta página es para quien administra la infraestructura de tu organización (equipo de sistemas, o quien administra el servidor IBM i) — no hace falta leerla para programar en WARP. Sirve para dejar funcionando, una sola vez, el servicio que reparte las licencias entre todo el equipo de desarrollo. Quienes solo van a usar Warpgate reciben del administrador los tres datos finales (Host, Port, Fingerprint) y los colocan en su @License — ver Licenciamiento.
1. Descargar
license-service(administra el servicio): Linux · Windowslicense-gui(consulta visual, opcional — ver sección 5): Linux · Windows- Sumas de verificación: SHA256SUMS.txt
En Linux, dar permiso de ejecución: chmod +x license-service license-gui.
2. Solicitar la licencia de organización
En el servidor donde va a correr el servicio:
license-service create-request --license-id "<Nombre de tu organización>" --out request.json
license-service gen-hwid
El segundo comando muestra varios datos del equipo; copiar el valor de primary_os_id (una cadena de 32 caracteres). Enviar a warpgate@softwarehouse.pe:
- El archivo
request.json. - El valor de
primary_os_id. - Cuántos puestos (seats) simultáneos necesita tu equipo.
Software House responde con un archivo license.json para ese servidor, válido por 90 días y renovable — mismo modelo sin costo que la licencia individual, ver Licenciamiento.
3. Instalar el servicio
Windows, con la ventana license-gui (recomendado)
-
Dejar
license-gui.exeylicense-service.exeen la misma carpeta (por ejemploC:\license). El servicio queda registrado apuntando a esa carpeta: no moverla después. -
Abrir
license-gui.execon clic derecho → Ejecutar como administrador. En la pantalla de conexión, pulsar Servidor local: instalar y mantener…. -
La ventana tiene estas pestañas:
-
1. Solicitar licencia: escribir el identificador de licencia y pulsar Crear solicitud. Genera
request.jsony unrequest-hwid.txtcon el identificador de la máquina (con botón Copiar). Enviar ambos archivos a Software House (ver sección 2). -
2. Instalar servidor: elegir el
license.jsonrecibido y completar:- Nombre del servidor: por defecto, el nombre completo del equipo.
- Puerto:
7443por defecto. - Red de usuarios (firewall):
LocalSubneto un rango (por ejemplo192.168.0.0/24). La regla de firewall solo se crea si el Firewall de Windows está activo en algún perfil. - Consultar con la GUI desde: redes autorizadas para consultar a distancia (ver sección 7).
Luego pulsar Instalar servicio y arrancar. En orden, la ventana registra el servicio, instala la licencia, genera el certificado, ajusta los permisos del directorio de datos (solo
SYSTEMy Administradores), crea la regla de firewall si procede, inicia el servicio y comprueba que escucha. Al final muestra la huella y el bloque@Licenselisto para copiar a los clientes.También hay Desinstalar servicio…: detiene y elimina el servicio y quita la regla de firewall. Una casilla opcional permite borrar además los datos (licencia, certificado, base de puestos y log); por defecto se conservan.
-
3. Certificado TLS: para usar el certificado de la organización (ver sección 6).
-
4. Log del servicio: se refresca solo cada 3 segundos; tiene Copiar y Limpiar log… (vacía el archivo sin borrarlo; el servicio sigue escribiendo).
-
El servicio de Windows se llama WarpgateLicense y tiene inicio automático. Sus datos están en C:\ProgramData\license-service\:
| Archivo | Contenido |
|---|---|
active_license.json | Licencia instalada. |
tls_cert.der, tls_key.der | Certificado TLS y su clave. |
seats.sqlite | Base de puestos (sesiones). |
auth.json | Opcional: consulta remota y límites (ver secciones 7 y 8). |
license-service.log | Log del servicio. |
Certificados LDAP: el cliente LDAP (
ldaps://) confía en las autoridades certificadoras del almacén del sistema. Si el controlador de dominio usa una CA interna, esa CA debe estar instalada en el almacén del equipo que ejecuta el servicio (en Windows, el almacén de certificados de Windows). Si no, la conexión LDAPS falla y el servicio deniega el acceso.
Windows, por consola (alternativa)
license-service install-license --license license.json
license-service gen-tls-cert --subject-alt-name <host-o-dominio-del-servidor>
license-service install-windows-service --bind 0.0.0.0:7443
Esto registra license-service como un servicio de Windows normal (inicio automático, se reinicia solo si falla) — se administra desde el Administrador de servicios como cualquier otro. El segundo comando muestra la huella SHA-256 en pantalla: ese valor es el Fingerprint que necesitan quienes se conecten.
Linux, como servicio con systemd (recomendado)
- Copiar el binario y crear un usuario propio para el servicio:
sudo install -m 0755 license-service /usr/local/bin/license-service sudo useradd --system --no-create-home --shell /usr/sbin/nologin warpgate-license sudo install -d -o warpgate-license -g warpgate-license -m 0700 /var/lib/license-service - Instalar la licencia y generar el certificado, como ese usuario:
Anotar la huella SHA-256 que imprime el segundo comando: ese valor es elsudo -u warpgate-license env LICENSE_SERVICE_DIR=/var/lib/license-service \ license-service install-license --license /ruta/license.json sudo -u warpgate-license env LICENSE_SERVICE_DIR=/var/lib/license-service \ license-service gen-tls-cert --subject-alt-name <host-o-dominio-del-servidor>Fingerprint. - Descargar la unidad de
systemd, copiarla y activar el servicio:sudo cp license-service.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now license-service
Con esto el servicio arranca solo al reiniciar el servidor y se reinicia solo si falla. Logs: journalctl -u license-service -f. Abrir el puerto TCP 7443 solo para la subred de los usuarios de Warpgate.
Linux, prueba rápida (sin systemd)
Para probar antes de instalar como servicio permanente:
license-service install-license --license license.json
license-service gen-tls-cert --subject-alt-name <host-o-dominio-del-servidor>
license-service run --bind 0.0.0.0:7443
Corre en primer plano; se detiene al cerrar la terminal. Útil para verificar que todo funciona antes del paso anterior.
4. Entregar los datos al equipo
Cada desarrollador coloca estos tres datos en el @License de su proyecto (ver Licenciamiento):
- Host: el servidor donde corre el servicio.
- Port:
7443(o el que se haya indicado en--bind). - Fingerprint: el que imprimió
gen-tls-cert.
Si en el .warpcfg hay Host, manda el servicio: no existe respaldo automático al archivo de licencia individual, así que si el servicio cae, generate/build/reverse fallan. Host sin Fingerprint es un error.
Si la organización usa Active Directory, el servicio puede exigir autenticación Kerberos — consultar a Software House para habilitarlo.
Modo sin dominio (sin auth.json)
Si no hay Kerberos configurado, la identidad de quien toma un puesto es el nombre que declara su equipo (no se verifica). Además:
- Las consultas (
license-gui,license-service queryy tambiénwarpgate --action license-statusen modo organización) solo se aceptan desde la propia máquina del servicio. Desde otra PC, el servicio rechaza la consulta:warpgatemuestra «Su usuario no está autorizado a consumir la licencia de warpgate. Solicite acceso al administrador.»,license-guimuestra «no estás autorizado para esta operación», y el servicio anota en su logconsulta rechazada desde <IP>. Para permitir consultas remotas, ver la sección 7. - Tomar un puesto (
generate,build,reverse) no tiene esa restricción: funciona desde cualquier PC que alcance el servicio. - Instalar o reemplazar licencias es siempre solo desde el servidor (o por administradores Kerberos).
5. Consultar el servicio con license-gui
license-gui es una ventana aparte, sin línea de comandos, para ver el estado de la licencia sin afectar los puestos en uso (no consume un seat): licencia activa y vencimiento, seats en uso y disponibles, y el historial de sesiones de cada usuario (incluyendo si una sesión se cerró normalmente o se perdió).
Al abrirla, pide los mismos tres datos que cualquier cliente — Host, Port, Fingerprint — más un SPN opcional si el servicio exige Kerberos. Quien administra el servicio también puede instalar una licencia nueva desde ahí mismo, sin reiniciar nada.
Sus pestañas son Usuarios, Licencias (instalar una licencia solo está permitido a administradores), Estadísticas y Log del servicio. Desde otra PC funciona solo si se habilitó la consulta remota (o con Kerberos).
6. Certificado TLS
Por defecto el servicio usa un certificado autofirmado, válido desde 1975 hasta 4096 (prácticamente no caduca). El cliente no revisa fechas, cadena de CA ni nombre: solo compara la huella SHA-256 del certificado con el Fingerprint configurado.
Si seguridad exige el certificado de la organización, se puede usar desde la pestaña 3. Certificado TLS de la ventana, eligiendo el tipo:
| Tipo | Archivos | Notas |
|---|---|---|
| PFX/P12 | Un archivo + contraseña | Lo que exporta Windows; acepta PFX modernos y antiguos. |
| PEM | Certificado (.pem/.crt/.cer) + clave (.key/.pem) | La clave puede ir en el mismo archivo. Acepta claves PKCS#8, RSA clásica y EC clásica. |
| DER | Certificado y clave binarios | — |
La ventana convierte al formato interno y guarda (el anterior queda como tls_cert.der.previous y tls_key.der.previous), reinicia el servicio si se marca la opción, y muestra la huella (con botón Copiar) y el bloque @License. Si el archivo trae la cadena completa, se usa el certificado propio del servidor (el que corresponde a la clave).
Errores explicados por la ventana: la clave no corresponde al certificado, contraseña de PFX incorrecta, tipo equivocado, y clave PEM cifrada con contraseña (no soportada: quitar la contraseña con openssl pkcs8 -in clave.pem -out clave-sin-clave.pem -nocrypt, o entregar un PFX).
Importante: cambiar el certificado cambia la huella, y hay que actualizarla en todos los clientes (
@Licensey la pantalla de conexión delicense-gui). La renovación de un certificado de una CA corporativa la vuelve a cambiar.
Equivalente por consola (luego reiniciar el servicio):
license-service import-tls-cert --format pem|pfx|der --cert <archivo> [--key <archivo>] [--password-env VARIABLE]
7. Consulta remota de solo lectura
Sin Kerberos, para que license-gui y license-status puedan consultar desde otras PC se crea el archivo auth.json en el directorio de datos (C:\ProgramData\license-service\auth.json; /var/lib/license-service/ con systemd):
{ "query_allowed_networks": ["192.168.0.10", "10.1.2.0/24"] }
- Acepta IPs o rangos CIDR, IPv4 e IPv6.
- Solo aplica sin Kerberos y requiere reiniciar el servicio.
- Crear el archivo sin BOM (JSON estricto).
- Desde esas redes se ven usuarios, historial, estadísticas y log, pero no se pueden instalar licencias y no aparece el botón Servidor local….
- Se autorizan direcciones, no personas: mantener la lista corta.
Atajo: el campo Consultar con la GUI desde de la pestaña 2. Instalar servidor escribe este auth.json (no sobrescribe uno existente).
8. Límites de conexión
El servicio corta la conexión nueva antes del TLS y lo anota en el log con la IP (1 de cada 50 rechazos). No afecta a quien ya tiene un puesto.
- Total: puestos × 2 + 20 (se calcula con la licencia).
- Sin sesión todavía (handshake o autenticando): máximo 50.
- Por IP: 5 por defecto; configurable con
"max_connections_per_ip": Nenauth.json(0= sin límite). Las conexiones desde la propia máquina del servicio quedan exentas de este tope.
Subir el valor por IP si muchos usuarios comparten IP (servidor de compilación, escritorio remoto). Requiere reiniciar el servicio.
9. Estadísticas y exportación
license-gui consulta y administra sin consumir un puesto. La pestaña Estadísticas permite elegir el período (7, 30, 90 días o 1 año) y muestra:
- Sesiones y usuarios distintos.
- Pico de puestos simultáneos y cuándo ocurrió.
- Porcentaje del tiempo de uso con todos los puestos ocupados, y tiempo de uso.
- Rechazos por falta de puestos (se registran solo desde esta versión; no hay datos anteriores).
- Gráficos del pico por día y por hora del día (hora local), y detalle por día.
- Una lectura orientativa: «conviene ampliar» si hubo rechazos; «al límite» si el pool estuvo lleno el 5 % o más del tiempo de uso; «sobra capacidad» si el pico fue la mitad o menos de lo contratado. Avisa si hay menos de 7 días con uso.
Sirve para decidir si conviene comprar más puestos.
El menú Exportar ▾ ofrece: resumen de usuarios (CSV), historial de sesiones del período (CSV), log del servicio (texto) y estadísticas del período (CSV). Los CSV usan ; y BOM UTF-8 (abren bien en Excel en español) y neutralizan los valores que empiezan con =, +, - o @ (inyección de fórmulas). El historial y el log requieren poder ver a todos los usuarios.
10. Problemas frecuentes
| Síntoma | Causa y solución |
|---|---|
| «no está autorizado» / «no estás autorizado» desde otra PC | Las consultas solo se aceptan desde el servidor (sección «Modo sin dominio»). Habilitar la sección 7 o usar Kerberos; revisar el log. |
| Error de huella | La huella está mal copiada o el certificado cambió (sección 6). |
TcpTestSucceeded : False | Firewall, red, o el servicio no está escuchando. |
| El servicio no inicia | Revisar license-service.log. Causa típica: archivos de C:\ProgramData\license-service sin permisos para SYSTEM (al instalar con la ventana ya se ajustan). |
Estructura de un archivo .warp
Un .warp es una secuencia de secciones @Nombre { ... }. Qué secciones aplican depende de @Properties/Type:
| Sección | Program | Screen | Table |
|---|---|---|---|
@Documentation | nivel superior | nivel superior | anidada en @Structure |
@Properties | sí | sí | sí (sólo Type) |
@Structure | no | no | sí |
@Fields | no | no | anidada en @Structure |
@Indexes | no | no | anidada en @Structure |
@References | sí | sí | no |
@Variables | sí | sí | no |
@Parameters | sí | no | no |
@Layout | sí (opcional) | sí | no |
@Source | sí | sí | no |
Un .warproj/.warpcfg no describe un objeto — usan sus propias secciones de nivel superior (@Project, @GeneratorConfig) — ver Archivo de proyecto.
Sintaxis general
- Comentarios:
//hasta fin de línea. - Strings: comillas dobles o simples indistintamente (
"texto"o'texto'son lo mismo semánticamente). - Variables: siempre con
&(&Usuario); nombres de campo de tabla van sin&dentro de unFor Each/New. - Cada sección de nivel superior lleva
@; los nombres de control dentro de@Layoutpueden llevar@opcionalmente (@Label/Labelson equivalentes).
Índice de la referencia
- @Documentation
- @Properties
- @Structure, @Fields, @Indexes
- @References
- @Variables
- Tipos de dato
- @Parameters
- @Layout
- @Source: sentencias de control
- @Source: Event y Screen
- Funciones builtin
- Expresiones y operadores
- Archivo de proyecto
@Documentation
Metadatos informativos, no afectan la generación de código.
En Program/Screen (nivel superior)
Pares planos Clave : valor:
| Clave | Descripción |
|---|---|
Author Name | Nombre de quien escribió el programa. |
Author User | User ID de quien lo escribió. |
App Id | Identificador corto de la aplicación. |
App Name | Nombre visible de la aplicación. |
App Description | Descripción larga de la aplicación. |
Module Id | Identificador corto del módulo. |
Module Name | Nombre visible del módulo. |
Module Description | Descripción larga del módulo. |
Created At | Fecha/timestamp libre (p.ej. "12/06/2025"), no se parsea ni valida. |
@Documentation {
Author Name : Nombre del Programador
Author User : Codigo de Usuario
App Id : ID de la Aplicación
App Name : Nombre de la Aplicación
Module Id : ID del Módulo (si aplica)
Created At : "12/06/2025"
}
En Table (anidada dentro de @Structure)
Aquí @Documentation no tiene claves propias, sólo sub-secciones:
@Author:Name,User,Company.@Application:Id,Name,Description.@Module:Id,Name,Description.@Repository:Official,Type(p.ej.git),Branch,Version.@History: contenedor de@Creationy cualquier cantidad de@Change.@Creation:Date,Author,User,Company,Reason.@Change(repetir el bloque por cada entrada):Date,Type(por convenciónbreaking/feature/fix/security/performance/refactor/config/docs, no forzado por el compilador),Impact(por convenciónhigh/medium/low),Version,Reason,Author,User,Company.
@Structure {
Name : "GRIDDEMO"
@Documentation {
@Author {
Name : Programador
}
@History {
@Creation {
Date : "2025-06-12"
Author : Programador
Reason : "Alta inicial"
}
@Change {
Date : "2026-01-10"
Type : feature
Impact : medium
Reason : "Agrega columna Ciudad"
}
}
}
@Fields { ... }
}
@Properties
Program
| Clave | Descripción |
|---|---|
Type | El tipo de objeto que compila este archivo (Program). |
Name | Nombre del programa. |
Description | Descripción del programa. Máximo 50 caracteres: termina en el parámetro TEXT de los mandatos CL de compilación (CRTBNDRPG/CRTDSPF/CHGPFM), que tiene ese límite real en IBM i (ver E00281). |
Print Mode | Modo de impresión, para un programa tipo reporte. |
Commitment | Si este programa (Program o Screen) corre sus tablas modificadas bajo control de compromiso. Sobreescribe el Commitment de proyecto (@GeneratorConfig) sólo para este objeto. Valores: True/False. Requerido por las sentencias Commit/Rollback. |
Commit on Exit | Si este objeto (Program o Screen) emite un commit final antes de terminar; en un Screen, al cerrar la pantalla (F3). Sobreescribe el de proyecto sólo para este programa; sólo efectivo si Commitment está activo. Valores: True/False. Las sentencias Commit/Rollback requieren Commitment activo. |
DDS | Nombre del DDS al que está atado este programa. |
Path | Carpeta de salida de este programa, relativa al proyecto. |
@Properties {
Type : Program
Name : PTEST01
Description : "Programa de prueba"
Commitment : True
Commit on Exit : True
Path : "./programs/"
}
Screen
Mismas claves que Program (Type con valor Screen), más 7 propias:
| Clave | Descripción |
|---|---|
Popup | Genera el DSPF de este Screen con el keyword DDS WINDOW (ventana emergente) en vez de pantalla completa. Exige las 4 claves de abajo. Valores: True/False. |
Window Row | Fila (1-based) de la esquina superior izquierda de la ventana. Requerida si Popup: True. |
Window Column | Columna (1-based) de la esquina superior izquierda de la ventana. Requerida si Popup: True. |
Window Rows | Alto de la ventana, en filas. Requerida si Popup: True. |
Window Columns | Ancho de la ventana, en columnas. Requerida si Popup: True. |
Function Keys Row | Fila donde se dibuja la fila de teclas de función de este Screen (también FunctionKeysRow). Manda sobre el Function Keys Row de @GeneratorConfig/@Screen; sin ninguno se usa Rows - 1. Rango válido: de 1 a Rows - 1; fuera de rango da E00346. Debe dejarse libre esa fila en @Layout: no hay aviso de invasión. |
Function Keys Color | Color de toda la fila de teclas de este Screen (también FunctionKeysColor): Green, White, Red, Blue, Yellow, Pink o Turquoise. Manda sobre el del proyecto; un nombre inválido da E00347. |
@Properties {
Type : Screen
Name : SCONFIRM
Popup : True
Window Row : 5
Window Column : 10
Window Rows : 10
Window Columns : 40
}
Ejemplo con la fila de teclas movida y coloreada:
@Properties {
Type : Screen
Name : SCLIENTES
Function Keys Row : 22
Function Keys Color: Blue
}
Un Screen con Popup: True se genera, compila y despliega exactamente igual que cualquier otro Screen — sólo cambia el DSPF (WINDOW en vez de pantalla completa). Para mostrarlo desde otro Screen, referencialo en @References/Screens e invocalo con Call(Nombre, ...) — ver @Source: sentencias de control.
Table
Sólo Type:
@Properties {
Type : "Table"
}
El resto de la descripción de una tabla va en @Structure — ver @Structure, @Fields, @Indexes.
@Structure, @Fields, @Indexes
Sólo aplican a archivos tabla (@Properties/Type : "Table").
@Structure
| Clave | Descripción |
|---|---|
Name | Nombre de la tabla. Debe empezar con letra, sólo letras/dígitos, máximo 10 caracteres (límite de nombre de objeto DDS). |
Record Format (o RecordFormat) | Opcional. Nombre del formato de registro (línea R del DDS) del PF y de cada LF; el RPG generado lo usa en rename(<formato> : row). Sin esta propiedad, el formato se llama igual que la tabla (Name). El nombre del archivo físico/lógico y el PFILE() de los LF siguen siendo el de la tabla. Útil para tablas ya existentes en el IBM i cuyo formato difiere del nombre del archivo. Hasta 10 caracteres, solo letras y dígitos, y debe empezar por letra (E00349 si no). |
Description | Descripción de la tabla. |
@Structure {
Name : PEDIDOS
Record Format : PEDREG
Description : "Pedidos"
...
}
Contiene además, anidadas: @Fields, @Indexes y opcionalmente @Documentation (ver @Documentation).
@Fields
Una línea Nombre tipo(...) por columna:
@Fields {
CodCli char(6) Description("Código de Cliente")
Nombre char(20) Description("Nombre")
Saldo number(9,2) Default(0)
FecAlta date
}
Tipos permitidos: sólo char, number, date (no field/list/matrix — esos son exclusivos de @Variables, ver Tipos de dato).
Modificadores:
| Modificador | Descripción |
|---|---|
Description("texto") | Descripción del campo. Requiere string entre comillas. |
Default(valor) | Valor por defecto: string entre comillas o número suelto, según el tipo del campo. No permitido en campos date. |
AllowNull | Permite NULL en este campo. Un campo PrimaryKey no puede llevar este modificador. |
@Indexes
Una entrada Kind(Nombre, [Campos...], "Descripción opcional") por acceso:
@Indexes {
PrimaryKey ( GRIDDEMOPK, [CodCli] )
Unique ( GRIDDEMOEMAIL, [Email], "Email único" )
Index ( GRIDDMNOM, [Nombre] )
}
| Kind | Descripción |
|---|---|
PrimaryKey | Acceso primario de la tabla. Sólo uno por tabla; sus campos no pueden ser AllowNull. |
Unique | Acceso secundario único. |
Index | Acceso secundario no único. |
Ejemplo completo
@Properties {
Type : "Table"
}
@Structure {
Name : "GRIDDEMO"
Description : "Clientes de ejemplo"
@Fields {
CodCli char(6) Description("Código de Cliente")
Nombre char(20) Description("Nombre")
Ciudad char(15) Description("Ciudad")
}
@Indexes {
PrimaryKey ( GRIDDEMOPK, [CodCli] )
Index ( GRIDDMNOM, [Nombre] )
}
}
@References
Lista otros archivos .warp de los que depende este Program/Screen. A diferencia de las demás secciones, usa Clave = [ "..." ] (lista entre corchetes), no Clave : valor.
| Clave | Descripción |
|---|---|
Programs | Otros Program .warp que este referencia (para Call(Nombre, ...) resuelto en tiempo de compilación). |
Screens | Screen .warp que este referencia — igual que Programs, habilita invocarlo con Call(Nombre, ...) (típicamente un Screen con @Properties/Popup: True, mostrado como ventana emergente). |
Tables | Tablas .warp que este referencia (habilita For Each In/New In/field(...) sobre ellas). |
@References {
Programs = [
"../programs/POTRO.warp"
],
Screens = [
],
Tables = [
"../tables/TLADD50.warp",
"../tables/TLGRIDDEMO.warp"
]
}
Las rutas son relativas al archivo que las declara. No aplica a archivos tabla (una tabla no referencia otros objetos).
@Variables
Declara variables dentro de un ámbito nombrado: global (visibles en todo el archivo) o el nombre de un Procedure/Function declarado en @Source (visibles sólo dentro de ese procedimiento/función, además de las global).
@Variables {
global {
Usuario char(10)
Fecha date
Saldo number(9,2)
}
Procedimiento {
contador number(3)
}
Saluda {
nombre char(10)
}
}
Una línea Nombre tipo(...) por variable — mismo formato que @Fields, pero con más tipos disponibles (ver Tipos de dato): char, varchar, number, date, field(Campo), list(tipo), matrix(filas, cols, tipo), struct(NombreTemplate).
Ámbito local
local es un ámbito reservado, junto a global y a los nombres de Procedure/Function. Sus variables sólo son visibles en el cuerpo principal de @Source (fuera de cualquier Procedure/Function) y en @Layout. Usarlas dentro de un Procedure/Function produce E00146.
@Variables {
global {
Usuario char(10)
}
local {
contador number(3)
mensaje char(40)
}
}
- Sólo tiene sentido en programas tipo
Program; enScreenno está soportado. - No puede repetir un nombre ya declarado en
global(E00298). - No admite templates de
struct(E00234, como cualquier ámbito distinto deglobal). - En el RPG generado se declara a nivel de módulo, igual que
global; la restricción de visibilidad la aplica sólo el compilador. - Un parámetro de
@Parameterspuede declararse englobalo enlocal. Enlocalsolo lo ve el cuerpo principal, no losProcedure/Function.
Struct: definición de template inline
Dentro del ámbito global (sólo ahí) también se puede definir un template de struct con struct Nombre [ ... ], antes o después de instanciarlo con struct(Nombre) — ver Tipos de dato: Struct para la sintaxis completa y sus límites (incluye structs anidados y list(struct(...)) como miembro).
Modificador
Sólo Description("texto") está disponible en @Variables (a diferencia de @Fields, no admite Default/AllowNull):
@Variables {
global {
NroSolicitud field(ADHOJA50) Description("Número de Solicitud")
}
}
Constantes
Dentro de cualquier ámbito (global, local o el de un Procedure/Function), en Program y en Screen, se puede declarar una constante con const Nombre tipo = valor:
@Variables {
global {
const MaxItems number(3,0) = 100
const Precio number(9,2) = -5.5
const Titulo char(30) = "Reporte de pases"
const Saludo varchar(30) = "Hola" Description("Saludo inicial")
}
}
- Sólo se admiten
char,varcharynumber; cualquier otro tipo produceE00310. - El valor debe ser un literal del mismo tipo: texto entre comillas, o un número (entero o decimal) con signo
-opcional. Debe caber en el tipo (el texto no puede ser más largo que la longitud; el número no puede tener más decimales que la escala ni más dígitos enteros que los que admite la precisión) o se produceE00311. - Se puede leer en cualquier expresión, pero nunca modificar: asignarla, usarla como contador de un
For, pasarla como argumentoout/inoutde unCallo como destino de unInputproduceE00312. Pasarla a unaProcedure/Functionque modifica ese parámetro produceE00179. - Un parámetro de
@Parametersno puede ser una constante (E00313). - En un
Blockimprimible de unProgramse puede mostrar, por ejemplo con@Label(1,1,&Titulo). En unaScreenno puede mostrarse en un campo de pantalla (Label/Input/Column):E00314; cópiela a una variable.
En el RPG generado se declara como constante nombrada (dcl-c NOMBRE valor;):
- Un texto
char(n)se rellena con blancos hastan, de modo que se comporta como una variablechar(n); unvarcharno se rellena. - Un número se escribe con exactamente los decimales de su escala (
const Precio number(9,2) = -5.5generadcl-c Precio -5.50;). - Una constante numérica de RPG no tiene precisión propia (la fija el contexto en que se usa), pero WARP la trata como el tipo declarado en los chequeos de tipo.
Límites de longitud/precisión (variable standalone, distinto de un campo de tabla)
| Tipo | Límite en @Fields | Límite en @Variables |
|---|---|---|
char | 1–32.766 | 1–16.773.104 |
varchar | no válido en @Fields | 1–16.773.100 |
number (precisión) | 1–30 | 1–63 |
Estos límites mayores en @Variables reflejan que una variable declarada ahí no está atada a un archivo físico DDS como sí lo está un campo de @Fields.
Tipos de dato
| Tipo | Sintaxis | Dónde | Descripción |
|---|---|---|---|
char | char(longitud) | @Fields, @Variables | Cadena de longitud fija. longitud requerida (1-32.766 en @Fields; 1-16.773.104 en @Variables). |
varchar | varchar(longitud) | sólo @Variables | Cadena de longitud variable. longitud requerida (1-16.773.100). Length(...) devuelve el contenido real en tiempo de ejecución, a diferencia de char. |
number | number(precisión, escala) | @Fields, @Variables | Numérico. precisión requerida (1-30 en @Fields; 1-63 en @Variables); escala opcional, default 0. |
date | date | @Fields, @Variables | Fecha, sin parámetros. En @Fields no puede llevar Default(...). |
time | time | @Fields, @Variables | Hora (hora/minuto/segundo), sin parámetros. En @Fields no puede llevar Default(...). |
timestamp | timestamp | @Fields, @Variables | Fecha y hora combinadas (con microsegundos), sin parámetros. En @Fields no puede llevar Default(...). |
field | field(NombreCampo) o field(Tabla.Campo) | sólo @Variables | Hereda el tipo de un campo de @Structure de alguna tabla referenciada. Ver field: heredar el tipo de un campo. |
list | list(tipo) | sólo @Variables | Lista dinámica de tipo (escalar, struct(Template) o field(...); no admite otro list ni matrix anidado, ver E00283). Sin tamaño en la declaración — la capacidad interna máxima es fija (9999 elementos) y no configurable. Se lee/escribe un elemento con &Variable[índice]; se agrega/quita/consulta con métodos (ver List: métodos). |
matrix | matrix(filas, cols, tipo) | sólo @Variables | Grilla 2D de tamaño fijo filas x cols de tipo. Se lee/escribe un elemento con &Variable[fila, col]. |
struct | struct(NombreTemplate) | sólo @Variables | Instancia de un template struct NombreTemplate [ ... ] definido inline en el ámbito global (ver Struct: templates y acceso a miembro). Se accede a un miembro con &Variable.Miembro. |
Las constantes (const Nombre tipo = valor) sólo admiten char, varchar y number; ver @Variables: Constantes.
Ejemplos
@Variables {
global {
Usuario char(10)
Destino varchar(200)
Saldo number(9,2)
FechaAlta date
HoraAlta time
FechaHoraAlta timestamp
Estado field(ADSTS50)
Lista1 list(number(5))
Tablero matrix(8, 8, char(1))
}
}
field: heredar el tipo de un campo
field(...) toma el tipo del campo indicado, buscándolo en las tablas declaradas en @References/Tables. Tiene dos formas:
field(Campo): busca el campo en todas las tablas referenciadas. Si existe en más de una, se reportaE00149(ambiguo): “El campo ‘{field}’ de la variable field(…) es ambiguo: existe en más de una tabla de @References/Tables; indique la tabla con field(Tabla.Campo).”field(Tabla.Campo): busca el campo sólo en esa tabla, para desambiguar. No distingue mayúsculas en la tabla ni en el campo. Si la tabla no está referenciada o el campo no existe en ella, se reportaE00148(campo no encontrado).
El tipo resultante es el del campo en ambas formas. Ambas valen en cualquier ámbito de @Variables y también como miembro de un struct.
// Las tablas Clientes y Proveedores (en @References/Tables) tienen un campo Codigo
@Variables {
global {
struct Contacto [
Codigo field(Clientes.Codigo)
]
CodCliente field(Clientes.Codigo)
CodProveedor field(Proveedores.Codigo)
Nombre field(Clientes.Nombre) // también válido si el campo no es ambiguo
}
}
Con field(Codigo) a secas, este ejemplo reportaría E00149.
Interoperabilidad char/varchar
char y varchar interoperan en asignaciones — la regla es sólo de longitud (el origen debe entrar en el destino), sin importar cuál de los dos es fijo o variable:
&VarcharDestino = &CharOrigen // válido si longitud(CharOrigen) <= longitud(VarcharDestino)
&CharDestino = &VarcharOrigen
List/Matrix: indexación
&Lista1[1] = 100
&Tablero[3, 5] = "X"
El índice es 1-based. List/Matrix sólo son válidos en @Variables, nunca en @Fields.
List: métodos
list no lleva tamaño en la declaración (list(tipo), sin parámetro de capacidad): la capacidad interna máxima es fija en 9999 elementos, definida por el compilador y no configurable desde el lenguaje. En vez de fijar el tamaño de antemano, se administra el contenido con métodos, con sintaxis de miembro (&Variable.Metodo(...)):
| Método | Uso | Descripción |
|---|---|---|
Add(valor) | sentencia | Agrega valor al final de la lista. valor debe ser compatible en tipo con el elemento de la lista (si el elemento es struct(Template), valor debe ser una &Variable de ese mismo template exacto). |
Clear() | sentencia | Vacía la lista: limpia los elementos en uso (blancos o ceros) y deja la cuenta en 0. |
Init() | sentencia | Deja todos los elementos de la lista (los 9999) en su valor vacío (blancos en char/varchar, ceros en number, valor inicial en date/time/timestamp, struct limpios) y la cuenta (Count()) en 0. No recibe argumentos (E00282) ni se usa como expresión (E00280). |
Remove(indice) | sentencia | Quita el elemento en la posición indice (1-based, tipo number), recorre los siguientes una posición hacia atrás y limpia el último espacio que queda libre. Un indice fuera de 1..Count() no hace nada. |
Split(texto[, separadores[, AllSep]]) | sentencia | Reemplaza el contenido de la lista (limpia lo anterior) con los trozos de texto. Ver Split(). |
Count() | expresión | Devuelve la cantidad actual de elementos (tipo number). Sólo se puede usar dentro de una expresión, nunca como sentencia suelta. |
Json() | expresión | Devuelve la lista como arreglo JSON (sólo list(struct(...))). Ver Json(). |
&Lista1.Add(10)
&Lista1.Add(20)
&Lista1.Add(30)
For &i = 1 To &Lista1.Count()
// ... &Lista1[&i] ...
EndFor
&Lista1.Remove(2)
&Lista1.Clear()
&Lista1.Init()
No existe un For Each sobre una lista en memoria — el recorrido se hace con el For clásico por contador (For &i = 1 To &Lista.Count()) más el indexado &Variable[&i] (For Each In sigue siendo exclusivo de tablas de base de datos, ver @Source: sentencias de control).
Borrado real. Clear(), Remove(indice) y JsonSplit (al reemplazar el contenido anterior) limpian de verdad las posiciones que dejan de usarse, no sólo ajustan el contador. Así se mantiene siempre esta invariante: todo elemento posterior al último está vacío (blancos o ceros). Un programa RPG que reciba el arreglo (ver @Parameters) puede leerlo hasta la primera línea vacía. Con 9999 elementos no queda ninguna posición vacía después del último.
Init() frente a Clear(). En RPG, Init() es un único clear del arreglo completo (más contador = 0), sin bucle. Clear() sólo limpia hasta el contador: es más rápido cuando se usa con pocas filas, pero deja intactos los elementos escritos por índice (&Lista[10] = x) sin pasar por Add(), porque esas escrituras no mueven el contador. Init() limpia toda la capacidad (con char(250) son unos 2,5 MB), por lo que conviene usarlo al inicio o cuando haga falta, no dentro de un bucle muy repetido.
El caso típico de Init() es usar una list como arreglo de tamaño fijo (&Trama[1], &Trama[10]) que luego se pasa a otro programa con Call:
&Trama.Init()
&Trama[1] = "Cabecera"
&Trama[10] = "Pie"
// Clear() no limpiaría &Trama[10]: se escribió por índice y Count() sigue en 0
&Trama.Init()
Ver el ejemplo completo en example/programs/PINIT01.warp.
Nota:
Clear(),Remove()yJsonSplitdejan vacío lo que queda después del último elemento.
Si Add(valor) se llama cuando la lista ya está en su capacidad máxima (9999 elementos), el programa emite un dsply visible con el mensaje Lista {nombre} alcanzó su capacidad máxima (9999) y no agrega el elemento — no aborta el programa completo.
list(struct(Template)) es válido (una lista de instancias de un mismo struct). Anidar list/matrix entre sí no lo es, en ninguna combinación: list(list(...)), list(matrix(...)), matrix(list(...)) y matrix(matrix(...)) reportan el error de compilación E00283 (“‘&{variable}’ es un {list/matrix} cuyo elemento no puede ser {list/matrix} (sin equivalente RPG generable): use un tipo escalar, struct(…) o field(…).”), detectado en el análisis semántico — no es posible declarar un arreglo cuyo elemento sea a su vez otro arreglo.
Split(): dividir un texto en una lista
&Lista.Split(texto[, separadores[, AllSep]]) es una sentencia (como Add/Clear; usada como expresión da E00280) y solo vale sobre list(char(n)) o list(varchar(n)) (E00363 si no).
- Reemplaza el contenido de la lista: limpia lo anterior y deja los trozos de
texto, hasta 9999. Al llenarse muestra «Lista X llena (max 9999)» condsplyy descarta el resto. Un trozo más largo que el elemento se recorta. - Sin
separadores, corta por blancos. - Cada carácter de
separadoreses un separador:"-;"separa por guion y por punto y coma. - Sin
AllSep, los separadores consecutivos, iniciales y finales se ignoran:"uno,dos,,tres"con","da 3 trozos. - Con
AllSep(tercer argumento, la palabraAllSep, que exige indicarseparadores), cada separador delimita dos trozos y aparecen los vacíos: el mismo texto da 4 trozos:uno,dos, `` ytres. - Un texto vacío da 0 trozos.
- Los blancos finales de un
charde origen se ignoran. Una variablecharde separadores se recorta: sus blancos finales no son separador (para un blanco como separador use un literal). textoyseparadoresdeben serchar/varchar(E00359); se admiten de 1 a 3 argumentos (E00358); el tercero debe ser la palabraAllSep(E00361).
&Partes.Split(&Texto, ",")
For &i = 1 To &Partes.Count()
// ... &Partes[&i] ...
EndFor
&Partes.Split("uno,dos,,tres", ",", AllSep) // 4 trozos, uno vacío
Requisito de release.
Splitgenerafor-each ... in %split(...). En IBM i 7.5 viene de base. En 7.3 y 7.4 hacen falta dos PTF de abril de 2021: el del compilador (5770WDS) y el del runtime (5770SS1), este último en cada sistema donde corra el programa.
Release PTF de compilador PTF de runtime 7.3 SI76100 SI76098 7.4 SI76101 (para TGTRLS V7R3M0: SI76109) SI76099 Sin el PTF de runtime el programa compila, pero falla al ejecutar con
MCH4437(export no encontrado). Es el mismo requisito que Upper y Lower.
AllSep(*ALLSEP) exige además 7.4 TR8 / 7.5 TR2 o sus PTF: 7.4, compilador SI83497 y runtime SI83471; 7.5, compilador SI83480 (para TGTRLS V7R4M0: SI83496) y runtime SI83468. Estos números provienen de un resumen de búsqueda: verifíquelos con IBM.
Json(): convertir un struct o una lista a JSON
&Variable.Json() devuelve el contenido de la variable como texto JSON (varchar). Se puede aplicar a un struct(...) (produce un objeto) o a una list(struct(...)) (produce un arreglo), incluidos los anidados por camino (&env.Json(), &env.Cab.Detalle.Json()). Se usa sólo como expresión, nunca como sentencia suelta (E00294), y no recibe argumentos (E00282):
&jsonObjeto = &unRegistro.Json()
&jsonArreglo = &listaRegistros.Json()
Para el template Registro [ codigo number(3,0), nombre char(20), monto number(7,2), fecha date ], los resultados tienen esta forma:
{"codigo":1,"nombre":"Ana","monto":1250.75,"fecha":"2026-10-16"}
[{"codigo":1,"nombre":"Ana",...},{"codigo":2,"nombre":"Luis",...}]
- Claves: el nombre del miembro tal como se declaró en el template, en el orden de declaración.
- Lista: el arreglo contiene sólo los elementos en uso (
Count()). char/varchar: cadena JSON entre comillas, con"y\escapados y los espacios finales recortados. Los caracteres de control no reciben tratamiento especial.number: número JSON, sin comillas.date: cadena"yyyy-mm-dd".- Anidados: un miembro
struct(...)genera un objeto y un miembrolist(struct(...))un arreglo, p.ej.{"Cab":{"epm":"a","Detalle":[{"Pgm":"P1","Libreria":"L1"}]},"Total":2}. - Los miembros escalares de un
structsólo pueden serchar/varchar/number/date/field; el generador también sabe emitirtime("hh:mm:ss") ytimestamp("yyyy-mm-ddThh:mm:ss.ffffff") por si el tipo de miembro se amplía.
Tamaño de la variable destino. Para un struct, la expresión tiene el tamaño del peor caso: por cada texto, 2 × largo + 2; por cada número, precisión + 2; por cada fecha, 12; más los nombres de clave, comillas, llaves y comas. Con structs anidados el peor caso crece con cada lista anidada (se calcula sobre sus 100 elementos). La variable que recibe el resultado debe poder contenerlo; si no, se reporta el error habitual de asignación incompatible (E00147). Declare el destino con holgura (p.ej. varchar(300) para un template pequeño). Para una list no se exige tamaño de destino, pero si el arreglo generado excede el máximo de un varchar de RPG (16 773 100 caracteres) el programa falla en ejecución, no trunca.
Errores: E00294 (usado como sentencia), E00295 (la variable no es struct ni list), E00296 (otro método sobre un struct; el único válido es Json), E00297 (list de tipos escalares: por ahora no soportado). Ver Diagnósticos.
No requiere librerías ni PTF en IBM i: cuando se usa .Json(), el compilador genera en el RPG los procedimientos de apoyo necesarios (Json_<Template> y JsonList_<Template>). Un ejemplo completo está en example/JSON01.warp.
Para partir el texto resultante en líneas (p.ej. para imprimirlo) ver JsonSplit.
Tramas de ancho fijo
Una trama de ancho fijo es una línea de texto donde cada miembro de un struct ocupa una cantidad fija de posiciones, en el orden de declaración, sin separadores. El ancho total es la suma de los anchos de los miembros.
| Forma | Uso | Descripción |
|---|---|---|
®.FixedWidth() | expresión | Devuelve la trama del struct: un char del ancho total. Usarlo como sentencia da E00306. |
®.FromFixedWidth(&texto) | sentencia | Llena el struct desde una trama (operación inversa). &texto es char/varchar o un literal. Usarlo como expresión da E00306. |
FixedWidthSplit(®istros, &lineas) | sentencia | Convierte una list(struct(...)) en una línea de trama por registro, dentro de una list(char)/list(varchar). Reemplaza el contenido de &lineas. |
FixedWidthParse(&lineas, ®istros) | sentencia | Operación inversa: lee cada línea de &lineas como un registro de ®istros. Reemplaza el contenido de ®istros. |
Formato por miembro:
| Tipo | Posiciones | Formato |
|---|---|---|
char(n) / varchar(n) | n | Alineado a la izquierda, relleno con blancos (se recorta si sobra). |
number(p,s) | p | Alineado a la derecha, con ceros a la izquierda y decimal implícito (sin punto). Un negativo lleva - en la primera posición, por lo que hay que dejar holgura de un dígito en p. |
date | 8 | YYYYMMDD. |
time | 6 | HHMMSS. |
timestamp | 20 | YYYYMMDDHHMMSSnnnnnn (con microsegundos). |
Al leer (FromFixedWidth, FixedWidthParse): una línea más corta que la trama se lee como si estuviera rellena con blancos; un número inválido da 0; una fecha inválida da la fecha mínima (0001-01-01).
FixedWidth() y FromFixedWidth() trabajan con UN struct; para una list use FixedWidthSplit / FixedWidthParse. Llamar a FixedWidth() sobre una list da E00305 con la sugerencia de usar FixedWidthSplit(&lista, &lineas).
FixedWidthSplit y FixedWidthParse aceptan también un camino a una list que es miembro de un struct, por ejemplo FixedWidthSplit(&pase.Items, &lineas) y FixedWidthParse(&lineas, &pase.Items). Una list miembro admite hasta 100 registros: FixedWidthParse se detiene al llenarse y emite el aviso FixedWidthParse: lista llena (max 100).
Sólo se admiten structs cuyos miembros sean char, varchar, number, date (o un field que resuelva a alguno de ellos). Un struct o list anidado, o una trama de más de 32766 posiciones, da E00305. Los elementos de la lista de líneas deben poder contener la trama completa (E00309).
Ejemplo con struct Cliente [codigo char(5) nombre varchar(12) saldo number(9,2) alta date]: la trama mide 34 posiciones (codigo 1-5, nombre 6-17, saldo 18-26, alta 27-34).
@Variables {
global {
struct Cliente [
codigo char(5)
nombre varchar(12)
saldo number(9,2)
alta date
]
c struct(Cliente)
otro struct(Cliente)
clientes list(struct(Cliente))
copia list(struct(Cliente))
lineas list(char(40))
trama char(40)
}
}
@Source {
&c.codigo = "A001"
&c.nombre = "Ana Perez"
&c.saldo = -1250.5
&c.alta = Today()
&trama = &c.FixedWidth()
&otro.FromFixedWidth(&trama)
&clientes.Add(&c)
&clientes.Add(&otro)
FixedWidthSplit(&clientes, &lineas)
FixedWidthParse(&lineas, &copia)
}
Un ejemplo completo está en example/FIXEDWIDTH01.warp. Errores: E00305 a E00309 (ver Diagnósticos).
Nota: los
numberse leen con decimal implícito y signo-en la primera posición; lasdateen formatoYYYYMMDD. Una trama más corta que el registro se lee como si tuviera blancos al final.
Struct: templates y acceso a miembro
Un template con nombre y miembros, definido inline dentro del ámbito global de @Variables:
@Variables {
global {
struct Person [
Id number(10, 0)
Nombre varchar(50)
Email varchar(100)
Edad number(3, 0)
]
personaGlobal struct(Person)
Nombre varchar(50)
}
ImprimirPersona {
persona struct(Person)
}
}
&personaGlobal.Nombre = "Su Nombre"
&Nombre = &personaGlobal.Nombre
Do ImprimirPersona(&personaGlobal)
-
Los miembros del template admiten
char/varchar/number/date/field(NombreCampo),struct(OtroTemplate)ylist(struct(OtroTemplate))(ver Structs anidados).timeytimestampno se admiten como miembro, nimatrixnilistde tipos escalares (E00237: “sólo se permiten char/varchar/number/date/field, struct(Template) y list(struct(Template)) (no se admite matrix ni list de tipos escalares)”). -
Se accede/asigna un miembro con
&Variable.Miembro(lectura y escritura). -
Se puede pasar como parámetro y devolver desde una
Procedure/Functionpropia — ver Procedure / Function. -
Un arreglo de
structse declara conlist(struct(NombreTemplate))— ver List: métodos. -
El indexado
&Lista[i]de unalist(struct(Template))es válido, pero sólo en dos formas exactas: como lectura, el valor completo de una asignación a otra&Variableque seastruct(...)del mismo template (&otraVariable = &Lista[i]); como escritura,&Lista[i] = &otraVariable(otra&Variablestruct(...)del mismo template, nunca una expresión o un literal). Si el lado de la asignación que debería ser la variable struct no es sintácticamente una&Variable, se reportaE00241; si el template no coincide exactamente con el de la lista, se reportaE00242. Los chequeos de índice (cantidad, tiponumber, rango 1..9999) aplican igual que para cualquier otralist:&otroRegistro = &Lista[1] &Lista[1] = &otroRegistro For &i = 1 To &Lista.Count() &actual = &Lista[&i] EndFor -
No soportado todavía:
structcomo parámetro de un programa externo (@Parameters/Call) — sólo como parámetro de unaProcedure/Functioninterna.
Structs anidados y listas dentro de un struct
Un miembro de un template puede ser a su vez struct(OtroTemplate) (struct anidado) o list(struct(OtroTemplate)) (lista de structs). El template referenciado puede estar declarado más abajo:
struct Detalle [
Pgm char(10)
Libreria char(10)
]
struct Cabecera [
epm char(10)
Estado char(10)
Detalle list(struct(Detalle))
]
struct Envio [
Cab struct(Cabecera)
Total number(9, 0)
]
&env struct(Envio)
&fila struct(Detalle)
El acceso es por camino, con tantos niveles como haga falta:
&env.Cab.epm = "x"
&x = &env.Cab.epm
&fila.Pgm = "PGM1"
&env.Cab.Detalle.Add(&fila)
&n = &env.Cab.Detalle.Count()
&fila = &env.Cab.Detalle[1]
&env.Cab.Detalle[2] = &fila
&env.Cab.Detalle.Remove(1)
&env.Cab.Detalle.Clear()
&texto = &env.Json()
&textoDet = &env.Cab.Detalle.Json()
- Capacidad: una
listque es miembro de unstructreserva 100 elementos (no los 9999 de unalistsuelta).Addsobre una lista llena muestra un mensaje y no agrega el elemento, igual que en lalistsuelta. Un índice literal mayor que 100 se reporta comoE00188. - Tamaño máximo: si la variable ocuparía más que el máximo de una variable RPG (16 773 104 bytes) se reporta
E00300(p.ej. unalist(struct)de 9999 elementos cuyo struct lleve una lista anidada grande). - Ciclos: un template que se contiene a sí mismo, directa o indirectamente, se reporta como
E00299. - Templates inexistentes:
struct(X)olist(struct(X))dentro de un miembro con unXno declarado se reporta comoE00238. - No admitido como miembro:
matrixylistde tipos escalares (E00237); tampocotimenitimestamp(E00237). - Procedures: se puede pasar un struct con anidados a una
Procedure; si la rutina modifica una lista anidada, el parámetro se pasa por referencia (noconst). - RPG generado: los templates se emiten en orden de dependencia; la lista miembro es un arreglo
dim(100)con un contador oculto como subcampo del mismo template (se copia junto con el struct), y las variables que contienen listas se inicializan coninz(*likeds). Json(): ver Json(); los anidados generan objetos y arreglos anidados.
Un ejemplo completo está en example/NSTJSON01.warp.
@Parameters
Declara los parámetros de un Program o de un Screen (pantalla completa o Popup): una entrada modo:&Variable por línea. El tipo viene de la declaración correspondiente en @Variables/global o @Variables/local, no se repite acá.
| Modo | Descripción |
|---|---|
in | Parámetro de entrada: quien llama pasa un valor, este programa no puede devolverlo modificado. |
inout | Entrada/salida: quien llama pasa un valor, y este programa puede modificarlo de vuelta. |
out | Salida: este programa lo setea, quien llama sólo lo lee de vuelta. |
@Variables {
global {
Fecha date
Hora char(8)
}
}
@Parameters {
inout:&Fecha,
inout:&Hora
}
No aplica a tablas.
Un Screen declara @Parameters igual que un Program, con los mismos modos (in, inout, out). Los parámetros están disponibles desde Event Init, antes de mostrar la pantalla, y los out/inout vuelven a quien llamó al cerrar el Screen.
Ejemplo: un Popup que recibe un título, modifica una cantidad y devuelve la opción elegida (basado en example/screens/SPOPPRM.warp).
@Properties {
Type : Screen
Popup : True
Name : SPOPPRM
Path : Screens
}
@Variables {
Global {
Titulo char(30)
Cantidad number(5)
Seleccion char(1)
}
}
@Parameters {
in:&Titulo,
inout:&Cantidad,
out:&Seleccion
}
Otro Screen (o un Program) lo invoca con Call, pasando las variables en el mismo orden; el analyzer valida cantidad, tipo y modo:
@References {
Screens = [ "./SPOPPRM.warp" ]
}
@Source {
Event 'AbrirPopup' 4
Call(SPOPPRM, &t, &n, &s)
If &s = "S"
&Resultado = "Aceptado"
EndIf
EndEvent
}
Parámetros de tipo list
Un parámetro de @Parameters puede ser una list(char(n)) o una list(varchar(n)) (un arreglo de líneas de texto). Llega como un arreglo dim(9999) sin envoltura: en RPG, char(n) dim(9999), con const si el modo es in. Como la cantidad no viaja, el programa cuenta las líneas hasta la primera línea vacía (o hasta 9999) para fijar Count(). Con out, el parámetro parte de una lista vacía.
@Variables {
global {
Lineas list(char(80))
}
}
@Parameters {
in:&Lineas
}
Cualquier otra list (por ejemplo list(number) o list(struct(...))), o una matrix, da E00304: sólo se admiten list de char o varchar.
Para enviar una list a otro programa: Call("PGM", &lineas) o Call(Programa, &lineas) pasa el arreglo dim(9999) por referencia (con la firma real si el programa está en @References/Programs). La cantidad no se envía: el receptor lee hasta la primera línea vacía, por eso las operaciones de borrado de list limpian de verdad los elementos (ver Tipos de dato: List).
Nota: en una
Screen, un parámetro usado en@Layoutdebe tener más de 10 caracteres: con 10 o menos da el errorE00369, porque el campo DDS toma ese mismo nombre y no compila en IBM i.
@Layout
Describe la distribución de una pantalla o reporte: Label, Input, Column, DataGridView, Block, o cualquier otro Nombre(...) como control genérico. Los nombres de control pueden llevar @ opcionalmente (@Label/Label son equivalentes).
Controles con gramática propia
| Control | Sintaxis | Válido en | Descripción |
|---|---|---|---|
Label | Label(x, y, texto[, Style(...)]...) | Program, Screen | Etiqueta estática en x,y. texto puede ser &Variable, &variable.miembro (camino de un struct), "literal" o un nombre de campo suelto. x,y = 1,1 (esquina exacta) está prohibido — DDS rechaza cualquier campo ahí. Style(...) (sólo en Screen) le da color y atributos; ver Style. |
Block | Block("Nombre")[ Label(...), ... ] | Program | Grupo nombrado e imprimible de Labels — se imprime una instancia con Print("Nombre") desde @Source. |
Input | Input("Nombre", x, y, texto, &Variable[, habilitado][, Style(...)]...) | Screen | Campo editable en x,y. "Nombre" identifica el control (se lee con CurrentInput()). &Variable (o un camino &variable.miembro) es donde se guarda el valor ingresado. habilitado opcional (default true): literal, &Variable (o camino &variable.miembro), Funcion() de retorno number (no-cero = habilitado) o una condición completa, reevaluado antes de cada redibujado. x,y = 1,1 también prohibido. |
Column | Column(texto, variableOCampo[, Input|Hidden][, Style(...)]...) | sólo dentro de DataGridView | variableOCampo también admite un camino &variable.miembro. El 3er argumento opcional hace la columna editable (Input) o invisible (Hidden). Style(...) le da color y atributos, evaluados por fila; ver Style en Column. |
DataGridView | DataGridView("Nombre", x, y, filas[, separador])[ Column(...), ... ] | Screen | Grilla nombrada en x,y mostrando filas visibles. El nombre es obligatorio y único entre Input/DataGridView. Ver DataGridView para la guía completa. |
Ejemplo (Screen)
@Layout {
@Label(1,2,&Programa)
@Label(1,70,&Fecha)
@Input("txtNroSolicitud",3,1,"Nro. Solicitud: ",&NroSolicitud,true)
@DataGridView("GridSolicitudes",6,1,8," ") [
@Column("Pais",&nPais)
@Column("Usuario",&cUsuario)
]
}
Miembros de struct en el layout
Label, Input (variable destino y habilitado) y Column aceptan caminos de miembros de un struct, incluso anidados: &cli.Codigo, &ped.Cli.Nombre. También funcionan con Print en un Program.
@Layout {
@Label(1,2,&cli.Codigo)
@Input("TxtNombre",3,2,"Nombre:",&cli.Nombre,&cli.Activo)
@Input("TxtCli",4,2,"Cliente:",&ped.Cli.Nombre)
@DataGridView("GridFilas",6,1,8," ") [
@Column("Nombre",&fila.Nombre)
]
}
Cada camino se convierte en un campo DDS: se cambia cada . por _, se pasa a mayúsculas y se trunca a 10 caracteres (cli.Codigo -> CLI_CODIGO, ped.Cli.Nombre -> PED_CLI_NO). Si dos variables o caminos terminan con el mismo nombre DDS (por ejemplo cli.Codigo y cli_Codigo) se reporta E00210. Un miembro que no existe reporta E00178.
Mayúsculas y minúsculas en Input de texto
Un Input cuya variable es char o varchar admite minúsculas: el DDS lleva CHECK(LC), de modo que el terminal 5250 ya no convierte lo tipeado a mayúsculas. El programa recibe exactamente lo que escribió el usuario (por ejemplo Lima). Los Input numéricos, de fecha y de hora no cambian.
Si se compara lo tipeado contra literales, conviene normalizarlo con Upper:
If Upper(&Ciudad) = "LIMA"
...
EndIf
Condiciones completas
El último parámetro opcional habilitado de Input (y la condición opcional de Style) admite, además del literal true/false, estas formas:
&Variable: no-cero = habilitado.Funcion(): función sin argumentos de retornonumber; no-cero = habilitado.- Una condición completa: comparaciones (
= <> < <= > >=),And,Or,Not, números, textos, variables y llamadasFuncion()sin argumentos. Es verdadera cuando se cumple.
@Input("TxtOp",7,2,"Operacion:",&Op, Not &Bloqueado = 1 And &Estado <> "9")
La condición se reevalúa antes de cada redibujado, con el mismo mecanismo de indicadores que ya usaba la habilitación dinámica. Precedencia: ver Not.
No se admiten campos de tabla sueltos, índices ni métodos en la condición: reportan E00322.
Style: color y atributos de pantalla
Style(...) se coloca al final de los parámetros de un Label, un Input o una Column de DataGridView, y sólo es válido en un Screen (en otro tipo de objeto da E00325). Asigna color y atributos de pantalla 5250 (DDS COLOR y DSPATR).
Label(x, y, texto, Style(...)[, Style(...)])
Input("Nombre", x, y, texto, &Var[, habilitado][, Style(...)]...)
Column(texto, &Var[, Input|Hidden][, Style(...)]...)
Sintaxis: Style(Nombre, Nombre, ...[, condición]).
Colores (a lo sumo uno por Style; si no, E00321):
| Nombre | DDS |
|---|---|
Green | COLOR(GRN) |
White | COLOR(WHT) |
Red | COLOR(RED) |
Blue | COLOR(BLU) |
Yellow | COLOR(YLW) |
Pink | COLOR(PNK) |
Turquoise | COLOR(TRQ) |
Atributos (combinables entre sí y con un color):
| Nombre | DDS | Efecto |
|---|---|---|
Reverse | DSPATR(RI) | Video inverso. |
High | DSPATR(HI) | Alta intensidad. |
Underline | DSPATR(UL) | Subrayado. |
Blink | DSPATR(BL) | Parpadeo. |
Hidden | DSPATR(ND) | No se muestra lo escrito (campo oculto). |
Un nombre desconocido da E00320. Un Style sin color ni atributo da E00324.
Equivalencias para quien migra: 'RI' = Reverse, '+' = High, MGN = Pink.
Style con condición
El último argumento de un Style puede ser una condición, con las mismas formas que habilitado (variable, Funcion() o condición completa). El Style se aplica mientras la condición sea verdadera.
Reglas por elemento:
- A lo sumo un
Stylesin condición (el “normal”): se aplica sólo si ningúnStylecon condición está activo. - Hasta tres
Stylecon condición. Más de los permitidos daE00323. - Si dos
Stylecon condición se cumplen a la vez, gana el primero declarado. - Cada
Stylecon condición consume un indicador del mismo rango que la habilitación dinámica (30-89). - Una pantalla monocromo ignora
COLOR.
@Layout {
Label(1,2,&Titulo,Style(Reverse,High))
Label(3,2,"F3=Salir",Style(Red))
Label(5,2,&Desc,Style(Blue,&Estado = "1" Or &Estado = "2"),Style(Pink,&Estado = "3"),Style(Green))
Input("TxtOp",7,2,"Operacion:",&Op,Not &Bloqueado = 1 And &Estado <> "9",Style(Yellow,Underline))
Input("TxtImp",8,2,"Importe:",&Importe,&Bloqueado = 0,Style(Turquoise,&Bloqueado = 1))
}
En &Desc, el texto es azul si &Estado es "1" o "2", rosado si es "3" y verde en cualquier otro caso. Ejemplo completo: example/screens/SSTYLE01.warp.
Style en Column del DataGridView
Column acepta Style(...) después del modo opcional (Input/Hidden). Las reglas son las mismas (colores, atributos, a lo sumo un Style sin condición y tres con condición por columna, gana el primero declarado, errores E00320-E00324):
DataGridView("GridPed",2,2,10)
[
Column("Pedido",&VNroPed)
Column("Cantidad",&VCant,Style(Red,High,&VCant > 100),Style(Green))
]
Diferencia clave: la condición de un Style de columna se evalúa por fila, en el momento de LoadRow() (justo antes de guardar la fila en el subfile), con los valores que tengan las variables en ese instante. Por eso LoadRow() debe ir después de asignar las variables de la fila. Los indicadores se guardan junto con cada fila, de modo que el color de una fila no cambia si luego cambian las variables. Estos indicadores comparten el rango 30-89 con el resto de Style y con habilitado. Ejemplo completo: example/screens/SJNGRD01.warp.
Limitaciones
- La habilitación dinámica por fila del
DataGridView(Column(..., Input)condicionado) aún no existe (planificada).
Ejemplo (Program, con Block imprimible)
@Layout {
@Block("Encabezado") [
Label(1,1,"Reporte de Ventas")
Label(2,1,&FechaHoy)
]
}
@Source {
Print("Encabezado")
}
@Layout es opcional en Program (un programa puramente batch, sin salida impresa, no la necesita) pero obligatoria en Screen. No aplica a tablas.
@Source: sentencias de control
El cuerpo imperativo de un Program/Screen: asignaciones, control de flujo, acceso a tablas, declaraciones de Procedure/Function, invocaciones y reportes.
For Each In: recorrer una tabla
For Each In Tabla [Index NombreIndice]
[Where Campo = expr]...
// cuerpo, corre por cada fila encontrada
[When None
// corre si no hubo ninguna fila
]
EndFor
Index: opcional, selecciona el acceso (por defecto el de acceso secuencial).Where: cero o más, filtra por igualdad.When None: opcional, corre si el loop no encontró ninguna fila.
Join In: unir otra tabla
For Each In Pedidos
Join In Clientes On CodCli = CodCli
Join In Zonas
Where Estado = "A"
&VNombre = NomCli
&VZona = NomZona
EndFor
Sintaxis: una o más líneas Join In Tabla2 [On campoLlave = expr [And campoLlave2 = expr2 ...]] inmediatamente después de For Each In Tabla [Index Idx] y antes de los Where.
- La tabla unida se lee por su
PrimaryKey(en RPG, unchainpor el archivo lógico de la llave primaria) una vez por cada registro leído de la tabla recorrida. ElJoinno declaraIndex. - Sin
On(implícito): cada campo de laPrimaryKeyde la tabla unida debe existir con el mismo nombre en la tabla recorrida o en otra tabla unida antes (si no,E00316). - Con
On: los campos del lado de la tabla unida deben ser exactamente los de suPrimaryKey, sin repetir ni omitir (el orden da igual; si no,E00317). El otro lado puede ser un campo de la tabla recorrida o de una tabla unida antes, o bien una variable o expresión. Si ambos lados son campos de la tabla unida, el izquierdo es el de la unida. Los tipos deben ser compatibles (E00318). - Sin pareja: si no hay registro con esa llave en la tabla unida, el bucle no omite la fila; continúa y los campos de la tabla unida quedan en blanco o en cero.
- Visibilidad: todos los campos de las tablas unidas se pueden usar en los
Wherey en el cuerpo. Si un nombre existe en la tabla recorrida y en la unida (típicamente los de la llave), se usa siempre el de la tabla recorrida. UnWheresobre un campo que sólo existe en la tabla unida se evalúa dentro del bucle, después delchain(no posiciona el archivo). - Sólo lectura: asignar un campo de una tabla unida da
E00319. - Se pueden encadenar varios
Join In. La tabla unida debe tenerPrimaryKeyen@Indexes(si no,E00315). JoinyOnno son palabras reservadas: sólo se reconocen en la cabecera delFor Each(Joinseguido deIn), así que siguen sirviendo como nombres de campo.- DataGridView: el
For Eachque carga la grilla enEvent Loadtambién admiteJoin In. Los campos de la tabla unida se asignan a las variables de lasColumnantes deLoadRow(), igual que los de la tabla recorrida. UnWheresobre un campo exclusivo de la unida filtra las filas después delchain.
Delete(): borrar la fila actual
For Each In Tabla
If &Vencido
Delete()
EndIf
EndFor
Sin argumentos — borra la fila que la iteración tiene cargada en ese momento. Sólo válido dentro de un For Each In (no dentro de un For Each Row, que recorre el subfile del DataGridView, no una tabla); válido tanto en Program como en Screen. Una fila borrada no se actualiza además, aunque el cuerpo también le haya asignado algún campo antes del Delete().
For Each Row: recorrer un DataGridView
For Each Row
// cuerpo — corre una vez por cada fila ya cargada en el subfile
EndFor
Sin Index/Where/When None (no hay tabla que recorrer). Sólo válido dentro de cualquier Event de un Screen con DataGridView. Antes de cada iteración escribe cada Column (incluidas Hidden) en su variable atada — ver DataGridView.
For clásico: contador
For &Variable = inicio To fin [Step incremento]
// cuerpo
EndFor
Cuenta desde inicio hasta fin, incrementando &Variable en incremento en cada vuelta. Step es opcional (default 1); con un Step negativo, inicio debe ser mayor que fin para recorrer hacia abajo.
Recorrer un List
No existe un For Each sobre un list en memoria (For Each In es exclusivo de tablas de base de datos, ver más abajo). Se recorre con el For clásico, usando Count() como límite y el indexado &Variable[&i]:
For &i = 1 To &Lista.Count()
// ... &Lista[&i] ...
EndFor
Ver Tipos de dato: List para la sintaxis completa de Add/Clear/Init/Remove/Count.
New: insertar una fila
New In Tabla
Campo = expr
...
[When Duplicate
// bloque de sentencias: corre si la fila ya existe
]
EndNew
New arma una fila con los valores asignados y la inserta. Si la fila ya existe, corre el bloque When Duplicate.
Cómo funciona
- Se hace una lectura (
chain) por laPrimaryKeyde la tabla, con los valores que elNewacaba de armar. - Si la fila no existe, se escribe la fila nueva (
write) y el bloqueWhen Duplicateno corre. - Si la fila existe, corre el bloque sobre esa fila. Al terminar se graba con
update, sólo si el bloque asignó algún campo de esa tabla.
Dentro del bloque, un campo suelto (por ejemplo Estado) es el de la fila ya grabada, no el que armó el New. Los valores del New que el bloque no asigna se descartan.
New sin When Duplicate
Si la tabla tiene PrimaryKey, el New también lee primero por ella: si la fila ya existe no se inserta nada y tampoco es un error (el programa sigue). Si no existe, se escribe. Sin PrimaryKey en @Indexes, se escribe directamente y una llave duplicada da un error de ejecución.
Qué admite el bloque
When Duplicate es un bloque de sentencias completo, no sólo asignaciones Campo = expr: If/Else, While, Do Case, Do Procedimiento(), Call, asignaciones a &variables, For Each/New sobre otras tablas, etc. Un When Duplicate con sólo asignaciones sigue funcionando igual que antes.
Los nombres se resuelven en este orden:
- Los campos de la tabla del
New(la fila releída; se pueden leer y asignar). - Los campos de los
For Eachque envuelven alNew(sólo lectura).
Reglas
- La tabla debe tener una
PrimaryKeyen@Indexes; si no, daE00339. Ya no depende de que algúnFor Eachuse unIndexsobre esa tabla. - No se puede reasignar un campo de la llave primaria dentro del bloque (
E00340, error). Antes se aceptaba: es un cambio incompatible menor. Exitdirectamente dentro del bloque daE00326; dentro de unWhile/Forpropio del bloque sí es válido.Delete()no es válido dentro del bloque (aplica el error habitual deDelete()fuera de unFor Each).
Límite
La escritura falla ante cualquier violación de unicidad. Si el choque fue contra otro índice único (no la PrimaryKey), el chain por la llave primaria no encuentra la fila y se intenta el write, que dará error de ejecución.
Ejemplo
Programa example/programs/PDUP01.warp con la tabla example/tables/TFOLIO01.warp (llave primaria Empresa, Guia, Correl). Si el folio existe, alterna su Estado y suma uno a Veces:
Procedure Mantener_Folio()
&Accion = "I"
New In FOLIO01
Empresa = &Empresa
Guia = &Guia
Correl = &Correl
Estado = 1
Descrip = "Folio nuevo"
When Duplicate
If Estado = 0
Estado = 1
&Accion = "D"
Else
Estado = 0
&Accion = "H"
EndIf
Veces = Veces + 1
EndNew
EndProc
El RPG generado relee por el archivo lógico de la PrimaryKey (con su propia estructura de datos) y escribe por el PF:
chain (rFOLIO01.EMPRESA : rFOLIO01.GUIA : rFOLIO01.CORREL) FOLIO0101.row rFOLIO0101;
if %found(FOLIO0101);
// ... bloque ...
update FOLIO0101.row rFOLIO0101;
else;
write FOLIO01.row rFOLIO01;
endif;
If / While
If condicion
...
Else
...
EndIf
While condicion
...
EndWhile
condicion combina comparaciones con And/Or (p.ej. &a = 1 And &b = 2 Or &c = 3). And liga más fuerte que Or (a And b Or c se lee como (a And b) Or c).
Do Case: elegir una rama
Do Case
Case &Opcion = "1"
&Etiqueta = "Alta"
Case &Opcion = "2" Or &Opcion = "3"
&Etiqueta = "Cambio"
Otherwise
&Etiqueta = "Desconocida"
EndCase
Se ejecuta solo la primera rama Case cuya condición se cumple, sin caer a las siguientes. Otherwise es opcional y corre si ninguna condición se cumple. Debe haber al menos un Case.
- Las condiciones usan las mismas expresiones que
If(comparaciones conAnd/Or/Not). - Dentro de una rama se puede usar cualquier sentencia, incluidos
ExityLoadRow()(esta última, sólo dondeLoadRow()es válido: ver DataGridView). - Se traduce al
select/when/other/endslde RPG. Reemplaza al encadenado deIf/Elseanidados, ya que WARP no tieneElse If. Case,OtherwiseyEndCase(igual queExit) son palabras reservadas: no pueden usarse como nombre de campo, de variable declarada ni de procedimiento; hay que renombrarlos. UnCase,OtherwiseoEndCasefuera de unDo CasedaE00327.- Un error de sintaxis (sin ramas
Caseo sinEndCase) daE00008.
Exit: salir de un bucle
While &Contador < 100
&Contador = &Contador + 1
If &Contador = 10
Exit
EndIf
EndWhile
Sin argumentos. Sale del bucle más interno que lo contiene. Los bucles válidos son For Each In Tabla, For Each Row, For &v = a To b y While; también el For Each que carga el DataGridView en Event Load (deja de cargar filas).
- Sólo el bucle más interno: un
Exitdentro de unWhileanidado en unFor Eachsale delWhile, no delFor Each. - Grabado en
For Each In: si el cuerpo ya asignó campos de la tabla antes delExit, el compilador graba (update) el registro antes de salir. Si hubo unDelete()antes, no se graba. When NoneyWhen Duplicate: el cuerpo deWhen None(deFor Each) y el deWhen Duplicate(deNew) no cuentan como bucle; unExitahí se refiere al bucle exterior, si existe.For &i = ...:&iconserva su valor al salir.- Dentro de un
Do Caseque está dentro de un bucle es válido. - Procedure, Function y Event: un
Exitno sale del bucle de quien la llama; debe haber un bucle en el mismo cuerpo. Fuera de un bucle daE00326. Exites palabra reservada: no puede usarse como nombre de campo, de variable declarada ni de procedimiento; hay que renombrarlo. Sigue funcionando como parte de una clave de@Properties/@GeneratorConfig(p. ej.Commit on Exit : True).
Un ejemplo completo está en example/programs/PEXIT01.warp.
Commit / Rollback: confirmar o deshacer cambios
New In Pedidos
Numero = &num
EndNew
Commit
...
Rollback
Sin argumentos. Commit confirma los cambios hechos a las tablas bajo control de compromiso (RPG commit;) y Rollback los deshace (RPG rolbk;). Valen tanto en un Program como en un Screen.
Dentro de un For Each que modificó campos del registro actual, Commit primero graba el registro pendiente y luego confirma. Como el Commit libera el bloqueo del registro, las asignaciones a los campos del registro actual hechas DESPUÉS del Commit en esa misma vuelta ya no se graban (el registro se considera cerrado). Para seguir modificándolo, hágase antes del Commit o en otra vuelta.
Ejemplo en un Screen (con Commitment : True en @Properties):
Event 'Confirmar' 4 "Commit"
Commit
&Aviso = "Cambios confirmados"
EndEvent
Event 'Deshacer' 6 "Rollback"
Rollback
&Aviso = "Cambios deshechos"
EndEvent
- Requieren
Commitment : True, en el@Propertiesdel objeto o en el@GeneratorConfigdel proyecto. Sin él la generación falla con «Commit requiere Commitment activo (en @Properties del objeto o en @GeneratorConfig)» (igual paraRollback). - Dónde: válidas en Program y Screen, en cualquier lugar del
@Source, también dentro de unProcedure, unEvento un bucle. - En Screen: con
Commitmentactivo, el Screen iniciaSTRCMTCTL LCKLVL(*CHG)al arrancar y abre bajo control de compromiso las tablas que modifica;CommityRollbackfuncionan igual que en un Program (también dentro de unEvento unProcedure). - Journal: las tablas modificadas deben estar journalizadas en el IBM i (
STRJRNPF) para poder abrirse bajo control de compromiso. Warpgate no las journaliza. - Dentro de
For Each In: si el cuerpo ya asignó campos del registro actual,Commitprimero graba (update) el registro pendiente y luego confirma.Rollback, en cambio, no debe ir dentro delFor Eachtras asignar campos, porque el registro se graba al final de la vuelta; colócalo fuera del bucle, después deEndFor. Commit on Exit:Commit on Exit : Truesigue confirmando solo al terminar el programa; en un Screen, al cerrar la pantalla (F3). ConCommit on Exit : False, lo no confirmado al terminar se deshace al terminar el trabajo.- No son palabras reservadas: un campo llamado
Commitsigue funcionando (Commit = 1).
Ver también Commitment y Commit on Exit.
Try / Catch: capturar errores de ejecución
Try
&Resultado = &Total / &Cantidad
Catch 102
Message("La cantidad no puede ser cero", Error)
Catch
Message(Trim(ErrorMessage()), Error)
EndTry
Try … Catch [filtro] … EndTry atrapa los errores de ejecución de RPG en lugar de dejar que aborten el programa. Baja a monitor; ... on-error ...; ... endmon;. Vale en un Program y en un Screen, en cualquier lugar del @Source.
Filtros de Catch (opcionales, en la misma línea):
| Forma | Atrapa | RPG |
|---|---|---|
Catch | Cualquier error. | on-error; |
Catch File | Errores de archivo (*FILE: estados 01000-02999 en IBM i 7.5). | on-error *file; |
Catch Program | Errores de programa (*PROGRAM: 00100-00999 y 9999). | on-error *program; |
Catch 102, 202 | Los códigos de estado indicados, separados por comas (cada uno entre 100 y 9999; E00365 si no). | on-error 00102 : 00202; |
- Orden: los
Catchse evalúan en orden y gana el primero que cubra el error. UnCatchsin filtro debe ir al final: si hay otro después, es el errorE00368(«nunca se ejecuta»). - Estructura: un
Trynecesita al menos unCatch(E00367) y suEndTry(E00364). ErrorCode()yErrorMessage(): solo valen dentro de unCatch; fuera danE00366.ErrorCode()devuelve unnumber(5)con el estado del error (%status) yErrorMessage()unchar(80)con el texto del mensaje (posiciones 91-170 de la PSDS). Ver Funciones builtin.- Un error dentro de un
Catchno lo atrapa el mismoTry: hay que anidar otroTry. ExityReturnse admiten dentro deTryy deCatch.- Con
Commitment: es útil combinarCatchconRollbackpara deshacer los cambios cuando algo falla.
Códigos de estado frecuentes:
| Estado | Situación |
|---|---|
00100 | Valor fuera de rango en una operación de cadena. |
00101 | Raíz cuadrada de un número negativo. |
00102 | División por cero (también Mod con divisor cero). |
00103 | El resultado no cabe en el campo. |
00105 | Carácter inválido al convertir a número (p. ej. Val). |
00112 | Fecha u hora inválida. |
00121 | Índice de arreglo inválido. |
00202 | Falló la llamada a un programa o procedimiento. Un error dentro de un procedimiento llamado llega al llamador como 00202. |
00211 | Error al llamar: el programa llamado no existe o no se puede resolver (Call a un programa inexistente). |
00907 | Dato decimal inválido. |
01211 | Archivo no abierto. |
Lo que no se captura: el error de pantalla (sesión o dispositivo) no es un error de programa, y WARP no genera operaciones con indicador de error o extensión (E) propios.
Un ejemplo completo está en example/screens/STRY01.warp.
Procedure / Function
Procedure Nombre(¶m)
...
EndProc
Function Nombre(¶m) tipoRetorno
...
Return expr
EndFunc
Procedure(sin retorno,subes sinónimo): se invoca conDo Nombre(expr, ...).Function(con retorno, tipo obligatorio justo después de la lista de parámetros — misma sintaxis que@Fields/@Variables): se invoca como expresión,&variable = Nombre(expr, ...), tipado contra&variable.- Un parámetro/retorno puede ser
struct(Template)— ver Tipos de dato: Struct. El argumento pasado debe ser siempre una variable declarada con ese mismo template exacto (nunca una expresión ni un template distinto, aunque tenga los mismos miembros).
Call vs Do
| Sentencia | Uso |
|---|---|
Call(Nombre, expr, ...) | Invoca un programa o pantalla externo (uno de @References/Programs o @References/Screens), resuelto en tiempo de compilación. |
Call("Nombre", expr, ...) | Invoca un programa externo por nombre, resuelto en tiempo de ejecución. |
Do Nombre(expr, ...) | Invoca un Procedure de este mismo archivo (siempre nombre sin comillas). |
Un Screen referenciado en @References/Screens se invoca con Call(Nombre, ...) exactamente igual que un Program — típicamente para mostrarlo como ventana emergente (ver @Properties). El Screen invocado puede declarar @Parameters igual que un Program.
Para enviar una list a otro programa: Call("PGM", &lineas) o Call(Programa, &lineas) pasa el arreglo dim(9999) por referencia (con la firma real si el programa está en @References/Programs). La cantidad de elementos no se envía: el receptor lee hasta la primera línea vacía. Ver @Parameters: parámetros de tipo list.
Print("NombreBlock")
Imprime una instancia de un Block de @Layout — ver @Layout. Escribe líneas de 132 caracteres al spool de QPRINT (OVRPRTF + open al inicio, close al final); con Print Mode: Screen el spool además se muestra (DSPSPLF) y se borra (DLTSPLF) al terminar el programa.
Member / Export: reportes con miembros de datos
Member(Tabla, expr)
Export(Tabla, destino)
Ver la guía completa: Reportes con Member()/Export().
Member(Tabla, expr): liga un miembro de datos aTablapara todo el programa (ADDPFM+OVRDBF, antes de cualquieropen). EnProgram, sólo a nivel superior de@Source; enScreen, sólo a nivel superior deEvent Init(ver @Source: Event y Screen). A lo sumo una vez por tabla.Export(Tabla, destino): copia el miembro activo deTablaa un stream file del IFS (CPYTOIMPF, delimitado por coma). Sólo válido enProgram(no enScreen); sin restricción de posición dentro de él.
JsonSplit: partir un JSON en líneas
JsonSplit(&json, &lista [, sangria])
Parte el texto JSON de &json en líneas legibles, una por elemento y con sangría por nivel, y las guarda en &lista, reemplazando su contenido. Sirve, por ejemplo, para imprimir el resultado de Json().
- 1.º argumento: una
&Variableo miembro de structchar/varchar, o un texto literal. Cualquier otra expresión daE00301. - 2.º argumento: una
&Variablelist(varchar(n))olist(char(n)). Si no,E00302; tampoco puede ser un parámetroin(E00151). - 3.º argumento (opcional): sangría en espacios por nivel, un
number. Por defecto 2; si no es número,E00303. - Se corta una línea en cada
{,[y,, y antes de cada}o], siempre que no estén dentro de comillas (se respeta\"dentro de los textos). La coma y el cierre quedan juntos (},). Los espacios fuera de textos se descartan. - Máximo 9999 líneas (la capacidad de una
list): al llenarse se muestra el mensajeJsonSplit: lista llena (max 9999)y se descarta el resto. Una línea más larga que el elemento de la lista se recorta. Como referencia, un arreglo de objetos de 15 campos ocupa 17 líneas por objeto más 2, es decir unos 588 objetos.
Para {"epm":"X","Items":[{"Pgm":"P1"},{"Pgm":"P2"}]} con sangría 2, las líneas resultantes son:
{
"epm":"X",
"Items":[
{
"Pgm":"P1"
},
{
"Pgm":"P2"
}
]
}
Uso típico para imprimir, con un Block("Linea") de @Layout que tenga un Label sobre &linea:
JsonSplit(&json, &lineas)
For &i = 1 To &lineas.Count()
&linea = &lineas[&i]
Print("Linea")
EndFor
En el RPG se genera un procedimiento JsonSplit_<tipo json>_<tipo elemento> por cada pareja de tipos usada. Un ejemplo completo está en example/JSONSPLIT01.warp. Errores: E00301, E00302, E00303 (ver Diagnósticos). Al reemplazar el contenido, las líneas anteriores se limpian (ver List: métodos).
Para convertir una list(struct(...)) en líneas de ancho fijo (y viceversa) ver Tramas de ancho fijo: FixedWidthSplit y FixedWidthParse.
Return
Return [expr]
Sale de un Function. Una Function debe devolver un valor: Return sin valor dentro de ella es el error E00351, y una Function sin ningún Return con valor es el error E00352.
@Source: Event y Screen
Sentencias exclusivas de archivos Screen.
Event
Event NombreEstandar
...
EndEvent
Event 'Etiqueta' NumeroTecla ["Texto"]
...
EndEvent
Dos formas: evento estándar (nombre bare, sin comillas) o evento personalizado atado a una tecla de función (nombre entre comillas + número de tecla, 1-24, es decir F1-F24; F13-F24 vía Shift).
Eventos estándar
| Evento | Cuándo dispara |
|---|---|
Enter | Al presionar Enter (sin tecla de función activa). CurrentInput() identifica qué Input tenía foco, decodificado de la posición del cursor tras EXFMT. |
Close | Fijo a F3 en el DSPF generado (CF03) — siempre corre (incluso vacío) y termina la pantalla; no requiere declaración para atar la tecla. |
Load | Una sola vez, antes del primer despliegue — típicamente contiene el For Each/LoadRow() que llena el DataGridView (la grilla se reinicia al entrar). Page Up/Down son scroll nativo sobre lo ya cargado; este evento sólo vuelve a correr con Refresh/Refresh Keep o con F5 (ver más abajo). |
Refresh | Al presionar F5, si F5 está declarada en @FunctionKeys del .warpcfg y no está tomada por un Event 'Nombre' 5 personalizado. Ejecuta Event Refresh (si existe) y después recarga el DataGridView (si el Screen tiene uno con Event Load). Ver Refresh y Refresh Keep. |
Init | Una sola vez, la primera vez que arranca esta pantalla, antes del primer despliegue. No hace nada si no se declara. Único lugar de un Screen donde Member(...) es válido — ver Reportes con Member()/Export(). |
Eventos personalizados
Event 'BuscarCliente' 4
Message("Se presionó F4", Info)
EndEvent
Por defecto, la etiqueta mostrada en la fila de teclas de función de la pantalla es el nombre del evento (F4=BuscarCliente).
Texto de la tecla
Después del número de tecla se puede agregar un texto opcional entre comillas, que es el que se muestra en esa fila (F4=Buscar):
Event 'BuscarCliente' 4 "Buscar"
Message("Se presionó F4", Info)
EndEvent
- Sólo se admite en eventos con tecla (1-24); no puede estar vacío ni pasar de 20 caracteres (
E00344). - Si se omite, se usa el nombre del evento.
- El nombre del evento se convierte en el nombre de un subprocedimiento RPG, así que debe ser un identificador (letras, dígitos y
_, empezando por letra); de lo contrario es el errorE00357. El texto con espacios va como tercer valor:Event 'Nombre' 6 "Texto con espacios". - El texto del
Eventtiene prioridad sobre el que se declare para esa misma tecla en@FunctionKeysdel.warpcfgdel proyecto (ver Proyecto), igual que antes ocurría con el nombre.
Color de la tecla
Al final se puede agregar un color, con o sin texto: Green, White, Red, Blue, Yellow, Pink o Turquoise (los mismos de Style):
Event 'Borrar' 7 "Borrar" Red
Message("Registro borrado", Info)
EndEvent
Event 'Buscar' 4 Blue
Message("Se presionó F4", Info)
EndEvent
- El texto y el color son opcionales; sin texto se usa el nombre del evento (
F4=Buscaren el segundo ejemplo). - El color de la tecla manda sobre
Function Keys Color(de@Propertieso de@Screen). Aplica también a cada juego de la rotación de F24. - Un color inválido da
E00347. - Las teclas declaradas solo en
@FunctionKeysno admiten color propio; usanFunction Keys Color.
Message
Message(texto, Error|Warning|Info)
Muestra un mensaje en la línea de mensaje de la pantalla: Error (rojo), Warning (amarillo), Info (color normal de texto). Se muestra en el siguiente refresco de pantalla y se limpia después de un ciclo.
LoadRow / RefreshSelectRow / For Each Row
Exclusivos de pantallas con DataGridView — ver la guía completa: DataGridView.
| Sentencia | Uso |
|---|---|
LoadRow() | Sin argumentos. Toma los valores actuales de las variables atadas a cada Column y agrega una fila a la grilla. Válida en el cuerpo de cualquier Event de un Screen (directo, dentro de If, Case, While, For o For Each) y en cualquier Procedure. En Event Load la grilla se reinicia al entrar; en cualquier otro Event o Procedure las filas se agregan a las existentes. No se permite en una Function ni fuera de un Event/Procedure (E00198). Un For Each que cargue la grilla debe declarar Index. |
RefreshSelectRow() | Sin argumentos. Inversa de LoadRow(): relee la fila bajo el cursor y escribe cada columna (incluidas Hidden) de vuelta en su variable atada. Válida en cualquier Event de un Screen con DataGridView. |
For Each Row ... EndFor | Recorre todas las filas ya cargadas en el subfile (no la tabla), escribiendo cada Column en su variable atada antes de cada iteración. Sin Index/Where/When None. |
CurrentInput
&Control = CurrentInput()
Devuelve el Nombre del Input/DataGridView (de @Layout) que tiene el foco actualmente. Para un DataGridView, coincide si el cursor cae en cualquier parte de su rectángulo (encabezado, fila de atención o cualquier fila de datos), no sólo en una celda exacta. Válida en cualquier Event, Procedure o Function de un Screen (una Procedure llamada desde un evento ve la posición igual), porque el driver calcula la posición del cursor tras cada lectura de pantalla. No vale en sentencias sueltas de nivel superior de @Source (nunca se ejecutan) ni fuera de un Screen: es el error E00192. Si la Procedure se invoca desde Event Init (antes de la primera lectura), el cursor aún no tiene posición y CurrentInput() devuelve vacío.
Cursor
If Cursor(&Codigo)
&Aviso = "Ayuda de codigos"
EndIf
Es verdadera si el cursor está sobre el control atado a &Codigo, sea un Input o una columna visible del DataGridView del @Layout. Se usa como condición directa; no se combina con = 1, porque el resultado ya es una condición.
- Para un
Inputcompara la fila y el rango de columnas del campo. - Para una columna del grid compara las filas de datos y el rango de columnas de esa columna. Una columna
Hiddennunca coincide. - Mismo ámbito que
CurrentInput():Event,ProcedureoFunctionde unScreen(E00192en otro lugar). Si se invoca desdeEvent Init, aún no hay posición y da falso. - Errores:
E00336(cantidad de argumentos),E00337(el argumento no es una&variable),E00338(la variable no es de ningúnInputni columna del grid).
Ejemplo de uso dentro de una Procedure (ver example/screens/SFOCUS01.warp):
Procedure PideAyuda()
If Cursor(&Cod)
&Aviso = "Ayuda de codigos"
Else
If Cursor(&Descrip)
&Aviso = "Ayuda de descripciones"
EndIf
EndIf
EndProc
Refresh y Refresh Keep: recargar el DataGridView
Refresh
Refresh Keep
Sentencias sin argumentos que recargan el DataGridView de la pantalla. La recarga no es inmediata: el código posterior al Refresh en el mismo evento sigue ejecutándose, y al comenzar la siguiente vuelta del driver se limpia el subfile y se vuelve a ejecutar Event Load.
Refreshvuelve a la primera fila.Refresh Keepconserva la fila donde estaba el usuario (recortada al nuevo total si la grilla quedó con menos filas).
Reglas:
- Sólo válidas en un
Screen; en otro tipo de fuente danE00335. Si el Screen no tieneDataGridView, no hacen nada. - Se pueden llamar desde cualquier
EventoProcedure. - No son palabras reservadas: un
Event Refreshy un campo llamadoRefreshsiguen funcionando. - Limitación: la recarga repite la secuencia de la primera carga. Un
Refreshdentro deEvent Loadse vuelve a evaluar en cada vuelta.
Ejemplo (ver example/screens/SREFRSH01.warp):
Event Enter
Refresh Keep
EndEvent
Cambio de comportamiento de F5
Si F5 está declarada en @FunctionKeys y no la reclama un evento personalizado, F5 ejecuta Event Refresh (si existe) y después recarga el DataGridView (si el Screen tiene uno con Event Load). Antes sólo se llamaba a Event Refresh. Así, Event Refresh sirve para preparar datos (por ejemplo, limpiar un filtro) antes de la recarga.
SetFocus: ubicar el cursor
&Variable.SetFocus()
Pone el cursor en el Input cuya variable es &Variable la próxima vez que se muestre la pantalla. Es una sentencia: no devuelve valor.
- Sólo válida en un
Screen(E00331). - La variable debe ser la de algún
Inputdel@Layout(E00332). - No recibe argumentos (
E00333). - No es una expresión; no puede usarse dentro de una (
E00334). - Si hay varios
SetFocusen una misma vuelta, gana el primero. - Se puede usar en
Event Initpara fijar el foco inicial.
Event Init
&Cod.SetFocus()
EndEvent
Event Enter
If &Cod = ""
&Aviso = "Falta el codigo"
&Cod.SetFocus()
EndIf
EndEvent
Ejemplo completo: example/screens/SFOCUS01.warp.
Implementación (para el lector avanzado): el compilador escribe la fila y la columna del campo en campos ocultos CSRLOC y los pone en cero tras cada lectura. En pantallas con DataGridView usa los CSRLOC de la grilla. En popups las coordenadas son relativas a la ventana.
Funciones builtin
Usables como expresión en cualquier lugar, p.ej. &Usuario = USERID(). La mayoría son de cero argumentos con tipo de retorno fijo; Val() es la excepción (ver su propia entrada).
| Función | Retorno | Descripción |
|---|---|---|
USERID() | char(10) | Perfil de usuario IBM i que ejecuta este programa. Sin argumentos. |
PGNAME() | char(10) | Nombre de objeto de este mismo programa — constante de compilación, no una consulta en tiempo de ejecución. Sin argumentos. |
TODAY() | date | Fecha actual del sistema. Sin argumentos. |
TIME() | char(8) | Hora actual del sistema formateada "HH:MM:SS" (8 caracteres). Sin argumentos. |
Val(&CharVariable) | igual al destino de la asignación | Parsea un valor char a Number, usando la precisión/escala del destino de la asignación — a diferencia de las demás builtin, no tiene tipo de retorno fijo. Sólo válida directamente como &NumberVariable = Val(&CharExpr); usarla en cualquier otro lugar (anidada en otra expresión, como argumento de Do/Call, etc.) es un error. |
CurrentInput() | char(30) | Ver @Source: Event y Screen. |
Cursor(&variable) | condición | Verdadera si el cursor está sobre el control atado a &variable (un Input o una columna visible del DataGridView). Se usa como condición directa: If Cursor(&Codigo), sin = 1. Mismo ámbito que CurrentInput(). Errores: E00336, E00337, E00338. Ver @Source: Event y Screen. |
String(&VariableNumber, enteros[, decimales][, relleno]) | char(enteros [+ decimales]) | Convierte un Number a char de ancho fijo, p.ej. para concatenarlo en un Message(...) (que sólo acepta texto). enteros/decimales deben ser literales enteros (constantes de compilación): el ancho del retorno es enteros + decimales y no lleva separador decimal. decimales es opcional (por defecto 0). relleno es opcional: un texto literal de exactamente un carácter (E00341 si no). Sin relleno, los ceros a la izquierda de la parte entera se reemplazan por espacios; con "0" se conservan los ceros; con otro carácter (p.ej. "*") se usa ese carácter. La unidad de la parte entera siempre se muestra: String(0, 5) da " 0". Los números negativos muestran el signo -, que ocupa una de las posiciones del ancho pedido (el resultado sigue midiendo enteros + decimales): con relleno "0" reemplaza el primer carácter (String(-2, 5, "0") = -0002); sin relleno queda pegado al primer dígito (String(-15, 6) = -15); con otro relleno, igual (String(-15, 6, "*") = ***-15). Si el número usa todas las posiciones, el signo reemplaza el dígito más significativo, así que para negativos hay que pedir una posición más en enteros. Positivos y cero no cambian. Formas: String(n, 5), String(n, 5, 2), String(n, 5, "0"), String(n, 5, 2, "0"), String(n, 5, "*"). Errores: E00206, E00207, E00208, E00341. |
Int(expresión) | Number | Devuelve la parte entera de un número: trunca hacia cero, no redondea (Int(10 / 3) es 3). Recibe exactamente 1 argumento de tipo Number (E00342 si la cantidad es distinta de 1; E00343 si no es numérico). Si el argumento es una variable o campo de tipo conocido, el resultado es Number(enteros del argumento, 0); con una expresión compuesta (p.ej. &a / &b) el tipo no se restringe y se puede guardar en cualquier Number (el RPG trunca al asignar). Se traduce a %dec(expr : 30 : 0). |
SubString(&Variable, inicio, longitud) | char(longitud) | Recorta un pedazo de ancho fijo de un valor char (variable o literal) para que quepa en una variable más chica — p.ej. &Corto40 = SubString(&Largo120, 1, 40). Sin esto, asignar un char más ancho directo a uno más angosto es error de compilación. inicio/longitud deben ser literales enteros. Cuando el tamaño del origen se conoce en tiempo de compilación, que inicio + longitud - 1 lo exceda también es error de compilación. |
Trim(&Variable) | igual al argumento (char/varchar) | Quita blancos a izquierda y derecha. Un solo argumento. |
LTrim(&Variable) | igual al argumento | Quita blancos a la izquierda. Un solo argumento. |
RTrim(&Variable) | igual al argumento | Quita blancos a la derecha. Un solo argumento. |
Upper(&Variable) | igual al argumento | Convierte un char/varchar a mayúsculas, incluidas las letras acentuadas. Un solo argumento (si no, error E00225); sobre un tipo que no es char/varchar da E00226. Se traduce a %upper. Ver Requisito de release de Upper y Lower. |
Lower(&Variable) | igual al argumento | Convierte un char/varchar a minúsculas, incluidas las letras acentuadas. Mismas reglas que Upper. Se traduce a %lower. |
Length(&Variable) | number | Tamaño de un valor char/varchar: para varchar, el contenido real en tiempo de ejecución; para char, su longitud fija declarada. Un solo argumento. |
IndexOf(needle, &haystack) | number | Busca needle dentro de haystack y devuelve la posición cruda (1-based si se encuentra, 0 si no), sin conversión aplicada. Exactamente dos argumentos (texto a buscar, texto donde buscar). |
Round(expresión, decimales) | number(30, decimales) | Redondea expresión con half-adjust (la mitad hacia arriba, %dech de RPG) a decimales. decimales debe ser un literal entero entre 0 y 29 (constante de compilación: define la escala del resultado). La expresión se evalúa completa antes de redondear, así que una división no se trunca antes. El primer argumento debe ser Number. Errores: E00328 (cantidad de argumentos), E00329 (decimales no es literal o está fuera de rango), E00330 (primer argumento no numérico). |
Replace(texto, buscar, nuevo) | varchar | Reemplaza todas las ocurrencias de buscar por nuevo, sin solaparse (%scanrpl). El resultado es varchar con el largo del texto de origen: si el reemplazo es más largo, al asignarlo a una variable más chica se trunca. Los blancos de buscar cuentan (igual que IndexOf): use Trim() si hace falta. |
Contains(texto, buscar) | condición | Verdadera si texto contiene buscar. Se usa como condición (If, While, And, Or), no se asigna a una variable. Mismo criterio de blancos que IndexOf. |
StartsWith(texto, buscar) | condición | Verdadera si texto empieza con buscar. Mismas reglas que Contains. |
EndsWith(texto, buscar) | condición | Verdadera si texto termina con buscar. Mismas reglas que Contains; se traduce a %scanr, que requiere IBM i 7.4 o superior. Para un char con blancos finales use EndsWith(Trim(&Texto), "fin"). |
PadLeft(texto, ancho[, relleno]) | char(ancho) | Alinea a la derecha rellenando por la izquierda. ancho es un literal entero entre 1 y 32766; relleno es un literal de un carácter (por defecto un espacio). Ignora los blancos finales del texto; si el texto es más largo que ancho, se corta por la izquierda. |
PadRight(texto, ancho[, relleno]) | char(ancho) | Alinea a la izquierda rellenando por la derecha. Mismas reglas que PadLeft; si el texto es más largo que ancho, se corta por la derecha. |
Translate(texto, desde, hasta) | igual a texto | Cambia cada carácter de desde por el que ocupa la misma posición en hasta (%xlate). |
Abs(n) | number | Valor absoluto (%abs). |
Sqrt(n) | number | Raíz cuadrada (%sqrt). |
Mod(a, b) | number | Resto de la división entera (%rem). Exige operandos sin decimales (E00362; use Int() para truncar). El signo del resultado es el del dividendo: Mod(17, 5) es 2 y Mod(-17, 5) es -2. |
ErrorCode() | number(5) | Estado del error atrapado (%status), p. ej. 102 para una división por cero. Sin argumentos. Solo dentro de un Catch (E00366 fuera). Ver Try / Catch. |
ErrorMessage() | char(80) | Texto del mensaje del error atrapado (subcampo de la PSDS, posiciones 91-170). Sin argumentos. Solo dentro de un Catch (E00366 fuera). Aplique Trim() para quitar los blancos finales. |
Check(texto, caracteres) | number | Posición (desde 1) del primer carácter de texto que no está en caracteres; 0 si todos están. Los blancos finales de un char no cuentan (son relleno). Mapea a %check. |
Concat(texto1, texto2) | varchar | Une dos textos (+ de RPG). Cada operando conserva sus blancos. |
Div(numero, divisor) | number | Cociente entero, truncado hacia cero (Div(-7, 2) es -3). Mapea a %div. |
Min(numero1, numero2) | number | El menor de dos números. Mapea a %min. |
Max(numero1, numero2) | number | El mayor de dos números. Mapea a %max. |
DateTimeToTimestamp(fecha, hora) | timestamp | Une un date y un time en un timestamp (la hora no tiene microsegundos, quedan en 000000). Se convierte con %timestamp a partir del texto ISO. |
Requisito de release de Upper y Lower
Upper y Lower se traducen a los BIFs de RPG %upper y %lower, que dependen del release de IBM i:
- IBM i 7.5: disponibles de forma nativa.
- IBM i 7.3 y 7.4: sólo con los PTF de abril de 2021. Compilador: SI76100 (7.3) o SI76101 (7.4). Además, el PTF de runtime SI76098 (7.3) o SI76099 (7.4) debe estar instalado en cada sistema donde corra el programa; sin él, el programa falla con MCH4437.
- IBM i 7.2 y anteriores: no existen.
Fecha, hora y timestamp
Year/Month/Days/Hour/Minute/Second son polimórficas: aceptan date/time/timestamp según corresponda (Year/Month/Days con date o timestamp; Hour/Minute/Second con time o timestamp) — no hace falta un nombre distinto por tipo.
format (donde aplica) es siempre opcional: si se omite, cae al valor configurado en @GeneratorConfig (Date Format/Time Format/Timestamp Format); si tampoco hay configuración, se usa el formato por defecto del sistema.
| Función | Retorno | Descripción |
|---|---|---|
Year(&Variable) | number(4) | Año de un date/timestamp. |
Month(&Variable) | number(2) | Mes de un date/timestamp. |
Days(&Variable) | number(2) | Día del mes de un date/timestamp. |
Hour(&Variable) | number(2) | Hora de un time/timestamp. |
Minute(&Variable) | number(2) | Minuto de un time/timestamp. |
Second(&Variable) | number(2) | Segundo de un time/timestamp. |
DateDiff(&Fecha1, &Fecha2, Unidad) | number | Diferencia entre dos date, en Days/Months/Years (palabra suelta, no string). Positivo cuando Fecha1 es posterior a Fecha2, negativo en caso contrario. |
TimeDiff(&Hora1, &Hora2, Unidad) | number | Igual que DateDiff, sobre time, con Hours/Minutes/Seconds. |
TimestampDiff(&Ts1, &Ts2, Unidad) | number | Igual que DateDiff, sobre timestamp, con las 6 unidades habilitadas: Seconds/Minutes/Hours/Days/Months/Years. |
DateAdd(&Fecha, cantidad, Unidad) | date | Suma (o resta, si cantidad es negativa) días/meses/años a una fecha. cantidad puede ser cualquier expresión number (no sólo literal). |
TimeAdd(&Hora, cantidad, Unidad) | time | Igual que DateAdd, sobre time, con Hours/Minutes/Seconds. |
TimestampAdd(&Ts, cantidad, Unidad) | timestamp | Igual que DateAdd, sobre timestamp, con las 6 unidades. |
IsDate(valor[, format]) | number(1) | Valida si valor (char/varchar/number) es una fecha válida en format (opcional — uno de Iso/Usa/Eur/Jis/Mdy/Dmy/Ymd/Jul, o un patrón de texto; ver Patrones de formato). Devuelve 1/0 (WARP no tiene tipo booleano). No es una expresión pura — igual que Val(), sólo es válida directamente como &NumberVariable = IsDate(...). |
IsTime(valor[, format]) | number(1) | Igual que IsDate, sobre time (format uno de Hms/Iso/Usa/Eur/Jis). |
IsTimestamp(valor[, format]) | number(1) | Igual que IsDate, sobre timestamp (format uno de Iso/Usa/Eur/Jis). Iso es el formato recomendado y de mayor compatibilidad. |
StringToDate(string[, format]) | date | Convierte char/varchar a date. format admite también un patrón de texto. |
NumberToDate(number[, format]) | date | Convierte number a date — mismas reglas de format que StringToDate (incluye patrones de texto). |
StringToTime(string[, format]) | time | Convierte char/varchar a time. |
StringToTimestamp(string[, format]) | timestamp | Convierte char/varchar a timestamp. |
DateToString(&Fecha[, format]) | char(10) | Convierte date a texto — mismas reglas de format. Con un patrón de texto devuelve char del largo del patrón (máximo 10). |
TimeToString(&Hora[, format]) | char(8) | Convierte time a texto. |
TimestampToString(&Ts[, format]) | char(26) | Convierte timestamp a texto (26 = ancho ISO con microsegundos). |
Now() | timestamp | Timestamp actual del sistema. Sin argumentos. |
Patrones de formato de fecha
En StringToDate, NumberToDate, IsDate y DateToString, el segundo argumento format acepta, además de las palabras sueltas (Iso, Usa, Eur, Jis, Mdy, Dmy, Ymd, Jul), un patrón de texto literal con YYYY (año de 4 dígitos), MM y DD. Cada uno debe aparecer exactamente una vez, en cualquier orden, con o sin separadores, y sin distinguir mayúsculas. Ejemplos: "YYYYMMDD", "DDMMYYYY", "DD/MM/YYYY", "YYYY-MM-DD", "MM.DD.YYYY".
- Se resuelve en compilación: el RPG extrae año, mes y día con
%substpor posición fija y los lee como*iso. char/varchar: el valor debe tener exactamente el largo del patrón. Unvarcharmás corto falla en ejecución.NumberToDate/IsDateconnumber: el número se pasa a texto con ceros a la izquierda (%editc(n : 'X')) y los separadores del patrón no cuentan (un número no los tiene). Elnumberdebe tener tantos dígitos como el patrón (p.ej.number(8)para"DDMMYYYY").DateToString: devuelvechardel largo del patrón (máximo 10); p.ej."YYYYMMDD"dachar(8).- No se admite año de 2 dígitos (
YY), campos repetidos o faltantes, ni letras distintas deY/M/D: se reportaE00253.
&Fecha = StringToDate(&TextoFecha, "DD/MM/YYYY")
&Fecha2 = NumberToDate(&Numero, "DDMMYYYY") // &Numero: number(8)
&EsValida = IsDate(&Numero, "YYYYMMDD")
&Texto = DateToString(&Fecha, "YYYYMMDD") // char(8)
Texto y cálculo: Replace, Contains, Pad, Translate, Abs, Sqrt, Mod
Contains,StartsWithyEndsWithson condiciones: se escriben directamente enIf,While,AnduOr, igual queCursor.Replace,Contains,StartsWith,EndsWithyTranslatetratan los blancos de los argumentos como parte del texto. En uncharcon blancos finales, apliqueTrim()antes de comparar.PadLeftyPadRightdevuelven siempre unchardelanchoindicado;anchoyrellenodeben ser literales (E00361en caso contrario).- Errores de cantidad de argumentos (
E00358), de tipo texto (E00359) y de tipo numérico (E00360) se reportan con el nombre de la función. Sqrtde un número negativo yModcon divisor cero son errores de ejecución de RPG (estados00101y00102) que abortan el programa si no se atrapan conTry/Catch.
&Limpio = Replace(&Texto, "-", " ")
If Contains(&Texto, "mundo")
Message("Contiene la palabra", Info)
EndIf
If StartsWith(&Codigo, "AB") And EndsWith(Trim(&Codigo), "99")
Message("Código válido", Info)
EndIf
&Codigo5 = PadLeft(&Numero, 5, "0") // "00042"
&Etiqueta = PadRight(&Nombre, 20) // alineado a la izquierda, 20 caracteres
&Mayus = Translate(&Texto, "abc", "ABC")
&Resto = Mod(17, 5) // 2
&Absoluto = Abs(&Diferencia)
&Raiz = Sqrt(&Area)
Ejemplos
&Usuario = USERID()
&Programa = PGNAME()
&FechaHoy = TODAY()
&HoraActual = TIME()
&Cantidad = Val(&CantidadTexto)
Message("Total: " + String(&Total, 9, 2), Info)
&PorParte = Int(&Total / &Partes) // parte entera: 100 / 3 -> 33
&Texto1 = String(&Fila, 5) // " 7" (espacios)
&Texto2 = String(&Fila, 5, "0") // "00007" (ceros)
&Texto3 = String(&Fila, 5, "*") // "****7"
&Texto4 = String(&Importe, 6, 2, "0") // 6 enteros + 2 decimales, con ceros
&Corto = SubString(&Largo, 1, 40)
&Limpio = Trim(&ConEspacios)
&Codigo = Upper(Trim(&Entrada))
&Texto = Lower(&Texto)
&Tam = Length(&Descripcion)
&Pos = IndexOf("@", &Email)
&Importe = Round(&Total / &TipoCambio, 2)
&Nuevo = Replace(&Texto, "a", "b")
&Cod5 = PadLeft(&Numero, 5, "0")
&Resto = Mod(&Total, 7)
&FechaVencimiento = DateAdd(&FechaHoy, 30, Days)
&DiasHastaVencer = DateDiff(&FechaVencimiento, &FechaHoy, Days)
&EsFechaValida = IsDate(&TextoFecha, Dmy)
&Ahora = Now()
&HorasTranscurridas = TimestampDiff(&Ahora, &Inicio, Hours)
Int y String con relleno
Cambio incompatible en
String. Antes,String(n, enteros)rellenaba con ceros a la izquierda. Ahora, por defecto, esos ceros se reemplazan por espacios. Para conservar el comportamiento anterior hay que indicar el relleno:String(n, 5, "0").
Equivalencias en RPG generado:
| Forma | RPG |
|---|---|
String(n, 5) (espacios) | %editw(%dec(n : total : dec) : ' 0') |
String(n, 5, "0") (ceros) | %editc(%dec(n : total : dec) : 'X') |
String(n, 5, "*") (otro carácter) | %xlate(' ' : '*' : %editw(...)) |
Signo de los negativos
String muestra el signo - de los números negativos. El signo ocupa una de las posiciones del ancho pedido, por lo que el resultado sigue midiendo enteros + decimales:
| Llamada | Resultado |
|---|---|
String(-2, 5, "0") | -0002 |
String(-15, 6) | -15 |
String(-15, 6, "*") | ***-15 |
Si el número usa todas las posiciones, el signo reemplaza el dígito más significativo; para negativos conviene pedir una posición más en enteros. Los positivos y el cero no cambian. Los decimales siguen sin punto decimal (12.00 con 2 decimales da 1200). Para esto, el RPG generado incluye un procedimiento de apoyo WarpStrSign (una sola vez por programa).
Int se traduce a %dec(expr : 30 : 0). Ejemplo completo: example/programs/PNUM01.warp.
Expresiones y operadores
Operadores aritméticos/concatenación
| Operador | Uso |
|---|---|
+ | Suma numérica entre Number, o concatenación entre char/varchar (según el tipo de los operandos). |
- | Resta numérica. |
* | Multiplicación numérica. |
/ | División numérica. |
&Total = &Precio * &Cantidad
&Saludo = "Hola, " + &Usuario
Operadores de comparación
=, <>, <, <=, >, >= — usados en Where, If, While.
Operadores lógicos
And/Or combinan comparaciones (típicamente en If/While): And liga más fuerte que Or. Not expr niega una condición.
If &a = 1 And &b = 2 Or &c = 3
...
EndIf
Not (negación lógica)
Not expr invierte la condición que le sigue. Es una palabra reservada, igual que And y Or.
Precedencia, de menor a mayor: Or, And, Not, comparaciones. Es decir, Not liga más flojo que una comparación y más fuerte que And:
// Not &a = 1 And &b = 2 se lee como (Not (&a = 1)) And (&b = 2)
If Not &a = 1 And &b = 2
...
EndIf
Para negar una condición compuesta, use paréntesis: Not (&a = 1 Or &b = 2).
Not también se puede usar en las condiciones de @Layout (ver @Layout: condiciones).
WARP no tiene un tipo booleano propio: cada operando se valida como cualquier otra expresión.
Continuación implícita de línea
En @Source, una expresión o sentencia puede repartirse en varias líneas sin ningún carácter especial. El salto de línea se ignora en estos casos:
- Después de un operador:
+ - * /, las comparaciones= <> < <= > >=,And,Or,Not, y después del=de una asignación. - Dentro de paréntesis: tras
(, tras cada,y antes de). Aplica a llamadas a función o builtin (DateAdd(…)),Do Proc(...),Call(...),Message(...),Member(...),Export(...), listas de parámetros deProcedure/Functiony expresiones entre paréntesis. And/Oral inicio de la línea siguiente también continúan la condición.
&texto = "uno" +
"dos"
&f = DateAdd(
&f,
1,
Days
)
If &n > 1
And &n < 10
...
EndIf
Reglas adicionales:
- Un operador aritmético o de comparación al inicio de la línea siguiente (por ejemplo
+ "dos") sigue siendo error (E00008), porque sería ambiguo. - No se puede continuar en medio de un identificador ni de un literal de texto.
- Los comentarios
//entre las líneas continuadas se ignoran. - Los números de línea de los diagnósticos siguen siendo los reales.
Literales
- Números:
100,9.5. - Texto:
"texto"o'texto'(equivalentes). - Fecha/hora: se obtienen con
TODAY()/TIME(), no hay literal de fecha.
Variables y campos
- Variable: siempre con
&(&Usuario). - Campo de tabla: sin
&, sólo dentro de unFor Each/Newsobre esa tabla (CodCli,Nombre). - Elemento de
list/matrix:&Variable[índice]/&Variable[fila, col](1-based). - Miembro de
struct:&Variable.Miembro(lectura y asignación); con structs anidados, por camino:&Variable.Cab.Miembro,&Variable.Cab.Lista.Add(&fila),&Variable.Cab.Lista[1]— ver Tipos de dato: Struct. - Método de
list:&Variable.Metodo(...)(Add/Clear/Init/Removecomo sentencia,Count()como expresión) — ver Tipos de dato: List. - Método
Json()destruct, delist(struct(...))y delistde escalares (list(char),list(number),list(date),list(time),list(timestamp); el resultado es un arreglo JSON, p. ej.["a","b"]o[1,2]) - Sentencia
JsonSplit(&json, &lista[, sangría]): parte un JSON en líneas — ver JsonSplit. - Tramas de ancho fijo: expresión
®.FixedWidth()y sentencias®.FromFixedWidth(&texto),FixedWidthSplit(®istros, &lineas),FixedWidthParse(&lineas, ®istros)— ver Tramas de ancho fijo.
Llamadas a función/procedimiento como expresión
&variable = NombreFuncion(expr, ...)
Ver Funciones builtin y @Source: sentencias de control para Functions propias.
Archivo de proyecto (.warproj / .warpcfg)
Ver también la guía: Proyecto.
.warproj — @Project
| Clave | Descripción |
|---|---|
Name | Nombre del proyecto. |
Description | Descripción del proyecto. |
Version | Versión del proyecto (string libre, p.ej. 1.0). |
Default Profile | Nombre del @Profile (definido en el .warpcfg, ver más abajo) que usan generate/build/reverse/license-status cuando el CLI no recibe --profile. Opcional si el .warpcfg sólo declara un @Profile. |
@Generator (anidada en @Project)
| Clave | Descripción |
|---|---|
Name | Nombre del generador. |
Description | Descripción del generador. |
Language | Lenguaje de destino. Valores: RPGLE, RPG. |
@Paths (anidada en @Generator)
| Clave | Descripción |
|---|---|
Config | Ruta al .warpcfg de este proyecto. |
Output | Carpeta donde se escribe el DDS/RPG generado. |
.warpcfg — @GeneratorConfig
| Clave | Descripción | Valores |
|---|---|---|
Date Format | Formato de fecha nativo con el que se declara cada variable Date (p.ej. date(*dmy/) en vez de date a secas). | ISO, USA, EUR, JIS, MDY, DMY, YMD, JUL |
Date Separator | Carácter separador de fecha (p.ej. / en date(*dmy/)). Un solo carácter. | — |
Time Format | Formato de hora nativo con el que se declara cada variable Time (p.ej. time(*hms:) en vez de time a secas), y default para IsTime/StringToTime/TimeToString cuando se omite su argumento format. Usa es 12 horas con sufijo AM/PM, sin segundos — estructuralmente distinto de los otros 4 (24 horas hh:mm:ss). | Hms, Iso, Usa, Eur, Jis |
Time Separator | Carácter separador de hora (p.ej. : en time(*hms:), también usado por TIME()). Un solo carácter. No aplica a Usa (sin separador entre campos). | — |
Timestamp Format | Default para IsTimestamp/StringToTimestamp/TimestampToString cuando se omite su argumento format. El tipo timestamp no admite un formato en su declaración, a diferencia de date/time. Se recomienda usar Iso por su mayor compatibilidad. | Iso, Usa, Eur, Jis |
Print Mode | Qué pasa con el spool QPRINT que escribe cada Print("Block"): con Screen el programa lo muestra (DSPSPLF) y lo borra al salir; Printer/File sólo lo dejan spooleado. Default Printer. | Printer, File, Screen |
Commitment | Habilita control de compromiso para todo el proyecto: los programas y pantallas (Program y Screen) generados declaran sus tablas modificadas commit usropn y las abren tras un STRCMTCTL best-effort (las tablas deben estar journaled). Un Program o Screen puede sobreescribirlo con @Properties/Commitment. Default False. | True, False |
Commit on Exit | Si los programas y pantallas generados emiten un commit final antes de terminar. Sobreescribible por @Properties/Commit on Exit. Sólo efectivo si Commitment es True. Default False. | True, False |
DDS Name | Patrón para nombrar el DDS del display file de cada Screen a partir de su nombre de objeto — p.ej. <ObjectName>D agrega una D. Default <ObjectName> (sin cambio). Sólo afecta el DSPF; el programa RPG driver generado mantiene el nombre de objeto tal cual. El resultado siempre se pasa a mayúsculas y trunca a 10 caracteres (límite de nombre de objeto OS/400) — las variantes que agregan D truncan a 9 primero para que la D nunca sea lo que se trunca. | <ObjectName>, <ObjectName>D, D<ObjectName>, D<ObjectNameWithoutFirstChar> |
Las claves de nivel superior (Date Format, Print Mode, Commitment, etc.), @Screen/@FunctionKeys y los formatos de fecha/hora/timestamp son únicos a nivel de proyecto: no se repiten por perfil.
@Profile (anidada en @GeneratorConfig)
Un .warpcfg declara uno o más perfiles de despliegue (típicamente Dev, UAT, Prod, aunque el nombre es libre) dentro de @GeneratorConfig. Cada @Profile "Nombre" agrupa su propia @Connection, @Deployment y @License — así un mismo proyecto puede apuntar a servidores/bibliotecas/licencias distintos según el ambiente, sin tener que mantener varios .warpcfg.
@GeneratorConfig {
@Profile "Dev" {
@Connection { ... }
@Deployment { ... }
}
@Profile "Prod" {
@Connection { ... }
@Deployment { ... }
@License { ... }
}
}
Es obligatorio declarar al menos un @Profile: @Connection/@Deployment/@License sueltos directamente dentro de @GeneratorConfig (sin envolverlos en @Profile) ya no son válidos (error E00290). Un @GeneratorConfig sin ningún @Profile da error E00291.
Cómo se elige el perfil activo para generate/build/reverse/license-status (la acción validate ignora los perfiles):
- El flag
--profile <Nombre>del CLI, sin distinguir mayúsculas. - Si se omite,
Default Profilede@Project(sección.warproj — @Projectmás arriba). - Si tampoco hay
Default Profilepero el.warpcfgsólo declara un@Profile, se usa ese. - Si nada de lo anterior aplica y hay varios perfiles declarados, el compilador falla con error
E00293, listando los perfiles disponibles.
Pedir un perfil que no existe (--profile o Default Profile) falla con error E00292, también con la lista de perfiles disponibles.
@Screen (anidada en @GeneratorConfig)
| Clave | Descripción |
|---|---|
Rows | Cantidad de filas de pantalla. Numérico, default 24. Solo se admiten los tamaños 24x80 y 27x132 de un 5250 (E00348 si no). Es del proyecto: no se puede cambiar por pantalla. |
Columns | Cantidad de columnas de pantalla. Numérico, default 80 (132 con Rows : 27). |
Function Keys Row | Fila donde se dibuja la fila de teclas de función (también se acepta FunctionKeysRow). Rango válido: de 1 a Rows - 1 (la última fila queda para los mensajes); fuera de rango da E00346. Si se omite se usa Rows - 1 (23 en 24x80; en un popup, la última fila de la ventana menos una). Un Function Keys Row en el @Properties de un Screen manda sobre este. |
Function Keys Color | Color de toda la fila de teclas (también FunctionKeysColor): Green, White, Red, Blue, Yellow, Pink o Turquoise, los mismos de Style. Un nombre inválido da E00347. Sin valor, la fila no lleva color. Un Function Keys Color en el @Properties de un Screen manda sobre este, y el color de una tecla en su Event manda sobre ambos. |
El compilador no avisa si la fila elegida invade el @Layout: quien la mueva debe dejar libre esa fila. Si las teclas no caben en una fila y no hay rotación con F24, la segunda fila sube desde la elegida.
@FunctionKeys (anidada en @Screen)
Una entrada F<n> : "Etiqueta" por línea (n de 1 a 24, p.ej. F3 : "Salir"). Se ata a un evento propio con Event 'Nombre' <n> ... EndEvent en el @Source de un Screen.
Rotación de teclas con F24. Si F24 está declarada aquí (p.ej. F24 : "Mas teclas") y ningún Event del archivo la reclama (Event 'X' 24), la fila de atención del Screen puede rotar. Se consideran todas las teclas (las de @FunctionKeys más las de los Event con tecla). Si caben en una fila, no hay rotación y la fila se genera como siempre. Si no caben, se reparten en juegos ordenados por número de tecla, cada uno de una sola fila y terminado en F24=<etiqueta>. Al pulsar F24 se muestra el juego siguiente (tras el último se vuelve al primero); la pulsación solo cambia la fila de teclas y no ejecuta ningún evento. El ancho útil es el de la pantalla menos 2 columnas (el de la ventana en un popup). Sin F24 declarada, o con un Event en F24, la fila muestra todas las teclas en una o dos filas.
@FunctionKeys {
F3 : "Salir"
F5 : "Refrescar"
F24 : "Mas teclas"
}
@Connection (anidada en @Profile)
Cómo llegar al IBM i:
| Clave | Descripción | Valores |
|---|---|---|
Host | Hostname o IP del IBM i. Requerida. | — |
User | Perfil de usuario para conectar. Requerida. | — |
Auth Method | Cómo autenticar. Requerida. password lee la contraseña de la variable de entorno WARPGATE_PASSWORD; interactive, al correr desde la extensión de VSCode, la pide con un cuadro de diálogo en cada Build & Deploy y en cada Reverse Engineer DDS (corriendo el binario directo por consola, se comporta igual que password). | key, password, interactive |
Key File | Ruta al archivo de clave privada. Requerida sólo si Auth Method: key. | — |
Protocol | Protocolo de transporte. Requerida. ftp no puede combinarse con Auth Method: key. | ssh, sftp, ftp |
Port | Puerto TCP. Numérico, default 22. | — |
Timeout | Timeout de conexión en segundos. Numérico (-128 a 127), default 30. | — |
ssh/sftp/ftp usan clientes nativos: no hace falta tener instalado ningún cliente ssh/scp/sftp/ftp en la máquina donde corre el compilador.
Ejemplos por protocolo y método de autenticación
SSH o SFTP con llave (ssh/sftp son equivalentes: el cliente nativo usa el mismo transporte para ambos):
@Connection {
Host : MIHOST
User : MIUSUARIO
Auth Method : key
Key File : ~/.ssh/id_ed25519_ibmi
Protocol : ssh
Port : 22
Timeout : 30
}
SSH o SFTP con contraseña — la contraseña no se escribe en el .warpcfg; se lee de la variable de entorno WARPGATE_PASSWORD, que debe existir antes de abrir VSCode (o la terminal desde la que se corra el compilador):
@Connection {
Host : MIHOST
User : MIUSUARIO
Auth Method : password
Protocol : sftp
Port : 22
Timeout : 30
}
FTP con contraseña — FTP no tiene concepto de autenticación por llave (Auth Method: key con Protocol: ftp es un error de validación), así que sólo admite password/interactive, ambos leídos de la misma variable WARPGATE_PASSWORD:
@Connection {
Host : MIHOST
User : MIUSUARIO
Auth Method : password
Protocol : ftp
Port : 21
Timeout : 30
}
Generar el archivo de Key File
Para Auth Method: key hace falta un par de llaves SSH: una privada (la que apunta Key File) y una pública, que debe quedar autorizada en el perfil de usuario del IBM i.
- Generar el par de llaves (en la máquina donde corre el compilador/VSCode, no en el IBM i):
Esto creassh-keygen -t ed25519 -f ~/.ssh/id_ed25519_ibmi -N ""id_ed25519_ibmi(privada — la ruta que va enKey File) yid_ed25519_ibmi.pub(pública).-N ""deja la llave sin passphrase, requisito paraAuth Method: key. - Copiar el contenido de
id_ed25519_ibmi.pub(una sola línea) al final del archivo.ssh/authorized_keysdentro del directorio home IFS del usuario del IBM i (p.ej./home/MIUSUARIO/.ssh/authorized_keys) — crear el archivo y el directorio si no existen. El demonio SSH del IBM i (SSHD, del producto 5733-SC1) tiene que estar activo y configurado para aceptar autenticación por llave. - Verificar que
Key Fileen@Connectionapunte a la ruta de la llave privada en la máquina local (no la.pub), y queAuth Methodseakey.
@Deployment (anidada en @Profile)
Dónde/cómo desplegar y compilar en el IBM i:
| Clave | Descripción | Valores |
|---|---|---|
CL Source | Nombre del member de fuente CL generado (nombre de objeto OS/400, máx. 10 caracteres). Requerida. | — |
Cleanup | Si se elimina el fuente staged tras compilar exitosamente. | true, false |
Target Release | Release de OS/400 destino de la compilación (p.ej. V7R4M0). | — |
Optimization | Nivel de optimización del compilador RPG. Se emite como OPTIMIZE(...) en el CRTBNDRPG de los programas. Si se omite, no se emite nada. Cualquier otro valor produce el error E00020. | *NONE, *BASIC, *FULL |
Debug | Vista de debug del programa. true emite DBGVIEW(*SOURCE); false emite DBGVIEW(*NONE). Si se omite, no se emite nada y el compilador usa su valor por defecto. | true, false |
Temp Path | Directorio staging del IFS usado durante el deploy. Requerida. | — |
Data Library | Biblioteca donde se crean los objetos físico/lógico de DDS compilados (nombre de objeto OS/400). Requerida. | — |
Objects Library | Biblioteca que contiene los members de fuente DDS/CL (QDDSSRC/QCLSRC) y donde se crea el objeto programa CL compilado (nombre de objeto OS/400). Requerida. | — |
Source Tables | Member de fuente que contiene el fuente DDS de físicos/lógicos (nombre de objeto OS/400). Requerida. | — |
Source Programs | Member de fuente que contiene el RPGLE/CL generado (nombre de objeto OS/400). | — |
Compile Library | Lista separada por comas de bibliotecas agregadas a la lista de bibliotecas del job de compilación, en orden, en vez de sólo Data Library — para programas cuyas tablas viven en varias bibliotecas. | — |
@License (anidada en @Profile)
Bloque opcional con la configuración de licenciamiento, propio de cada perfil — así Dev/UAT/Prod pueden consumir licencias o servicios de licenciamiento distintos. Las claves no distinguen mayúsculas. Ver la guía Licenciamiento.
| Clave | Descripción | Valores |
|---|---|---|
Host | Host del servicio de licencias. Con Host el modo es organización y File se ignora; sin Host, el modo es individual. | — |
Port | Puerto TCP del servicio. Por defecto 7443. | 1-65535 |
Fingerprint | Huella SHA-256 del certificado del servicio (acepta : y mayúsculas). | 64 hex |
SPN | Nombre de servicio Kerberos. Por defecto warpgate-license/<host>. | — |
File | Archivo de licencia individual. Por defecto ~/.config/warpgate/license.json (%APPDATA%\warpgate\license.json en Windows). | — |
@License {
Host : "servidor01.dominio.local"
Port : 7443
Fingerprint : "3f2a9c41d87b05e6a1c4f0937be2d5688a1f4c0d29e7b3a65c8d1f0e4b7a9c23"
}
Ejemplo completo
Ver Proyecto: .warproj y .warpcfg para un ejemplo completo con ambos archivos.
Diagnósticos y mensajes de error
El compilador reporta cada problema con un código E##### (p.ej. E00221), un nivel de severidad y un mensaje en español con los valores concretos interpolados.
Niveles
| Nivel | Significado |
|---|---|
error | Impide la generación de código; el objeto no compila. |
warning | No impide compilar, pero señala algo probablemente no intencional (p.ej. una variable declarada y nunca usada). |
Cómo se ven
Por línea de comandos (formato humano):
[2026-07-24T21:26:47Z] ERROR E00221 (line: 12, column: 5): La variable 'x' declara char(16773105), fuera del rango permitido (1-16773104).
O en JSON (--format json, el que consume la extensión de VSCode para pintar los subrayados rojos/amarillos en el editor):
{"errors": [{"line": 12, "column": 5, "severity": "error", "message": "..."}]}
Algunos ejemplos representativos
| Código | Situación |
|---|---|
E00001 | Se esperaba una sección @Nombre y se encontró otra cosa. |
E00128 | Un archivo referenciado por el .warpcfg (p.ej. la clave SSH de @Connection) no existe en esta máquina — no bloquea validate/generate, pero hará falta para build/reverse. |
E00148 | El campo de field(...) no se encontró: en field(Campo), en ninguna tabla de @References/Tables; en field(Tabla.Campo), porque la tabla no está referenciada o no contiene ese campo. |
E00149 | El campo de field(Campo) es ambiguo: existe en más de una tabla de @References/Tables; indique la tabla con field(Tabla.Campo). |
E00152 | Una variable de @Variables fue declarada pero nunca usada (warning). |
E00178 | Una variable usada en @Layout no está declarada en el ámbito global de @Variables. También se reporta cuando un camino &variable.miembro apunta a un miembro inexistente. |
E00179 | No se puede pasar ‘&{arg}’ (un parámetro in o una constante, de sólo lectura) a ‘{name}’, que lo modifica internamente (directa o indirectamente). |
E00192 | {name}() sólo puede usarse dentro de un Event, Procedure o Function de un Screen (la posición del cursor sólo existe tras leer la pantalla). |
E00195 | Uso inválido de RefreshSelectRow()/For Each Row fuera de un Screen con DataGridView. |
E00206 | String(): se esperaban de 2 a 4 argumentos (numero, enteros[, decimales][, relleno]), se encontraron {found}. |
E00210 | Dos variables o caminos de @Layout truncan al mismo campo DDS (los caminos de struct se aplanan: . pasa a _, mayúsculas, 10 caracteres; p. ej. cli.Codigo y cli_Codigo). |
E00221 | char fuera del rango permitido para una variable standalone en @Variables (1-16.773.104). |
E00222 | number con precisión fuera de rango (1-63) en @Variables. |
E00223 | number cuya escala es mayor que su precisión. |
E00224 | varchar fuera del rango permitido (1-16.773.100). |
E00237 | Tipo de miembro no admitido en un template de struct: sólo se permiten char/varchar/number/date/time/timestamp/field, struct(Template) y list(struct(Template)) (no se admite matrix ni list de tipos escalares). |
E00238 | struct(Nombre) referencia un template que no existe en @Variables; también se reporta para templates inexistentes dentro de miembros de otro template y de list(struct(X)). |
E00240 | Acceso &Variable.Miembro a un miembro que no existe en el template del struct. |
E00242 | Se pasó una variable struct(OtroTemplate) donde se esperaba un template distinto (la comparación es por nombre exacto, no por estructura). |
E00253 | El format de StringToDate/NumberToDate/IsDate/DateToString debe ser uno de Iso/Usa/Eur/Jis/Mdy/Dmy/Ymd/Jul, o un patrón de texto con YYYY, MM y DD una vez cada uno (p.ej. "YYYYMMDD", "DD/MM/YYYY"; en DateToString, de hasta 10 caracteres). |
E00269 | La Description de una tabla (@Structure) supera los 50 caracteres que admite TEXT en DDS. |
E00270 | La Description de un campo (@Fields) supera los 50 caracteres que admite TEXT en DDS. |
E00271 | La Description de un índice (@Indexes) supera los 50 caracteres que admite TEXT en DDS. |
E00278 | list no tiene el método invocado (válidos: Add, Clear, Init, Remove, Count, Split, Json). |
E00281 | La Description de @Properties (de un Program o Screen) supera los 50 caracteres que admite TEXT en los mandatos CL de compilación (CRTBNDRPG/CRTDSPF/CHGPFM). Mismo límite y criterio que E00269/E00270/E00271. |
E00284 | Port de @License es 0. |
E00285 | Host de @License no es un host válido. |
E00286 | Fingerprint de @License no es una huella SHA-256 válida. |
E00287 | Aviso: @License tiene Host pero no Fingerprint. |
E00288 | Aviso: @License tiene Host y File juntos (File se ignora). |
E00290 | @Connection/@Deployment/@License declarados directamente dentro de @GeneratorConfig, sin envolverlos en un @Profile "Nombre". |
E00291 | @GeneratorConfig no declara ningún @Profile. |
E00292 | El perfil pedido con --profile o Default Profile no existe entre los @Profile declarados (se listan los disponibles). |
E00293 | Hay varios @Profile declarados y no se indicó cuál usar (ni --profile ni Default Profile), así que el compilador no puede elegir uno solo (se listan los disponibles). |
E00294 | .Json() usado como sentencia suelta: devuelve el JSON como valor y debe usarse en una expresión (&texto = &Persona.Json()). |
E00295 | .Json() aplicado a una variable que no es struct ni list. |
E00296 | Se llamó a un método distinto de Json sobre un struct (el único método válido es Json). |
E00297 | .Json() sobre una list cuyo elemento no es un tipo escalar (char, varchar, number, date, time, timestamp) ni un struct. |
E00298 | Una variable del ámbito local también está declarada en global; el nombre no puede repetirse porque ambos ámbitos viven a nivel de módulo en el RPG generado. |
E00299 | El template de struct ‘{template}’ se contiene a sí mismo (directa o indirectamente) a través de sus miembros struct/list(struct). |
E00300 | La variable ‘{variable}’ ocuparía {size} bytes, más que el máximo de una variable RPG (16773104): reduzca el tamaño de sus miembros o de las listas que contiene (una list dentro de un struct reserva 100 elementos). |
E00301 | JsonSplit(): el primer argumento debe ser una &Variable (o miembro de struct) char/varchar, o un texto literal; se encontró {found}. |
E00302 | JsonSplit(): ‘&{variable}’ debe ser una list de char o varchar (ahí se guardan las líneas). |
E00303 | JsonSplit(): la sangría debe ser un número, se encontró {found}. |
E00304 | El parámetro ‘&{variable}’ de @Parameters no puede ser una list de ese tipo ni una matrix: sólo se admiten list de char o varchar (un arreglo de líneas de texto, que termina en la primera línea vacía). |
E00305 | {method}(): ‘&{variable}’ debe ser {expected} cuyos miembros sean char, varchar, number o date, con una trama de hasta 32766 posiciones.{hint} |
E00306 | {method}() debe usarse como {where}. |
E00307 | {function}(): se esperaba un valor char/varchar o una list de char/varchar, se encontró {found}. |
E00308 | {function}(): ‘&{variable}’ debe ser una list(struct(…)). |
E00309 | {function}(): la trama de {width} posiciones no cabe en los elementos de ‘&{variable}’ (char/varchar({element})). |
E00310 | La constante ‘{name}’ debe ser de tipo char, varchar o number. |
E00311 | El valor de la constante ‘{name}’ no es válido para su tipo: {detail}. |
E00312 | La constante ‘&{variable}’ no se puede modificar. |
E00313 | El parámetro ‘&{variable}’ de @Parameters no puede ser una constante. |
E00314 | La constante ‘&{variable}’ no puede mostrarse en un campo de pantalla: cópiela a una variable. (En un Block imprimible de un Program sí se puede usar.) |
E00315 | La tabla ‘{table}’ no tiene PrimaryKey en @Indexes: ‘Join In’ la lee por su llave primaria. |
E00316 | Join In {table}: el campo ‘{field}’ de su llave primaria ({key}) no existe en la tabla recorrida ni en las tablas ya unidas. Use ‘On’ para indicar de dónde sale cada campo. |
E00317 | Join In {table}: los campos de ‘On’ del lado de la tabla unida deben ser exactamente los de su llave primaria ({key}), sin repetir ni omitir ninguno. |
E00318 | Join In {table}: el campo ‘{field}’ ({target_type}) no acepta ‘{source}’ ({source_type}). |
E00319 | El campo ‘{field}’ pertenece a la tabla ‘{table}’ unida con ‘Join In’ y es de sólo lectura: no se puede asignar. |
E00320 | Style: ‘{name}’ no es un color ni un atributo conocido. Colores: Green, White, Red, Blue, Yellow, Pink, Turquoise. Atributos: Reverse, High, Underline, Blink, Hidden. |
E00321 | Style: sólo se admite un color por Style; se encontró ‘{name}’ además de ‘{previous}’. |
E00322 | La condición no es válida en @Layout: sólo admite variables, números, textos, comparaciones, And/Or/Not y llamadas Funcion() sin argumentos. |
E00323 | Demasiados Style en el mismo elemento: se admite a lo sumo uno sin condición y tres con condición. |
E00324 | Style sin color ni atributo: indique al menos uno (por ejemplo Style(Red) o Style(Reverse)). |
E00325 | Style sólo se admite en un Screen. |
E00326 | Exit sólo es válido dentro de un bucle (For Each, For Each Row, For o While). Dentro de una Procedure, Function o Event, el bucle debe estar en el mismo cuerpo. |
E00327 | ‘{keyword}’ es una palabra reservada que sólo es válida dentro de un ‘Do Case’ (Do Case / Case <condición> / Otherwise / EndCase). |
E00328 | Round(): se esperaban 2 argumentos (expresión, decimales), se encontraron {found}. |
E00329 | Round(): ‘decimales’ debe ser un literal entero entre 0 y {max} (constante en tiempo de compilación: define la escala del resultado). |
E00330 | Round(): el primer argumento debe ser de tipo Number (encontrado {type}). |
E00344 | El texto de la tecla del evento ‘{event}’ no puede estar vacío ni pasar de {max} caracteres. |
E00345 | El evento ‘{event}’ tiene el mismo nombre que una Procedure o Function de este archivo; ambos generan un subprocedimiento con ese nombre. Renombre uno de los dos. |
E00331 | SetFocus() sólo es válido en un Screen. |
E00332 | SetFocus(): ‘&{variable}’ no es la variable de ningún Input del @Layout. |
E00333 | SetFocus() no recibe argumentos (se pasaron {found}). |
E00334 | SetFocus() es una sentencia: no devuelve ningún valor y no puede usarse dentro de una expresión. |
E00335 | Refresh sólo es válido en un Screen. |
E00336 | Cursor(): se esperaba 1 argumento (&variable), se encontraron {found}. |
E00337 | Cursor(): el argumento debe ser una &variable. |
E00338 | Cursor(): ‘&{variable}’ no es la variable de ningún Input ni de ninguna columna del DataGridView del @Layout. |
E00339 | La tabla ‘{table}’ no tiene PrimaryKey en @Indexes: ‘When Duplicate’ relee el registro existente por su llave primaria. |
E00340 | El campo ‘{field}’ es parte de la llave primaria de ‘{table}’: ‘When Duplicate’ no puede reasignarlo. |
E00341 | String(): el relleno debe ser un texto literal de un solo carácter (por ejemplo “0” o “ “). |
E00342 | Int(): se esperaba 1 argumento (un número), se encontraron {found}. |
E00343 | Int(): el argumento debe ser de tipo Number (encontrado {type}). |
E00346 | Function Keys Row ({row}) fuera de rango: debe estar entre 1 y {max} (Rows - 1; la última fila queda para los mensajes). |
E00347 | ‘{color}’ no es un color válido para las teclas de función; use Green, White, Red, Blue, Yellow, Pink o Turquoise. |
E00348 | El tamaño de pantalla {rows}x{columns} no es válido: un 5250 solo admite 24x80 o 27x132. |
E00349 | El formato de registro ‘{name}’ no es válido: hasta {max} caracteres, solo letras y dígitos, y debe empezar por una letra. |
E00350 | El nombre de objeto ‘{name}’ no es válido para IBM i: hasta {max} caracteres, solo letras y dígitos, y debe empezar por una letra. Renombre el archivo (o la propiedad Name). |
E00351 | Return sin valor dentro de la Function ‘{name}’: una Function debe devolver un valor (Return &variable o Return expresión). |
E00352 | La Function ‘{name}’ no devuelve ningún valor: agregue al menos un Return con valor. |
E00198 | LoadRow() sólo puede usarse dentro de un Event o de una Procedure de un Screen (una Function no, porque se invoca como expresión). |
E00199 | Retirado: LoadRow() ya no exige estar dentro de un For Each. |
E00200 | Retirado: ya no hay límite de un solo For Each con LoadRow() dentro de Event Load. |
E00201 | El For Each que carga el DataGridView (con LoadRow()) debe declarar un Index: sin uno, no hay campos ordenados con los que reanudar la carga al presionar Page Down. |
E00353 | {element} termina en la columna {end}, pero la pantalla tiene {max} columnas: IBM i rechaza el campo o lo parte en dos filas. Acorte el texto o los anchos, o mueva el elemento a la izquierda. |
E00356 | {scope} declara la variable local ‘{variable}’, que tapa a la variable global de una Column del DataGridView: LoadRow() copiaría el valor de la local. Renombre la variable local. |
E00357 | El nombre del evento ‘{event}’ no es válido: se usa como nombre de subprocedimiento, así que solo admite letras, dígitos y _ y debe empezar por una letra. El texto que se ve en la fila de teclas va aparte: Event ‘Nombre’ 6 “Texto con espacios”. |
E00358 | {function}() espera {expected} argumento(s), pero recibió {found}. |
E00359 | {function}(): el argumento {arg} debe ser texto (char o varchar), no {type}. |
E00360 | {function}(): el argumento {arg} debe ser numérico, no {type}. |
E00361 | {function}(): el argumento {arg} debe ser un literal: {rule}. |
E00362 | Mod(): el argumento {arg} no puede tener decimales (RPG exige enteros en %rem); use Int() para truncarlo. |
E00363 | &{variable}.Split() solo admite una list de char o varchar (cada trozo es un texto). |
E00364 | Falta ‘EndTry’ para cerrar el bloque ‘Try’. |
E00365 | El código de estado ‘{code}’ de ‘Catch’ no es válido: debe ser un número entre 100 y 9999 (los errores de programa son 100-999 y los de archivo 1000-2999). |
E00366 | {function}() solo puede usarse dentro de un bloque ‘Catch’. |
E00367 | Un ‘Try’ necesita al menos un ‘Catch’. |
E00368 | Este ‘Catch’ nunca se ejecuta: un ‘Catch’ sin filtro anterior (línea {line}) ya atrapa todos los errores. Ponga el ‘Catch’ sin filtro al final. |
E00369 | El parámetro ‘{variable}’ de @Parameters se usa en @Layout y su nombre tiene 10 caracteres o menos: en una Screen el campo DDS toma ese mismo nombre y RPG lo confunde con el parámetro (no compila en IBM i). Use un nombre de más de 10 caracteres. |
E00370 | {function}(): el argumento {arg} debe ser {expected}, se encontró {type}. |
Cada mensaje tiene un código, un nivel (error/warning) y una plantilla con los datos concretos del caso ({variable}, {length}, etc.) que el compilador completa al reportarlo.
Idioma de los mensajes
El compilador trae embebidos los catálogos de mensajes en español y en inglés, con los mismos códigos y los mismos datos de cada plantilla. El idioma se elige con --lang (o -g):
warpgate --action validate --lang en
warpgate --action validate -g es
- Si se omite
--lang, se toma de la variable de entornoLANG(por ejemploes_PE.UTF-8usaes). - Si
LANGno indica un idioma real (C,POSIXo vacía), se usaen. - Un archivo externo
resources/messages_<idioma>.jsonjunto al ejecutable puede sobrescribir mensajes del catálogo embebido.
En VSCode, el idioma se controla con el setting warpgate.language (ver Comandos de VSCode).
Qué cubre el idioma
--lang no afecta sólo a los diagnósticos con línea/columna (E*, W*). Todos los textos de cara al usuario salen del catálogo y respetan el idioma elegido:
- Las líneas de log del CLI en validación, generación, ingeniería inversa y despliegue.
- El progreso y los errores del despliegue (
[Despliegue]en español,[Deployment]en inglés): conectando, subiendo, ejecutando, etc. - Los errores de conexión SSH/FTP/SFTP (por ejemplo, «no se pudo conectar» / «could not connect») y los errores de transporte.
- Los mensajes de licencias: denegaciones del servicio, licencia individual, estado (
license-status) y solicitud (license-request). - Los errores de generación, como
@Connectiono@Deploymentno encontrados.
Estos mensajes usan la serie de códigos M00001–M00106 (en messages_es.json y messages_en.json). A diferencia de los E*, no llevan línea ni columna.
Quedan siempre en español algunos errores internos del generador de código (invariantes del tipo «debería haberlo detectado la validación») y los textos que provienen del sistema operativo (por ejemplo, Connection refused (os error 111)).
Cuándo se disparan
- Al guardar/abrir un
.warpen VSCode (warpgate.validateOnSave/validateOnOpen), si hay un.warprojresuelto para ese archivo. - Manualmente con
warpgate --action validate— no toca IBM i, es puramente local. - Nota de licencia:
validateno exige licencia;generate/build/reversesí (ver Licenciamiento). - Como parte de
generate/build/reverse— si hay errores, no se llega a generar/desplegar código.
IntelliSense en VSCode
La extensión WaRPGate para VSCode agrega soporte de lenguaje para archivos .warp:
- Resaltado de sintaxis: secciones, keywords, tipos de dato, nombres de evento, llamadas a función, operadores.
- Autocompletado (
@/&como triggers): secciones, keywords de@Source, tipos de dato, funciones builtin (con snippet), claves de propiedad por sección, controles de@Layout. - Hover: la misma documentación de este libro, resumida en una línea, al pasar el cursor sobre cualquier keyword/función/tipo.
- Diagnósticos en vivo: valida al abrir/guardar (ver Diagnósticos), pintando subrayados rojos/amarillos directamente en el editor.
- Outline/breadcrumbs: estructura de secciones y
Procedure/Functioncomo símbolos navegables. - Ir a definición y links de documento: saltar a la tabla/programa/pantalla referenciado en
@References. - Layout Preview: vista previa en vivo de
@Layoutcomo una pantalla de terminal IBM i 5250 (24x80 por defecto, o el tamaño configurado en@Screendel.warpcfgdel proyecto). - Comandos: ver Comandos de VSCode para un ejemplo con captura de cada uno (
Validate File,Generate,Build & Deploy,Select Project File,Reverse Engineer DDS,Preview Layout,Select Layout Block).
El IntelliSense se mantiene sincronizado con cada nueva versión del lenguaje, de modo que el resaltado, el autocompletado y los diagnósticos siempre reflejan las capacidades vigentes del compilador.
Instalación
El .vsix empaquetado incluye los binarios del compilador para las 3 plataformas — no hace falta instalar el compilador por separado.
Comandos de VSCode
La extensión WaRPGate agrega 11 comandos a la paleta de comandos (Ctrl+Shift+P / F1), todos con el prefijo WaRPGate:. Esta página muestra un ejemplo real de cada uno, con capturas de pantalla.
WaRPGate: Validate File
Corre el compilador en modo validación sobre el archivo .warp activo: revisa la sintaxis y la semántica (tipos, referencias, variables no declaradas, etc.) sin generar ningún archivo. Los errores y warnings aparecen en el canal de salida “WaRPGate”.
Ejemplo: un Program que asigna una variable no declarada.
@Source {
&CodigoCliente = &Inexistente
}
WaRPGate: Generate
Corre el compilador completo sobre el archivo activo: valida y, si no hay errores, genera los archivos RPGLE/DDS/CL localmente (no despliega nada a IBM i). Es el equivalente a --action generate del CLI.
WaRPGate: Build & Deploy
Genera igual que el comando anterior y además sube los archivos al IBM i configurado en @Connection del .warpcfg, para compilarlos ahí (CRTPF/CRTLF/CRTBNDRPG/CRTDSPF). Como esto se conecta a un sistema real, siempre pide confirmación antes de continuar.
WaRPGate: Select Project File (.warproj)
Cuando el workspace tiene más de un .warproj, este comando elige cuál usar para validar/generar/desplegar. El proyecto elegido queda recordado para los demás comandos.
WaRPGate: Reverse Engineer DDS…
Reconstruye un archivo .warp de tabla a partir de un miembro DDS que ya existe en IBM i. Pide, en orden: la ruta QSYS.LIB del miembro DDS de la tabla, los miembros DDS de los índices (LF) a incluir en @Indexes (opcional), y el nombre del .warp a generar. Al final confirma antes de conectarse.
WaRPGate: Preview Layout
Muestra el @Layout del archivo activo como una pantalla de terminal IBM i 5250 (ver IntelliSense en VSCode). Si el Screen usa Popup: True, el preview dibuja la ventana en su posición y tamaño reales dentro de la pantalla completa.
Ejemplo: un Screen popup con un DataGridView de 3 columnas.
WaRPGate: Select Active Profile (@Profile)
Disponible en la paleta de comandos y en el menú contextual del editor sobre un archivo .warp. Abre un selector con los perfiles (@Profile) declarados en el .warpcfg del proyecto activo, más la opción “Use project default”. La elección queda recordada para ese proyecto (persiste al reiniciar VSCode) y se refleja en una barra de estado propia (ícono de capas), que también abre el mismo selector con un clic.
Sin una elección explícita, la extensión no envía --profile al compilador y el CLI cae solo al Default Profile declarado en @Project — ver Archivo de proyecto.
WaRPGate: Select Layout Block
Cuando un Program (reporte) tiene más de un Block en su @Layout, este comando elige cuál mostrar en el Preview Layout — el preview siempre muestra un único Block a la vez.
WaRPGate: Request License
Pide un nombre o empresa a nombre de quien se emitirá la licencia, abre un diálogo para guardar el archivo request.json y ejecuta --action license-request. Al terminar muestra el identificador del equipo con los botones Copy Machine ID (copia el identificador al portapapeles) y Reveal File (abre la ubicación del archivo en el explorador del sistema). Envía el request.json y el identificador del equipo a warpgate@softwarehouse.pe.
WaRPGate: Install License
Abre un selector para elegir el license.json recibido de Software House. Verifica que sea una licencia (si es el archivo de solicitud o no es un JSON válido, muestra un error) y lo copia a la ruta que lee el compilador: %APPDATA%\warpgate\license.json en Windows, ~/.config/warpgate/license.json en Linux/macOS. Si WARPGATE_LICENSE_FILE está definida, se usa esa ruta.
Si ya existe una licencia en esa ruta, pide confirmación antes de reemplazarla: la anterior deja de valer. Al terminar ofrece el botón Check Status.
WaRPGate: License Status
Ejecuta --action license-status y muestra el estado de la licencia, su vencimiento y el producto. Si warpgate.projectFile está configurado, usa el @License de ese proyecto (y el perfil activo, si hay uno elegido); si no, consulta la licencia individual de la ruta por defecto. El detalle completo queda en el canal de salida “WaRPGate”.
Para el flujo completo de solicitud e instalación, ver Licenciamiento.
Idioma de los mensajes del compilador
El setting warpgate.language (auto | es | en, por defecto auto) define el idioma de los diagnósticos que muestra el compilador. La extensión lo pasa como --lang en todas las llamadas al compilador. Con auto se usa el idioma de VSCode: español si empieza con es, inglés en otro caso.
Este setting solo afecta los mensajes del compilador; no cambia los menús ni los hovers de la extensión. Ver también Diagnósticos.