Aller au contenu
RPG II · RPG III · RPG/400 · RPG IV

Le cycle GAP du RPG, la boucle qu'on ne voit pas

Un programme RPG peut lire tout un fichier, imprimer un état avec ses sous-totaux et s'arrêter seul, sans une seule boucle ni un seul READ. C'est le cycle GAP. Voici d'où il vient, pourquoi il existe, et ce qu'il fait vraiment, relevé pas à pas dans le débogueur STRISDB.

1959annonce de l'IBM 1401, la machine du premier RPG
7étapes, répétées à chaque enregistrement
0boucle et READ dans le programme
98arrêts relevés dans STRISDB

En une phrase

Le cycle GAP (ou cycle RPG, RPG program cycle en anglais) est la boucle que le compilateur ajoute de lui-même à un programme RPG qui déclare un fichier primaire : lire un enregistrement, fermer le groupe précédent s'il est terminé (calculs et impression des totaux), traiter l'enregistrement, et recommencer jusqu'à la fin du fichier.

D'où ça vient

Avant les ordinateurs, la gestion se faisait avec des cartes perforées et des tabulatrices : des machines comptables qui lisaient un paquet de cartes trié, additionnaient des montants et imprimaient un état. On ne les programmait pas en écrivant du code, mais en câblant un tableau de connexions. Leur fonctionnement était un cycle machine : une carte entre, on cumule, on imprime ; et quand le numéro de client change sur la carte suivante, on imprime le sous-total avant de continuer.

