@Source: control statements
The imperative body of a Program/Screen: assignments, control flow, table access, Procedure/Function declarations, calls and reports.
For Each In: iterating over a table
For Each In Table [Index IndexName]
[Where Field = expr]...
// body, runs for each row found
[When None
// runs if no row was found
]
EndFor
Index: optional, picks the access path (defaults to the sequential-access one).Where: zero or more, filters by equality.When None: optional, runs if the loop found no rows.
Join In: joining another table
For Each In Orders
Join In Customers On CustCode = CustCode
Join In Zones
Where Status = "A"
&VName = CustName
&VZone = ZoneName
EndFor
Syntax: one or more Join In Table2 [On keyField = expr [And keyField2 = expr2 ...]] lines right after For Each In Table [Index Idx] and before the Where clauses.
- The joined table is read by its
PrimaryKey(in RPG, achainon the primary-key logical file) once for each record read from the table being iterated.Joindeclares noIndex. - Without
On(implicit): eachPrimaryKeyfield of the joined table must exist with the same name in the iterated table or in another table joined earlier (otherwiseE00316). - With
On: the fields on the joined-table side must be exactly itsPrimaryKeyfields, none repeated or missing (order does not matter; otherwiseE00317). The other side can be a field of the iterated table or of a previously joined table, or a variable or expression. If both sides are fields of the joined table, the left one is the joined one. Types must be compatible (E00318). - No match: if the joined table has no record with that key, the loop does not skip the row; it continues and the joined table’s fields are blank or zero.
- Visibility: all fields of the joined tables can be used in the
Whereclauses and in the body. If a name exists in both the iterated and the joined table (typically the key fields), the iterated table’s field is always used. AWhereon a field that only exists in the joined table is evaluated inside the loop, after thechain(it does not position the file). - Read-only: assigning a joined-table field gives
E00319. - Several
Join Inlines can be chained. The joined table must have aPrimaryKeyin@Indexes(otherwiseE00315). JoinandOnare not reserved words: they are only recognized in theFor Eachheader (Joinfollowed byIn), so they remain usable as field names.- DataGridView: the
For Eachthat loads the grid inEvent Loadalso supportsJoin In. Fields of the joined table are assigned to theColumnvariables beforeLoadRow(), just like those of the traversed table. AWhereon a field that exists only in the joined table filters rows after thechain.
Delete(): deleting the current row
For Each In Table
If &Expired
Delete()
EndIf
EndFor
No arguments — deletes the row the iteration currently has loaded. Only valid inside a For Each In (not inside a For Each Row, which iterates a DataGridView’s subfile, not a table); valid in both Program and Screen. A deleted row isn’t also updated, even if the body assigned one of its fields earlier in the same iteration.
For Each Row: iterating over a DataGridView
For Each Row
// body — runs once per row already loaded into the subfile
EndFor
No Index/Where/When None (there’s no table being iterated). Only valid inside any Event of a Screen with a DataGridView. Before each iteration it writes each Column (including Hidden ones) into its bound variable — see DataGridView.
Classic For: counter
For &Variable = start To end [Step increment]
// body
EndFor
Counts from start to end, incrementing &Variable by increment each time around. Step is optional (default 1); with a negative Step, start must be greater than end to count downward.
Iterating over a List
There’s no For Each over an in-memory list (For Each In is exclusive to database tables, see below). It’s traversed with a classic For, using Count() as the limit and indexing (&Variable[&i]):
For &i = 1 To &List.Count()
// ... &List[&i] ...
EndFor
See Data types: List for the full syntax of Add/Clear/Init/Remove/Count.
New: inserting a row
New In Table
Field = expr
...
[When Duplicate
// statement block: runs if the row already exists
]
EndNew
New builds a row from the assigned values and inserts it. If the row already exists, the When Duplicate block runs.
How it works
- A read (
chain) is done by the table’sPrimaryKey, with the values theNewhas just built. - If the row does not exist, the new row is written (
write) and theWhen Duplicateblock does not run. - If the row exists, the block runs on that row. At the end it is saved with
update, only if the block assigned some field of that table.
Inside the block, a bare field (for example Status) is the one from the already stored row, not the one the New built. Values from the New that the block does not assign are discarded.
New without When Duplicate
If the table has a PrimaryKey, New also reads by it first: if the row already exists nothing is inserted, and that is not an error (the program carries on). If it does not exist, it is written. Without a PrimaryKey in @Indexes the row is written directly, and a duplicate key causes a runtime error.
What the block accepts
When Duplicate is a full statement block, not just Field = expr assignments: If/Else, While, Do Case, Do Procedure(), Call, assignments to &variables, For Each/New over other tables, etc. A When Duplicate with only assignments keeps working as before.
Names are resolved in this order:
- The fields of the
Newtable (the re-read row; readable and assignable). - The fields of the
For Eachloops enclosing theNew(read-only).
Rules
- The table must have a
PrimaryKeyin@Indexes; otherwise you getE00339. It no longer depends on someFor Eachusing anIndexover that table. - A primary key field cannot be reassigned inside the block (
E00340, error). It used to be accepted: this is a minor breaking change. Exitdirectly inside the block givesE00326; inside aWhile/Forof the block itself it is valid.Delete()is not valid inside the block (the usual error forDelete()outside aFor Eachapplies).
Limit
The write fails on any uniqueness violation. If the collision was against another unique index (not the PrimaryKey), the chain by the primary key does not find the row and the write is attempted, which gives a runtime error.
Example
Program example/programs/PDUP01.warp with table example/tables/TFOLIO01.warp (primary key Empresa, Guia, Correl). If the folio exists, it toggles its Estado and adds one to 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
The generated RPG re-reads through the PrimaryKey logical file (with its own data structure) and writes through the PF:
chain (rFOLIO01.EMPRESA : rFOLIO01.GUIA : rFOLIO01.CORREL) FOLIO0101.row rFOLIO0101;
if %found(FOLIO0101);
// ... block ...
update FOLIO0101.row rFOLIO0101;
else;
write FOLIO01.row rFOLIO01;
endif;
If / While
If condition
...
Else
...
EndIf
While condition
...
EndWhile
condition combines comparisons with And/Or (e.g. &a = 1 And &b = 2 Or &c = 3). And binds tighter than Or (a And b Or c reads as (a And b) Or c).
Do Case: choosing a branch
Do Case
Case &Option = "1"
&Label = "Create"
Case &Option = "2" Or &Option = "3"
&Label = "Change"
Otherwise
&Label = "Unknown"
EndCase
Only the first Case branch whose condition is met is executed, with no fall-through to the following ones. Otherwise is optional and runs if no condition is met. There must be at least one Case.
- Conditions use the same expressions as
If(comparisons withAnd/Or/Not). - Any statement can be used inside a branch, including
ExitandLoadRow()(the latter only whereLoadRow()is valid: see DataGridView). - It is translated to RPG
select/when/other/endsl. It replaces chained nestedIf/Else, since WARP has noElse If. Case,OtherwiseandEndCase(likeExit) are reserved words: they cannot be used as a field, declared variable or procedure name; they must be renamed. ACase,OtherwiseorEndCaseoutside aDo CasegivesE00327.- A syntax error (no
Casebranches or noEndCase) givesE00008.
Exit: leaving a loop
While &Counter < 100
&Counter = &Counter + 1
If &Counter = 10
Exit
EndIf
EndWhile
No arguments. Leaves the innermost loop that contains it. Valid loops are For Each In Table, For Each Row, For &v = a To b and While; also the For Each that loads the DataGridView in Event Load (it stops loading rows).
- Innermost loop only: an
Exitinside aWhilenested in aFor Eachleaves theWhile, not theFor Each. - Saving in
For Each In: if the body already assigned table fields before theExit, the compiler saves (update) the record before leaving. If aDelete()came earlier, it is not saved. When NoneandWhen Duplicate: the body ofWhen None(ofFor Each) and ofWhen Duplicate(ofNew) do not count as loops; anExitthere refers to the outer loop, if any.For &i = ...:&ikeeps its value on exit.- Inside a
Do Casethat is inside a loop it is valid. - Procedure, Function and Event: an
Exitdoes not leave the caller’s loop; there must be a loop in the same body. Outside a loop it givesE00326. Exitis a reserved word: it cannot be used as a field, declared variable or procedure name; it must be renamed. It still works as part of an@Properties/@GeneratorConfigkey (e.g.Commit on Exit : True).
A complete example is in example/programs/PEXIT01.warp.
Commit / Rollback: confirm or undo changes
New In Orders
Number = &num
EndNew
Commit
...
Rollback
No arguments. Commit confirms the changes made to tables under commitment control (RPG commit;) and Rollback undoes them (RPG rolbk;). They work in both a Program and a Screen.
Inside a For Each that modified fields of the current record, Commit first writes the pending record and then commits. Since Commit releases the record lock, assignments to the current record’s fields made AFTER the Commit in that same iteration are no longer saved (the record is considered closed). To keep modifying it, do so before the Commit or in another iteration.
Example in a Screen (with Commitment : True in @Properties):
Event 'Confirmar' 4 "Commit"
Commit
&Aviso = "Changes committed"
EndEvent
Event 'Deshacer' 6 "Rollback"
Rollback
&Aviso = "Changes rolled back"
EndEvent
- They require
Commitment : True, in the object’s@Propertiesor in the project’s@GeneratorConfig. Without it generation fails with “Commit requiere Commitment activo (en @Properties del objeto o en @GeneratorConfig)” (same forRollback). - Where: valid in Program and Screen, anywhere in
@Source, also inside aProcedure, anEventor a loop. - In a Screen: with
Commitmentenabled, the Screen startsSTRCMTCTL LCKLVL(*CHG)on startup and opens the tables it modifies under commitment control;CommitandRollbackwork the same as in a Program (also inside anEventor aProcedure). - Journal: the modified tables must be journaled on the IBM i (
STRJRNPF) to be opened under commitment control. Warpgate does not journal them. - Inside
For Each In: if the body already assigned fields of the current record,Commitfirst saves (update) the pending record and then commits.Rollback, on the other hand, should not go inside theFor Eachafter assigning fields, because the record is saved at the end of the iteration; place it outside the loop, afterEndFor. Commit on Exit:Commit on Exit : Truestill commits only when the program ends; in a Screen, when the screen is closed (F3). WithCommit on Exit : False, anything uncommitted is undone when the job ends.- They are not reserved words: a field named
Commitstill works (Commit = 1).
See also Commitment and Commit on Exit.
Try / Catch: catching runtime errors
Try
&Result = &Total / &Quantity
Catch 102
Message("The quantity cannot be zero", Error)
Catch
Message(Trim(ErrorMessage()), Error)
EndTry
Try … Catch [filter] … EndTry catches RPG runtime errors instead of letting them abort the program. It lowers to monitor; ... on-error ...; ... endmon;. It is valid in a Program and in a Screen, anywhere in @Source.
Catch filters (optional, on the same line):
| Form | Catches | RPG |
|---|---|---|
Catch | Any error. | on-error; |
Catch File | File errors (*FILE: statuses 01000-02999 on IBM i 7.5). | on-error *file; |
Catch Program | Program errors (*PROGRAM: 00100-00999 and 9999). | on-error *program; |
Catch 102, 202 | The listed status codes, comma separated (each between 100 and 9999; otherwise E00365). | on-error 00102 : 00202; |
- Order: the
Catchblocks are evaluated in order and the first one that covers the error wins. ACatchwithout a filter must come last: if another follows it, it is errorE00368(“never runs”). - Structure: a
Tryneeds at least oneCatch(E00367) and itsEndTry(E00364). ErrorCode()andErrorMessage(): only valid inside aCatch; outside they giveE00366.ErrorCode()returns anumber(5)with the error status (%status) andErrorMessage()achar(80)with the message text (PSDS positions 91-170). See Builtin functions.- An error inside a
Catchis not caught by the sameTry: nest anotherTry. ExitandReturnare allowed insideTryandCatch.- With
Commitment: combiningCatchwithRollbackis useful to undo the changes when something fails.
Common status codes:
| Status | Situation |
|---|---|
00100 | Value out of range in a string operation. |
00101 | Square root of a negative number. |
00102 | Division by zero (also Mod with a zero divisor). |
00103 | The result does not fit in the field. |
00105 | Invalid character when converting to a number (e.g. Val). |
00112 | Invalid date or time. |
00121 | Invalid array index. |
00202 | A program or procedure call failed. An error inside a called procedure reaches the caller as 00202. |
00211 | Call error: the called program does not exist or cannot be resolved (Call to a missing program). |
00907 | Invalid decimal data. |
01211 | File not open. |
What is not caught: a screen error (session or device) is not a program error, and WARP does not generate operations with their own error indicator or (E) extender.
A complete example is in example/screens/STRY01.warp.
Procedure / Function
Procedure Name(¶m)
...
EndProc
Function Name(¶m) returnType
...
Return expr
EndFunc
Procedure(no return value,subis a synonym): called withDo Name(expr, ...).Function(with a return value, type required right after the parameter list — same syntax as@Fields/@Variables): called as an expression,&variable = Name(expr, ...), type-checked against&variable.- A parameter/return value can be
struct(Template)— see Data types: Struct. The argument passed must always be a variable declared with that exact same template (never an expression nor a different template, even if it has the same members).
Call vs Do
| Statement | Use |
|---|---|
Call(Name, expr, ...) | Calls an external program or screen (one of @References/Programs or @References/Screens), resolved at compile time. |
Call("Name", expr, ...) | Calls an external program by name, resolved at runtime. |
Do Name(expr, ...) | Calls a Procedure from this same file (always an unquoted name). |
A Screen referenced in @References/Screens is called with Call(Name, ...) exactly like a Program — typically to show it as a pop-up window (see @Properties). The called Screen can declare @Parameters just like a Program.
To send a list to another program: Call("PGM", &lines) or Call(Program, &lines) passes the dim(9999) array by reference (with the real signature if the program is in @References/Programs). The element count is not sent: the receiver reads up to the first empty line. See @Parameters: list parameters.
Print("BlockName")
Prints an instance of a Block from @Layout — see @Layout. Writes 132-character lines to the QPRINT spool (OVRPRTF + open at the start, close at the end); with Print Mode: Screen the spool is also shown (DSPSPLF) and deleted (DLTSPLF) when the program finishes.
Member / Export: reports with data members
Member(Table, expr)
Export(Table, destination)
See the full guide: Reports with Member()/Export().
Member(Table, expr): attaches a data member toTablefor the whole program (ADDPFM+OVRDBF, before anyopen). InProgram, only at the top level of@Source; inScreen, only at the top level ofEvent Init(see @Source: Event and Screen). At most once per table.Export(Table, destination): copiesTable’s active member to an IFS stream file (CPYTOIMPF, comma-delimited). Only valid inProgram(not inScreen); no position restriction within it.
JsonSplit: splitting a JSON into lines
JsonSplit(&json, &list [, indent])
Splits the JSON text in &json into readable lines, one per element and indented per level, and stores them in &list, replacing its contents. Useful, for example, to print the result of Json().
- 1st argument: a
char/varchar&Variableor struct member, or a text literal. Any other expression givesE00301. - 2nd argument: a
list(varchar(n))orlist(char(n))&Variable. OtherwiseE00302; it cannot be aninparameter either (E00151). - 3rd argument (optional): indentation in spaces per level, a
number. Default 2; if it is not a number,E00303. - A line is cut at every
{,[and,, and before every}or], as long as they are not inside quotes (\"inside texts is respected). The comma and the closing character stay together (},). Spaces outside texts are discarded. - Maximum 9999 lines (the capacity of a
list): when full, the messageJsonSplit: lista llena (max 9999)is shown and the rest is discarded. A line longer than the list element is truncated. As a rule of thumb, an array of objects with 15 fields takes 17 lines per object plus 2, that is about 588 objects.
For {"epm":"X","Items":[{"Pgm":"P1"},{"Pgm":"P2"}]} with indentation 2, the resulting lines are:
{
"epm":"X",
"Items":[
{
"Pgm":"P1"
},
{
"Pgm":"P2"
}
]
}
Typical use to print, with a @Layout Block("Linea") that has a Label on &linea:
JsonSplit(&json, &lines)
For &i = 1 To &lines.Count()
&linea = &lines[&i]
Print("Linea")
EndFor
In RPG, one procedure JsonSplit_<json type>_<element type> is generated for each pair of types used. A complete example is in example/JSONSPLIT01.warp. Errors: E00301, E00302, E00303 (see Diagnostics). When replacing the content, the previous lines are cleared (see List: methods).
To convert a list(struct(...)) into fixed-width lines (and back) see Fixed-width records: FixedWidthSplit and FixedWidthParse.
Return
Return [expr]
Exits a Function. A Function must return a value: a Return without a value inside it is error E00351, and a Function with no Return carrying a value is error E00352.