Sie sind nun wieder eingeladen, die Diskussion spezieller technischer Probleme mit zu verfolgen. Bitte schicken Sie Fragen, Anregungen oder Antworten zu den vorgestellten Themen – ebenso wie Ihre Kritik – an unsere e-Mail-Adressen: dieter.bender@MidrangeMagazin.de oder Redaktion@MidrangeMagazin.de

Frage:

Wir haben jetzt unsere RPG-Programmierung auf ILE umgestellt und stellen fest, dass die Programmerstellung relativ kompliziert geworden ist. Wie vermeiden wir Bindefehler nach Programmänderungen?

Antwort:

Die Programmerstellung ist in der Tat komplizierter geworden. Reichte es bei OPM-Programmen weitgehend aus, alle Default-Parameter bei der Erstellung zu übernehmen, so sind bei ILE-Programmen einige Default-Einstellungen schlichtweg in der Mehrzahl der Fälle falsch. Zudem besteht die Gefahr, dass nach Programmänderungen anders und damit meist fehlerhaft gebunden wird.

Die ganz große Lösung, nämlich ein Change-Management-System einzuführen, scheitert in vielen Fällen weniger an der Höhe der Lizenzgebühren, als an dem Einführungsaufwand oder an der Spezialisierung der AS/400-Lösungen. Die Verwaltungen der RPG-Sourcen ist leicht in offene Standards zu integrieren, aber es fehlt an Unterstützung für die Compiler und die Binde-Befehle.
Die kleine Lösung für die Programmerstellung, also CL Skripte zu schreiben und Programme nur per Programm zu erstellen, wird oft als lästig empfunden und ist auch zu wenig kontrollierbar.

Eine Lösung dazwischen kann ein kleiner Präprozessor sein, der die Erstellungsbefehle aus der Quelle ausliest. Diese werden als Kommentar für den Compiler unsichtbar gemacht und alle Objekte lassen sich dann mit einem einzigen Programm erstellen. Für die Erstellungsbefehle reserviert man sich kleine Escape-Sequenzen, die ansonsten in der Quelle nicht benutzt werden dürfen. Für mein kleines Beispiel habe ich D*B als Beginn einer Präprozessor-Anweisung definiert, die Anweisung endet mit B*D oder mit dem Zeilenende. Fortsetzungen können mit D*B+ eingeleitet werden.

D*B OVRDBF SOURCE QRPGLESRC
D*B+ OVRSCOPE(*JOB)
D*B CRTRPGMOD CRTCPP
D*B+ DBGVIEW(*SOURCE)
D*B+ REPLACE(*YES)
D*B CRTPGM CRTCPP
D*B+ BNDDIR(QC2LE)
D*B+ ACTGRP(CRTCPP)
D*B+ DETAIL(*FULL)

Abbildung 1

/* D*B CRTBNDCL ANZCMDCP DFTACTGRP(*NO) ACTGRP(QILE) B*D */

Abbildung 2

In den beiden kleinen Ausschnitten (Abbildung 1 und 2) ist zu sehen, wie das in einer RPG- oder CL-Quelle aussehen könnte. Man kann auch andere Escape-Sequenzen definieren.
Der Präprozessor muss nun dazu in der Lage sein, den voll qualifizierten Namen der Quelldatei als Parameter zu übernehmen; weitere Parameter sind nicht erforderlich, da alle Parameter für den Compile in der Quelle stehen (Abbildung 3).

/IF NOT DEFINED (CRTCPP_QRPGLEH)
/DEFINE CRTCPP_QRPGLEH
/**************************************************************/
/* Header File Name: CRTCPP */
/* Generischer Compile eines Objektes */
/* nach Source Einträgen */
/**************************************************************/
D CRTCPP PR EXTPGM(‚CRTCPP‘)
D FileName 10A CONST
D Library 10A CONST
D Member 10A CONST
/ENDIF

Abbildung 3

Ein zusätzlicher Prototyp wird noch für das C API system () benötigt, mit dem die Übersetzungsbefehle aus dem Programm ausgeführt werden (Abbildung 4). Der Präprozessor selber ist dann in ILE RPG geschrieben.