Le 5 octobre 1959, IBM annonce l'IBM 1401, un ordinateur pensé pour remplacer ces machines. Pour lui naissent deux générateurs d'états : FARGO, puis le RPG, pour Report Program Generator (« générateur de programmes d'états »), qui le remplace vite. Les sources ne s'accordent pas sur l'année exacte du RPG : l'encyclopédie Wikipédia retient 1959, des anciens programmeurs du 1401 le situent vers 1961. Le but, lui, est connu : que les techniciens des tabulatrices passent à l'ordinateur sans tout réapprendre. Le RPG reprend donc le cycle de la machine : le programmeur remplit des feuilles de programmation en colonnes (quel fichier, quelles zones, quels totaux, quelle mise en page) et le compilateur génère la boucle autour.

En France, le RPG s'est appelé le GAP, pour « Générateur Automatique de Programmes ». Le nom apparaît avec le System/3 : on parlait de GAP II sur les System/3, /34 et /36, puis de GAP III. Le manuel de Max Rémy paru chez Masson s'intitule d'ailleurs « Générateur automatique de programme : le langage GAP/RPG ». D'où le nom de cycle GAP, que l'on entend encore dans les équipes IBM i.

  1. 1959
    IBM 1401, FARGO puis RPGL'IBM 1401 est annoncé en octobre ; FARGO, puis le RPG au tout début des années 1960, y reprennent le cycle des tabulatrices.
  2. 1969
    RPG II (GAP II)Vers 1969, sur le System/3, puis les System/32, /34 et /36. Le cycle est au cœur du langage.
  3. 1978
    RPG IIISur le System/38, annoncé en octobre 1978 et commercialisé en 1979 : fichiers décrits en externe, opérations structurées (IF, DO). On peut écrire tout un programme sans le cycle.
  4. 1988
    RPG/400Le RPG III de l'AS/400, celui des exemples de cette page.
  5. 1994
    RPG IV (ILE RPG)Noms longs, fonctions intégrées, procédures. Le cycle reste disponible.
  6. 2013
    RPG en format libre (7.1, par PTF)Les spécifications H, F, D et P s'écrivent en format libre (CTL-OPT, DCL-F, DCL-S…). Les spécifications I et O, celles du cycle, restent en colonnes.
  7. 2015
    **FREEPar PTF sur 7.1 et 7.2, d'origine en 7.3 : le code peut commencer en colonne 1 et dépasser la colonne 80. C'est le « full free ».

Pourquoi c'était comme ça

Le cycle n'est pas une bizarrerie : il collait exactement aux contraintes de l'époque.

  • Les données arrivaient dans l'ordre. Un paquet de cartes, puis une bande magnétique, se lisent du début à la fin, sans revenir en arrière. Lire l'enregistrement suivant était la seule opération possible : inutile de la faire écrire au programmeur.
  • Le travail, c'était l'état imprimé. Factures, relevés, paie, statistiques : presque tous les programmes lisaient un fichier trié et imprimaient des lignes avec des sous-totaux par client, par agence, par mois. Le cycle et ses ruptures font exactement ça.
  • La mémoire se comptait en milliers de caractères. L'IBM 1401 existait en six tailles, de 1 400 à 16 000 caractères. Une logique toute faite, générée par le compilateur, prenait moins de place et de temps que des programmes écrits à la main.
  • Les mêmes gens, la même logique. Les opérateurs de tabulatrices retrouvaient dans le cycle le fonctionnement de leurs machines, et les feuilles en colonnes ressemblaient à leurs tableaux de connexions.

Le revers : la logique du programme est en partie invisible. Ce qui se passe dépend de l'ordre des étapes du cycle, que l'on ne lit nulle part dans le source. C'est pour cela qu'il déroute tant les développeurs d'aujourd'hui.

Le cycle en 7 étapes

À chaque enregistrement, le programme repasse par les mêmes étapes, toujours dans cet ordre. C'est l'ordre que décrit le manuel de référence du RPG d'IBM, et c'est celui que l'on observe dans le débogueur (niveau 2).

Le cycle GAP en 7 étapes Sorties d'en-tête et de détail, lecture, calculs de total, sorties de total, test de LR qui mène à la fin du programme, transfert des zones, calculs de détail, puis retour à la première étape. 1 Sorties d'en-tête et de détail 2 Lecture d'un enregistrement 3 Calculs de total 4 Sorties de total 5 LR est allumé ? 6 Transfert des zones 7 Calculs de détail enregistrement suivant oui : fin
  1. Sorties d'en-tête et de détail. Lignes H et D des spécifications O. Au premier cycle, seules les lignes 1P (en-têtes) s'impriment.
  2. Lecture d'un enregistrement. Le fichier primaire (IP) est lu sans instruction READ. Le cycle compare les zones de contrôle : si elles changent, L1 (à L9) s'allume. En fin de fichier, LR s'allume.
  3. Calculs de total. Les calculs marqués L1… L9 ou LR en colonnes 7-8. Ils voient encore les valeurs du groupe précédent. Sautés au premier enregistrement.
  4. Sorties de total. Les lignes T. Puis, si la page est pleine (indicateur OF), les lignes de débordement : nouvel en-tête.
  5. LR est allumé ?. Si oui, le programme ferme ses fichiers et s'arrête. Sinon, il continue.
  6. Transfert des zones. Les zones de l'enregistrement lu à l'étape 2 passent dans les variables du programme.
  7. Calculs de détail. Les calculs sans indicateur de niveau. Puis retour à l'étape 1.

Retenez surtout ceci : le total d'un groupe se calcule et s'imprime après la lecture du premier enregistrement du groupe suivant, mais avant que ses zones n'arrivent dans les variables. Tout le reste en découle.

L'allégorie de la caissière

Imaginez une caissière de supermarché à l'ancienne, avec son tapis roulant et ses barres « client suivant ». Elle n'a pas à réfléchir à l'ordre de son travail : il est toujours le même, comme le cycle.

Elle prend l'article qui arrive et le bipe. Elle prend le suivant et le bipe. Un jour, l'article qu'elle saisit est une barre de séparation : le client précédent a fini. Avant de biper quoi que ce soit du nouveau client, elle imprime le ticket de l'ancien, avec ses articles et son total. Puis elle repart à zéro et bipe le premier article du nouveau client. Au premier client de la journée, elle n'imprime aucun ticket avant : il n'y a personne à encaisser. Le soir, le magasin ferme : elle imprime le ticket du dernier client, puis le ticket Z avec le total de la journée.

À la caisseDans le cycleCe qu'il faut comprendre
Le tapis roulant qui apporte les articlesLe fichier primaireLa caissière ne va pas chercher les articles : ils arrivent seuls, un par un. Pas de READ.
Biper un articleLes calculs de détailAjouter le prix au ticket en cours.
La barre « client suivant » posée sur le tapisLa zone de contrôle (L1)Elle ne la voit qu'en prenant l'article suivant : la rupture se découvre à la lecture.
Imprimer le ticket du client qui partCalculs et sorties de totalElle le fait avant de biper l'article du nouveau client, avec les montants de l'ancien.
Pas de ticket avant le premier clientPas de total au premier enregistrementIl n'y a encore personne à encaisser.
Changer le rouleau de papierLe débordement (OF)Nouvelle page, on réimprime l'en-tête.
La fermeture du magasinLRDernier ticket (le dernier groupe), puis le total de la journée, puis on éteint.

Niveau 1 Découvrir : un programme sans boucle

Lire et imprimer un fichier sans écrire de boucle

Le fichier de démonstration contient six ventes fictives, triées par région. Sa description DDS :

     A          R CYCVTR                    TEXT('Ventes par client')
     A            REGION        10A         TEXT('Region')
     A            CLIENT        20A         TEXT('Client')
     A            MONTNT         7P 2       TEXT('Montant')

Voici un programme RPG/400 complet qui imprime ces six ventes. Lisez-le : il n'y a aucune spécification de calcul (ligne C), donc ni boucle, ni lecture, ni test de fin de fichier.

     H
     F* Niveau 1 : le cycle lit, imprime et s'arrete tout seul
     FCYCVTE  IP  E                    DISK
     FQSYSPRT O   F     132     OF     PRINTER
     ICYCVTR      01
     OQSYSPRT H  201   1P
     O       OR        OF
     O                                   24 'VENTES PAR CLIENT'
     O        D  1     01
     O                         REGION    12
     O                         CLIENT    34
     O                         MONTNTJ   46
IP (ligne F de CYCVTE)
I pour entrée, P pour primaire : c'est ce fichier que le cycle lit tout seul. C'est cette lettre P qui met le cycle en route.
01 (ligne I)
Indicateur allumé par le cycle chaque fois qu'il lit un enregistrement du format CYCVTR.
H … 1P
Ligne d'en-tête imprimée au premier cycle (1P, first page) et à chaque nouvelle page (OF).
D … 01
Ligne de détail, imprimée à chaque cycle où un enregistrement a été lu.

Résultat réel, compilé par CRTRPGPGM et exécuté sur IBM i 7.5 (spoule QSYSPRT) :

       VENTES PAR CLIENT
  NORD        DUPONT                   120.00
  NORD        MARTIN                    80.50
  OUEST       DURAND                    75.00
  SUD         BERNARD                  200.00
  SUD         PETIT                     50.00
  SUD         ROBERT                    30.00

Le programme s'est arrêté seul : à la lecture qui suit ROBERT, le cycle a trouvé la fin du fichier, allumé LR (last record) et terminé le programme.

Écran du débogueur STRISDB affichant le source complet du programme CYCLE1 : spécifications H, F, I et O, aucune spécification C
Le programme CYCLE1 ouvert dans STRISDB (ISDB/400) : douze lignes, aucune instruction de calcul.

Dans le débogueur, la touche F5 (pas à pas) montre ce que le cycle exécute. Pour CYCLE1, les arrêts se succèdent ainsi : 600 800 900 500 700 500 600 900 1000 1100 1200 … L'instruction 500 (la ligne I) revient deux fois par cycle : une fois pour la lecture, une fois pour le transfert des zones. Et chaque ligne de détail s'imprime au début du cycle suivant (600 à 1200), juste avant la lecture d'après.

À retenir au niveau 1

  • Un fichier déclaré IP est lu par le cycle : pas de READ.
  • Le cycle tourne tant qu'il reste des enregistrements : pas de boucle.
  • En fin de fichier, il allume LR et arrête le programme : pas de test de fin.

Niveau 2 Maîtriser : ruptures, totaux et ordre exact

Sous-totaux par région : la rupture de contrôle L1

Ajoutons des sous-totaux par région, un compteur de clients et un total général. Toujours sans boucle :

     H
     F* Niveau 2 : rupture L1 sur la region, totaux et LR
     FCYCVTE  IP  E                    DISK
     FQSYSPRT O   F     132     OF     PRINTER
     ICYCVTR      01
     I              REGION                          REGIONL1
     C* Calculs de detail : a chaque enregistrement lu
     C   01      TOTREG    ADD  MONTNT    TOTREG  92
     C   01      NBCLI     ADD  1         NBCLI   30
     C* Calculs de total : au changement de region
     CL1         TOTGEN    ADD  TOTREG    TOTGEN 112
     OQSYSPRT H  201   1P
     O       OR        OF
     O                                   24 'VENTES PAR REGION'
     O        D  1     01
     O                         REGION    12
     O                         CLIENT    34
     O                         MONTNTJ   46
     O        T  2     L1
     O                                   19 'TOTAL'
     O                         REGION    30
     O                         TOTREGJB  46
     O                         NBCLI ZB  52
     O                                   62 'CLIENT(S)'
     O        T  1     LR
     O                                   27 'TOTAL GENERAL'
     O                         TOTGENJ   46

Numéros d'instruction : STRISDB numérote les lignes du membre source de 100 en 100. La 5e ligne (la ligne I) est l'instruction 500, la 11e (le calcul de total) l'instruction 1100, et ainsi de suite.

REGION … L1 (ligne I)
REGION devient zone de contrôle de niveau 1 : quand sa valeur change d'un enregistrement à l'autre, le cycle allume L1.
Lignes C sans niveau (800, 900)
Calculs de détail, faits à chaque enregistrement (indicateur 01).
CL1 (1100)
Calcul de total, fait seulement quand L1 est allumé : on ajoute le total de la région au total général.
Lignes T … L1 et T … LR
Sorties de total. Le B (blank after) remet TOTREG et NBCLI à zéro après l'impression.

Résultat réel, compilé par CRTRPGPGM et exécuté sur IBM i 7.5 (spoule QSYSPRT) :

       VENTES PAR REGION
  NORD        DUPONT                   120.00
  NORD        MARTIN                    80.50
              TOTAL NORD               200.50      2 CLIENT(S)
  OUEST       DURAND                    75.00
              TOTAL OUEST               75.00      1 CLIENT(S)
  SUD         BERNARD                  200.00
  SUD         PETIT                     50.00
  SUD         ROBERT                    30.00
              TOTAL SUD                280.00      3 CLIENT(S)
              TOTAL GENERAL            555.50

Le cycle pas à pas : la mémoire à chaque étape

Avancez étape par étape dans CYCLE2 et regardez ce que le cycle fait de la mémoire : l'enregistrement qui attend dans le tampon d'entrée, les variables du programme, les indicateurs 1P, 01, L1, OF et LR, et l'état qui s'imprime. Les lignes du source en vert sont celles que le cycle exécute, celles barrées sont examinées puis sautées. Les 47 étapes suivent l'ordre relevé dans STRISDB, et l'état obtenu est celui du spoule réel.

Cycle 1 · étape 1 sur 7 : Sorties d'en-tête et de détail

 100 H 200 F* Niveau 2 : rupture L1 sur la region, totaux et LR 300 FCYCVTE  IP  E                    DISK 400 FQSYSPRT O   F     132     OF     PRINTER 500 ICYCVTR      01 600 I              REGION                          REGIONL1 700 C* Calculs de detail : a chaque enregistrement lu 800 C   01      TOTREG    ADD  MONTNT    TOTREG  92 900 C   01      NBCLI     ADD  1         NBCLI   301000 C* Calculs de total : au changement de region1100 CL1         TOTGEN    ADD  TOTREG    TOTGEN 1121200 OQSYSPRT H  201   1P1300 O       OR        OF1400 O                                   24 'VENTES PAR REGION'1500 O        D  1     011600 O                         REGION    121700 O                         CLIENT    341800 O                         MONTNTJ   461900 O        T  2     L12000 O                                   19 'TOTAL'2100 O                         REGION    302200 O                         TOTREGJB  462300 O                         NBCLI ZB  522400 O                                   62 'CLIENT(S)'2500 O        T  1     LR2600 O                                   27 'TOTAL GENERAL'2700 O                         TOTGENJ   46

Tampon d'entrée (l'enregistrement lu)

