Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

WARP & Warpgate: la evolución en el desarrollo para IBM i

Read this in English

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:

  1. Abrir VSCode → pestaña Extensiones (Ctrl+Shift+X).
  2. 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 con Member/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 en Output — puramente local, nunca se conecta al IBM i (aunque el CL generado ya referencia rutas remotas, según @Connection/@Deployment).
  • build: hace lo mismo que generate, 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 .warp de 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

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ínea Nombre tipo(...) por columna. Sólo char/number/date — ver Tipos de dato. Modificadores disponibles: Description(...), Default(...), AllowNull.
  • @Indexes: PrimaryKey (obligatoria, sus campos no pueden ser AllowNull), Unique, Index — cada uno como Kind(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 devuelve CurrentInput() cuando el cursor cae ahí. habilitado es opcional (default true): literal, &Variable, Funcion() de retorno number o 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, no LIMA); para ignorar la diferencia al comparar use Upper(&campo). Los Input numé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 Hidden no 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

EventoCuándo dispara
InitUna sola vez, antes del primer despliegue.
LoadUna sola vez, antes del primer despliegue — típicamente llena el DataGridView con LoadRow() (la grilla se reinicia al entrar).
EnterAl presionar Enter (sin tecla de función activa).
RefreshAl presionar F5, si está declarada en @FunctionKeys del proyecto. Después recarga el DataGridView, si lo hay.
CloseFijo 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 entre Input/DataGridView de la misma pantalla.
  • Sólo un DataGridView por Screen.
  • Sólo válido dentro de @Layout de un Screen, nunca de un Program.

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 varchar en @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 ADDPFM si 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 cualquier Export(...) 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 de If/While/For/Event/etc.).
  • En un Screen: sólo a nivel superior de Event Init (no anidado, y no en ningún otro Event) — Init corre una única vez, antes del loop principal de la pantalla, el mismo lugar donde iría el prólogo de un Program:
@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 cualquier For Each/New sobre Tabla en 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 Exit se pueden sobreescribir por objeto en el @Properties de un .warp puntual.
  • @Screen: tamaño de pantalla por defecto (24x80 si se omite) y las teclas de función (@FunctionKeys) disponibles para los Screen del proyecto — la etiqueta que se muestra en la línea de atención. Un Screen liga una tecla a un evento propio con Event 'Nombre' <n> ... EndEvent.
  • @Profile "Nombre": un ambiente de despliegue (Dev, UAT, Prod, el nombre es libre). Cada perfil agrupa su propia @Connection, @Deployment y, opcionalmente, @License. Un .warpcfg debe declarar al menos un @Profile.
    • @Connection: cómo llega el compilador al IBM i para --action build/reverse (no aplica a validate/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). El Host/Port/Fingerprint los 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 .warpcfg tiene Host, manda el servicio: no hay respaldo automático al archivo de licencia individual.
  • Sin dominio (sin Kerberos), license-status en 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.

ClaveDescripción
HostHost del servicio de licencias. Si está presente, el modo es organización y File se ignora.
PortPuerto TCP del servicio, de 1 a 65535. Por defecto 7443.
FingerprintHuella SHA-256 del certificado del servicio: 64 caracteres hexadecimales (se aceptan mayúsculas y separadores :).
SPNNombre de servicio Kerberos. Por defecto warpgate-license/<host>.
FileRuta del archivo de licencia individual.

Sin Host, el modo es individual.

Flags y variables de entorno

FlagVariable de entornoEquivale a
--license-hostWARPGATE_LICENSE_HOSTHost
--license-portWARPGATE_LICENSE_PORTPort
--license-fingerprintWARPGATE_LICENSE_FINGERPRINTFingerprint
--license-spnWARPGATE_LICENSE_SPNSPN
--license-fileWARPGATE_LICENSE_FILEFile

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ódigoSignificado
3No hay licencia disponible. El compilador muestra un mensaje con lo que se debe hacer.
4Se perdió la licencia a mitad de la ejecución.
130Ejecució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.

  1. 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
    
  2. Enviar a warpgate@softwarehouse.pe:
    • El archivo request.json generado.
    • El identificador del equipo que el comando imprime en pantalla (necesario para emitir la licencia).
  3. Software House responde con un archivo license.json firmado, ligado a ese equipo, válido por 90 días.
  4. 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
  5. 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:

  1. WaRPGate: Request License — pide el nombre o empresa, guarda el request.json y muestra el identificador del equipo (botones Copy Machine ID y Reveal File). Enviar ambos a warpgate@softwarehouse.pe.
  2. WaRPGate: Install License — selecciona el license.json recibido y lo copia a la ruta por defecto (pide confirmación si ya existe una licencia, que dejaría de valer).
  3. 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ódigoSituación
E00284Port de @License es 0.
E00285Host de @License no es un host válido.
E00286Fingerprint no es una huella SHA-256 válida.
E00287Aviso: hay Host sin Fingerprint.
E00288Aviso: 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

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)

  1. Dejar license-gui.exe y license-service.exe en la misma carpeta (por ejemplo C:\license). El servicio queda registrado apuntando a esa carpeta: no moverla después.

  2. Abrir license-gui.exe con clic derecho → Ejecutar como administrador. En la pantalla de conexión, pulsar Servidor local: instalar y mantener….

  3. La ventana tiene estas pestañas:

    • 1. Solicitar licencia: escribir el identificador de licencia y pulsar Crear solicitud. Genera request.json y un request-hwid.txt con 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.json recibido y completar:

      • Nombre del servidor: por defecto, el nombre completo del equipo.
      • Puerto: 7443 por defecto.
      • Red de usuarios (firewall): LocalSubnet o un rango (por ejemplo 192.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 SYSTEM y Administradores), crea la regla de firewall si procede, inicia el servicio y comprueba que escucha. Al final muestra la huella y el bloque @License listo 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\:

