< Programmeren in REXX

   Programmeren    in REXX


In dit hoofdstuk gaan we dieper in op de mogelijkheden van het PARSE bevel.

Waarvoor dient PARSE ?

Parse laat toe een bron op te splitsen volgens een sjabloon (Engels: template) waarbij de verschillende delen aan variabelen worden toegekend.

parse bron sjabloon

Bijvoorbeeld:

zin='Dit is een zin'
parse var zin woord1 woord2 woord3 woord4

Hierbij is de bron de inhoud van de variabele zin en bestaat het sjabloon uit «woord1 woord2 woord3 woord4».

Na uitvoering zal «Dit» in variabele woord1 komen, «is» in variabele woord2, enzovoort.

Korte herhaling van de verschillende bronnen

De bronnen kunnen zijn:

  • ARG - de argumenten ofte parameters die aan het programma, een subroutine of functie worden meegegeven.
  • PULL - de eerste zin die in de stack te vinden is. Bij ontstentenis hiervan, de gegevens die aan het klavier zullen worden ingevoerd.
  • LINEIN - vergelijkbaar met pull, maar er wordt niet naar de stack gekeken, enkel naar input aan het klavier.
  • VAR variable - de inhoud van de opgegeven variabele.
  • VALUE uitdrukking WITH - het resultaat van de interpretatie van de uitdrukking.

en een paar buitenbeentjes:

  • SOURCE - de karakteristieken van het uitvoerend programma (minstens: besturingssysteem, oproeptype en naam en plaats van de broncode)
  • VERSION - de geïnstalleerde versie en release van REXX.
Het sleutelwoord with is bij parse value écht nodig. De bron is een uitdrukking die complexe vormen kan aannemen, en het sleutelwoord dient als scheiding met het sjabloon.

Een veel voorkomende fout is een with te gebruiken bij een parse var:

parse var zin with woord1 woord2 

Dit resulteert niet in een syntax-fout, doch het eerste woord van zin zal terechtkomen in variabele with.

Bespreking van de sjabloonvormen

Sjablonen kunnen bestaan uit:

  • een eenvoudige opsomming van tokens (namen van variabelen);
  • variabelen samen met constante zoek-strings;
  • variabelen samen met relatieve of absolute positie-aanduidingen.

Ook combinaties van deze vormen zijn mogelijk.

Het voorbeeld waarmee we dit hoofdstuk zijn begonnen is van de eerste soort.

Indien de bron meer woorden bevat dan er variabelen zijn opgegeven in het sjabloon, dan krijgt de laatste variabele van het sjabloon de rest uit de bron. Dus bij:
parse value "Dit is een zin." with w1 w2 w3

zal variabele w3 de waarde «een zin.» ontvangen.

Plaatshouder

Willen we vermijden dat de laatste variabele het overtollige deel van de bron bevat, dan lijkt het toevoegen van een extra "wegwerp"-variabele in het sjabloon een eenvoudige oplossing. Dit zal wel werken, maar we verbruiken nodeloos geheugen indien de wegwerpvariabele nooit meer gebruikt zal worden.

Een meer elegante oplossing bestaat erin een plaatshouder te voorzien. Op elke plaats in het sjabloon waar we een woord uit de bron niet nodig zullen hebben schrijven we dan een punt om de plaats te reserveren, zonder dat de inhoud zal worden opgeslagen in geheugen.

Dit is een voorbeeld:

parse value "Dit is een zin" with w1 . w3 .

Nu bevat w1 de waarde «Dit», en w3 de waarde «een».

Meer nog, vermits de laatste variabele van een sjabloon de rest van de bronstring zal bevatten, inclusief de spaties, zal een plaatshouder ons in het volgend voorbeeld ook uit de nood kunnen helpen. Schrijven we:

parse value "Jan  Klaas      " with voornaam naam

dan zal naam de waarde « Klaas     » bevatten, inclusief de 5 spaties aan het eind, én een spatie vooraan. Er wordt inderdaad slechts één spatie weggenomen tussen de woorden van de bron.

Gebruiken we echter een plaatshouder zoals hier:

parse value "Jan  Klaas      " with voornaam naam .

dan zal variabele naam slechts de waarde «Klaas» bevatten en neemt de plaatshouder de spaties voor zijn rekening.

Een plaatshouder kan ook nuttig zijn in alle meer complexe sjablonen die we verder behandelen.

Sjablonen met constante zoekterm

Een constante zoekterm in een sjabloon geeft aan dat de bron moet opgesplitst worden in 2 delen waar de constante (de eerste maal) voorkomt. Elk van de delen kan dan verder nog worden opgesplitst via andere sjabloonvormen. De zoekterm zelf maakt geen deel meer uit van het resultaat. Indien de zoekterm niet voorkomt in de bron, dan zal het eerste deel de volledige bron bevatten, en het tweede deel leeg blijven (een nullstring zijn).