REGION—
CLIENT—
MONTNT—

Variables du programme

REGION(blancs)
CLIENT(blancs)
MONTNT0,00
TOTREG0,00
NBCLI0
TOTGEN0,00

Indicateurs

  • 1P allumé
  • 01 éteint
  • L1 éteint
  • OF éteint
  • LR éteint

Premier cycle : 1P (first page) est allumé, la ligne d'en-tête s'imprime. La ligne de détail est examinée, mais 01 est éteint : rien n'a encore été lu.

État imprimé

       VENTES PAR REGION

Le déroulé réel, relevé dans STRISDB

Nous avons lancé STRISDB PGM(CYCLE2) et appuyé sur F5 jusqu'à la fin du programme : 98 arrêts. Le débogueur s'arrête sur chaque ligne que le cycle examine, y compris celles dont la condition est fausse. Voici ces arrêts, découpés en cycles :

CycleInstructions où ISDB s'arrêteCe qui se passe
1 1200 1400 1500 500 1300 500 800 900 Premier cycle : les lignes 1P impriment l'en-tête. Lecture de DUPONT (NORD). Pas de total : il n'y a pas encore de groupe à fermer. Transfert des zones, puis calculs de détail.
2 1200 1500 1600 1700 1800 500 1100 1900 2500 1300 500 800 900 La ligne de détail de DUPONT s'imprime. Lecture de MARTIN : même région, pas de rupture. Les lignes de total sont examinées mais pas exécutées.
3 1200 1500 1600 1700 1800 500 1100 1900 2000 2100 2200 2300 2400 2500 1300 500 800 900 MARTIN s'imprime. Lecture de DURAND (OUEST) : la région change, L1 s'allume. Calcul de total, puis impression de « TOTAL NORD » (2000 à 2400). Ensuite seulement, les zones d'OUEST arrivent dans les variables.
4 1200 1500 1600 1700 1800 500 1100 1900 2000 2100 2200 2300 2400 2500 1300 500 800 900 DURAND s'imprime. Lecture de BERNARD (SUD) : nouvelle rupture, « TOTAL OUEST » s'imprime.
5 1200 1500 1600 1700 1800 500 1100 1900 2500 1300 500 800 900 BERNARD s'imprime. Lecture de PETIT : même région, pas de rupture.
6 1200 1500 1600 1700 1800 500 1100 1900 2500 1300 500 800 900 PETIT s'imprime. Lecture de ROBERT : même région.
7 1200 1500 1600 1700 1800 1100 1900 2000 2100 2200 2300 2400 2500 2600 2700 ROBERT s'imprime. La lecture suivante tombe sur la fin du fichier : LR s'allume, et avec lui L1. Total de SUD (1100, 1900 à 2400), puis total général (2500 à 2700), puis fin du programme. Plus d'arrêt en 500 : il n'y a plus rien à lire.