ArchivoContenido
active_license.jsonLicencia instalada.
tls_cert.der, tls_key.derCertificado TLS y su clave.
seats.sqliteBase de puestos (sesiones).
auth.jsonOpcional: consulta remota y límites (ver secciones 7 y 8).
license-service.logLog 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)

  1. 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
    
  2. Instalar la licencia y generar el certificado, como ese usuario:
    sudo -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>
    
    Anotar la huella SHA-256 que imprime el segundo comando: ese valor es el Fingerprint.
  3. 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 query y también warpgate --action license-status en modo organización) solo se aceptan desde la propia máquina del servicio. Desde otra PC, el servicio rechaza la consulta: warpgate muestra «Su usuario no está autorizado a consumir la licencia de warpgate. Solicite acceso al administrador.», license-gui muestra «no estás autorizado para esta operación», y el servicio anota en su log consulta 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:

TipoArchivosNotas
PFX/P12Un archivo + contraseñaLo que exporta Windows; acepta PFX modernos y antiguos.
PEMCertificado (.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.
DERCertificado 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 (@License y la pantalla de conexión de license-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": N en auth.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íntomaCausa y solución
«no está autorizado» / «no estás autorizado» desde otra PCLas 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 huellaLa huella está mal copiada o el certificado cambió (sección 6).
TcpTestSucceeded : FalseFirewall, red, o el servicio no está escuchando.
El servicio no iniciaRevisar 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ónProgramScreenTable
@Documentationnivel superiornivel superioranidada en @Structure
@Propertiessísísí (sólo Type)
@Structurenonosí
@Fieldsnonoanidada en @Structure
@Indexesnonoanidada en @Structure
@Referencessísíno
@Variablessísíno
@Parameterssínono
@Layoutsí (opcional)síno
@Sourcesí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 un For Each/New.
  • Cada sección de nivel superior lleva @; los nombres de control dentro de @Layout pueden llevar @ opcionalmente (@Label/Label son equivalentes).

Índice de la referencia

@Documentation

Metadatos informativos, no afectan la generación de código.

En Program/Screen (nivel superior)

Pares planos Clave : valor:

ClaveDescripción
Author NameNombre de quien escribió el programa.
Author UserUser ID de quien lo escribió.
App IdIdentificador corto de la aplicación.
App NameNombre visible de la aplicación.
App DescriptionDescripción larga de la aplicación.
Module IdIdentificador corto del módulo.
Module NameNombre visible del módulo.
Module DescriptionDescripción larga del módulo.
Created AtFecha/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 @Creation y cualquier cantidad de @Change.
    • @Creation: Date, Author, User, Company, Reason.
    • @Change (repetir el bloque por cada entrada): Date, Type (por convención breaking/feature/fix/security/performance/refactor/config/docs, no forzado por el compilador), Impact (por convención high/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

ClaveDescripción
TypeEl tipo de objeto que compila este archivo (Program).
NameNombre del programa.
DescriptionDescripció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 ModeModo de impresión, para un programa tipo reporte.
CommitmentSi 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 ExitSi 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.
DDSNombre del DDS al que está atado este programa.
PathCarpeta 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:

ClaveDescripción
PopupGenera 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 RowFila (1-based) de la esquina superior izquierda de la ventana. Requerida si Popup: True.
Window ColumnColumna (1-based) de la esquina superior izquierda de la ventana. Requerida si Popup: True.
Window RowsAlto de la ventana, en filas. Requerida si Popup: True.
Window ColumnsAncho de la ventana, en columnas. Requerida si Popup: True.
Function Keys RowFila 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 ColorColor 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

ClaveDescripción
NameNombre 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).
DescriptionDescripció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:

ModificadorDescripció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.
AllowNullPermite 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] )
}
KindDescripción
PrimaryKeyAcceso primario de la tabla. Sólo uno por tabla; sus campos no pueden ser AllowNull.
UniqueAcceso secundario único.
IndexAcceso 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.

ClaveDescripción
ProgramsOtros Program .warp que este referencia (para Call(Nombre, ...) resuelto en tiempo de compilación).
ScreensScreen .warp que este referencia — igual que Programs, habilita invocarlo con Call(Nombre, ...) (típicamente un Screen con @Properties/Popup: True, mostrado como ventana emergente).
TablesTablas .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; en Screen no está soportado.
  • No puede repetir un nombre ya declarado en global (E00298).
  • No admite templates de struct (E00234, como cualquier ámbito distinto de global).
  • 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 @Parameters puede declararse en global o en local. En local solo lo ve el cuerpo principal, no los Procedure/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, varchar y number; cualquier otro tipo produce E00310.
  • 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 produce E00311.
  • Se puede leer en cualquier expresión, pero nunca modificar: asignarla, usarla como contador de un For, pasarla como argumento out/inout de un Call o como destino de un Input produce E00312. Pasarla a una Procedure/Function que modifica ese parámetro produce E00179.
  • Un parámetro de @Parameters no puede ser una constante (E00313).
  • En un Block imprimible de un Program se puede mostrar, por ejemplo con @Label(1,1,&Titulo). En una Screen no 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 hasta n, de modo que se comporta como una variable char(n); un varchar no se rellena.
  • Un número se escribe con exactamente los decimales de su escala (const Precio number(9,2) = -5.5 genera dcl-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)

