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: the evolution of IBM i development

Leer esto en español

WARP is a high-level programming language with declarative syntax, designed to optimize and speed up development on IBM i (AS/400) environments. Instead of hand-writing extensive RPGLE code or DDS definitions, WARP lets you define programs, interactive screens and tables in clean, expressive .warp files.

Through the Warpgate compiler, WARP code is automatically transformed into highly optimized RPGLE and DDS source, ready to run on the system.

Key features

  • High-performance engineering: the Warpgate compiler is built 100% in Rust, guaranteeing ultra-fast compilation speed, memory safety and native portability across Windows, GNU/Linux and macOS.
  • CI/CD pipeline integration: being a native, cross-platform executable capable of building from the command line, it integrates easily into continuous integration/deployment pipelines (GitHub Actions, GitLab CI, Azure DevOps, etc.), bringing modern DevOps practices to the IBM i platform.
  • Multi-protocol remote deployment: transfers and deploys directly to remote IBM i servers over SSH, SFTP and FTP, configurable from the project file (see @Connection/Protocol).
  • Modernization and productivity: drastically reduces the lines of code needed and removes the verbosity of traditional RPG, streamlining development teams’ workflow.
  • Continuous evolution: constantly improving and gaining new language features and code generation capabilities.

Installation

Warpgate is distributed as a VSCode extension, available on the Visual Studio Marketplace:

  1. Open VSCode → Extensions tab (Ctrl+Shift+X).
  2. Search for “WaRPGate for IBM i” (publisher Software House) and install it.

The extension already includes the compiler for Windows, GNU/Linux and macOS — no separate install needed. To generate and deploy code (generate/build/reverse) a license is required — see Licensing.

Built by Software House

WARP and Warpgate are designed, developed and maintained by Giuliano Gonzales Zeballos, creator and lead architect at Software House, a firm dedicated to software engineering, systems architecture and modernization solutions for enterprise environments.

About this book

This book documents:

  • The guides: how to build, step by step, each kind of object (Program, Screen, Table) and the bigger features (DataGridView, Member/Export reports).
  • The language reference: every section (@Properties, @Variables, @Source, …), every data type, every control statement and every builtin function, with its exact syntax and validation rules.

What is a .warp file?

A .warp file describes one object: a Program (business logic, with or without printed output), a Screen (an interactive 5250 screen) or a table (a DDS physical file). The type is declared in @Properties/Type.

A .warp file is organized into top-level sections, marked with @Name { ... }:

@Documentation {
    Author Name : "Programmer Name"
}
@Properties {
    Type        : Program
    Name        : PTESTFN
    Description : "Minimal example"
}
@References {
    Programs = []
    Screens  = []
    Tables   = []
}
@Variables {
    global {
        Username char(10)
    }
}
@Parameters {
}
@Source {
    &Username = USERID()
}

Which sections are valid, and in what order, depends on Type — a Program has @Layout/@Source, a table has @Structure/@Fields/@Indexes, a Screen has @Layout/@Source but no @Structure. Each reference chapter details which file type each section applies to.

How to compile

The compiler is a CLI binary (cli, distributed as warpgate/warpgate.exe inside the VSCode extension) that takes a project (.warproj) and a source file:

warpgate --project my-project.warproj --source programs/PMYPROG.warp --action validate
warpgate --project my-project.warproj --source programs/PMYPROG.warp --action generate
warpgate --project my-project.warproj --source programs/PMYPROG.warp --action build --profile Prod
  • validate: only runs semantic analysis (the same thing the VSCode extension triggers on save) — never touches the IBM i.
  • generate: generates RPGLE/DDS/CL and writes it to Output — purely local, never connects to the IBM i (even though the generated CL already references remote paths, based on @Connection/@Deployment).
  • build: does the same as generate, and additionally uploads and compiles the generated code on the IBM i configured in @Connection (CRTPF/CRTLF/CRTBNDRPG/CRTDSPF, depending on the type).
  • reverse: reconstructs a table .warp file from an existing DDS on the IBM i (reverse engineering).

When the .warpcfg declares more than one @Profile (Dev/UAT/Prod, see Project file), generate/build/reverse and license-status need to know which one to use — either with the --profile <Name> flag, or, if omitted, with Default Profile in @Project. validate ignores this flag entirely.

See Project file for the .warproj/.warpcfg format.

Getting started: your first Program

A Program is the basic unit of business logic: it receives parameters, does something (reads/writes tables, computes, calls other programs) and finishes. It doesn’t have to show a screen or print anything.

1. Minimal file

@Documentation {
    Author Name : "Your Name"
}
@Properties {
    Type        : Program
    Name        : PGREET
    Description : "Minimal greeting program"
}
@References {
    Programs = []
    Screens  = []
    Tables   = []
}
@Variables {
    global {
        Username char(10)
        Greeting char(30)
    }
}
@Parameters {
}
@Source {
    &Username = USERID()
    &Greeting = "Hello, " + &Username
}

This is already a valid .warp file: it declares the object (@Properties), its variables (@Variables) and its logic (@Source). @References/@Parameters are empty because this program doesn’t depend on other objects or receive parameters.

2. Adding parameters

A Program receives parameters declared in @Parameters, referencing variables already declared in @Variables/global:

@Variables {
    global {
        MyDate  date
        MyTime  char(8)
    }
}
@Parameters {
    inout:&MyDate,
    inout:&MyTime
}

The modes are in (input only), inout (input and can be modified) and out (output only) — see @Parameters.

3. Calling another program

To call an external program, add it to @References/Programs and use Call(...):

@References {
    Programs = ["../programs/POTHER.warp"]
}
@Source {
    Call(POTHER, &MyDate, &MyTime)
}

4. Local procedures and functions

Procedure/Function live inside @Source, with their own variables in @Variables (one entry per procedure/function name, in addition to global):

@Variables {
    global {
        Username char(10)
    }
    Greet {
        name char(10)
    }
}
@Source {
    Function Greet(&name) char(20)
        Return "Hello " + &name
    EndFunc

    &Username = USERID()
    Message("Welcome", Info)  // only valid in Screen; use Print() in Program
}

To call a Procedure (no return value) use Do Name(...); a Function (with a return value) is called as an expression: &variable = Name(...).

5. Next steps

For the full syntax of each section, see the Language reference.

Tables: @Structure, @Fields, @Indexes

A table .warp file describes a DDS physical file: its columns and its keyed access paths. It’s referenced from a Program/Screen via @References/Tables.

1. Minimal file

@Properties {
    Type : "Table"
}
@Structure {
    Name        : "GRIDDEMO"
    Description : "Sample customers"
    @Fields {
        CustCode char(6)   Description("Customer code")
        Name     char(20)  Description("Name")
        City     char(15)  Description("City")
    }
    @Indexes {
        PrimaryKey ( GRIDDEMOPK, [CustCode] )
        Index ( GRIDDMNOM, [Name] )
    }
}
  • @Fields: one Name type(...) line per column. Only char/number/date — see Data types. Available modifiers: Description(...), Default(...), AllowNull.
  • @Indexes: PrimaryKey (required, its fields can’t be AllowNull), Unique, Index — each one as Kind(Name, [Field, ...], "optional description").

2. Referencing it from a Program

@References {
    Tables = ["../tables/TLGRIDDEMO.warp"]
}

The path is relative to the file that references it. Once referenced, its fields become available as bare identifiers inside a For Each/New (see below), and as field(FieldName) in @Variables to inherit its type.

3. Reading rows: For Each In

@Source {
    For Each In GRIDDEMO Index GRIDDMNOM
    Where CustCode = &VCustCode
        &VName = Name
        &VCity = City
    When None
        Message("Customer not found", Error)
    EndFor
}

Index picks the access path (defaults to the sequential-access one if omitted); Where filters by equality; When None runs if no row was found.

4. Inserting rows: New

@Source {
    New In GRIDDEMO
        CustCode = &VCustCode
        Name     = &VName
        City     = &VCity
    When Duplicate
        Message("That code already exists", Error)
    EndNew
}

When Duplicate runs if the insert collides with an existing unique key.

5. Extended documentation

A table can also carry @Documentation (nested inside @Structure, unlike a Program where it goes at the top level) with @Author, @Application, @Module, @Repository and @History (@Creation + @Change per changelog entry) — see @Structure, @Fields, @Indexes for the full detail of each sub-section.