Repères : 500 = la ligne I (lecture, puis transfert des zones), 800-900 = calculs de détail, 1100 = calcul de total, 1200-1800 = en-tête et détail, 1300 = débordement (OF), 1900-2400 = total de région, 2500-2700 = total général.

STRISDB arrêté sur l'instruction 1300, ligne surlignée en rouge, juste après la première lecture
Premier cycle : juste après la première lecture (500), ISDB passe directement à 1300. Les totaux (1100, 1900) sont sautés : il n'y a pas encore de groupe à fermer.

La preuve : au moment du total, les variables ont encore l'ancien groupe

Un point d'arrêt sur la ligne 1100 (BREAK 1100, puis F17 pour exécuter) arrête le programme au calcul de total. Au deuxième arrêt, le cycle vient de lire DURAND, de la région OUEST : c'est ce changement de région qui a allumé L1. Pourtant, F11 sur REGION affiche NORD, et TOTREG vaut 200,50, le total de NORD.

STRISDB arrêté sur le calcul de total à l'instruction 1100, ligne surlignée en rouge
Arrêt sur le calcul de total (1100), au moment de la rupture NORD → OUEST.
Fenêtre Variables de programme de STRISDB : REGION vaut NORD
REGION vaut encore NORD…
Fenêtre Variables de programme de STRISDB : TOTREG vaut 200,50
… et TOTREG le total de NORD : 200,50.

