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

Data types

TypeSyntaxWhereDescription
charchar(length)@Fields, @VariablesFixed-length string. length required (1-32,766 in @Fields; 1-16,773,104 in @Variables).
varcharvarchar(length)@Variables onlyVariable-length string. length required (1-16,773,100). Length(...) returns the actual content length at runtime, unlike char.
numbernumber(precision, scale)@Fields, @VariablesNumeric. precision required (1-30 in @Fields; 1-63 in @Variables); scale optional, default 0.
datedate@Fields, @VariablesDate, no parameters. In @Fields it can’t carry Default(...).
timetime@Fields, @VariablesTime (hour/minute/second), no parameters. In @Fields it can’t carry Default(...).
timestamptimestamp@Fields, @VariablesCombined date and time (with microseconds), no parameters. In @Fields it can’t carry Default(...).
fieldfield(FieldName) or field(Table.Field)@Variables onlyInherits the type of a field from some referenced table’s @Structure. See field: inheriting a field’s type.
listlist(type)@Variables onlyDynamic list of type (scalar, struct(Template), or field(...); another nested list or matrix is not allowed, see E00283). No size in the declaration — the maximum internal capacity is fixed (9999 elements) and not configurable. Read/write an element with &Variable[index]; add/remove/query with methods (see List: methods).
matrixmatrix(rows, cols, type)@Variables onlyFixed-size 2D grid of rows x cols of type. Read/write an element with &Variable[row, col].
structstruct(TemplateName)@Variables onlyInstance of a struct TemplateName [ ... ] template defined inline in the global scope (see Struct: templates and member access). Access a member with &Variable.Member.

Constants (const Name type = value) only support char, varchar and number; see @Variables: Constants.

Examples

@Variables {
    global {
        Username     char(10)
        Destination  varchar(200)
        Balance      number(9,2)
        RegDate      date
        RegTime      time
        RegTimestamp timestamp
        Status       field(ADSTS50)
        List1        list(number(5))
        Board        matrix(8, 8, char(1))
    }
}

field: inheriting a field’s type

field(...) takes the type of the indicated field, looking it up in the tables declared in @References/Tables. It has two forms:

  • field(Field): looks the field up in all referenced tables. If it exists in more than one, E00149 (ambiguous) is reported: “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(Table.Field): looks the field up only in that table, to disambiguate. Table and field names are case-insensitive. If the table isn’t referenced or the field doesn’t exist in it, E00148 (field not found) is reported.

The resulting type is the field’s type in both forms. Both work in any @Variables scope and also as a struct member.

// Tables Customers and Suppliers (in @References/Tables) both have a Code field
@Variables {
    global {
        struct Contact [
            Code  field(Customers.Code)
        ]
        CustomerCode  field(Customers.Code)
        SupplierCode  field(Suppliers.Code)
        Name          field(Customers.Name)   // also valid when the field isn't ambiguous
    }
}

A bare field(Code) would report E00149 in this example.

char/varchar interoperability

char and varchar interoperate in assignments — the rule is purely about length (the source must fit into the destination), regardless of which one is fixed or variable:

&VarcharDestination = &CharSource  // valid if length(CharSource) <= length(VarcharDestination)
&CharDestination     = &VarcharSource

List/Matrix: indexing

&List1[1] = 100
&Board[3, 5] = "X"

The index is 1-based. List/Matrix are only valid in @Variables, never in @Fields.

List: methods

list carries no size in its declaration (list(type), no capacity parameter): the maximum internal capacity is fixed at 9999 elements, defined by the compiler and not configurable from the language. Instead of a fixed size up front, content is managed through methods, using member syntax (&Variable.Method(...)):

MethodUseDescription
Add(value)statementAppends value to the end of the list. value must be type-compatible with the list’s element type (if the element is struct(Template), value must be a &Variable of that exact same template).
Clear()statementEmpties the list: clears the elements in use (blanks or zeros) and sets the count to 0.
Init()statementLeaves all the list’s elements (all 9999) at their empty value (blanks for char/varchar, zeros for number, initial value for date/time/timestamp, cleared structs) and the count (Count()) at 0. Takes no arguments (E00282) and cannot be used as an expression (E00280).
Remove(index)statementRemoves the element at position index (1-based, number type), shifts the following ones back one position and clears the last slot left free. An index outside 1..Count() does nothing.
Split(text[, separators[, AllSep]])statementReplaces the list contents (clears the previous ones) with the pieces of text. See Split().
Count()expressionReturns the current number of elements (number type). Can only be used inside an expression, never as a standalone statement.
Json()expressionReturns the list as a JSON array (list(struct(...)) only). See Json().
&List1.Add(10)
&List1.Add(20)
&List1.Add(30)