6. Reverse engineering from an existing DDS

If the table already exists on the IBM i, there’s no need to write it by hand: the WaRPGate: Reverse Engineer command in the VSCode extension connects to the IBM i, reads the DDS (and optionally its associated LFs as @Indexes) and generates the corresponding .warp file.

Screens: @Layout and Screen

A Screen is an interactive 5250 screen: it defines its layout (@Layout) and reacts to user events (@Source).

1. Minimal structure

@Properties {
    Type : Screen
    Name : SFORM01
    Description : "Sample screen"
    Path : Screens
}
@References {
    Tables = ["../tables/TLADD50.warp"]
}
@Variables {
    Global {
        Username char(10)
        MyDate   Date
    }
}
@Parameters {
}
@Layout {
    @Label(1,2,&Username)
    @Label(1,70,&MyDate)
    @Input("txtRequestNo", 3, 1, "Request No.: ", &RequestNo, true)
}
@Source {
    Event Init
        &Username = UserId()
        &MyDate   = Today()
    EndEvent
}

Unlike a Program, a Screen doesn’t have its own @Structure — it only references tables to read/write.

2. @Layout controls

  • Label(x, y, text): static text/variable. x,y = 1,1 (exact corner) is forbidden — DDS rejects it.
  • Input("Name", x, y, text, &Variable, enabled): editable field. "Name" is what CurrentInput() returns when the cursor lands there. enabled is optional (default true): a literal, &Variable, or a function returning number.
  • DataGridView("Name", x, y, rows)[...]: grid — see DataGridView.

Control names can be prefixed with @ (@Label, @Input) or not — it’s purely stylistic, same meaning.

3. Standard events

EventWhen it fires
InitOnce, before the first display.
LoadOnce, before the first display — typically fills the DataGridView with LoadRow().
EnterOn pressing Enter (with no function key active).
RefreshOn pressing F5, if declared in the project’s @FunctionKeys.
CloseFixed to F3 — always runs and closes the screen, no declaration needed.
@Source {
    Event Init
        &Username = UserId()
    EndEvent

    Event Load
        For Each In LADD50 Index LADD5010
        Where SPT25COUNTRY = &nCountry
            &cState = ADSTS50
            LoadRow()
        EndFor
    EndEvent

    Event Enter
        &Control = CurrentInput()
        Message("Active control: " + &Control, Info)
    EndEvent
}

4. Custom function keys

Besides the standard events, you can attach an event to any F1-F24 key (F13-F24 via Shift):

@Source {
    Event 'FindCustomer' 4
        Message("F4 was pressed", Info)
    EndEvent
}

The label shown in the attention line is configured in the project’s .warpcfg @FunctionKeys — see Project.

5. Messages to the user

Message("Rows found: " + String(&nRows, 5), Info)

Error (red), Warning (yellow), Info (normal color). Shown on the next refresh and cleared after one cycle.

6. Next step

If the screen needs to show a list of rows with selection/pagination, see DataGridView.

DataGridView: paginated grids

DataGridView shows a list of rows from a table on a Screen, with native pagination (Page Up/Down) over a DDS subfile. There can only be one per Screen.

1. Full example

@Properties {
    Type : Screen
    Name : SGRIDDEMO
}
@References {
    Tables = ["tables/TLGRIDDEMO.warp"]
}
@Variables {
    global {
        VCustCode char(6)
        VName     char(20)
        VCity     char(15)
        VTotal    number(5)
        VLast     char(6)
    }
}
@Layout {
    DataGridView("GridDemo", 2, 2, 5)
    [
        Column("Code", &VCustCode)
        Column("Name", &VName)
        Column("City", &VCity)
    ]
}
@Source {
    Event Load
        For Each In GRIDDEMO Index GRIDDMNOM
            &VCustCode = CustCode
            &VName     = Name
            &VCity     = City
            LoadRow()
        EndFor
    EndEvent

    Event Enter
        &VTotal = 0
        For Each Row
            &VTotal = &VTotal + 1
            &VLast = &VCustCode
        EndFor
        Message("Rows: " + String(&VTotal, 5) + " Last: " + &VLast, Info)
    EndEvent
}

2. Loading the grid: Event Load + LoadRow()

LoadRow() takes the current values of the variables bound to each Column and appends a row to the grid. It’s only valid inside a For Each in the body of Event Load (that For Each must declare Index). It loads the entire result set in a single pass — Page Up/Down are native terminal scrolling over what’s already loaded; Load never runs again.

3. Reading the selection: RefreshSelectRow() and CurrentInput()

When the cursor lands inside the grid’s rectangle (header, attention row, or any data row), CurrentInput() returns the grid’s name. RefreshSelectRow() re-reads the row under the cursor and writes each column (including Hidden ones) back into its bound variable — it’s the reverse of LoadRow():

Event Enter
    &Control = CurrentInput()
    If &Control = "GridDemo"
        RefreshSelectRow()
        Message("You selected: " + &VCustCode, Info)
    EndIf
EndEvent

4. Iterating over all loaded rows: For Each Row

For Each Row ... EndFor iterates over all rows already loaded into the subfile (not the database table), writing each Column into its bound variable before every iteration — same direction as RefreshSelectRow(). It doesn’t support Index/Where/When None because there’s no table being iterated:

Event Enter
    &VTotal = 0
    For Each Row
        &VTotal = &VTotal + 1
    EndFor
EndEvent

5. Hidden columns

Column("Internal ID", &VInternalId, Hidden)

A Hidden column loads its data (participates in LoadRow()/RefreshSelectRow()/For Each Row) but doesn’t show its cell or header title — useful for carrying an internal key without taking up screen space.

6. Editable columns (Input)

Column("Quantity", &VQuantity, Input)

Lets the user edit the value directly in the grid.

7. Rules to keep in mind

  • The name ("GridDemo") is required and must be unique among Input/DataGridView controls on the same screen.
  • Only one DataGridView per Screen.
  • Only valid inside a Screen’s @Layout, never a Program’s.

Reports with Member() / Export()

Pattern for generating a report: accumulate rows into a physical table (a data member exclusive to the user/run) and, when done, dump it to a text file on the IFS.

This feature uses varchar in @Variables — see Data types.

1. Full example

@Properties {
    Type : Program
    Name : PTESTME
}
@References {
    Tables = ["../tables/TLT3D01.warp"]
}
@Variables {
    global {
        User        char(10)
        Destination varchar(200)
        T301TIPLOT  char(1)
        T301NROTAR  char(19)
    }
}
@Source {
    &User        = USERID()
    &Destination = "/tmp/report.txt"

    Member(LT3D01, &User)

    For Each In LT3D01 Index LT3D0101
    Where T301TIPLOT = &T301TIPLOT
    Where T301NROTAR = &T301NROTAR
        // ... accumulate rows into LT3D01, e.g. with New In LT3D01 ...
    EndFor

    Export(LT3D01, &Destination)
}

2. Member(Table, expr): one member per user/run

Member(LT3D01, &User)

Attaches a data member (parameterized by expr, typically &User so each user gets their own) to Table for the whole program:

  • Creates it with ADDPFM if it doesn’t exist yet (tolerates the error if it already exists).
  • Leaves it active with OVRDBF — effective for the rest of the program, including any open of that table and any later Export(...) over it.

Position rules (at most once per table, in either case):

  • In a Program: only at the top level of @Source (not nested inside If/While/For/Event/etc.).
  • In a Screen: only at the top level of Event Init (not nested, and not in any other Event) — Init runs exactly once, before the screen’s main loop, the same place a Program’s prologue would go:
@Properties {
    Type : Screen
    Name : SGRIDDEMO
}
@Source {
    Event Init
        Member(LT3D01, &User)
    EndEvent

    Event Load
        For Each In LT3D01 Index LT3D0101
            // ... fills the DataGridView, or reads/writes rows ...
        EndFor
    EndEvent
}

Export(...) still isn’t allowed in a Screen — there’s no use case for exporting a report from an interactive screen.

Important: Member(Table, expr) must appear before any For Each/New over Table in the same body — using it after already reading/writing the table is a runtime error, not a compile-time one.

3. Export(Table, destination): dump to IFS

Export(LT3D01, &Destination)