On avance ensuite en pas à pas : les lignes 2000 à 2400 impriment « TOTAL NORD », 1300 teste le débordement, puis 500 transfère enfin les zones de DURAND. Arrivé au calcul de détail (800), REGION vaut OUEST et TOTREG a été remis à zéro par le B de la sortie de total.

STRISDB arrêté sur le calcul de détail à l'instruction 800, le point d'arrêt 1100 apparaît en vert
Arrêt au calcul de détail (800). La ligne verte est le point d'arrêt posé en 1100.
Fenêtre Variables de programme de STRISDB : REGION vaut OUEST
Après le transfert, REGION vaut OUEST…
Fenêtre Variables de programme de STRISDB : TOTREG vaut 0,00
… et TOTREG repart de 0,00.

C'est la caissière qui imprime le ticket de l'ancien client avant de biper l'article du nouveau. En fin de fichier, la fenêtre « Fin du programme » apparaît après l'instruction 2700, la dernière ligne du total général :

Fenêtre Fin du programme de STRISDB : le débogage du programme est terminé
Le cycle a allumé LR, fait le dernier total et terminé le programme.

Les pièges du niveau 2

  • Pas de total au premier enregistrement. Le premier enregistrement allume bien L1 (sa région diffère de « rien »), mais le cycle saute les calculs et les sorties de total à ce moment-là. Un calcul L1 n'est donc pas un bon endroit pour initialiser quelque chose avant le premier groupe : utilisez 1P ou les calculs de détail conditionnés par L1.
  • Au total, les variables appartiennent au groupe qui se termine. Un calcul de total qui utilise une zone de l'enregistrement lu (MONTNT, CLIENT) prend celle du dernier enregistrement du groupe précédent, pas celle du nouvel enregistrement.
  • LR allume aussi L1 à L9. En fin de fichier, tous les niveaux sont fermés : le dernier groupe a son total (TOTAL SUD) avant le total général. Inutile de le coder à part.
  • Le débogueur s'arrête sur des lignes qui ne s'exécutent pas. ISDB s'arrête sur 1100 à chaque cycle, même sans rupture : il montre la ligne que le cycle examine, la condition L1 est testée ensuite. Seuls les arrêts sur 2000 à 2400 prouvent qu'un total s'imprime.
  • Une zone éditée prend plus de place qu'on ne le croit. En écrivant ce programme, le nombre de clients était d'abord placé en position 35 : il n'apparaissait jamais. TOTREG (9 chiffres) édité par le code J occupe 13 positions, de 34 à 46, et recouvrait le compteur, sans aucun message du compilateur.
  • Une zone de contrôle d'un fichier décrit en externe s'écrit en deux fois. En RPG/400, la ligne I doit porter le nom externe en positions 21-30 et le nom RPG en 53-58, puis L1 en 59-60. Sans le nom en 53-58, la compilation échoue (QRG4119) ; sans le nom externe, aussi (QRG4078).