TipoLímite en @FieldsLímite en @Variables
char1–32.7661–16.773.104
varcharno válido en @Fields1–16.773.100
number (precisión)1–301–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

TipoSintaxisDóndeDescripción
charchar(longitud)@Fields, @VariablesCadena de longitud fija. longitud requerida (1-32.766 en @Fields; 1-16.773.104 en @Variables).
varcharvarchar(longitud)sólo @VariablesCadena de longitud variable. longitud requerida (1-16.773.100). Length(...) devuelve el contenido real en tiempo de ejecución, a diferencia de char.
numbernumber(precisión, escala)@Fields, @VariablesNumérico. precisión requerida (1-30 en @Fields; 1-63 en @Variables); escala opcional, default 0.
datedate@Fields, @VariablesFecha, sin parámetros. En @Fields no puede llevar Default(...).
timetime@Fields, @VariablesHora (hora/minuto/segundo), sin parámetros. En @Fields no puede llevar Default(...).
timestamptimestamp@Fields, @VariablesFecha y hora combinadas (con microsegundos), sin parámetros. En @Fields no puede llevar Default(...).
fieldfield(NombreCampo) o field(Tabla.Campo)sólo @VariablesHereda el tipo de un campo de @Structure de alguna tabla referenciada. Ver field: heredar el tipo de un campo.
listlist(tipo)sólo @VariablesLista 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).
matrixmatrix(filas, cols, tipo)sólo @VariablesGrilla 2D de tamaño fijo filas x cols de tipo. Se lee/escribe un elemento con &Variable[fila, col].
structstruct(NombreTemplate)sólo @VariablesInstancia 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 reporta E00149 (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 reporta E00148 (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étodoUsoDescripción
Add(valor)sentenciaAgrega 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()sentenciaVacía la lista: limpia los elementos en uso (blancos o ceros) y deja la cuenta en 0.
Init()sentenciaDeja 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)sentenciaQuita 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]])sentenciaReemplaza el contenido de la lista (limpia lo anterior) con los trozos de texto. Ver Split().
Count()expresiónDevuelve la cantidad actual de elementos (tipo number). Sólo se puede usar dentro de una expresión, nunca como sentencia suelta.
Json()expresiónDevuelve 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() y JsonSplit dejan 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)» con dsply y descarta el resto. Un trozo más largo que el elemento se recorta.
  • Sin separadores, corta por blancos.
  • Cada carácter de separadores es 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 palabra AllSep, que exige indicar separadores), cada separador delimita dos trozos y aparecen los vacíos: el mismo texto da 4 trozos: uno, dos, `` y tres.
  • Un texto vacío da 0 trozos.
  • Los blancos finales de un char de origen se ignoran. Una variable char de separadores se recorta: sus blancos finales no son separador (para un blanco como separador use un literal).
  • texto y separadores deben ser char/varchar (E00359); se admiten de 1 a 3 argumentos (E00358); el tercero debe ser la palabra AllSep (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. Split genera for-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.

ReleasePTF de compiladorPTF de runtime
7.3SI76100SI76098
7.4SI76101 (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 miembro list(struct(...)) un arreglo, p.ej. {"Cab":{"epm":"a","Detalle":[{"Pgm":"P1","Libreria":"L1"}]},"Total":2}.
  • Los miembros escalares de un struct sólo pueden ser char/varchar/number/date/field; el generador también sabe emitir time ("hh:mm:ss") y timestamp ("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.

FormaUsoDescripción
&reg.FixedWidth()expresiónDevuelve la trama del struct: un char del ancho total. Usarlo como sentencia da E00306.
&reg.FromFixedWidth(&texto)sentenciaLlena el struct desde una trama (operación inversa). &texto es char/varchar o un literal. Usarlo como expresión da E00306.
FixedWidthSplit(&registros, &lineas)sentenciaConvierte 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, &registros)sentenciaOperación inversa: lee cada línea de &lineas como un registro de &registros. Reemplaza el contenido de &registros.

Formato por miembro:

TipoPosicionesFormato
char(n) / varchar(n)nAlineado a la izquierda, relleno con blancos (se recorta si sobra).
number(p,s)pAlineado 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.
date8YYYYMMDD.
time6HHMMSS.
timestamp20YYYYMMDDHHMMSSnnnnnn (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 number se leen con decimal implícito y signo - en la primera posición; las date en formato YYYYMMDD. 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) y list(struct(OtroTemplate)) (ver Structs anidados). time y timestamp no se admiten como miembro, ni matrix ni list de 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/Function propia — ver Procedure / Function.

  • Un arreglo de struct se declara con list(struct(NombreTemplate)) — ver List: métodos.

  • El indexado &Lista[i] de una list(struct(Template)) es válido, pero sólo en dos formas exactas: como lectura, el valor completo de una asignación a otra &Variable que sea struct(...) del mismo template (&otraVariable = &Lista[i]); como escritura, &Lista[i] = &otraVariable (otra &Variable struct(...) 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 reporta E00241; si el template no coincide exactamente con el de la lista, se reporta E00242. Los chequeos de índice (cantidad, tipo number, rango 1..9999) aplican igual que para cualquier otra list:

    &otroRegistro = &Lista[1]
    &Lista[1] = &otroRegistro
    For &i = 1 To &Lista.Count()
        &actual = &Lista[&i]
    EndFor
    
  • No soportado todavía: struct como parámetro de un programa externo (@Parameters/Call) — sólo como parámetro de una Procedure/Function interna.

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 list que es miembro de un struct reserva 100 elementos (no los 9999 de una list suelta). Add sobre una lista llena muestra un mensaje y no agrega el elemento, igual que en la list suelta. Un índice literal mayor que 100 se reporta como E00188.
  • 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. una list(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) o list(struct(X)) dentro de un miembro con un X no declarado se reporta como E00238.
  • No admitido como miembro: matrix y list de tipos escalares (E00237); tampoco time ni timestamp (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 (no const).
  • 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 con inz(*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á.

ModoDescripción
inParámetro de entrada: quien llama pasa un valor, este programa no puede devolverlo modificado.
inoutEntrada/salida: quien llama pasa un valor, y este programa puede modificarlo de vuelta.
outSalida: 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 @Layout debe tener más de 10 caracteres: con 10 o menos da el error E00369, 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

ControlSintaxisVálido enDescripción
LabelLabel(x, y, texto[, Style(...)]...)Program, ScreenEtiqueta 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.
BlockBlock("Nombre")[ Label(...), ... ]ProgramGrupo nombrado e imprimible de Labels — se imprime una instancia con Print("Nombre") desde @Source.
InputInput("Nombre", x, y, texto, &Variable[, habilitado][, Style(...)]...)ScreenCampo 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.
ColumnColumn(texto, variableOCampo[, Input|Hidden][, Style(...)]...)sólo dentro de DataGridViewvariableOCampo 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.
DataGridViewDataGridView("Nombre", x, y, filas[, separador])[ Column(...), ... ]ScreenGrilla 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 retorno number; no-cero = habilitado.
  • Una condición completa: comparaciones (= <> < <= > >=), And, Or, Not, números, textos, variables y llamadas Funcion() 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):

NombreDDS
GreenCOLOR(GRN)
WhiteCOLOR(WHT)
RedCOLOR(RED)
BlueCOLOR(BLU)
YellowCOLOR(YLW)
PinkCOLOR(PNK)
TurquoiseCOLOR(TRQ)

Atributos (combinables entre sí y con un color):

NombreDDSEfecto
ReverseDSPATR(RI)Video inverso.
HighDSPATR(HI)Alta intensidad.
UnderlineDSPATR(UL)Subrayado.
BlinkDSPATR(BL)Parpadeo.
HiddenDSPATR(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 Style sin condición (el “normal”): se aplica sólo si ningún Style con condición está activo.
  • Hasta tres Style con condición. Más de los permitidos da E00323.
  • Si dos Style con condición se cumplen a la vez, gana el primero declarado.
  • Cada Style con 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, un chain por el archivo lógico de la llave primaria) una vez por cada registro leído de la tabla recorrida. El Join no declara Index.
  • Sin On (implícito): cada campo de la PrimaryKey de 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 su PrimaryKey, 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 Where y 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. Un Where sobre un campo que sólo existe en la tabla unida se evalúa dentro del bucle, después del chain (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 tener PrimaryKey en @Indexes (si no, E00315).
  • Join y On no son palabras reservadas: sólo se reconocen en la cabecera del For Each (Join seguido de In), así que siguen sirviendo como nombres de campo.
  • DataGridView: el For Each que carga la grilla en Event Load también admite Join In. Los campos de la tabla unida se asignan a las variables de las Column antes de LoadRow(), igual que los de la tabla recorrida. Un Where sobre un campo exclusivo de la unida filtra las filas después del chain.

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

  1. Se hace una lectura (chain) por la PrimaryKey de la tabla, con los valores que el New acaba de armar.
  2. Si la fila no existe, se escribe la fila nueva (write) y el bloque When Duplicate no corre.
  3. 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:

  1. Los campos de la tabla del New (la fila releída; se pueden leer y asignar).
  2. Los campos de los For Each que envuelven al New (sólo lectura).

Reglas

  • La tabla debe tener una PrimaryKey en @Indexes; si no, da E00339. Ya no depende de que algún For Each use un Index sobre 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.
  • Exit directamente dentro del bloque da E00326; dentro de un While/For propio del bloque sí es válido.
  • Delete() no es válido dentro del bloque (aplica el error habitual de Delete() fuera de un For 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 con And/Or/Not).
  • Dentro de una rama se puede usar cualquier sentencia, incluidos Exit y LoadRow() (esta última, sólo donde LoadRow() es válido: ver DataGridView).
  • Se traduce al select/when/other/endsl de RPG. Reemplaza al encadenado de If/Else anidados, ya que WARP no tiene Else If.
  • Case, Otherwise y EndCase (igual que Exit) son palabras reservadas: no pueden usarse como nombre de campo, de variable declarada ni de procedimiento; hay que renombrarlos. Un Case, Otherwise o EndCase fuera de un Do Case da E00327.
  • Un error de sintaxis (sin ramas Case o sin EndCase) da E00008.

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 Exit dentro de un While anidado en un For Each sale del While, no del For Each.
  • Grabado en For Each In: si el cuerpo ya asignó campos de la tabla antes del Exit, el compilador graba (update) el registro antes de salir. Si hubo un Delete() antes, no se graba.
  • When None y When Duplicate: el cuerpo de When None (de For Each) y el de When Duplicate (de New) no cuentan como bucle; un Exit ahí se refiere al bucle exterior, si existe.
  • For &i = ...: &i conserva su valor al salir.
  • Dentro de un Do Case que está dentro de un bucle es válido.
  • Procedure, Function y Event: un Exit no sale del bucle de quien la llama; debe haber un bucle en el mismo cuerpo. Fuera de un bucle da E00326.
  • Exit es 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 @Properties del objeto o en el @GeneratorConfig del proyecto. Sin él la generación falla con «Commit requiere Commitment activo (en @Properties del objeto o en @GeneratorConfig)» (igual para Rollback).
  • Dónde: válidas en Program y Screen, en cualquier lugar del @Source, también dentro de un Procedure, un Event o un bucle.
  • En Screen: con Commitment activo, el Screen inicia STRCMTCTL LCKLVL(*CHG) al arrancar y abre bajo control de compromiso las tablas que modifica; Commit y Rollback funcionan igual que en un Program (también dentro de un Event o un Procedure).
  • 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, Commit primero graba (update) el registro pendiente y luego confirma. Rollback, en cambio, no debe ir dentro del For Each tras asignar campos, porque el registro se graba al final de la vuelta; colócalo fuera del bucle, después de EndFor.
  • Commit on Exit: Commit on Exit : True sigue confirmando solo al terminar el programa; en un Screen, al cerrar la pantalla (F3). Con Commit on Exit : False, lo no confirmado al terminar se deshace al terminar el trabajo.
  • No son palabras reservadas: un campo llamado Commit sigue 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):

FormaAtrapaRPG
CatchCualquier error.on-error;
Catch FileErrores de archivo (*FILE: estados 01000-02999 en IBM i 7.5).on-error *file;
Catch ProgramErrores de programa (*PROGRAM: 00100-00999 y 9999).on-error *program;
Catch 102, 202Los códigos de estado indicados, separados por comas (cada uno entre 100 y 9999; E00365 si no).on-error 00102 : 00202;
  • Orden: los Catch se evalúan en orden y gana el primero que cubra el error. Un Catch sin filtro debe ir al final: si hay otro después, es el error E00368 («nunca se ejecuta»).
  • Estructura: un Try necesita al menos un Catch (E00367) y su EndTry (E00364).
  • ErrorCode() y ErrorMessage(): solo valen dentro de un Catch; fuera dan E00366. ErrorCode() devuelve un number(5) con el estado del error (%status) y ErrorMessage() un char(80) con el texto del mensaje (posiciones 91-170 de la PSDS). Ver Funciones builtin.
  • Un error dentro de un Catch no lo atrapa el mismo Try: hay que anidar otro Try.
  • Exit y Return se admiten dentro de Try y de Catch.
  • Con Commitment: es útil combinar Catch con Rollback para deshacer los cambios cuando algo falla.

Códigos de estado frecuentes:

EstadoSituación
00100Valor fuera de rango en una operación de cadena.
00101Raíz cuadrada de un número negativo.
00102División por cero (también Mod con divisor cero).
00103El resultado no cabe en el campo.
00105Carácter inválido al convertir a número (p. ej. Val).
00112Fecha u hora inválida.
00121Índice de arreglo inválido.
00202Falló la llamada a un programa o procedimiento. Un error dentro de un procedimiento llamado llega al llamador como 00202.
00211Error al llamar: el programa llamado no existe o no se puede resolver (Call a un programa inexistente).
00907Dato decimal inválido.
01211Archivo 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(&param)
    ...
EndProc

Function Nombre(&param) tipoRetorno
    ...
    Return expr
EndFunc
  • Procedure (sin retorno, sub es sinónimo): se invoca con Do 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

SentenciaUso
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

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 a Tabla para todo el programa (ADDPFM + OVRDBF, antes de cualquier open). En Program, sólo a nivel superior de @Source; en Screen, sólo a nivel superior de Event Init (ver @Source: Event y Screen). A lo sumo una vez por tabla.
  • Export(Tabla, destino): copia el miembro activo de Tabla a un stream file del IFS (CPYTOIMPF, delimitado por coma). Sólo válido en Program (no en Screen); 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 &Variable o miembro de struct char/varchar, o un texto literal. Cualquier otra expresión da E00301.
  • 2.º argumento: una &Variable list(varchar(n)) o list(char(n)). Si no, E00302; tampoco puede ser un parámetro in (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 mensaje JsonSplit: 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

EventoCuándo dispara
EnterAl presionar Enter (sin tecla de función activa). CurrentInput() identifica qué Input tenía foco, decodificado de la posición del cursor tras EXFMT.
CloseFijo a F3 en el DSPF generado (CF03) — siempre corre (incluso vacío) y termina la pantalla; no requiere declaración para atar la tecla.
LoadUna 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).
RefreshAl 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.
InitUna 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 error E00357. El texto con espacios va como tercer valor: Event 'Nombre' 6 "Texto con espacios".
  • El texto del Event tiene prioridad sobre el que se declare para esa misma tecla en @FunctionKeys del .warpcfg del 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=Buscar en el segundo ejemplo).
  • El color de la tecla manda sobre Function Keys Color (de @Properties o 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 @FunctionKeys no admiten color propio; usan Function 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.

SentenciaUso
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 ... EndForRecorre 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 Input compara 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 Hidden nunca coincide.
  • Mismo ámbito que CurrentInput(): Event, Procedure o Function de un Screen (E00192 en otro lugar). Si se invoca desde Event 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ún Input ni 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.

  • Refresh vuelve a la primera fila.
  • Refresh Keep conserva 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 dan E00335. Si el Screen no tiene DataGridView, no hacen nada.
  • Se pueden llamar desde cualquier Event o Procedure.
  • No son palabras reservadas: un Event Refresh y un campo llamado Refresh siguen funcionando.
  • Limitación: la recarga repite la secuencia de la primera carga. Un Refresh dentro de Event Load se 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 Input del @Layout (E00332).
  • No recibe argumentos (E00333).
  • No es una expresión; no puede usarse dentro de una (E00334).
  • Si hay varios SetFocus en una misma vuelta, gana el primero.
  • Se puede usar en Event Init para 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ónRetornoDescripció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()dateFecha 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ónParsea 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ónVerdadera 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)NumberDevuelve 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 argumentoQuita blancos a la izquierda. Un solo argumento.
RTrim(&Variable)igual al argumentoQuita blancos a la derecha. Un solo argumento.
Upper(&Variable)igual al argumentoConvierte 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 argumentoConvierte un char/varchar a minúsculas, incluidas las letras acentuadas. Mismas reglas que Upper. Se traduce a %lower.
Length(&Variable)numberTamañ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)numberBusca 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)varcharReemplaza 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ónVerdadera 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ónVerdadera si texto empieza con buscar. Mismas reglas que Contains.
EndsWith(texto, buscar)condiciónVerdadera 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 textoCambia cada carácter de desde por el que ocupa la misma posición en hasta (%xlate).
Abs(n)numberValor absoluto (%abs).
Sqrt(n)numberRaíz cuadrada (%sqrt).
Mod(a, b)numberResto 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)numberPosició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)varcharUne dos textos (+ de RPG). Cada operando conserva sus blancos.
Div(numero, divisor)numberCociente entero, truncado hacia cero (Div(-7, 2) es -3). Mapea a %div.
Min(numero1, numero2)numberEl menor de dos números. Mapea a %min.
Max(numero1, numero2)numberEl mayor de dos números. Mapea a %max.
DateTimeToTimestamp(fecha, hora)timestampUne 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ónRetornoDescripció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)numberDiferencia 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)numberIgual que DateDiff, sobre time, con Hours/Minutes/Seconds.
TimestampDiff(&Ts1, &Ts2, Unidad)numberIgual que DateDiff, sobre timestamp, con las 6 unidades habilitadas: Seconds/Minutes/Hours/Days/Months/Years.
DateAdd(&Fecha, cantidad, Unidad)dateSuma (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)timeIgual que DateAdd, sobre time, con Hours/Minutes/Seconds.
TimestampAdd(&Ts, cantidad, Unidad)timestampIgual 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])dateConvierte char/varchar a date. format admite también un patrón de texto.
NumberToDate(number[, format])dateConvierte number a date — mismas reglas de format que StringToDate (incluye patrones de texto).
StringToTime(string[, format])timeConvierte char/varchar a time.
StringToTimestamp(string[, format])timestampConvierte 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()timestampTimestamp 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 %subst por posición fija y los lee como *iso.
  • char/varchar: el valor debe tener exactamente el largo del patrón. Un varchar más corto falla en ejecución.
  • NumberToDate/IsDate con number: 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). El number debe tener tantos dígitos como el patrón (p.ej. number(8) para "DDMMYYYY").
  • DateToString: devuelve char del largo del patrón (máximo 10); p.ej. "YYYYMMDD" da char(8).
  • No se admite año de 2 dígitos (YY), campos repetidos o faltantes, ni letras distintas de Y/M/D: se reporta E00253.