Copies Table’s active member (the one Member(...) set, if any) to an IFS stream file with CPYTOIMPF, comma-delimited format (RCDDLM(*CRLF) STRDLM(*NONE) FLDDLM(',')), replacing the destination if it already exists (MBROPT(*REPLACE)).

Rules: only valid in Program; unlike Member, no position restriction — use it wherever convenient, typically after the For Each/New that filled the report table.

Project: .warproj and .warpcfg

Every .warp file is compiled in the context of a project (.warproj), which in turn points to a generator config file (.warpcfg) holding the code generation rules and the IBM i deployment settings.

1. .warproj

@Project {
    Name            : Example
    Description     : "Sample project"
    Version         : 1.0
    Default Profile : Dev
    @Generator {
        Name        : "waRPGate generator"
        Language    : RPGLE
        Description : "RPGLE generator"
        @Paths {
            Config : ../example/generator/rpg/rpg.warpcfg
            Output : output/rpg
        }
    }
}
  • @Paths/Config: path to the .warpcfg (relative to the .warproj).
  • @Paths/Output: folder where the generated RPGLE/DDS is written.

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      : "Exit"
            F5      : "Refresh"
            F24     : "More keys"
        }
    }
    @Profile "Dev" {
        @Connection {
            Host        : DEVSERVER
            User        : myuser
            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    : 40
            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        : myuser
            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    : 40
            Debug           : false
            Temp Path       : /tmp
            Data Library    : DTALIB02
            Objects Library : OBJLIB01
            Source Tables   : QDDSSRC
            Source Programs : QRPGLESRC
            Compile Library : DTALIB01,DTALIB02,DTALIB03,OBJLIB01
        }
    }
}

What each block controls

  • Top level (Date Format, Print Mode, Commitment, Commit on Exit, DDS Name): code generation rules, applied to the whole project. Commitment/Commit on Exit can be overridden per object in a specific .warp file’s @Properties.
  • @Screen: default screen size (24x80 if omitted) and the function keys (@FunctionKeys) available to the project’s Screens — the label shown in the attention line. A Screen attaches a key to its own event with Event 'Name' <n> ... EndEvent.
  • @Profile "Name": a deployment environment (Dev, UAT, Prod, the name is free-form). Each profile groups its own @Connection, @Deployment, and, optionally, @License. A .warpcfg must declare at least one @Profile.
    • @Connection: how the compiler reaches the IBM i for --action build/reverse (doesn’t apply to validate/generate, which are purely local).
    • @Deployment: libraries and source member where the generated code gets uploaded and compiled (CRTPF/CRTLF/CRTCLPGM).

With more than one @Profile, it’s necessary to indicate which one to use: either with --profile <Name> on the CLI, or by leaving Default Profile set in @Project (as in the .warproj example above). The VSCode extension has its own picker — see VSCode commands.

See Project file for the full list of keys and their allowed values.

Licensing

The compiler’s generate, build and reverse actions require a license. The validate action does not, so source validation (and the extension’s IntelliSense) always works.

The license is managed automatically: it’s taken when using the compiler and released when it’s done, with no manual intervention.

License modes

Organization

A license service manages a pool of seats. The compiler:

  • Connects securely to your organization’s license service (identifying the server with Fingerprint, in the project configuration). Host/Port/Fingerprint are handed out by whoever administers that service — see Setting up the license service if that’s you.
  • Each user holds a single seat at a time.
  • If the compiler closes or loses the connection, the seat is released automatically — no one is left holding a license they’re not using.
  • Compatible with Kerberos/Active Directory authentication, to integrate with your existing corporate credentials.

Individual

A signed license file, bound to the machine, validated locally with no network. The default path is:

  • Linux/macOS: ~/.config/warpgate/license.json
  • Windows: %APPDATA%\warpgate\license.json

Another path can be set with File (in @License), --license-file or WARPGATE_LICENSE_FILE.

Configuration

@License block in the .warpcfg

Optional block inside each @Profile (see Project file) — each profile (Dev/UAT/Prod) can have its own license configuration, to consume different licenses or licensing services depending on the environment. Properties are case-insensitive.

@GeneratorConfig {
    @Profile "Prod" {
        @License {
            Host        : "servidor01.dominio.local"
            Port        : 7443
            Fingerprint : "3f2a9c41d87b05e6a1c4f0937be2d5688a1f4c0d29e7b3a65c8d1f0e4b7a9c23"
        }
    }
}

--profile/Default Profile picks which profile (and therefore which @License) to use on each run — see Project: .warproj and .warpcfg.

KeyDescription
HostLicense service host. If present, the mode is organization and File is ignored.
PortService TCP port, 1 to 65535. Default 7443.
FingerprintSHA-256 fingerprint of the service certificate: 64 hexadecimal characters (uppercase and : separators are accepted).
SPNKerberos service name. Default warpgate-license/<host>.
FilePath to the individual license file.

Without Host, the mode is individual.

Flags and environment variables

FlagEnvironment variableEquivalent to
--license-hostWARPGATE_LICENSE_HOSTHost
--license-portWARPGATE_LICENSE_PORTPort
--license-fingerprintWARPGATE_LICENSE_FINGERPRINTFingerprint
--license-spnWARPGATE_LICENSE_SPNSPN
--license-fileWARPGATE_LICENSE_FILEFile

Precedence: flag > environment variable > .warpcfg.

With several @Profile declared, license-status also respects --profile/Default Profile to know which @License to read from — see Project: .warproj and .warpcfg.

Exit codes

CodeMeaning
3No license available. The compiler prints a message with what to do.
4The license was lost mid-run.
130Run interrupted (Ctrl-C/SIGTERM).

Requesting a license

These support actions don’t take a seat and don’t need --source; --project is optional.

warpgate --action license-request --license-id <ID> --license-out request.json

It writes the request file (--license-out is optional; default request.json) and shows the machine identifier. Once you receive the license file, copy it to the default path (or to the one set with File). Installing a new license replaces the previous one: the previous one stops being valid by serial number.

How to get a license (free for now, time-limited)

Warpgate is currently distributed free of charge: Software House issues free individual licenses, valid for 90 days and renewable. This policy may change later, without affecting already-issued licenses.

  1. On the machine where the compiler will run, generate the request:
    warpgate --action license-request --license-id <name or company> --license-out request.json
    
  2. Send to warpgate@softwarehouse.pe:
    • The generated request.json file.
    • The machine identifier the command prints on screen (needed to issue the license).
  3. Software House replies with a signed license.json file, tied to that machine, valid for 90 days.
  4. Copy that file to the default path (or the one set by --license-file/File/WARPGATE_LICENSE_FILE):
    • Linux/macOS: ~/.config/warpgate/license.json
    • Windows: %APPDATA%\warpgate\license.json
  5. Confirm with warpgate --action license-status (it should show the status and the expiry date).

Once the 90 days are up, generate/build/reverse stop working until you renew — repeat this same procedure to get a new license. validate keeps working regardless, even without a license. For a license with seats for several users (organization mode), also write to warpgate@softwarehouse.pe.

To check the current state:

warpgate --action license-status

It shows the mode, the license, the expiration and, in organization mode, the seats in use and available.

License scope

A license covers one product and one major version (for example, the 0.x series). It can be perpetual or have an expiration date.

Development

Development builds accept WARPGATE_LICENSE=off to skip the requirement. Release binaries do not.

CodeSituation
E00284@License Port is 0.
E00285@License Host is not a valid host.
E00286Fingerprint is not a valid SHA-256 fingerprint.
E00287Warning: Host without Fingerprint.
E00288Warning: Host and File together (File is ignored).

Setting up the license service (organization mode)

This page is for whoever administers your organization’s infrastructure (systems team, or the IBM i server administrator) — you don’t need to read it to program in WARP. It covers the one-time setup of the service that hands out licenses across your whole development team. People who just use Warpgate get three final values (Host, Port, Fingerprint) from the administrator and put them in their @License — see Licensing.

1. Download

On Linux, make them executable: chmod +x license-service license-gui.

2. Request the organization license

On the server where the service will run:

license-service create-request --license-id "<Your organization's name>" --out request.json
license-service gen-hwid

The second command prints several machine details; copy the primary_os_id value (a 32-character string). Send to warpgate@softwarehouse.pe:

  • The request.json file.
  • The primary_os_id value.
  • How many simultaneous seats your team needs.