Le même programme sans cycle

Pour mesurer ce que le cycle fait à votre place, voici le même état en RPG IV full free, avec une boucle explicite. Il a été compilé par CRTBNDRPG et exécuté sur le même fichier : son état imprimé est identique, ligne pour ligne, à celui de CYCLE2.

**free
// Le même état que CYCLE2, sans le cycle : tout ce qu'il faisait est écrit à la main
ctl-opt dftactgrp(*no) actgrp(*new);

dcl-f CYCVTE usage(*input);            // plus de IP : on lit soi-même
dcl-f QSYSPRT printer(132);

dcl-ds ligne len(132);
  zone char(132) pos(1);
end-ds;

dcl-s regionPrec char(10);
dcl-s totReg packed(9:2);
dcl-s nbCli packed(3:0);
dcl-s totGen packed(11:2);
dcl-s premier ind inz(*on);

imprimer(8 : 'VENTES PAR REGION');      // ce que faisait la ligne H conditionnée par 1P
imprimer(1 : '');

read CYCVTR;                            // étape 2 du cycle : lire
dow not %eof(CYCVTE);
  if not premier and REGION <> regionPrec;
    totalRegion();                      // la rupture L1, détectée à la main
  endif;
  premier = *off;
  regionPrec = REGION;

  totReg += MONTNT;                     // calculs de détail
  nbCli += 1;

  zone = *blanks;                       // sortie de détail
  %subst(zone : 3 : 10) = REGION;
  %subst(zone : 15 : 20) = CLIENT;
  %subst(zone : 37 : 10) = %editc(MONTNT : 'J');
  write QSYSPRT ligne;

  read CYCVTR;