For &i = 1 To &List1.Count()
    // ... &List1[&i] ...
EndFor

&List1.Remove(2)
&List1.Clear()
&List1.Init()

There’s no For Each over an in-memory list — traversal is done with a classic For counter (For &i = 1 To &List.Count()) plus indexing (&Variable[&i]); For Each In remains exclusive to database tables (see @Source: control statements).

Real clearing. Clear(), Remove(index) and JsonSplit (when replacing the previous content) truly clear the positions that stop being used, rather than just adjusting the counter. This keeps the following invariant: every element after the last one is empty (blanks or zeros). An RPG program that receives the array (see @Parameters) can read it up to the first empty line. With 9999 elements there is no empty position after the last one.

Init() versus Clear(). In RPG, Init() is a single clear of the whole array (plus counter = 0), with no loop. Clear() only clears up to the counter: it is faster when used with few rows, but it leaves untouched any elements written by index (&List[10] = x) without going through Add(), because those writes do not move the counter. Init() clears the whole capacity (with char(250) that is about 2.5 MB), so use it at the start or when needed, not inside a heavily repeated loop.

The typical use of Init() is a list used as a fixed-size array (&Frame[1], &Frame[10]) that is then passed to another program with Call:

&Frame.Init()
&Frame[1] = "Header"
&Frame[10] = "Footer"
// Clear() would not clear &Frame[10]: it was written by index and Count() is still 0
&Frame.Init()

See the full example in example/programs/PINIT01.warp.

Note: Clear(), Remove() and JsonSplit clear what comes after the last element.

If Add(value) is called while the list is already at its maximum capacity (9999 elements), the program issues a visible dsply with the message Lista {name} alcanzó su capacidad máxima (9999) and does not add the element — it doesn’t abort the whole program.

list(struct(Template)) is valid (a list of instances of the same struct). Nesting list/matrix inside each other is not, in any combination: list(list(...)), list(matrix(...)), matrix(list(...)), and matrix(matrix(...)) produce the compile error E00283 (“‘&{variable}’ es un {list/matrix} cuyo elemento no puede ser {list/matrix} (sin equivalente RPG generable): use un tipo escalar, struct(…) o field(…).”), caught in semantic analysis — it isn’t possible to declare an array whose element is itself another array.

Split(): split a text into a list

&List.Split(text[, separators[, AllSep]]) is a statement (like Add/Clear; used as an expression it is E00280) and is only valid on list(char(n)) or list(varchar(n)) (otherwise E00363).

  • It replaces the list contents: it clears the previous ones and leaves the pieces of text, up to 9999. When full it shows “Lista X llena (max 9999)” with dsply and discards the rest. A piece longer than the element is truncated.
  • Without separators, it splits on blanks.
  • Each character of separators is a separator: "-;" splits on hyphen and on semicolon.
  • Without AllSep, consecutive, leading and trailing separators are ignored: "one,two,,three" with "," gives 3 pieces.
  • With AllSep (third argument, the word AllSep, which requires separators), each separator delimits two pieces and empty ones appear: the same text gives 4 pieces: one, two, `` and three.
  • An empty text gives 0 pieces.
  • Trailing blanks of a char source are ignored. A char variable used as separators is trimmed: its trailing blanks are not a separator (use a literal for a blank separator).
  • text and separators must be char/varchar (E00359); 1 to 3 arguments are allowed (E00358); the third must be the word AllSep (E00361).
&Parts.Split(&Text, ",")
For &i = 1 To &Parts.Count()
    // ... &Parts[&i] ...
EndFor

&Parts.Split("one,two,,three", ",", AllSep)   // 4 pieces, one of them empty

Release requirement. Split generates for-each ... in %split(...). IBM i 7.5 has it built in. On 7.3 and 7.4 two April 2021 PTFs are needed: the compiler one (5770WDS) and the runtime one (5770SS1), the latter on every system where the program runs.