/**************************************************************/
/* Header File Name: SYSTEM */
/* Definitionen für Benutzung C API system () */
/* Aufruf Systemfunktion */
/**************************************************************/
/IF NOT DEFINED (SYSTEM_QRPGLEH)
/DEFINE SYSTEM_QRPGLEH
/*=============================================================*/
* return: 0 = command successfull
* -1 = Null pointer exception
* 1 = execution not successfull
*—————————————————–
Dsystem PR 10I 0 EXTPROC(’system‘)
* Commandstring
D * VALUE OPTIONS(*STRING)
/ENDIF

Abbildung 4

Im globalen Teil des RPG-Programms werden die Copy-Strecken mit /COPY-Anweisungen eingebunden. Die Prototypen der lokalen Subprozeduren finden sich dort ebenso wie einige Konstanten-Deklarationen und das Main Interface, das die Beschreibung des Prototyps wiederholt.
Die Datei-Source soll später dazu dienen, die im Parameter übergebene Quelldatei zu lesen; sie muss also USROPN verarbeitet werden. Auf die externe Beschreibung kann man nicht zugreifen, da der Name des Formates zur Übersetzungszeit nicht bekannt ist.
Es sind lediglich vier Zustandsvariablen erforderlich; alle anderen Deklarationen erfolgen in den Subprozeduren als lokale Variablen (Abbildung 5).

H copyright(‚Dieter Bender 03/2002‘)
FSource IF F 112 DISK USROPN
DZeile E DS EXTNAME(QRPGLESRC)
/*— Import Prototypen
/COPY QRPGLEH,SYSTEM
/COPY QRPGLEH,CRTCPP
/*— lokale Prototypen
D Work PR
D getCommand PR N
D getStart PR N
D getAppend PR N
D findLine PR
/*— Konstanten
D TRUE C *ON
D FALSE C *OFF
/*— Zustands Variablen
D command s 1024A VARYING
D line s 128A VARYING
D startFlag s N INZ(FALSE)
D appendFlag s N INZ(FALSE)
/*— Main Interface
D CRTCPP PI
D FileName 10A CONST
D Library 10A CONST
D Member 10A CONST
/*— very unimportant procedure main ————————*/
C Callp Work
C return
/*============================================================*/

Abbildung 5

In der Main-Prozedur wird sofort eine Unterprozedur aufgerufen, damit überall im Programm lokale Variablen zur Verfügung stehen.
Die eigentliche Hauptverarbeitung findet in einer Prozedur „Work“ statt, die wie alle weiteren Prozeduren im selben Modul angeordnet ist und damit Zugriff auf alle globalen Variablen hat.
Die Prozedur „Work“ ist zuständig für das Zuordnen der korrekten Datei sowie das Öffnen und Schließen von selbiger Datei. In einer Schleife wird die Prozedur „getCommand“ aufgerufen und solange diese TRUE zurückgibt wird der aktuelle Wert der globalen Variablen „command“ mit Hilfe von „system ()“ als OS/400-Befehl ausgeführt. Mit dieser Verarbeitungslogik werden alle Anweisungen aus den Kommentaren der Quelldatei heraus gezogen und nacheinander ausgeführt (Abbildung 6).

P Work B
/*————————————————————*/
C callp system (‚OVRDBF Source ‚
C + %trim(Library) + ‚/‘
C + %trim(FileName) + ‚ ‚
C + %trim(Member)
C + ‚ LVLCHK(*NO)‘
C + ‚ OVRSCOPE(*JOB)‘)
C open Source
c
c dow getCommand
c callp system (command)
c enddo
c
C close Source
c callp system (‚DLTOVR *ALL LVL(*JOB)‘)

C return
P Work E
/*============================================================*/

Abbildung 6

Die Prozedur „getCommand“ versucht mittels einer weiteren Prozedur „getStart“, den Beginn eines Befehles aus der Source zu bekommen (Abbildung 7). Ist dies erfolgreich, wird solange „getAppend“ aufgerufen, bis der Befehl komplett ist. Die Teile des Befehls werden in der globalen Variablen „command“ zusammenmontiert. An dieser Stelle ist das Detail wichtig, dass „command“ mit dem Schlüsselwort VARYING deklariert ist. Das erspart das Trimmen der Teilstrings, ist allerdings im Debugger etwas lästig. Die beiden Flags – „StartFlag“ und „AppendFlag“ – steuern, dass wirklich alles verarbeitet wird, und zwar nichts doppelt.