Voorbeeld:

parse value "123-4567890-12" with banknummer '-' kaartnummer '-' controlenummer

splitst het bankkaartnummer in 3 afzonderlijke delen op de plaatsen waar de zoekterm "-" voorkomt.

parse value "Jan Klaas,Binnenweg 3,Ergenstevelde,+(32)123 45 67 89",
      with voornaam naam . ',' straat ',' stad ',' .

is een voorbeeld van opsplitsing van velden uit een CSV-bestand (zie hier voor meer uitleg over een CSV-bestand). Bij elke komma wordt een deel van de bron afgesplitst en verder ontleedt. Hier wordt het eerste deel nog verder opgesplitst in 2 variabelen (voornaam en naam) en wordt de eventuele rest in dit veld door de plaatshouder opgevangen. Een aandachtig lezer zal opmerken dat het misloopt als het eerste veld "Jan van Gent" zou bevatten. Dit is duidelijk geen al te goede programmatie.

Alles wat na de derde komma komt wordt ook door een plaatshouder opgevangen. Die plaatshouder hoeft strikt gezien zelfs niet meer te worden geschreven.

Laten we echter volgend voorbeeld even bestuderen:

parse value "Klaas, Jan" with naam ',  ' voornaam .

Onze zoekterm bevat 2 spaties na de komma. Onder deze vorm komt de zoekterm niet voor in de bron, met als gevolg dat naam de volledige bron zal bevatten en dat voornaam een nullstring wordt.

Sjablonen met variabele zoekterm

Zoektermen kunnen ook via een variabele worden opgegeven. In dat geval wordt de variabele tussen haakjes gezet om ze te onderscheiden van de rest in het sjabloon.

In volgend voorbeeld maken we het karakter dat de velden in een CSV bestand van elkaar scheidt variabel.

scheiding=","
parse value "Jan Klaas,Binnenweg 3,Ergenstevelde,+(32)123 45 67 89",
      with  voornaam naam (scheiding) straat (scheiding) stad (scheiding) .

Sjablonen met posities

Er zullen gevallen zijn waar we de opbouw van onze bron tot op de kolom kennen en waar we dan willen splitsen op bepaalde absolute of relatieve posities.

Sjablonen met absolute posities

Een absolute positie wordt aangegeven door een getal, zonder plus- of minteken.

Voorbeeld:

parse value "123-4567890-12" with banknummer 4 5 kaarnummer 12 13 controlenummer .

Hier zullen de karakters op posities 1 t.e.m. 3 in variabele banknummer komen. Met het karakter op positie 4 gebeurt niets. Posities 5 tot 11 gaan naar kaartnummer, positie 12 gaat verloren en de posities 13 tot aan de eerste spatie of het eind van de bron gaan in controlenummer. Kortom, we hebben dezelfde uitslag als in bij het eerste voorbeeld met constante zoekterm, maar omdat we het formaat van een (Belgisch) bankkaart nummer kennen is het mogelijk op deze manier te parsen.

Gebruik van absolute posities is typisch voor het behandelen van gegevens met vaste kolombreedtes. Bekijk dit voorbeeld:

record.1='Lateur    Frank               Stijn Streuvels    '
record.2='Kyvon     Adrianus Marinus    André van Duin     '
record.3='Arouet    François Marie      Voltaire           '
do i=1 to 3
   parse var record.i 1 eigennaam 11 voornaam 31 pseudoniem
   say strip(voornaam) strip(eigennaam) 'is bij ons beter bekend als' pseudoniem
end

We splitsen dus op posities 1, 10 en 30. De opgave van positie 1 is niet nodig, maar verhoogt wel de leesbaarheid.

Vraag: Waarom moeten we hier de voor- en eigennaam strippen alvorens hem af te drukken ? Hadden we niet met een plaatshouder kunnen werken om de overtollige spaties weg te nemen ?
Antwoord: Indien we een plaatshouder hadden gebruikt, dan spelen we delen van de dubbele voornamen kwijt. Het strippen is nodig want door op deze manier te parsen nemen we alle spaties ook mee.


Bij meer uitgebreide records schrijft men het soms als volgt:

parse var record.i  1 eigennaam,  /* eerste veld */
                   11 voornaam,   /* tweede veld */
                   31 pseudoniem  /* derde veld */

waarbij de commentaren best wat zinvoller kunnen gemaakt worden. Vergeet hierbij ook de vervolg-komma's niet !

Sjablonen met relatieve posities

Als we weten hoe breed elke kolom is, dan kunnen we werken met relatieve posities. Die stellen we voor als positieve of negatieve getallen. Met negatieve getallen verplaatsen we ons terug in de bron !