enddo;

if not premier;                         // à LR, le cycle fait aussi le dernier L1
  totalRegion();
endif;
zone = *blanks;
%subst(zone : 15 : 13) = 'TOTAL GENERAL';
%subst(zone : 32 : 15) = %editc(totGen : 'J');
write QSYSPRT ligne;

*inlr = *on;
return;

dcl-proc imprimer;
  dcl-pi *n;
    colonne int(10) const;
    texte varchar(132) const;
  end-pi;
  zone = *blanks;
  if %len(texte) > 0;
    %subst(zone : colonne : %len(texte)) = texte;
  endif;
  write QSYSPRT ligne;
end-proc;

dcl-proc totalRegion;
  totGen += totReg;                     // ce que faisait la ligne de calcul L1
  zone = *blanks;
  %subst(zone : 15 : 5) = 'TOTAL';
  %subst(zone : 21 : 10) = regionPrec;
  %subst(zone : 34 : 13) = %editc(totReg : 'J');
  %subst(zone : 50 : 3) = %editc(nbCli : 'Z');
  %subst(zone : 54 : 9) = 'CLIENT(S)';
  write QSYSPRT ligne;
  imprimer(1 : '');
  totReg = 0;                           // le B (blanc après) des spécifications O
  nbCli = 0;
end-proc;

Tout ce que le cycle cachait devient visible : la lecture (read), la boucle (dow not %eof), la détection de rupture (comparer REGION à la valeur précédente), le cas du premier enregistrement (premier), le dernier total à la fin du fichier, et la remise à zéro. Plus long, mais lisible par n'importe quel développeur.

Le cycle aujourd'hui

  • Il existe toujours. Les programmes RPG II, RPG III et RPG/400 à cycle tournent sur IBM i 7.5 et 7.6, et le RPG IV le connaît encore : il suffit d'une spécification F en colonnes déclarant un fichier IP. La déclaration libre DCL-F n'a pas d'option pour un fichier primaire.
  • Il explique le *INLR = *ON de tous les programmes. Sans fichier primaire, rien n'allume LR : si le programme ne le fait pas lui-même, ou ne sort pas par RETURN, le cycle relance ses calculs indéfiniment. Avec RETURN sans LR, le programme s'arrête mais garde ses fichiers ouverts et ses variables pour l'appel suivant.
  • On peut s'en passer complètement. Un programme écrit avec CTL-OPT MAIN(…) ou un module NOMAIN n'a pas de cycle du tout : ni LR à allumer, ni boucle cachée.
  • Faut-il réécrire l'existant ? Pas par principe : ces programmes marchent. On les réécrit en boucle explicite quand on doit les faire évoluer souvent, pour que tout le monde comprenne leur logique. Le savoir lire reste indispensable en maintenance.