ReleaseCompiler PTFRuntime PTF
7.3SI76100SI76098
7.4SI76101 (for TGTRLS V7R3M0: SI76109)SI76099

Without the runtime PTF the program compiles but fails at run time with MCH4437 (export not found). It is the same requirement as Upper and Lower.

AllSep (*ALLSEP) additionally requires 7.4 TR8 / 7.5 TR2 or their PTFs: 7.4, compiler SI83497 and runtime SI83471; 7.5, compiler SI83480 (for TGTRLS V7R4M0: SI83496) and runtime SI83468. These numbers come from a search summary: verify them with IBM.

Json(): converting a struct or a list to JSON

&Variable.Json() returns the variable’s content as JSON text (varchar). It can be applied to a struct(...) (produces an object) or to a list(struct(...)) (produces an array), including nested ones by path (&env.Json(), &env.Cab.Detalle.Json()). It is used only as an expression, never as a standalone statement (E00294), and takes no arguments (E00282):

&jsonObject = &aRecord.Json()
&jsonArray  = &recordList.Json()

For the template Record [ code number(3,0), name char(20), amount number(7,2), date date ], the results look like this:

{"code":1,"name":"Ana","amount":1250.75,"date":"2026-10-16"}
[{"code":1,"name":"Ana",...},{"code":2,"name":"Luis",...}]
  • Keys: the member name as declared in the template, in declaration order.
  • List: the array contains only the elements in use (Count()).
  • char/varchar: quoted JSON string, with " and \ escaped and trailing spaces trimmed. Control characters get no special treatment.
  • number: JSON number, unquoted.
  • date: "yyyy-mm-dd" string.
  • Nested: a struct(...) member produces an object and a list(struct(...)) member an array, e.g. {"Cab":{"epm":"a","Detalle":[{"Pgm":"P1","Libreria":"L1"}]},"Total":2}.
  • Scalar struct members can only be char/varchar/number/date/field; the generator can also emit time ("hh:mm:ss") and timestamp ("yyyy-mm-ddThh:mm:ss.ffffff") should the member types be extended.

Target variable size. For a struct, the expression has the worst-case size: for each text, 2 × length + 2; for each number, precision + 2; for each date, 12; plus key names, quotes, braces and commas. With nested structs the worst case grows with each nested list (computed over its 100 elements). The variable receiving the result must be able to hold it; otherwise the usual incompatible-assignment error (E00147) is reported. Declare the target generously (e.g. varchar(300) for a small template). For a list, no target size is enforced, but if the generated array exceeds the maximum of an RPG varchar (16,773,100 characters) the program fails at runtime instead of truncating.

Errors: E00294 (used as a statement), E00295 (the variable is neither struct nor list), E00296 (another method on a struct; Json is the only valid one), E00297 (list of scalar types: not supported for now). See Diagnostics.

No libraries or PTFs are required on IBM i: when .Json() is used, the compiler generates the needed helper procedures in the RPG (Json_<Template> and JsonList_<Template>). A complete example is in example/JSON01.warp.

To split the resulting text into lines (e.g. to print it) see JsonSplit.

Fixed-width records

A fixed-width record is a line of text where each member of a struct takes a fixed number of positions, in declaration order, with no separators. The total width is the sum of the members’ widths.

FormUseDescription
&rec.FixedWidth()expressionReturns the struct’s record: a char of the total width. Using it as a statement gives E00306.
&rec.FromFixedWidth(&text)statementFills the struct from a record (the inverse operation). &text is char/varchar or a literal. Using it as an expression gives E00306.
FixedWidthSplit(&records, &lines)statementConverts a list(struct(...)) into one record line per entry, inside a list(char)/list(varchar). Replaces the content of &lines.
FixedWidthParse(&lines, &records)statementInverse operation: reads each line of &lines as an entry of &records. Replaces the content of &records.

Format per member:

TypePositionsFormat
char(n) / varchar(n)nLeft-aligned, padded with blanks (truncated if too long).
number(p,s)pRight-aligned, leading zeros, implied decimal (no point). A negative carries - in the first position, so leave one digit of slack in p.
date8YYYYMMDD.
time6HHMMSS.
timestamp20YYYYMMDDHHMMSSnnnnnn (with microseconds).

When reading (FromFixedWidth, FixedWidthParse): a line shorter than the record is read as if padded with blanks; an invalid number gives 0; an invalid date gives the minimum date (0001-01-01).