Software House replies with a license.json file for that server, valid for 90 days and renewable — same free-of-charge model as the individual license, see Licensing.

3. Install the service

Windows

license-service install-license --license license.json
license-service gen-tls-cert --subject-alt-name <server-host-or-domain>
license-service install-windows-service --bind 0.0.0.0:7443

This registers license-service as a regular Windows service (starts automatically, restarts itself on failure) — manage it from the Services console like any other. The second command prints the SHA-256 fingerprint on screen: that value is the Fingerprint needed by anyone connecting.

  1. Copy the binary and create a dedicated user for the service:
    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. Install the license and generate the certificate, as that user:
    sudo -u warpgate-license env LICENSE_SERVICE_DIR=/var/lib/license-service \
      license-service install-license --license /path/to/license.json
    sudo -u warpgate-license env LICENSE_SERVICE_DIR=/var/lib/license-service \
      license-service gen-tls-cert --subject-alt-name <server-host-or-domain>
    
    Write down the SHA-256 fingerprint the second command prints: that value is the Fingerprint.
  3. Download the systemd unit file, copy it and enable the service:
    sudo cp license-service.service /etc/systemd/system/
    sudo systemctl daemon-reload
    sudo systemctl enable --now license-service
    

This makes the service start on boot and restart itself on failure. Logs: journalctl -u license-service -f. Only open TCP port 7443 to the subnet of Warpgate users.

Linux, quick test (no systemd)

To test before installing it as a permanent service:

license-service install-license --license license.json
license-service gen-tls-cert --subject-alt-name <server-host-or-domain>
license-service run --bind 0.0.0.0:7443

Runs in the foreground; stops when you close the terminal. Useful to verify everything works before the step above.

4. Hand the values to your team

Each developer puts these three values in their project’s @License (see Licensing):

  • Host: the server where the service runs.
  • Port: 7443 (or whatever was set with --bind).
  • Fingerprint: the one gen-tls-cert printed.

If your organization uses Active Directory, the service can require Kerberos authentication — contact Software House to enable it.

5. Checking the service with license-gui

license-gui is a separate window, no command line, for checking the license status without touching the seats in use (it doesn’t consume a seat): active license and expiry, seats in use and available, and each user’s session history (including whether a session closed normally or was lost).

On opening, it asks for the same three values as any client — Host, Port, Fingerprint — plus an optional SPN if the service requires Kerberos. Whoever administers the service can also install a new license from there, with no restart needed.

Structure of a .warp file

A .warp file is a sequence of @Name { ... } sections. Which sections apply depends on @Properties/Type:

SectionProgramScreenTable
@Documentationtop leveltop levelnested inside @Structure
@Propertiesyesyesyes (only Type)
@Structurenonoyes
@Fieldsnononested inside @Structure
@Indexesnononested inside @Structure
@Referencesyesyesno
@Variablesyesyesno
@Parametersyesnono
@Layoutyes (optional)yesno
@Sourceyesyesno

A .warproj/.warpcfg doesn’t describe an object — they use their own top-level sections (@Project, @GeneratorConfig) — see Project file.

General syntax

  • Comments: // to end of line.
  • Strings: double or single quotes interchangeably ("text" and 'text' are semantically the same).
  • Variables: always with & (&Username); table field names go without & inside a For Each/New.
  • Every top-level section carries @; control names inside @Layout may carry @ optionally (@Label/Label are equivalent).

Reference index

@Documentation

Informational metadata, doesn’t affect code generation.

In Program/Screen (top level)

Flat Key : value pairs:

KeyDescription
Author NameName of who wrote the program.
Author UserUser ID of who wrote it.
App IdShort application identifier.
App NameVisible application name.
App DescriptionLong application description.
Module IdShort module identifier.
Module NameVisible module name.
Module DescriptionLong module description.
Created AtFree-form date/timestamp (e.g. "12/06/2025"), not parsed or validated.
@Documentation {
    Author Name          : Programmer Name
    Author User          : User Code
    App Id               : Application ID
    App Name             : Application Name
    Module Id            : Module ID (if applicable)
    Created At           : "12/06/2025"
}

In Table (nested inside @Structure)

Here @Documentation has no keys of its own, only sub-sections:

  • @Author: Name, User, Company.
  • @Application: Id, Name, Description.
  • @Module: Id, Name, Description.
  • @Repository: Official, Type (e.g. git), Branch, Version.
  • @History: container for @Creation and any number of @Change entries.
    • @Creation: Date, Author, User, Company, Reason.
    • @Change (repeat the block for each entry): Date, Type (by convention breaking/feature/fix/security/performance/refactor/config/docs, not enforced by the compiler), Impact (by convention high/medium/low), Version, Reason, Author, User, Company.
@Structure {
    Name : "GRIDDEMO"
    @Documentation {
        @Author {
            Name : Programmer
        }
        @History {
            @Creation {
                Date   : "2025-06-12"
                Author : Programmer
                Reason : "Initial creation"
            }
            @Change {
                Date    : "2026-01-10"
                Type    : feature
                Impact  : medium
                Reason  : "Adds City column"
            }
        }
    }
    @Fields { ... }
}

@Properties

Program

KeyDescription
TypeThe kind of object this file compiles to (Program).
NameProgram name.
DescriptionProgram description. Maximum 50 characters: it ends up in the TEXT parameter of the compile-time CL commands (CRTBNDRPG/CRTDSPF/CHGPFM), which has that real limit on IBM i (see E00281).
Print ModePrint mode, for a report-style program.
CommitmentWhether this program runs its modified tables under commitment control. Overrides the project’s Commitment (@GeneratorConfig) only for this program. Values: True/False.
Commit on ExitWhether this program issues a final commit before finishing. Overrides the project’s value only for this program; only effective if Commitment is enabled. Values: True/False.
DDSName of the DDS this program is attached to.
PathOutput folder for this program, relative to the project.
@Properties {
    Type            : Program
    Name            : PTEST01
    Description     : "Test program"
    Commitment      : True
    Commit on Exit  : True
    Path            : "./programs/"
}

Screen

Same keys as Program (Type with value Screen), plus 5 of its own:

KeyDescription
PopupGenerates this Screen’s DSPF with the DDS WINDOW keyword (a pop-up window) instead of a full screen. Requires the 4 keys below. Values: True/False.
Window RowRow (1-based) of the window’s top-left corner. Required if Popup: True.
Window ColumnColumn (1-based) of the window’s top-left corner. Required if Popup: True.
Window RowsWindow height, in rows. Required if Popup: True.
Window ColumnsWindow width, in columns. Required if Popup: True.
@Properties {
    Type    : Screen
    Name    : SCONFIRM
    Popup   : True
    Window Row     : 5
    Window Column  : 10
    Window Rows    : 10
    Window Columns : 40
}

A Screen with Popup: True is generated, compiled and deployed exactly like any other Screen — only the DSPF changes (WINDOW instead of a full screen). To show it from another Screen, reference it in @References/Screens and call it with Call(Name, ...) — see @Source: control statements.

Table

Only Type:

@Properties {
    Type : "Table"
}

The rest of a table’s description goes in @Structure — see @Structure, @Fields, @Indexes.

@Structure, @Fields, @Indexes

Only apply to table files (@Properties/Type : "Table").

@Structure

KeyDescription
NameTable name. Must start with a letter, letters/digits only, max 10 characters (DDS object name limit).
DescriptionTable description.

Also contains, nested: @Fields, @Indexes and optionally @Documentation (see @Documentation).

@Fields

One Name type(...) line per column:

@Fields {
    CustCode char(6)   Description("Customer code")
    Name     char(20)  Description("Name")
    Balance  number(9,2) Default(0)
    RegDate  date
}

Allowed types: only char, number, date (not field/list/matrix — those are exclusive to @Variables, see Data types).

Modifiers:

ModifierDescription
Description("text")Field description. Requires a quoted string.
Default(value)Default value: a quoted string or a bare number, depending on the field’s type. Not allowed on date fields.
AllowNullAllows NULL on this field. A PrimaryKey field can’t carry this modifier.

@Indexes

One Kind(Name, [Fields...], "optional description") entry per access path:

