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

@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.