Indien de eerste kolom 10 karakters breed is, en de tweede 20, dan kunnen we ons vorig voorbeeld omvormen tot:

parse var record.i eigennaam +10 voornaam +20 pseudoniem

Ook absolute of relatieve posities kunnen variabel worden gemaakt, zoals in dit aangepast voorbeeld:

parse value 10 20 with breedte1 breedte2 .
...
parse var record.i eigennaam +(breedte1) voornaam +(breedte2) pseudoniem

Indien we absolute posities willen gebruiken, dan moeten we ze als volgt schrijven:

parse value 1 11 21 41 with veld1 veld2 veld3 .
...
parse var record.i =(veld1) eigennaam =(veld2) voornaam =(veld3) pseudoniem

Voor absolute posities gebruiken we dus een gelijkheidsteken, en geen + of -.

Laten we tot slot nog even naar volgend voorbeeld kijken:

parse value 'C:\Program Files\ooRexx' with drive '\' +0 subdir1 '\' +0 subdir2

We splitsen waar een \-teken voorkomt, maar door relatief +0 te gebruiken trappelen we als het ware ter plaatse en zal het \-teken ook deel gaan uitmaken van de variabele die erna komt. Dus subdir1 zal «\Program Files» bevatten.

Nog meer mogelijkheden

De bron meermaals parsen

Bij de opmerkingen aan het eind van de bespreking van ons eerste REXX programma hebben we vermeld dat we:

z=z+1             /* 1 bij aan priemgetallenteller */
priem.z=i         /* gevonden priemgetal toevoegen aan reeks */
priem.0=z         /* element nul van stem aanpassen */

konden herleiden tot:

parse value z+1 i with z . 1 priem.0 priem.z

Wat zal er nu gebeuren ? REXX interpreteert van links naar rechts. Als we veronderstellen dat i op een bepaald moment gelijk is aan «23», namelijk ons 9de priemgetal, en ook dat z nog steeds «8» bevat, dan zal

  1. de interpretatie van de bron leiden tot «9 23»;
  2. het parsen beginnen door het eerste woord van de bron, «9» in variabele z te zetten en door de rest te elimineren middels de plaatshouder;
  3. teruggesprongen worden naar absolute positie 1 van de bron;
  4. het parsen daar hernemen en de waarde «9» aan variabele priem.0 toegekend worden;
  5. en tenslotte wordt «23» gestockeerd in variabele priem.9, want z heeft ondertussen reeds de waarde «9» !

Verschillende bronnen tegelijk parsen

Enkel de bevelen ARG en PARSE ARG kunnen meer dan één bron hebben. Zij behandelen namelijk de parameters die aan een subroutine of interne functie zijn meegegeven. Deze worden van elkaar gescheiden door komma's. Dan moet voor elke parameter een apart sjabloon gemaakt worden en moeten de sjablonen ook van elkaar worden gescheiden door een komma. We komen dus tot dit veralgemeend formaat:

parse arg sjabloon1 , sjabloon2 , sjabloon3

Elk sjabloon kan daarbij zijn opgebouwd zoals we al hebben geleerd.

Laten we dit voorbeeld bestuderen:

aantal=3
musketiers="Porthos, Athos, Aramis, d'Artagnan"
hoeveel=func(aantal,musketiers)
say 'De 3 musketiers waren met' hoeveel', want naast' m1', 'm2' en 'm3' was er ook nog' vierde
exit
Func:
   parse arg subtotaal , m1 ',' m2 ',' m3 ',' vierde
   return subtotaal+1

Wat moeten we hiervan leren ?

  1. de functie krijgt 2 parameters binnen. Ze zijn gescheiden door een komma;
  2. de komma in het parse bevel, net na het woord subtotaal, scheidt deze parameters van elkaar;
  3. de eerste parameter gaat dus in variabele subtotaal;
  4. de tweede parameter - die in dit geval zelf ook komma's bevat - wordt door het tweede sjabloon (m1 ',' m2 ',' m3 ',' vierde) opgesplitst. Hier zijn de komma's constante zoektermen !

We mogen dus terecht volgend resultaat op het scherm zien verschijnen:

De 3 musketiers waren met 4, want naast Porthos, Athos en Aramis was er ook nog d'Artagnan.
Als een REXX programma vanuit het besturingssysteem (bv. op de opdrachtprompt van Windows) wordt opgeroepen kan men slechts één enkele parameter meegeven. Een eventuele komma in de parameter behoort dan in dat geval tot de bron zelf, en moet eventueel met een constante zoekterm worden gevonden.


Aan een interne subroutine of functie kan men wél meer parameters meegeven zoals we hier hebben geleerd. Ook wanneer een ander REXX programma of een host-commando ons oproept, kunnen zij meer dan één parameter doorgeven.