@Indexes {
    PrimaryKey ( GRIDDEMOPK, [CustCode] )
    Unique     ( GRIDDEMOEMAIL, [Email], "Unique email" )
    Index      ( GRIDDMNOM, [Name] )
}
KindDescription
PrimaryKeyThe table’s primary access path. Only one per table; its fields can’t be AllowNull.
UniqueUnique secondary access path.
IndexNon-unique secondary access path.

Full example

@Properties {
    Type : "Table"
}
@Structure {
    Name        : "GRIDDEMO"
    Description : "Sample customers"
    @Fields {
        CustCode char(6)   Description("Customer code")
        Name     char(20)  Description("Name")
        City     char(15)  Description("City")
    }
    @Indexes {
        PrimaryKey ( GRIDDEMOPK, [CustCode] )
        Index ( GRIDDMNOM, [Name] )
    }
}

@References

Lists other .warp files this Program/Screen depends on. Unlike other sections, it uses Key = [ "..." ] (a bracketed list), not Key : value.

KeyDescription
ProgramsOther Program .warp files this one references (for Call(Name, ...) resolved at compile time).
ScreensScreen .warp files this one references — same as Programs, enables calling it with Call(Name, ...) (typically a Screen with @Properties/Popup: True, shown as a pop-up window).
TablesTable .warp files this one references (enables For Each In/New In/field(...) over them).
@References {
    Programs = [
        "../programs/POTHER.warp"
    ],
    Screens = [
    ],
    Tables = [
        "../tables/TLADD50.warp",
        "../tables/TLGRIDDEMO.warp"
    ]
}

Paths are relative to the file that declares them. Doesn’t apply to table files (a table doesn’t reference other objects).

@Variables

Declares variables inside a named scope: global (visible throughout the file) or the name of a Procedure/Function declared in @Source (visible only inside that procedure/function, in addition to global ones).

@Variables {
    global {
        Username    char(10)
        MyDate      date
        Balance     number(9,2)
    }
    MyProcedure {
        counter    number(3)
    }
    Greet {
        name      char(10)
    }
}

One Name type(...) line per variable — same format as @Fields, but with more available types (see Data types): char, varchar, number, date, field(Field), list(type), matrix(rows, cols, type), struct(TemplateName).

Struct: inline template definition

Inside the global scope (only there) you can also define a struct template with struct Name [ ... ], before or after instantiating it with struct(Name) — see Data types: Struct for the full syntax and its limits.

Modifier

Only Description("text") is available in @Variables (unlike @Fields, it doesn’t support Default/AllowNull):

@Variables {
    global {
        RequestNo  field(ADHOJA50)  Description("Request number")
    }
}

Length/precision limits (standalone variable, different from a table field)

TypeLimit in @FieldsLimit in @Variables
char1–32,7661–16,773,104
varcharnot valid in @Fields1–16,773,100
number (precision)1–301–63

These larger limits in @Variables reflect that a variable declared there isn’t tied to a DDS physical file the way an @Fields field is.

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)@Variables onlyInherits the type of a FieldName field from some referenced table’s @Structure.
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.

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))
    }
}

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 (the element count goes back to 0).
Remove(index)statementRemoves the element at position index (1-based, number type) and shifts the following ones back one position.
Count()expressionReturns the current number of elements (number type). Can only be used inside an expression, never as a standalone statement.
&List1.Add(10)
&List1.Add(20)
&List1.Add(30)

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

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

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

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.

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/time/timestamp/field(FieldName) — you can’t nest another struct, nor use list/matrix as a member.

  • 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: a struct nested inside another struct, or struct as a parameter of an external program (@Parameters/Call) — only as a parameter of an internal Procedure/Function.

@Parameters

Declares a Program’s parameters: one mode:&Variable entry per line. The type comes from the matching declaration in @Variables/global, it isn’t repeated here.

ModeDescription
inInput parameter: the caller passes a value, this program can’t return it modified.
inoutInput/output: the caller passes a value, and this program can modify it back.
outOutput: this program sets it, the caller only reads it back.
@Variables {
    global {
        MyDate  date
        MyTime  char(8)
    }
}
@Parameters {
    inout:&MyDate,
    inout:&MyTime
}

Doesn’t apply to Screen (doesn’t receive parameters) or tables.

@Layout

Describes the layout of a screen or report: Label, Input, Column, DataGridView, Block, or any other Name(...) as a generic control. Control names may carry @ optionally (@Label/Label are equivalent).

Controls with their own grammar

ControlSyntaxValid inDescription
LabelLabel(x, y, text)Program, ScreenStatic label at x,y. text can be &Variable, "literal" or a bare field name. x,y = 1,1 (exact corner) is forbidden — DDS rejects any field there.
BlockBlock("Name")[ Label(...), ... ]ProgramA named, printable group of Labels — an instance is printed with Print("Name") from @Source.
InputInput("Name", x, y, text, &Variable[, enabled])ScreenEditable field at x,y. "Name" identifies the control (read with CurrentInput()). &Variable is where the entered value is stored. enabled optional (default true): a literal, &Variable or a function returning number (non-zero = enabled), re-evaluated before every redraw. x,y = 1,1 is also forbidden.
ColumnColumn(text, variableOrField[, Input|Hidden])inside DataGridView onlyThe optional 3rd argument makes the column editable (Input) or invisible (Hidden).
DataGridViewDataGridView("Name", x, y, rows[, separator])[ Column(...), ... ]ScreenA named grid at x,y showing rows visible rows. The name is required and unique among Input/DataGridView controls. See DataGridView for the full guide.

Example (Screen)

@Layout {
    @Label(1,2,&Program)
    @Label(1,70,&MyDate)
    @Input("txtRequestNo",3,1,"Request No.: ",&RequestNo,true)
    @DataGridView("GridRequests",6,1,8," ") [
        @Column("Country",&nCountry)
        @Column("User",&cUser)
    ]
}

Example (Program, with a printable Block)

@Layout {
    @Block("Header") [
        Label(1,1,"Sales Report")
        Label(2,1,&Today)
    ]
}
@Source {
    Print("Header")
}

@Layout is optional in Program (a purely batch program, with no printed output, doesn’t need it) but required in Screen. Doesn’t apply to tables.

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

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/Remove/Count.

New: inserting a row

New In Table
    Field = expr
    ...
[When Duplicate
    // runs if the insert collides with an existing unique key
]
EndNew

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

Procedure / Function

Procedure Name(&param)
    ...
EndProc

Function Name(&param) returnType
    ...
    Return expr
EndFunc
  • Procedure (no return value, sub is a synonym): called with Do 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

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

Print

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 to Table for the whole program (ADDPFM + OVRDBF, before any open). In Program, only at the top level of @Source; in Screen, only at the top level of Event Init (see @Source: Event and Screen). At most once per table.
  • Export(Table, destination): copies Table’s active member to an IFS stream file (CPYTOIMPF, comma-delimited). Only valid in Program (not in Screen); no position restriction within it.

Return

Return [expr]

Exits a Function (the value is required if you want to honor the declared return type).

@Source: Event and Screen

Statements exclusive to Screen files.

Event

Event StandardName
    ...
EndEvent

Event 'Label' KeyNumber
    ...
EndEvent

Two forms: standard event (bare, unquoted name) or a custom event attached to a function key (quoted name + key number, 1-24, i.e. F1-F24; F13-F24 via Shift).

Standard events

EventWhen it fires
EnterOn pressing Enter (with no function key active). CurrentInput() identifies which Input had focus, decoded from the cursor position after EXFMT.
CloseFixed to F3 in the generated DSPF (CF03) — always runs (even empty) and ends the screen; doesn’t require a declaration to attach the key.
LoadOnce, before the first display — typically contains the For Each/LoadRow() that fills the DataGridView. Page Up/Down are native scrolling over what’s already loaded; this event never runs again.
RefreshOn pressing F5, if F5 is declared in the .warpcfg’s @FunctionKeys and isn’t taken by a custom Event 'Name' 5.
InitOnce, the first time this screen starts, before the first display. Does nothing if not declared. The only place in a Screen where Member(...) is valid — see Reports with Member()/Export().

Custom events

Event 'FindCustomer' 4
    Message("F4 was pressed", Info)
EndEvent

The label shown in the screen’s attention line is configured in the project’s .warpcfg @FunctionKeys (see Project), not in the Event itself.

Message

Message(text, Error|Warning|Info)

Shows a message on the screen’s message line: Error (red), Warning (yellow), Info (normal text color). Shown on the next screen refresh and cleared after one cycle.

