Programmeren in REXX/Parse in detail
In dit hoofdstuk gaan we dieper in op de mogelijkheden van het PARSE bevel.
Waarvoor dient PARSE ?
bewerkenParse 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
bewerkenDe 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.
Bespreking van de sjabloonvormen
bewerkenSjablonen 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.
Plaatshouder
bewerkenWillen 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
bewerkenEen 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
bewerkenZoektermen 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
bewerkenEr 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
bewerkenEen 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.
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
bewerkenAls 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
bewerkenDe bron meermaals parsen
bewerkenBij 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
- de interpretatie van de bron leiden tot «9 23»;
- 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;
- teruggesprongen worden naar absolute positie 1 van de bron;
- het parsen daar hernemen en de waarde «9» aan variabele priem.0 toegekend worden;
- en tenslotte wordt «23» gestockeerd in variabele priem.9, want z heeft ondertussen reeds de waarde «9» !
Verschillende bronnen tegelijk parsen
bewerkenEnkel 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 ?
- de functie krijgt 2 parameters binnen. Ze zijn gescheiden door een komma;
- de komma in het parse bevel, net na het woord subtotaal, scheidt deze parameters van elkaar;
- de eerste parameter gaat dus in variabele subtotaal;
- 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.
Een lijst "opeten"
bewerkenHet 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:
- indien de lijst later nog nodig is, maak dan een duplicaat in een hulpvariabele;
- start een lus die zal lopen tot de hulpvariabele leeg is;
- 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
bewerkenIn 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.