&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, StartsWith y EndsWith son condiciones: se escriben directamente en If, While, And u Or, igual que Cursor.
  • Replace, Contains, StartsWith, EndsWith y Translate tratan los blancos de los argumentos como parte del texto. En un char con blancos finales, aplique Trim() antes de comparar.
  • PadLeft y PadRight devuelven siempre un char del ancho indicado; ancho y relleno deben ser literales (E00361 en 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.
  • Sqrt de un número negativo y Mod con divisor cero son errores de ejecución de RPG (estados 00101 y 00102) que abortan el programa si no se atrapan con Try/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:

FormaRPG
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:

LlamadaResultado
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

OperadorUso
+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:

  1. Después de un operador: + - * /, las comparaciones = <> < <= > >=, And, Or, Not, y después del = de una asignación.
  2. 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 de Procedure/Function y expresiones entre paréntesis.
  3. And/Or al 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 un For Each/New sobre 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/Remove como sentencia, Count() como expresión) — ver Tipos de dato: List.
  • Método Json() de struct, de list(struct(...)) y de list de 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 &reg.FixedWidth() y sentencias &reg.FromFixedWidth(&texto), FixedWidthSplit(&registros, &lineas), FixedWidthParse(&lineas, &registros) — 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

ClaveDescripción
NameNombre del proyecto.
DescriptionDescripción del proyecto.
VersionVersión del proyecto (string libre, p.ej. 1.0).
Default ProfileNombre 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)