LoadRow / RefreshSelectRow / For Each Row

Exclusive to screens with a DataGridView — see the full guide: DataGridView.

StatementUse
LoadRow()No arguments. Takes the current values of the variables bound to each Column and appends a row to the grid. Only valid inside a For Each in the body of Event Load (that For Each must declare Index).
RefreshSelectRow()No arguments. The reverse of LoadRow(): re-reads the row under the cursor and writes each column (including Hidden ones) back into its bound variable. Valid in any Event of a Screen with a DataGridView.
For Each Row ... EndForIterates over every row already loaded into the subfile (not the table), writing each Column into its bound variable before every iteration. No Index/Where/When None.

CurrentInput

&Control = CurrentInput()

Returns the Name of the Input/DataGridView (from @Layout) that currently has focus. For a DataGridView, it matches if the cursor lands anywhere in its rectangle (header, attention row, or any data row), not just an exact cell. Only valid inside an Event’s body — using it anywhere else is an error.

Builtin functions

Usable as an expression anywhere, e.g. &Username = USERID(). Most take zero arguments with a fixed return type; Val() is the exception (see its own entry).

FunctionReturnDescription
USERID()char(10)IBM i user profile running this program. No arguments.
PGNAME()char(10)Object name of this same program — a compile-time constant, not a runtime lookup. No arguments.
TODAY()dateCurrent system date. No arguments.
TIME()char(8)Current system time formatted "HH:MM:SS" (8 characters). No arguments.
Val(&CharVariable)same as the assignment’s destinationParses a char value into a Number, using the destination of the assignment’s precision/scale — unlike other builtins, it has no fixed return type. Only valid directly as &NumberVariable = Val(&CharExpr); using it anywhere else (nested in another expression, as a Do/Call argument, etc.) is an error.
CurrentInput()char(30)See @Source: Event and Screen.
String(&NumberVariable, integerDigits[, decimalDigits])char(integerDigits [+ 1 + decimalDigits])Converts a Number to char, e.g. to concatenate it in a Message(...) (which only accepts text). integerDigits/decimalDigits must be integer literals (compile-time constants) — the return width depends on them. decimalDigits is optional (default 0, no decimal separator in the result). Fixed width, zero-padded (no leading-zero suppression); doesn’t handle negatives specially.
SubString(&Variable, start, length)char(length)Trims a fixed-width chunk out of a char value (variable or literal) so it fits into a smaller variable — e.g. &Short40 = SubString(&Long120, 1, 40). Without this, directly assigning a wider char to a narrower one is a compile-time error. start/length must be integer literals. When the source size is known at compile time, start + length - 1 exceeding it is also a compile-time error.
Trim(&Variable)same as the argument (char/varchar)Removes blanks from both left and right. A single argument.
LTrim(&Variable)same as the argumentRemoves blanks from the left. A single argument.
RTrim(&Variable)same as the argumentRemoves blanks from the right. A single argument.
Length(&Variable)numberSize of a char/varchar value: for varchar, the actual runtime content length; for char, its declared fixed length. A single argument.
IndexOf(needle, &haystack)numberSearches for needle inside haystack and returns the raw position found (1-based if found, 0 if not), with no conversion applied. Exactly two arguments (text to search for, text to search in).

Date, time and timestamp

Year/Month/Days/Hour/Minute/Second are polymorphic: they accept date/time/timestamp as appropriate (Year/Month/Days with date or timestamp; Hour/Minute/Second with time or timestamp) — there’s no need for a different name per type.

format (where it applies) is always optional: if omitted, it falls back to the value configured in @GeneratorConfig (Date Format/Time Format/Timestamp Format); if there’s no configuration either, the system default format is used.

FunctionReturnDescription
Year(&Variable)number(4)Year of a date/timestamp.
Month(&Variable)number(2)Month of a date/timestamp.
Days(&Variable)number(2)Day of the month of a date/timestamp.
Hour(&Variable)number(2)Hour of a time/timestamp.
Minute(&Variable)number(2)Minute of a time/timestamp.
Second(&Variable)number(2)Second of a time/timestamp.
DateDiff(&Date1, &Date2, Unit)numberDifference between two dates, in Days/Months/Years (bare word, not a string). Positive when Date1 is later than Date2, negative otherwise.
TimeDiff(&Time1, &Time2, Unit)numberSame as DateDiff, over time, with Hours/Minutes/Seconds.
TimestampDiff(&Ts1, &Ts2, Unit)numberSame as DateDiff, over timestamp, with all 6 units enabled: Seconds/Minutes/Hours/Days/Months/Years.
DateAdd(&Date, amount, Unit)dateAdds (or subtracts, if amount is negative) days/months/years to a date. amount can be any number expression (not just a literal).
TimeAdd(&Time, amount, Unit)timeSame as DateAdd, over time, with Hours/Minutes/Seconds.
TimestampAdd(&Ts, amount, Unit)timestampSame as DateAdd, over timestamp, with all 6 units.
IsDate(value[, format])number(1)Validates whether value (char/varchar/number) is a valid date in format (optional — one of Iso/Usa/Eur/Jis/Mdy/Dmy/Ymd/Jul). Returns 1/0 (WARP has no boolean type). Not a pure expression — that’s why, just like Val(), it’s only valid directly as &NumberVariable = IsDate(...).
IsTime(value[, format])number(1)Same as IsDate, over time (format one of Hms/Iso/Usa/Eur/Jis).
IsTimestamp(value[, format])number(1)Same as IsDate, over timestamp (format one of Iso/Usa/Eur/Jis). Iso is the recommended, most widely compatible format.
StringToDate(string[, format])dateConverts char/varchar to date.
NumberToDate(number[, format])dateConverts number to date — same format rules as StringToDate.
StringToTime(string[, format])timeConverts char/varchar to time.
StringToTimestamp(string[, format])timestampConverts char/varchar to timestamp.
DateToString(&Date[, format])char(10)Converts date to text — same format rules.
TimeToString(&Time[, format])char(8)Converts time to text.
TimestampToString(&Ts[, format])char(26)Converts timestamp to text (26 = ISO width with microseconds).
Now()timestampCurrent system timestamp. No arguments.

Examples

&Username = USERID()
&Program = PGNAME()
&Today = TODAY()
&CurrentTime = TIME()

&Quantity = Val(&QuantityText)

Message("Total: " + String(&Total, 9, 2), Info)

&Short = SubString(&Long, 1, 40)
&Clean = Trim(&WithSpaces)
&Size = Length(&Description)
&Pos = IndexOf("@", &Email)

&DueDate = DateAdd(&Today, 30, Days)
&DaysUntilDue = DateDiff(&DueDate, &Today, Days)
&IsValidDate = IsDate(&DateText, Dmy)

&Now = Now()
&HoursElapsed = TimestampDiff(&Now, &Start, Hours)

Expressions and operators

Arithmetic/concatenation operators

OperatorUse
+Numeric addition between Numbers, or concatenation between char/varchar (depending on the operand types).
-Numeric subtraction.
*Numeric multiplication.
/Numeric division.
&Total = &Price * &Quantity
&Greeting = "Hello, " + &Username

Comparison operators

=, <>, <, <=, >, >= — used in Where, If, While.

Logical operators

And/Or combine comparisons (typically in If/While): And binds tighter than Or.

If &a = 1 And &b = 2 Or &c = 3
    ...
EndIf

WARP has no boolean type of its own: each operand is validated like any other expression.

Literals

  • Numbers: 100, 9.5.
  • Text: "text" or 'text' (equivalent).
  • Date/time: obtained via TODAY()/TIME(), there’s no date literal.

Variables and fields

  • Variable: always with & (&Username).
  • Table field: without &, only inside a For Each/New over that table (CustCode, Name).
  • list/matrix element: &Variable[index] / &Variable[row, col] (1-based).
  • list method: &Variable.Method(...) (Add/Clear/Remove as a statement, Count() as an expression) — see Data types: List.
  • struct member: &Variable.Member (read and assignment) — see Data types: Struct.

Function/procedure calls as an expression

&variable = FunctionName(expr, ...)

See Builtin functions and @Source: control statements for your own Functions.

Project file (.warproj / .warpcfg)

See also the guide: Project.

.warproj — @Project