Een lijst "opeten"

Het kan gebeuren dat we een lange lijst data te verwerken krijgen. Indien deze lijst in één lange zin is vervat, dan bestaat een typische manier om de elementen van de lijst te behandelen er als volgt uit:

  1. indien de lijst later nog nodig is, maak dan een duplicaat in een hulpvariabele;
  2. start een lus die zal lopen tot de hulpvariabele leeg is;
  3. eet de hulpvariabele op door woord voor woord weg te parsen.

Het volgende voorbeeld zal dit duidelijk maken:

lijst="Jan Piet Paul Simon Matthias"
werk=lijst
do while werk \= ""
   parse var werk element werk
   ... verwerk element ...
end

Ons parse bevel zal er bij de eerste iteratie voor zorgen dat:

  • de variabele element de waarde «Jan» krijgt
  • de variabele werk gereduceerd wordt tot «Piet Paul Simon Matthias»

Bij elke volgende iteratie gaat er zo een woord van werk naar de variabele element. Na 5 iteraties zal werk "leeg" zijn en zal de lus dus stoppen.

Een laatste uitgewerkt voorbeeld

In dit programma verwachten we de naam van een map. Doch, optioneel kunnen ook andere parameters worden opgegeven die "/Vanaf startnummer" en "/Tot eindnummer" kunnen zijn.

Het gaat hier namelijk om het begin van een programma dat digitale foto's zal verwerken. Deze hebben typisch de vorm "IMG_nnnn.jpg", waarbij nnnn een volgnummer voorstelt. De verwerking kan beperkt worden tot de foto's met nummers gelegen tussen het opgegeven start- en eindnummer, anders worden alle foto's uit de map verwerkt.

Dit is ons programma:

Parse upper arg dir "" beginNr eindNr
dir=strip(dir)
do while left(dir,1)='/'
   parse var dir optie dir
   dir=strip(dir)
   Select
      when left(optie,2)='/V' then do
         parse var dir beginNr dir
         dir=strip(dir)
         if length(beginNr)<>4 | \datatype(beginNr,'W') then
            call exit 56,'/Van moet een getal van 4 cijfers zijn'
      end
      when left(optie,2)='/T' then do
         parse var dir eindNr dir 
         dir=strip(dir)
         if length(eindNr)<>4 | \datatype(eindNr,'W') then 
            call exit 57,'/Tot moet een getal van 4 cijfers zijn'
      end
      otherwise call exit 58,'Onbekende optie' optie 
  end /* select */   
end
/* Hier volgt de verwerking van de gegevens...*/
EXIT:
  parse arg foutcode ',' bericht
  if bericht\= then do
     say bericht
     exit foutcode
  end
  exit 0

Laten we veronderstellen dat we het programma oproepen als volgt:

rexx d:\RexxProgrammas\BewerkFotos /v 1212 /t 1233 d:\MijnFotos\Reis2011\

We willen dus foto's die in map d:\MijnFotos\Reis2011\ zitten verwerken. Maar we willen de verwerking ook beperken tot de foto's met nummers tussen 1212 en 1233.

Het eerste statement in ons programma zal de argumenten parsen. Daarbij maken we echter gebruik van nog een nieuwe techniek.

Door een nullstring in het sjabloon te steken gaan we op zoek naar de eerstvolgende "leegte". Die komen we uiteraard pas tegen op het einde van bron ! Dus, alles wat we als parameters hebben meegegeven gaat in de variabele dir. Dan pas komen we de nullstring tegen, zodat de variabelen beginNr en eindNr geïnitialiseerd worden als een nullstring. Dit zal ons later toelaten met een "if BeginNr<> then ..." na te gaan of we al dan niet een beginnummer tussen de parameters hebben gevonden.

Vervolgens gaan we alle parameters die in dir zitten opeten zoals we boven hebben geleerd. Komen we daarbij een woord tegen dat met een '/' begint, dan vermoeden we een optie te hebben gevonden. Met het select-blok gaan we vervolgens na of het een geldige optie is. De eerste letter is daarbij voldoende. Het moet gaan om /V of /T, anders geven we een foutbericht.

Bij een geldige optie hoort een getal. Dit halen we op door de variabele dir nog een woord verder op te eten.

Na het select-blok houden we dus nog enkel de echte mapnaam over in variabele dir.

Bemerk ook de elegante exit subroutine. Ze behandelt de eventuele foutcodes en -berichten. We komen hier nog op terug in een volgende hoofdstuk.

Hiermee hebben we hopelijk voldoende aangetoond welke de uitgebreide mogelijkheden van het parse bevel zijn.

This article is issued from Wikibooks. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.