ClaveDescripción
NameNombre del generador.
DescriptionDescripción del generador.
LanguageLenguaje de destino. Valores: RPGLE, RPG.

@Paths (anidada en @Generator)

ClaveDescripción
ConfigRuta al .warpcfg de este proyecto.
OutputCarpeta donde se escribe el DDS/RPG generado.

.warpcfg — @GeneratorConfig

ClaveDescripciónValores
Date FormatFormato 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 SeparatorCarácter separador de fecha (p.ej. / en date(*dmy/)). Un solo carácter.—
Time FormatFormato 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 SeparatorCará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 FormatDefault 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 ModeQué 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
CommitmentHabilita 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 ExitSi 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 NamePatró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):

  1. El flag --profile <Nombre> del CLI, sin distinguir mayúsculas.
  2. Si se omite, Default Profile de @Project (sección .warproj — @Project más arriba).
  3. Si tampoco hay Default Profile pero el .warpcfg sólo declara un @Profile, se usa ese.
  4. 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)

ClaveDescripción
RowsCantidad 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.
ColumnsCantidad de columnas de pantalla. Numérico, default 80 (132 con Rows : 27).
Function Keys RowFila 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 ColorColor 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:

ClaveDescripciónValores
HostHostname o IP del IBM i. Requerida.—
UserPerfil de usuario para conectar. Requerida.—
Auth MethodCó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 FileRuta al archivo de clave privada. Requerida sólo si Auth Method: key.—
ProtocolProtocolo de transporte. Requerida. ftp no puede combinarse con Auth Method: key.ssh, sftp, ftp
PortPuerto TCP. Numérico, default 22.—
TimeoutTimeout 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.

  1. Generar el par de llaves (en la máquina donde corre el compilador/VSCode, no en el IBM i):
    ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_ibmi -N ""
    
    Esto crea id_ed25519_ibmi (privada — la ruta que va en Key File) y id_ed25519_ibmi.pub (pública). -N "" deja la llave sin passphrase, requisito para Auth Method: key.
  2. Copiar el contenido de id_ed25519_ibmi.pub (una sola línea) al final del archivo .ssh/authorized_keys dentro 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.
  3. Verificar que Key File en @Connection apunte a la ruta de la llave privada en la máquina local (no la .pub), y que Auth Method sea key.