KeyDescription
NameProject name.
DescriptionProject description.
VersionProject version (free-form string, e.g. 1.0).
Default ProfileName of the @Profile (defined in the .warpcfg, see below) used by generate/build/reverse/license-status when the CLI doesn’t receive --profile. Optional if the .warpcfg declares only one @Profile.

@Generator (nested inside @Project)

KeyDescription
NameGenerator name.
DescriptionGenerator description.
LanguageTarget language. Values: RPGLE, RPG.

@Paths (nested inside @Generator)

KeyDescription
ConfigPath to this project’s .warpcfg.
OutputFolder where the generated DDS/RPG is written.

.warpcfg — @GeneratorConfig

KeyDescriptionValues
Date FormatNative date format used to declare every Date variable (e.g. date(*dmy/) instead of plain date).ISO, USA, EUR, JIS, MDY, DMY, YMD, JUL
Date SeparatorDate separator character (e.g. / in date(*dmy/)). A single character.—
Time FormatNative time format used to declare every Time variable (e.g. time(*hms:) instead of plain time), and default for IsTime/StringToTime/TimeToString when their format argument is omitted. Usa is 12-hour with an AM/PM suffix, no seconds — structurally different from the other 4 (24-hour hh:mm:ss).Hms, Iso, Usa, Eur, Jis
Time SeparatorTime separator character (e.g. : in time(*hms:), also used by TIME()). A single character. Doesn’t apply to Usa (no separator between fields).—
Timestamp FormatDefault for IsTimestamp/StringToTimestamp/TimestampToString when their format argument is omitted. The timestamp type doesn’t accept a format in its declaration, unlike date/time. Iso is recommended for its broader compatibility.Iso, Usa, Eur, Jis
Print ModeWhat happens to the QPRINT spool file each Print("Block") writes to: with Screen the program displays it (DSPSPLF) and deletes it on exit; Printer/File just leave it spooled. Default Printer.Printer, File, Screen
CommitmentEnables commitment control for the whole project: generated programs declare their modified tables commit usropn and open them after a best-effort STRCMTCTL (tables must be journaled). A Program can override it with @Properties/Commitment. Default False.True, False
Commit on ExitWhether generated programs issue a final commit before finishing. Overridable via @Properties/Commit on Exit. Only effective if Commitment is True. Default False.True, False
DDS NamePattern for naming each Screen’s display file DDS based on its object name — e.g. <ObjectName>D appends a D. Default <ObjectName> (unchanged). Only affects the DSPF; the generated RPG driver program keeps the object name as-is. The result is always uppercased and truncated to 10 characters (OS/400 object name limit) — the variants that append D truncate to 9 first so the D is never what gets truncated.<ObjectName>, <ObjectName>D, D<ObjectName>, D<ObjectNameWithoutFirstChar>

Top-level keys (Date Format, Print Mode, Commitment, etc.), @Screen/@FunctionKeys, and the date/time/timestamp formats are unique at the project level: they aren’t repeated per profile.

@Profile (nested inside @GeneratorConfig)

A .warpcfg declares one or more deployment profiles (typically Dev, UAT, Prod, though the name is free-form) inside @GeneratorConfig. Each @Profile "Name" groups its own @Connection, @Deployment, and @License — so a single project can point to different servers/libraries/licenses depending on the environment, without maintaining several .warpcfg files.

@GeneratorConfig {
    @Profile "Dev" {
        @Connection { ... }
        @Deployment { ... }
    }
    @Profile "Prod" {
        @Connection { ... }
        @Deployment { ... }
        @License { ... }
    }
}

Declaring at least one @Profile is mandatory: @Connection/@Deployment/@License declared directly inside @GeneratorConfig (without wrapping them in @Profile) are no longer valid (error E00290). A @GeneratorConfig with no @Profile at all gives error E00291.

How the active profile is chosen for generate/build/reverse/license-status (the validate action ignores profiles):

  1. The CLI’s --profile <Name> flag, case-insensitive.
  2. If omitted, Default Profile from @Project (see the .warproj — @Project section above).
  3. If there’s no Default Profile either but the .warpcfg only declares one @Profile, that one is used.
  4. If none of the above applies and there are several profiles declared, the compiler fails with error E00293, listing the available profiles.

Requesting a profile that doesn’t exist (via --profile or Default Profile) fails with error E00292, also listing the available profiles.

@Screen (nested inside @GeneratorConfig)

KeyDescription
RowsNumber of screen rows. Numeric, default 24.
ColumnsNumber of screen columns. Numeric, default 80.

@FunctionKeys (nested inside @Screen)

One F<n> : "Label" entry per line (n from 1 to 24, e.g. F3 : "Exit"). Attached to its own event with Event 'Name' <n> ... EndEvent in a Screen’s @Source.

@Connection (nested inside @Profile)

How to reach the IBM i:

KeyDescriptionValues
HostHostname or IP of the IBM i. Required.—
UserUser profile to connect with. Required.—
Auth MethodHow to authenticate. Required. password reads the password from the WARPGATE_PASSWORD environment variable; interactive, when run from the VSCode extension, prompts for it with a dialog box on every Build & Deploy (running the raw binary from a terminal, it behaves the same as password).key, password, interactive
Key FilePath to the private key file. Required only if Auth Method: key.—
ProtocolTransport protocol. Required. ftp can’t be combined with Auth Method: key.ssh, sftp, ftp
PortTCP port. Numeric, default 22.—
TimeoutConnection timeout in seconds. Numeric (-128 to 127), default 30.—

ssh/sftp/ftp use native clients: there’s no need to have any ssh/scp/sftp/ftp client installed on the machine running the compiler.

Examples by protocol and authentication method

SSH or SFTP with a key (ssh/sftp are equivalent: the native client uses the same transport for both):

@Connection {
    Host        : MYHOST
    User        : MYUSER
    Auth Method : key
    Key File    : ~/.ssh/id_ed25519_ibmi
    Protocol    : ssh
    Port        : 22
    Timeout     : 30
}

SSH or SFTP with a password — the password isn’t written into the .warpcfg; it’s read from the WARPGATE_PASSWORD environment variable, which must exist before opening VSCode (or the terminal the compiler runs from):

@Connection {
    Host        : MYHOST
    User        : MYUSER
    Auth Method : password
    Protocol    : sftp
    Port        : 22
    Timeout     : 30
}

FTP with a password — FTP has no concept of key-based authentication (Auth Method: key with Protocol: ftp is a validation error), so it only accepts password/interactive, both read from the same WARPGATE_PASSWORD variable:

@Connection {
    Host        : MYHOST
    User        : MYUSER
    Auth Method : password
    Protocol    : ftp
    Port        : 21
    Timeout     : 30
}

Generating the Key File

Auth Method: key needs an SSH key pair: a private one (the one Key File points to) and a public one, which must be authorized on the IBM i user profile.

  1. Generate the key pair (on the machine running the compiler/VSCode, not on the IBM i):
    ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_ibmi -N ""
    
    This creates id_ed25519_ibmi (private — the path that goes in Key File) and id_ed25519_ibmi.pub (public). -N "" leaves the key without a passphrase, a requirement for Auth Method: key.
  2. Copy the contents of id_ed25519_ibmi.pub (a single line) to the end of the .ssh/authorized_keys file inside the IBM i user’s IFS home directory (e.g. /home/MYUSER/.ssh/authorized_keys) — create the file and directory if they don’t exist. The IBM i’s SSH daemon (SSHD, from product 5733-SC1) must be active and configured to accept key-based authentication.
  3. Verify that Key File in @Connection points to the private key’s path on the local machine (not the .pub one), and that Auth Method is key.

@Deployment (nested inside @Profile)

Where/how to deploy and compile on the IBM i:

KeyDescriptionValues
CL SourceName of the generated CL source member (OS/400 object name, max. 10 characters). Required.—
CleanupWhether the staged source is removed after a successful compile.true, false
Target ReleaseTarget OS/400 release for the compile (e.g. V7R4M0).—
OptimizationCompiler optimization level.—
DebugWhether to compile with the debug view enabled.true, false
Temp PathIFS staging directory used during deploy. Required.—
Data LibraryLibrary where the compiled DDS physical/logical file objects are created (OS/400 object name). Required.—
Objects LibraryLibrary holding the DDS/CL source members (QDDSSRC/QCLSRC) and where the compiled CL program object is created (OS/400 object name). Required.—
Source TablesSource member holding the physical/logical DDS source (OS/400 object name). Required.—
Source ProgramsSource member holding the generated RPGLE/CL.—
Compile LibraryComma-separated list of libraries added to the compile job’s library list, in order, instead of just Data Library — for programs whose tables live across several libraries.—

