@Source: sentencias de control
El cuerpo imperativo de un Program/Screen: asignaciones, control de flujo, acceso a tablas, declaraciones de Procedure/Function, invocaciones y reportes.
For Each In: recorrer una tabla
For Each In Tabla [Index NombreIndice]
[Where Campo = expr]...
// cuerpo, corre por cada fila encontrada
[When None
// corre si no hubo ninguna fila
]
EndFor
Index: opcional, selecciona el acceso (por defecto el de acceso secuencial).Where: cero o más, filtra por igualdad.When None: opcional, corre si el loop no encontró ninguna fila.
Join In: unir otra tabla
For Each In Pedidos
Join In Clientes On CodCli = CodCli
Join In Zonas
Where Estado = "A"
&VNombre = NomCli
&VZona = NomZona
EndFor
Sintaxis: una o más líneas Join In Tabla2 [On campoLlave = expr [And campoLlave2 = expr2 ...]] inmediatamente después de For Each In Tabla [Index Idx] y antes de los Where.
- La tabla unida se lee por su
PrimaryKey(en RPG, unchainpor el archivo lógico de la llave primaria) una vez por cada registro leído de la tabla recorrida. ElJoinno declaraIndex. - Sin
On(implícito): cada campo de laPrimaryKeyde la tabla unida debe existir con el mismo nombre en la tabla recorrida o en otra tabla unida antes (si no,E00316). - Con
On: los campos del lado de la tabla unida deben ser exactamente los de suPrimaryKey, sin repetir ni omitir (el orden da igual; si no,E00317). El otro lado puede ser un campo de la tabla recorrida o de una tabla unida antes, o bien una variable o expresión. Si ambos lados son campos de la tabla unida, el izquierdo es el de la unida. Los tipos deben ser compatibles (E00318). - Sin pareja: si no hay registro con esa llave en la tabla unida, el bucle no omite la fila; continúa y los campos de la tabla unida quedan en blanco o en cero.
- Visibilidad: todos los campos de las tablas unidas se pueden usar en los
Wherey en el cuerpo. Si un nombre existe en la tabla recorrida y en la unida (típicamente los de la llave), se usa siempre el de la tabla recorrida. UnWheresobre un campo que sólo existe en la tabla unida se evalúa dentro del bucle, después delchain(no posiciona el archivo). - Sólo lectura: asignar un campo de una tabla unida da
E00319. - Se pueden encadenar varios
Join In. La tabla unida debe tenerPrimaryKeyen@Indexes(si no,E00315). JoinyOnno son palabras reservadas: sólo se reconocen en la cabecera delFor Each(Joinseguido deIn), así que siguen sirviendo como nombres de campo.- DataGridView: el
For Eachque carga la grilla enEvent Loadtambién admiteJoin In. Los campos de la tabla unida se asignan a las variables de lasColumnantes deLoadRow(), igual que los de la tabla recorrida. UnWheresobre un campo exclusivo de la unida filtra las filas después delchain.
Delete(): borrar la fila actual
For Each In Tabla
If &Vencido
Delete()
EndIf
EndFor
Sin argumentos — borra la fila que la iteración tiene cargada en ese momento. Sólo válido dentro de un For Each In (no dentro de un For Each Row, que recorre el subfile del DataGridView, no una tabla); válido tanto en Program como en Screen. Una fila borrada no se actualiza además, aunque el cuerpo también le haya asignado algún campo antes del Delete().
For Each Row: recorrer un DataGridView
For Each Row
// cuerpo — corre una vez por cada fila ya cargada en el subfile
EndFor
Sin Index/Where/When None (no hay tabla que recorrer). Sólo válido dentro de cualquier Event de un Screen con DataGridView. Antes de cada iteración escribe cada Column (incluidas Hidden) en su variable atada — ver DataGridView.
For clásico: contador
For &Variable = inicio To fin [Step incremento]
// cuerpo
EndFor
Cuenta desde inicio hasta fin, incrementando &Variable en incremento en cada vuelta. Step es opcional (default 1); con un Step negativo, inicio debe ser mayor que fin para recorrer hacia abajo.
Recorrer un List
No existe un For Each sobre un list en memoria (For Each In es exclusivo de tablas de base de datos, ver más abajo). Se recorre con el For clásico, usando Count() como límite y el indexado &Variable[&i]:
For &i = 1 To &Lista.Count()
// ... &Lista[&i] ...
EndFor
Ver Tipos de dato: List para la sintaxis completa de Add/Clear/Init/Remove/Count.
New: insertar una fila
New In Tabla
Campo = expr
...
[When Duplicate
// bloque de sentencias: corre si la fila ya existe
]
EndNew
New arma una fila con los valores asignados y la inserta. Si la fila ya existe, corre el bloque When Duplicate.
Cómo funciona
- Se hace una lectura (
chain) por laPrimaryKeyde la tabla, con los valores que elNewacaba de armar. - Si la fila no existe, se escribe la fila nueva (
write) y el bloqueWhen Duplicateno corre. - Si la fila existe, corre el bloque sobre esa fila. Al terminar se graba con
update, sólo si el bloque asignó algún campo de esa tabla.
Dentro del bloque, un campo suelto (por ejemplo Estado) es el de la fila ya grabada, no el que armó el New. Los valores del New que el bloque no asigna se descartan.
New sin When Duplicate
Si la tabla tiene PrimaryKey, el New también lee primero por ella: si la fila ya existe no se inserta nada y tampoco es un error (el programa sigue). Si no existe, se escribe. Sin PrimaryKey en @Indexes, se escribe directamente y una llave duplicada da un error de ejecución.
Qué admite el bloque
When Duplicate es un bloque de sentencias completo, no sólo asignaciones Campo = expr: If/Else, While, Do Case, Do Procedimiento(), Call, asignaciones a &variables, For Each/New sobre otras tablas, etc. Un When Duplicate con sólo asignaciones sigue funcionando igual que antes.
Los nombres se resuelven en este orden:
- Los campos de la tabla del
New(la fila releída; se pueden leer y asignar). - Los campos de los
For Eachque envuelven alNew(sólo lectura).
Reglas
- La tabla debe tener una
PrimaryKeyen@Indexes; si no, daE00339. Ya no depende de que algúnFor Eachuse unIndexsobre esa tabla. - No se puede reasignar un campo de la llave primaria dentro del bloque (
E00340, error). Antes se aceptaba: es un cambio incompatible menor. Exitdirectamente dentro del bloque daE00326; dentro de unWhile/Forpropio del bloque sí es válido.Delete()no es válido dentro del bloque (aplica el error habitual deDelete()fuera de unFor Each).
Límite
La escritura falla ante cualquier violación de unicidad. Si el choque fue contra otro índice único (no la PrimaryKey), el chain por la llave primaria no encuentra la fila y se intenta el write, que dará error de ejecución.
Ejemplo
Programa example/programs/PDUP01.warp con la tabla example/tables/TFOLIO01.warp (llave primaria Empresa, Guia, Correl). Si el folio existe, alterna su Estado y suma uno a Veces:
Procedure Mantener_Folio()
&Accion = "I"
New In FOLIO01
Empresa = &Empresa
Guia = &Guia
Correl = &Correl
Estado = 1
Descrip = "Folio nuevo"
When Duplicate
If Estado = 0
Estado = 1
&Accion = "D"
Else
Estado = 0
&Accion = "H"
EndIf
Veces = Veces + 1
EndNew
EndProc
El RPG generado relee por el archivo lógico de la PrimaryKey (con su propia estructura de datos) y escribe por el PF:
chain (rFOLIO01.EMPRESA : rFOLIO01.GUIA : rFOLIO01.CORREL) FOLIO0101.row rFOLIO0101;
if %found(FOLIO0101);
// ... bloque ...
update FOLIO0101.row rFOLIO0101;
else;
write FOLIO01.row rFOLIO01;
endif;
If / While
If condicion
...
Else
...
EndIf
While condicion
...
EndWhile
condicion combina comparaciones con And/Or (p.ej. &a = 1 And &b = 2 Or &c = 3). And liga más fuerte que Or (a And b Or c se lee como (a And b) Or c).
Do Case: elegir una rama
Do Case
Case &Opcion = "1"
&Etiqueta = "Alta"
Case &Opcion = "2" Or &Opcion = "3"
&Etiqueta = "Cambio"
Otherwise
&Etiqueta = "Desconocida"
EndCase
Se ejecuta solo la primera rama Case cuya condición se cumple, sin caer a las siguientes. Otherwise es opcional y corre si ninguna condición se cumple. Debe haber al menos un Case.
- Las condiciones usan las mismas expresiones que
If(comparaciones conAnd/Or/Not). - Dentro de una rama se puede usar cualquier sentencia, incluidos
ExityLoadRow()(esta última, sólo dondeLoadRow()es válido: ver DataGridView). - Se traduce al
select/when/other/endslde RPG. Reemplaza al encadenado deIf/Elseanidados, ya que WARP no tieneElse If. Case,OtherwiseyEndCase(igual queExit) son palabras reservadas: no pueden usarse como nombre de campo, de variable declarada ni de procedimiento; hay que renombrarlos. UnCase,OtherwiseoEndCasefuera de unDo CasedaE00327.- Un error de sintaxis (sin ramas
Caseo sinEndCase) daE00008.
Exit: salir de un bucle
While &Contador < 100
&Contador = &Contador + 1
If &Contador = 10
Exit
EndIf
EndWhile
Sin argumentos. Sale del bucle más interno que lo contiene. Los bucles válidos son For Each In Tabla, For Each Row, For &v = a To b y While; también el For Each que carga el DataGridView en Event Load (deja de cargar filas).
- Sólo el bucle más interno: un
Exitdentro de unWhileanidado en unFor Eachsale delWhile, no delFor Each. - Grabado en
For Each In: si el cuerpo ya asignó campos de la tabla antes delExit, el compilador graba (update) el registro antes de salir. Si hubo unDelete()antes, no se graba. When NoneyWhen Duplicate: el cuerpo deWhen None(deFor Each) y el deWhen Duplicate(deNew) no cuentan como bucle; unExitahí se refiere al bucle exterior, si existe.For &i = ...:&iconserva su valor al salir.- Dentro de un
Do Caseque está dentro de un bucle es válido. - Procedure, Function y Event: un
Exitno sale del bucle de quien la llama; debe haber un bucle en el mismo cuerpo. Fuera de un bucle daE00326. Exites palabra reservada: no puede usarse como nombre de campo, de variable declarada ni de procedimiento; hay que renombrarlo. Sigue funcionando como parte de una clave de@Properties/@GeneratorConfig(p. ej.Commit on Exit : True).
Un ejemplo completo está en example/programs/PEXIT01.warp.
Commit / Rollback: confirmar o deshacer cambios
New In Pedidos
Numero = &num
EndNew
Commit
...
Rollback
Sin argumentos. Commit confirma los cambios hechos a las tablas bajo control de compromiso (RPG commit;) y Rollback los deshace (RPG rolbk;). Valen tanto en un Program como en un Screen.
Dentro de un For Each que modificó campos del registro actual, Commit primero graba el registro pendiente y luego confirma. Como el Commit libera el bloqueo del registro, las asignaciones a los campos del registro actual hechas DESPUÉS del Commit en esa misma vuelta ya no se graban (el registro se considera cerrado). Para seguir modificándolo, hágase antes del Commit o en otra vuelta.
Ejemplo en un Screen (con Commitment : True en @Properties):
Event 'Confirmar' 4 "Commit"
Commit
&Aviso = "Cambios confirmados"
EndEvent
Event 'Deshacer' 6 "Rollback"
Rollback
&Aviso = "Cambios deshechos"
EndEvent
- Requieren
Commitment : True, en el@Propertiesdel objeto o en el@GeneratorConfigdel proyecto. Sin él la generación falla con «Commit requiere Commitment activo (en @Properties del objeto o en @GeneratorConfig)» (igual paraRollback). - Dónde: válidas en Program y Screen, en cualquier lugar del
@Source, también dentro de unProcedure, unEvento un bucle. - En Screen: con
Commitmentactivo, el Screen iniciaSTRCMTCTL LCKLVL(*CHG)al arrancar y abre bajo control de compromiso las tablas que modifica;CommityRollbackfuncionan igual que en un Program (también dentro de unEvento unProcedure). - Journal: las tablas modificadas deben estar journalizadas en el IBM i (
STRJRNPF) para poder abrirse bajo control de compromiso. Warpgate no las journaliza. - Dentro de
For Each In: si el cuerpo ya asignó campos del registro actual,Commitprimero graba (update) el registro pendiente y luego confirma.Rollback, en cambio, no debe ir dentro delFor Eachtras asignar campos, porque el registro se graba al final de la vuelta; colócalo fuera del bucle, después deEndFor. Commit on Exit:Commit on Exit : Truesigue confirmando solo al terminar el programa; en un Screen, al cerrar la pantalla (F3). ConCommit on Exit : False, lo no confirmado al terminar se deshace al terminar el trabajo.- No son palabras reservadas: un campo llamado
Commitsigue funcionando (Commit = 1).
Ver también Commitment y Commit on Exit.
Try / Catch: capturar errores de ejecución
Try
&Resultado = &Total / &Cantidad
Catch 102
Message("La cantidad no puede ser cero", Error)
Catch
Message(Trim(ErrorMessage()), Error)
EndTry
Try … Catch [filtro] … EndTry atrapa los errores de ejecución de RPG en lugar de dejar que aborten el programa. Baja a monitor; ... on-error ...; ... endmon;. Vale en un Program y en un Screen, en cualquier lugar del @Source.
Filtros de Catch (opcionales, en la misma línea):
| Forma | Atrapa | RPG |
|---|---|---|
Catch | Cualquier error. | on-error; |
Catch File | Errores de archivo (*FILE: estados 01000-02999 en IBM i 7.5). | on-error *file; |
Catch Program | Errores de programa (*PROGRAM: 00100-00999 y 9999). | on-error *program; |
Catch 102, 202 | Los códigos de estado indicados, separados por comas (cada uno entre 100 y 9999; E00365 si no). | on-error 00102 : 00202; |
- Orden: los
Catchse evalúan en orden y gana el primero que cubra el error. UnCatchsin filtro debe ir al final: si hay otro después, es el errorE00368(«nunca se ejecuta»). - Estructura: un
Trynecesita al menos unCatch(E00367) y suEndTry(E00364). ErrorCode()yErrorMessage(): solo valen dentro de unCatch; fuera danE00366.ErrorCode()devuelve unnumber(5)con el estado del error (%status) yErrorMessage()unchar(80)con el texto del mensaje (posiciones 91-170 de la PSDS). Ver Funciones builtin.- Un error dentro de un
Catchno lo atrapa el mismoTry: hay que anidar otroTry. ExityReturnse admiten dentro deTryy deCatch.- Con
Commitment: es útil combinarCatchconRollbackpara deshacer los cambios cuando algo falla.
Códigos de estado frecuentes:
| Estado | Situación |
|---|---|
00100 | Valor fuera de rango en una operación de cadena. |
00101 | Raíz cuadrada de un número negativo. |
00102 | División por cero (también Mod con divisor cero). |
00103 | El resultado no cabe en el campo. |
00105 | Carácter inválido al convertir a número (p. ej. Val). |
00112 | Fecha u hora inválida. |
00121 | Índice de arreglo inválido. |
00202 | Falló la llamada a un programa o procedimiento. Un error dentro de un procedimiento llamado llega al llamador como 00202. |
00211 | Error al llamar: el programa llamado no existe o no se puede resolver (Call a un programa inexistente). |
00907 | Dato decimal inválido. |
01211 | Archivo no abierto. |
Lo que no se captura: el error de pantalla (sesión o dispositivo) no es un error de programa, y WARP no genera operaciones con indicador de error o extensión (E) propios.
Un ejemplo completo está en example/screens/STRY01.warp.
Procedure / Function
Procedure Nombre(¶m)
...
EndProc
Function Nombre(¶m) tipoRetorno
...
Return expr
EndFunc
Procedure(sin retorno,subes sinónimo): se invoca conDo Nombre(expr, ...).Function(con retorno, tipo obligatorio justo después de la lista de parámetros — misma sintaxis que@Fields/@Variables): se invoca como expresión,&variable = Nombre(expr, ...), tipado contra&variable.- Un parámetro/retorno puede ser
struct(Template)— ver Tipos de dato: Struct. El argumento pasado debe ser siempre una variable declarada con ese mismo template exacto (nunca una expresión ni un template distinto, aunque tenga los mismos miembros).
Call vs Do
| Sentencia | Uso |
|---|---|
Call(Nombre, expr, ...) | Invoca un programa o pantalla externo (uno de @References/Programs o @References/Screens), resuelto en tiempo de compilación. |
Call("Nombre", expr, ...) | Invoca un programa externo por nombre, resuelto en tiempo de ejecución. |
Do Nombre(expr, ...) | Invoca un Procedure de este mismo archivo (siempre nombre sin comillas). |
Un Screen referenciado en @References/Screens se invoca con Call(Nombre, ...) exactamente igual que un Program — típicamente para mostrarlo como ventana emergente (ver @Properties). El Screen invocado puede declarar @Parameters igual que un Program.
Para enviar una list a otro programa: Call("PGM", &lineas) o Call(Programa, &lineas) pasa el arreglo dim(9999) por referencia (con la firma real si el programa está en @References/Programs). La cantidad de elementos no se envía: el receptor lee hasta la primera línea vacía. Ver @Parameters: parámetros de tipo list.
Print("NombreBlock")
Imprime una instancia de un Block de @Layout — ver @Layout. Escribe líneas de 132 caracteres al spool de QPRINT (OVRPRTF + open al inicio, close al final); con Print Mode: Screen el spool además se muestra (DSPSPLF) y se borra (DLTSPLF) al terminar el programa.
Member / Export: reportes con miembros de datos
Member(Tabla, expr)
Export(Tabla, destino)
Ver la guía completa: Reportes con Member()/Export().
Member(Tabla, expr): liga un miembro de datos aTablapara todo el programa (ADDPFM+OVRDBF, antes de cualquieropen). EnProgram, sólo a nivel superior de@Source; enScreen, sólo a nivel superior deEvent Init(ver @Source: Event y Screen). A lo sumo una vez por tabla.Export(Tabla, destino): copia el miembro activo deTablaa un stream file del IFS (CPYTOIMPF, delimitado por coma). Sólo válido enProgram(no enScreen); sin restricción de posición dentro de él.
JsonSplit: partir un JSON en líneas
JsonSplit(&json, &lista [, sangria])
Parte el texto JSON de &json en líneas legibles, una por elemento y con sangría por nivel, y las guarda en &lista, reemplazando su contenido. Sirve, por ejemplo, para imprimir el resultado de Json().
- 1.º argumento: una
&Variableo miembro de structchar/varchar, o un texto literal. Cualquier otra expresión daE00301. - 2.º argumento: una
&Variablelist(varchar(n))olist(char(n)). Si no,E00302; tampoco puede ser un parámetroin(E00151). - 3.º argumento (opcional): sangría en espacios por nivel, un
number. Por defecto 2; si no es número,E00303. - Se corta una línea en cada
{,[y,, y antes de cada}o], siempre que no estén dentro de comillas (se respeta\"dentro de los textos). La coma y el cierre quedan juntos (},). Los espacios fuera de textos se descartan. - Máximo 9999 líneas (la capacidad de una
list): al llenarse se muestra el mensajeJsonSplit: lista llena (max 9999)y se descarta el resto. Una línea más larga que el elemento de la lista se recorta. Como referencia, un arreglo de objetos de 15 campos ocupa 17 líneas por objeto más 2, es decir unos 588 objetos.
Para {"epm":"X","Items":[{"Pgm":"P1"},{"Pgm":"P2"}]} con sangría 2, las líneas resultantes son:
{
"epm":"X",
"Items":[
{
"Pgm":"P1"
},
{
"Pgm":"P2"
}
]
}
Uso típico para imprimir, con un Block("Linea") de @Layout que tenga un Label sobre &linea:
JsonSplit(&json, &lineas)
For &i = 1 To &lineas.Count()
&linea = &lineas[&i]
Print("Linea")
EndFor
En el RPG se genera un procedimiento JsonSplit_<tipo json>_<tipo elemento> por cada pareja de tipos usada. Un ejemplo completo está en example/JSONSPLIT01.warp. Errores: E00301, E00302, E00303 (ver Diagnósticos). Al reemplazar el contenido, las líneas anteriores se limpian (ver List: métodos).
Para convertir una list(struct(...)) en líneas de ancho fijo (y viceversa) ver Tramas de ancho fijo: FixedWidthSplit y FixedWidthParse.
Return
Return [expr]
Sale de un Function. Una Function debe devolver un valor: Return sin valor dentro de ella es el error E00351, y una Function sin ningún Return con valor es el error E00352.