@Deployment (anidada en @Profile)

Dónde/cómo desplegar y compilar en el IBM i:

ClaveDescripciónValores
CL SourceNombre del member de fuente CL generado (nombre de objeto OS/400, máx. 10 caracteres). Requerida.—
CleanupSi se elimina el fuente staged tras compilar exitosamente.true, false
Target ReleaseRelease de OS/400 destino de la compilación (p.ej. V7R4M0).—
OptimizationNivel 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
DebugVista 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 PathDirectorio staging del IFS usado durante el deploy. Requerida.—
Data LibraryBiblioteca donde se crean los objetos físico/lógico de DDS compilados (nombre de objeto OS/400). Requerida.—
Objects LibraryBiblioteca 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 TablesMember de fuente que contiene el fuente DDS de físicos/lógicos (nombre de objeto OS/400). Requerida.—
Source ProgramsMember de fuente que contiene el RPGLE/CL generado (nombre de objeto OS/400).—
Compile LibraryLista 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.

ClaveDescripciónValores
HostHost del servicio de licencias. Con Host el modo es organización y File se ignora; sin Host, el modo es individual.—
PortPuerto TCP del servicio. Por defecto 7443.1-65535
FingerprintHuella SHA-256 del certificado del servicio (acepta : y mayúsculas).64 hex
SPNNombre de servicio Kerberos. Por defecto warpgate-license/<host>.—
FileArchivo 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