Sources

Le fonctionnement du cycle et les exemples ont été vérifiés sur un IBM i 7.5 (voir la méthode). Les faits historiques et les règles du langage viennent de ces sources, consultées le 12 octobre 2026 :

  1. General RPG IV Program Cycle, IBM, ILE RPG Reference (IBM i 7.5). Les 7 étapes du cycle, les totaux sautés au premier cycle, LR et L1 à L9 allumés en fin de fichier.
  2. Program Flow in RPG Modules: Cycle Versus Linear, IBM, ILE RPG Reference (IBM i 7.5). Un module avec MAIN ou NOMAIN ne contient pas la logique du cycle.
  3. Equivalent Free-form Coding for Fixed-Form File Entries, IBM, ILE RPG Reference (IBM i 7.5). En format libre, un fichier est toujours entièrement procédural ou de sortie : DCL-F ne déclare jamais de fichier primaire.
  4. Free-form H, F, D and P statements for RPG, IBM Support, RPG Cafe. Le format libre des spécifications H, F, D et P, livré par PTF sur 7.1.
  5. IBM RPG, Wikipédia (en anglais). RPG présenté en 1959 pour l'IBM 1401, FARGO, l'héritage des tabulatrices, RPG II vers 1969, RPG IV en 1994, format libre en novembre 2013.
  6. RPG and FARGO on the 1401, IBM 1401 Restoration Project (Computer History Museum). Témoignages d'anciens programmeurs du 1401, qui situent FARGO et le RPG vers 1961.
  7. IBM 1401, Wikipédia (en anglais). Annonce le 5 octobre 1959, six tailles de mémoire, de 1 400 à 16 000 caractères.
  8. IBM System/38, Wikipédia (en anglais). Annonce le 24 octobre 1978, commercialisation en août 1979.
  9. Générateur automatique de programmes, Wikipédia. GAP, nom français du RPG, apparu avec le System/3 ; GAP II, puis GAP III.
  10. Générateur automatique de programme : le langage GAP/RPG, Max Rémy, Masson (collection ABC des langages). Manuel en français qui associe les noms GAP et RPG.

Questions fréquentes

Qu'est-ce que le cycle GAP ?

C'est la boucle que le compilateur RPG ajoute de lui-même à un programme qui déclare un fichier primaire : lire un enregistrement, détecter les ruptures, faire les calculs et les impressions de total, puis ceux de détail, et recommencer jusqu'à la fin du fichier. GAP (Générateur Automatique de Programmes) est le nom que le RPG a porté en France.

Pourquoi met-on *INLR = *ON à la fin des programmes RPG ?

LR (Last Record) est l'indicateur qui arrête le cycle. Avec un fichier primaire, le cycle l'allume seul en fin de fichier. Sans fichier primaire, rien ne l'allume : si le programme ne le fait pas (ou ne fait pas RETURN), le cycle relance les calculs indéfiniment.

Le cycle existe-t-il encore en RPG IV et en full free ?

Oui : un programme RPG IV qui déclare un fichier primaire (IP) en spécification F en colonnes tourne avec le cycle. La déclaration libre DCL-F ne sait pas déclarer de fichier primaire, et un programme écrit avec CTL-OPT MAIN ou dans un module NOMAIN n'a pas de cycle.

Pourquoi le total d'un groupe s'imprime-t-il avec les valeurs du groupe précédent ?

Parce que le cycle fait les calculs et les sorties de total juste après avoir lu l'enregistrement qui provoque la rupture, mais avant d'en transférer les zones dans les variables du programme. Les variables contiennent donc encore les valeurs du groupe qui se termine.

Faut-il réécrire les programmes à cycle ?

Pas forcément : ils fonctionnent toujours sur IBM i 7.5 et 7.6. On les réécrit en boucle explicite (READ, DOW) quand on doit les faire évoluer souvent, pour que leur logique soit visible de tous les développeurs.