@License (nested inside @Profile)

Optional block with the licensing configuration, specific to each profile — so Dev/UAT/Prod can consume different licenses or licensing services. Keys are case-insensitive. See the Licensing guide.

KeyDescriptionValues
HostLicense service host. With Host the mode is organization and File is ignored; without Host, the mode is individual.—
PortService TCP port. Default 7443.1-65535
FingerprintSHA-256 fingerprint of the service certificate (accepts : and uppercase).64 hex
SPNKerberos service name. Default warpgate-license/<host>.—
FileIndividual license file. Default ~/.config/warpgate/license.json (%APPDATA%\warpgate\license.json on Windows).—
@License {
    Host        : "servidor01.dominio.local"
    Port        : 7443
    Fingerprint : "3f2a9c41d87b05e6a1c4f0937be2d5688a1f4c0d29e7b3a65c8d1f0e4b7a9c23"
}

Full example

See Project: .warproj and .warpcfg for a complete example with both files.

Diagnostics and error messages

The compiler reports every issue with an E##### code (e.g. E00221), a severity level, and a message — currently only in Spanish — with the concrete values interpolated.

Levels

LevelMeaning
errorBlocks code generation; the object doesn’t compile.
warningDoesn’t block compiling, but flags something probably unintentional (e.g. a variable declared and never used).

What they look like

On the command line (human format):

[2026-07-24T21:26:47Z] ERROR E00221 (line: 12, column: 5): La variable 'x' declara char(16773105), fuera del rango permitido (1-16773104).

Or as JSON (--format json, the one the VSCode extension consumes to draw red/yellow underlines in the editor):

{"errors": [{"line": 12, "column": 5, "severity": "error", "message": "..."}]}

Some representative examples

CodeSituation
E00001A @Name section was expected and something else was found.
E00128A file referenced by the .warpcfg (e.g. the SSH key in @Connection) doesn’t exist on this machine — doesn’t block validate/generate, but will be needed for build/reverse.
E00152A variable from @Variables was declared but never used (warning).
E00195Invalid use of RefreshSelectRow()/For Each Row outside a Screen with a DataGridView.
E00221char outside the allowed range for a standalone variable in @Variables (1-16,773,104).
E00222number with precision out of range (1-63) in @Variables.
E00223number whose scale is greater than its precision.
E00224varchar outside the allowed range (1-16,773,100).
E00238struct(Name) references a template that doesn’t exist in @Variables.
E00240&Variable.Member access to a member that doesn’t exist in the struct’s template.
E00242A struct(OtherTemplate) variable was passed where a different template was expected (the comparison is by exact name, not by structure).
E00269A table’s Description (@Structure) exceeds the 50 characters allowed by TEXT in DDS.
E00270A field’s Description (@Fields) exceeds the 50 characters allowed by TEXT in DDS.
E00271An index’s Description (@Indexes) exceeds the 50 characters allowed by TEXT in DDS.
E00281The Description in @Properties (of a Program or Screen) exceeds the 50 characters allowed by TEXT in the compile-time CL commands (CRTBNDRPG/CRTDSPF/CHGPFM). Same limit and criterion as E00269/E00270/E00271.
E00284@License Port is 0.
E00285@License Host is not a valid host.
E00286@License Fingerprint is not a valid SHA-256 fingerprint.
E00287Warning: @License has Host but no Fingerprint.
E00288Warning: @License has Host and File together (File is ignored).
E00290@Connection/@Deployment/@License declared directly inside @GeneratorConfig, without wrapping them in a @Profile "Name".
E00291@GeneratorConfig doesn’t declare any @Profile.
E00292The profile requested with --profile or Default Profile doesn’t exist among the declared @Profiles (available ones are listed).
E00293Several @Profiles are declared and it wasn’t indicated which one to use (neither --profile nor Default Profile), so the compiler can’t pick just one (available ones are listed).

Each message has a code, a level (error/warning) and a template with the case’s actual details ({variable}, {length}, etc.) the compiler fills in when reporting it.

When they fire

  • On saving/opening a .warp file in VSCode (warpgate.validateOnSave/validateOnOpen), if there’s a .warproj resolved for that file.
  • Manually with warpgate --action validate — never touches the IBM i, purely local.
  • License note: validate does not require a license; generate/build/reverse do (see Licensing).
  • As part of generate/build/reverse — if there are errors, code generation/deployment doesn’t proceed.

IntelliSense in VSCode

The WaRPGate extension for VSCode adds language support for .warp files:

  • Syntax highlighting: sections, keywords, data types, event names, function calls, operators.
  • Autocomplete (@/& as triggers): sections, @Source keywords, data types, builtin functions (with a snippet), per-section property keys, @Layout controls.
  • Hover: this same book’s documentation, summarized in one line, when hovering over any keyword/function/type.
  • Live diagnostics: validates on open/save (see Diagnostics), drawing red/yellow underlines directly in the editor.
  • Outline/breadcrumbs: section structure and Procedure/Function as navigable symbols.
  • Go to definition and document links: jump to the table/program/screen referenced in @References.
  • Layout Preview: live preview of @Layout as an IBM i 5250 terminal screen (24x80 by default, or the size configured in the project’s .warpcfg @Screen).
  • Commands: see VSCode commands for a screenshot example of each (Validate File, Generate, Build & Deploy, Select Project File, Reverse Engineer DDS, Preview Layout, Select Layout Block).

IntelliSense stays in sync with every new language release, so highlighting, autocomplete and diagnostics always reflect the compiler’s current capabilities.

Installation

The packaged .vsix includes the compiler binaries for all 3 platforms — there’s no need to install the compiler separately.

VSCode commands

The WaRPGate extension adds 8 commands to the command palette (Ctrl+Shift+P / F1), all prefixed with WaRPGate:. This page shows a real example of each, with screenshots.

WaRPGate: Validate File

Runs the compiler in validation mode on the active .warp file: checks syntax and semantics (types, references, undeclared variables, etc.) without generating any file. Errors and warnings show up in the “WaRPGate” output channel.

Example: a Program that assigns an undeclared variable.

@Source {
    &CodigoCliente = &Inexistente
}

WaRPGate: Generate

Runs the full compiler on the active file: validates and, if there are no errors, generates the RPGLE/DDS/CL files locally (nothing is deployed to IBM i). It’s the equivalent of the CLI’s --action generate.

WaRPGate: Build & Deploy

Generates just like the previous command and additionally uploads the files to the IBM i configured in the .warpcfg’s @Connection, to compile them there (CRTPF/CRTLF/CRTBNDRPG/CRTDSPF). Since this connects to a real system, it always asks for confirmation before continuing.

WaRPGate: Select Project File (.warproj)

When the workspace has more than one .warproj, this command picks which one to use for validating/generating/deploying. The chosen project is remembered for the other commands.

WaRPGate: Reverse Engineer DDS…

Reconstructs a table .warp file from a DDS member that already exists on IBM i. It asks, in order: the QSYS.LIB path of the table’s DDS member, the index (LF) DDS members to include in @Indexes (optional), and the name of the .warp file to generate. At the end it confirms before connecting.

WaRPGate: Preview Layout

Shows the active file’s @Layout as an IBM i 5250 terminal screen (see IntelliSense in VSCode). If the Screen uses Popup: True, the preview draws the window at its real position and size within the full screen.

Example: a popup Screen with a 3-column DataGridView.

WaRPGate: Select Active Profile (@Profile)

Available from the command palette and from the editor’s context menu on a .warp file. Opens a picker with the profiles (@Profile) declared in the active project’s .warpcfg, plus the “Use project default” option. The choice is remembered for that project (persists across VSCode restarts) and is reflected in its own status bar item (layers icon), which also opens the same picker with a click.

Without an explicit choice, the extension doesn’t send --profile to the compiler and the CLI falls back to the Default Profile declared in @Project — see Project file.

WaRPGate: Select Layout Block

When a Program (report) has more than one Block in its @Layout, this command picks which one to show in the Layout Preview — the preview always shows a single Block at a time.