NivelSignificado
errorImpide la generación de código; el objeto no compila.
warningNo 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ódigoSituación
E00001Se esperaba una sección @Nombre y se encontró otra cosa.
E00128Un 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.
E00148El 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.
E00149El campo de field(Campo) es ambiguo: existe en más de una tabla de @References/Tables; indique la tabla con field(Tabla.Campo).
E00152Una variable de @Variables fue declarada pero nunca usada (warning).
E00178Una 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.
E00179No 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).
E00195Uso inválido de RefreshSelectRow()/For Each Row fuera de un Screen con DataGridView.
E00206String(): se esperaban de 2 a 4 argumentos (numero, enteros[, decimales][, relleno]), se encontraron {found}.
E00210Dos 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).
E00221char fuera del rango permitido para una variable standalone en @Variables (1-16.773.104).
E00222number con precisión fuera de rango (1-63) en @Variables.
E00223number cuya escala es mayor que su precisión.
E00224varchar fuera del rango permitido (1-16.773.100).
E00237Tipo 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).
E00238struct(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)).
E00240Acceso &Variable.Miembro a un miembro que no existe en el template del struct.
E00242Se pasó una variable struct(OtroTemplate) donde se esperaba un template distinto (la comparación es por nombre exacto, no por estructura).
E00253El 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).
E00269La Description de una tabla (@Structure) supera los 50 caracteres que admite TEXT en DDS.
E00270La Description de un campo (@Fields) supera los 50 caracteres que admite TEXT en DDS.
E00271La Description de un índice (@Indexes) supera los 50 caracteres que admite TEXT en DDS.
E00278list no tiene el método invocado (válidos: Add, Clear, Init, Remove, Count, Split, Json).
E00281La 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.
E00284Port de @License es 0.
E00285Host de @License no es un host válido.
E00286Fingerprint de @License no es una huella SHA-256 válida.
E00287Aviso: @License tiene Host pero no Fingerprint.
E00288Aviso: @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.
E00292El perfil pedido con --profile o Default Profile no existe entre los @Profile declarados (se listan los disponibles).
E00293Hay 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.
E00296Se 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.
E00298Una 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.
E00299El template de struct ‘{template}’ se contiene a sí mismo (directa o indirectamente) a través de sus miembros struct/list(struct).
E00300La 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).
E00301JsonSplit(): el primer argumento debe ser una &Variable (o miembro de struct) char/varchar, o un texto literal; se encontró {found}.
E00302JsonSplit(): ‘&{variable}’ debe ser una list de char o varchar (ahí se guardan las líneas).
E00303JsonSplit(): la sangría debe ser un número, se encontró {found}.
E00304El 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})).
E00310La constante ‘{name}’ debe ser de tipo char, varchar o number.
E00311El valor de la constante ‘{name}’ no es válido para su tipo: {detail}.
E00312La constante ‘&{variable}’ no se puede modificar.
E00313El parámetro ‘&{variable}’ de @Parameters no puede ser una constante.
E00314La 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.)
E00315La tabla ‘{table}’ no tiene PrimaryKey en @Indexes: ‘Join In’ la lee por su llave primaria.
E00316Join 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.
E00317Join 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.
E00318Join In {table}: el campo ‘{field}’ ({target_type}) no acepta ‘{source}’ ({source_type}).
E00319El campo ‘{field}’ pertenece a la tabla ‘{table}’ unida con ‘Join In’ y es de sólo lectura: no se puede asignar.
E00320Style: ‘{name}’ no es un color ni un atributo conocido. Colores: Green, White, Red, Blue, Yellow, Pink, Turquoise. Atributos: Reverse, High, Underline, Blink, Hidden.
E00321Style: sólo se admite un color por Style; se encontró ‘{name}’ además de ‘{previous}’.
E00322La condición no es válida en @Layout: sólo admite variables, números, textos, comparaciones, And/Or/Not y llamadas Funcion() sin argumentos.
E00323Demasiados Style en el mismo elemento: se admite a lo sumo uno sin condición y tres con condición.
E00324Style sin color ni atributo: indique al menos uno (por ejemplo Style(Red) o Style(Reverse)).
E00325Style sólo se admite en un Screen.
E00326Exit 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).
E00328Round(): se esperaban 2 argumentos (expresión, decimales), se encontraron {found}.
E00329Round(): ‘decimales’ debe ser un literal entero entre 0 y {max} (constante en tiempo de compilación: define la escala del resultado).
E00330Round(): el primer argumento debe ser de tipo Number (encontrado {type}).
E00344El texto de la tecla del evento ‘{event}’ no puede estar vacío ni pasar de {max} caracteres.
E00345El 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.
E00331SetFocus() sólo es válido en un Screen.
E00332SetFocus(): ‘&{variable}’ no es la variable de ningún Input del @Layout.
E00333SetFocus() no recibe argumentos (se pasaron {found}).
E00334SetFocus() es una sentencia: no devuelve ningún valor y no puede usarse dentro de una expresión.
E00335Refresh sólo es válido en un Screen.
E00336Cursor(): se esperaba 1 argumento (&variable), se encontraron {found}.
E00337Cursor(): el argumento debe ser una &variable.
E00338Cursor(): ‘&{variable}’ no es la variable de ningún Input ni de ninguna columna del DataGridView del @Layout.
E00339La tabla ‘{table}’ no tiene PrimaryKey en @Indexes: ‘When Duplicate’ relee el registro existente por su llave primaria.
E00340El campo ‘{field}’ es parte de la llave primaria de ‘{table}’: ‘When Duplicate’ no puede reasignarlo.
E00341String(): el relleno debe ser un texto literal de un solo carácter (por ejemplo “0” o “ “).
E00342Int(): se esperaba 1 argumento (un número), se encontraron {found}.
E00343Int(): el argumento debe ser de tipo Number (encontrado {type}).
E00346Function 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.
E00348El tamaño de pantalla {rows}x{columns} no es válido: un 5250 solo admite 24x80 o 27x132.
E00349El formato de registro ‘{name}’ no es válido: hasta {max} caracteres, solo letras y dígitos, y debe empezar por una letra.
E00350El 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).
E00351Return sin valor dentro de la Function ‘{name}’: una Function debe devolver un valor (Return &variable o Return expresión).
E00352La Function ‘{name}’ no devuelve ningún valor: agregue al menos un Return con valor.
E00198LoadRow() sólo puede usarse dentro de un Event o de una Procedure de un Screen (una Function no, porque se invoca como expresión).
E00199Retirado: LoadRow() ya no exige estar dentro de un For Each.
E00200Retirado: ya no hay límite de un solo For Each con LoadRow() dentro de Event Load.
E00201El 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.
E00357El 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}.
E00362Mod(): 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).
E00364Falta ‘EndTry’ para cerrar el bloque ‘Try’.
E00365El 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’.
E00367Un ‘Try’ necesita al menos un ‘Catch’.
E00368Este ‘Catch’ nunca se ejecuta: un ‘Catch’ sin filtro anterior (línea {line}) ya atrapa todos los errores. Ponga el ‘Catch’ sin filtro al final.
E00369El 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 entorno LANG (por ejemplo es_PE.UTF-8 usa es).
  • Si LANG no indica un idioma real (C, POSIX o vacía), se usa en.
  • Un archivo externo resources/messages_<idioma>.json junto 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 @Connection o @Deployment no 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 .warp en VSCode (warpgate.validateOnSave/validateOnOpen), si hay un .warproj resuelto para ese archivo.
  • Manualmente con warpgate --action validate — no toca IBM i, es puramente local.
  • Nota de licencia: validate no exige licencia; generate/build/reverse sí (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/Function como 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 @Layout como una pantalla de terminal IBM i 5250 (24x80 por defecto, o el tamaño configurado en @Screen del .warpcfg del 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.