P getCommand B
DgetCommand PI N
/*————————————————————*/
D Ergebnis S N INZ(FALSE)
C if getStart
c eval command = Line
c eval StartFlag = FALSE
c eval Ergebnis = TRUE
c dow getAppend
c eval command = command + ‚ ‚ + Line
c eval AppendFlag = FALSE
c enddo
c endif

c return Ergebnis
P getCommand E
/*============================================================*/

Abbildung 7

Die beiden Prozeduren „getStart“ und „getAppend“ rufen ihrerseits die Prozedur „findLine“ auf, die den eigentlichen kleinen Parser enthält. Mit ihren Rückgabewerten teilen sie der aufrufenden Prozedur „getCommand“ mit, ob noch unverarbeitete Teile der jeweiligen Art vorhanden sind (Abbildung 8).

P getStart B
DgetStart PI N
/*————————————————————*/
c callp findLine
C return StartFlag
P getStart E
/*============================================================*/
P getAppend B
DgetAppend PI N
/*————————————————————*/
c callp findLine
C return AppendFlag
P getAppend E
/*============================================================*/

Abbildung 8

Der Parser „findLine“ sucht solange in der Quelle, bis er die Escape-Sequenz für den Anfang gefunden hat bzw. bis das Dateiende erreicht ist. Wurde ein weiterer Baustein gefunden, wird geprüft, ob es sich um einen Anfang oder eine Folgezeile handelt, und das entsprechende Flag gesetzt.
Für jede Präprozessor-Zeile wird geprüft, ob sie bis zum Zeilenende geht oder durch eine Ende-Anweisung B*D beendet wird. Der Teilstring zwischen der Anfang- und Ende-Anweisung wird von führenden und folgenden Leerzeichen bereinigt und in die globale Variable „Line“ eingestellt (Abbildung 9).

P findLine B
D Start S 5I 0
D Ende S 5I 0
D Laenge S 5I 0
/*————————————————————*/
c dow not StartFlag
c and not AppendFlag
c and not %eof(Source)
c read Source Zeile
c eval Start = %scan(‚D*B‘ : Zeile )
c if Start > 0
c eval Start = Start + 3
c if %subst(Zeile : Start : 1) = ‚+‘
c eval AppendFlag = TRUE
c eval Start = Start + 1
c else
c eval StartFlag = TRUE
c endif
c eval Ende = %scan(‚B*D‘ : Zeile )
c if Ende > 0
c eval Laenge = Ende – Start – 1
c else
c eval Laenge = %len(Zeile) – Start
c endif
c eval Line = %trim(%subst(
c Zeile : Start :
c Laenge))
c endif
c enddo
C return
P findline E

Abbildung 9

Die Erstellungsbefehle für den Präprozessor sind zu Beginn des Beitrages in der Sprache beschrieben, die der Präprozessor verarbeitet. Bevor man diesen nicht erstellt hat, kann man ihn jedoch nicht verwenden (Siehe Abbildung 1); das Programm muss also zumindest beim ersten Mal von Hand erstellt werden. Wenn man den Präprozessor bei Änderungen verwendet, um sich selber zu erzeugen, kann es allerdings zu kleineren Randproblemen kommen, sozusagen wenn er sich selber das Hemd unter dem Hintern wegzieht.

Der Bedienerkomfort lässt sich noch etwas erhöhen, wenn man sich für den Aufruf eine kleine User-Option für das PDM baut, mit der man dann den Compile über diesen Präprozessor aufrufen kann; man erspart sich dann die manuelle Eingabe der Parameter. Dort kann auch hinterlegt werden, dass die gesamte Umwandlung mit allen Vor- und Nacharbeiten in einem Hintergrundprozess erfolgt.
Mit einem so kleinen Werkzeug lassen sich auch nachgelagerte Schritte – wie Berechtigungseinstellung oder ähnliches – einfach und elegant automatisieren. Und man stellt auf einfache Weise sicher, dass nach Programmänderungen alles wieder automatisch und technisch so erstellt wird, wie es bei der vorherigen Erstellung der Fall war.

Den Autor Dieter Bender erreichen Sie unter dieter.bender@midrangemagazin.de