FixedWidth() and FromFixedWidth() work on ONE struct; for a list use FixedWidthSplit / FixedWidthParse. Calling FixedWidth() on a list gives E00305 with a hint to use FixedWidthSplit(&list, &lines).

FixedWidthSplit and FixedWidthParse also accept a path to a list that is a member of a struct, for example FixedWidthSplit(&pase.Items, &lineas) and FixedWidthParse(&lineas, &pase.Items). A member list holds up to 100 records: FixedWidthParse stops when it fills up and emits the warning FixedWidthParse: lista llena (max 100).

Only structs whose members are char, varchar, number, date (or a field resolving to one of them) are supported. A nested struct or list, or a record longer than 32766 positions, gives E00305. The elements of the line list must be able to hold the whole record (E00309).

Example with struct Cliente [codigo char(5) nombre varchar(12) saldo number(9,2) alta date]: the record is 34 positions long (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)
}

A complete example is in example/FIXEDWIDTH01.warp. Errors: E00305 to E00309 (see Diagnostics).

Note: number members are read with an implied decimal point and a - sign in the first position; date members as YYYYMMDD. A line shorter than the record is read as if padded with blanks.

Struct: templates and member access

A named template with members, defined inline inside @Variables’s global scope:

@Variables {
    global {
        struct Person [
            Id      number(10, 0)
            Name    varchar(50)
            Email   varchar(100)
            Age     number(3, 0)
        ]
        globalPerson    struct(Person)
        Name            varchar(50)
    }
    PrintPerson {
        person   struct(Person)
    }
}
&globalPerson.Name = "Your Name"
&Name = &globalPerson.Name
Do PrintPerson(&globalPerson)
  • Template members support char/varchar/number/date/field(FieldName), struct(OtherTemplate) and list(struct(OtherTemplate)) (see Nested structs). time and timestamp aren’t supported as members, nor are matrix or list of scalar types (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)”).

  • A member is read/assigned with &Variable.Member (both read and write).

  • It can be passed as a parameter to, and returned from, an in-file Procedure/Function — see Procedure / Function.

  • An array of struct is declared with list(struct(TemplateName)) — see List: methods.

  • Indexing &List[i] on a list(struct(Template)) is valid, but only in two exact forms: as a read, the whole value of an assignment to another &Variable that is struct(...) of the same template (&otherVariable = &List[i]); as a write, &List[i] = &otherVariable (another &Variable struct(...) of the same template, never an expression or a literal). If the side of the assignment that should be the struct variable isn’t syntactically a &Variable, E00241 is reported; if the template doesn’t match exactly the list’s, E00242 is reported. Index checks (count, number type, 1..9999 range) apply the same as for any other list:

    &otherRecord = &List[1]
    &List[1] = &otherRecord
    For &i = 1 To &List.Count()
        &current = &List[&i]
    EndFor
    
  • Not supported yet: struct as a parameter of an external program (@Parameters/Call) — only as a parameter of an internal Procedure/Function.

Nested structs and lists inside a struct

A template member can itself be struct(OtherTemplate) (nested struct) or list(struct(OtherTemplate)) (list of structs). The referenced template may be declared further down:

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)

Access is by path, with as many levels as needed:

&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()
  • Capacity: a list that is a member of a struct reserves 100 elements (not the 9999 of a standalone list). Add on a full list shows a message and does not add the element, just like the standalone list. A literal index greater than 100 is reported as E00188.
  • Maximum size: if the variable would take more than the maximum of an RPG variable (16,773,104 bytes), E00300 is reported (e.g. a list(struct) of 9999 elements whose struct carries a large nested list).
  • Cycles: a template that contains itself, directly or indirectly, is reported as E00299.
  • Nonexistent templates: struct(X) or list(struct(X)) inside a member with an undeclared X is reported as E00238.
  • Not allowed as a member: matrix and list of scalar types (E00237); neither time nor timestamp (E00237).
  • Procedures: a struct with nested members can be passed to a Procedure; if the routine modifies a nested list, the parameter is passed by reference (not const).
  • Generated RPG: templates are emitted in dependency order; the member list is a dim(100) array with a hidden counter as a subfield of the same template (copied along with the struct), and variables that contain lists are initialized with inz(*likeds).
  • Json(): see Json(); nested members produce nested objects and arrays.

A complete example is in example/NSTJSON01.warp.