# Programmeren in REXX/Nog bevelen en functies

In dit hoofdstuk leren we nog een aantal functies en bevelen te gebruiken. Het zijn niet de gemakkelijkste, vandaar dat we ze nu pas behandelen.
We beginnen met de bespreking van functies die met datum en tijd werken.

# Beheer van datums en tijd

Omdat datums en tijden geen gebruik maken van het tientallig stelsel is het werken ermee niet altijd eenvoudig en vraagt het grote aandacht. Fouten zijn snel gemaakt.

## TIME

Met deze functie kan men de huidige tijd onder verschillende vormen opvragen. De optie bepaalt de vorm en enkel het eerste karakter is daarbij nodig:
* Time(Normal) «15:57:48» (dit is de standaard-optie)
* Time(Civil) «3:57pm» (bij ons weinig gebruikt)
* Time(Long) «15:57:48.315000» (microseconden)
* Time(Hours) «15» (uren sinds het begin v/d dag)
* Time(Minutes) «957» (minuten sinds het begin v/d dag)
* Time(Seconds) «57468» (seconden sinds het begin v/d dag)
En nog twee speciale opties:
* Time(Elapsed) «0.45455» (misschien)
* Time(Reset) «10.03100» (misschien)
De eerste reeks behoeft weinig meer uitleg, behalve dat bij het Long formaat, men op PC systemen geen grotere nauwkeurigheid dan milliseconden moet verwachten (de drie laatste decimalen zijn altijd 0).
Als de functie time meermaals in hetzelfde statement wordt opgeroepen dan zal ze steeds dezelfde waarde teruggeven. Het is alsof de tijd dan even stilstaat. Bovenstaande resultaten zijn allemaal samen in één statement uitgevoerd, dus slaan de uitkomsten op exact dezelfde tijd.
De twee laatste opties vragen wel wat meer uitleg. Ze laten toe de tijd te meten die voor het uitvoeren van een taak nodig was. Het is dus een soort chronometer. Bekijk deze volgorde:
In het eerste statement doen we een Reset van de chronometer. We zetten hem op nul. Vermits we niet geïnteresseerd zijn in de tijd die de chronometer op dat moment had gebruiken we een call time bevel i.p.v. tijd=time. Na de eerste reeks bewerkingen vragen we de Elapsed (verlopen) tijd op. De chronometer wordt daarbij niet terug op nul gezet, en zal dus blijven doorlopen. Na de tweede reeks bewerkingen vragen we weer de verlopen tijd op, maar tegelijkertijd zetten we de chronometer terug op nul. Uiteindelijk vragen we hoeveel tijd de derde reeks bewerkingen in beslag nam.
Noteer ook dat REXX slechts één chronometer bezit. Als hij wordt gestart in een subroutine, dan zal hij zijn waarde verliezen (en terug op nul vallen) bij het verlaten van de subroutine.
Hier volgen een aantal voorbeelden van tijdsrekening:
De eerste paar statements hierboven lijken nodeloos complex: we halen de seconden uit een tijd in hh:mm:ss formaat. Dat hadden we rechtstreeks met time('S') kunnen doen. De lezer heeft gelijk, het dient enkel als voorbeeld om te tonen hoe we een hh:mm:ss tijd in stukjes hakken.
Het laatste deel is zeker een functie waard als men deze omzetting vele malen moet uitvoeren. Ze kan er dan in een zeer compacte vorm als volgt uitzien:
Bestudeer deze functie nog eens aandachtig zodat alle aspecten ervan duidelijk worden. Ze lijkt misschien nogal cryptisch, maar ze is alvast performant omdat er geen werkvariabelen worden aangemaakt. We laten het aan de lezer over om de tegenovergestelde functie T2S te schrijven. Die is veel eenvoudiger, maar kan ook nuttig zijn.

## DATE

Het oorspronkelijk formaat, met slechts één mogelijke parameter optie, is nogal rudimentair. Men kan enkel de datum van vandaag in verschillende formaten opvragen. De optie - de eerste letter is daarbij voldoende - bepaalt het formaat. We vernoemen hier enkel de voornaamste opties die men in alle REXX implementaties zal terugvinden:
* Normal - dit formaat is standaard als geen optie wordt meegegeven. We krijgen de datum in de vorm dd mnd jjjj, bv. 16 mar 2011 (maand in 3 karakters, Engelse namen);
* Basedate - het aantal dagen sinds 1 januari van het jaar 1. Er wordt hier geen rekening gehouden met de aanpassingen aan de kalender zoals o.a. Paus Gregorius ze doorvoerde in 1582. Voor meer informatie over tijdrekening, zie het artikel over jaartelling. Een restdeling door 7 op een basisdatum geeft de dag in de week, waarbij maandag=0 en zondag=6. Anders gezegd, 1 januari 0001 was een maandag.
* Days - de hoeveelste dag van dit jaar;
* European - de datum in verkort Europees formaat (dd/mm/jj);
* Month - de maand in dit jaar, niet afgekorte Engelse naam, bv. "January";
* Ordered - de datum in formaat jj/mm/dd. Kan dienen om te sorteren, maar het jaartal heeft slechts 2 posities;
* Sorted - vandaar dat dit formaat beter aangepast is om te sorteren: jjjjmmdd;
* Usa - in Amerikaans formaat: dd/mm/jj;
* Weekday - de dag van de week in niet afgekorte Engelse tekst, bv. "Saturday".
OORexx biedt ook nog het Language formaat aan, waarbij de datum in lokaal formaat wordt gegeven, bv. 17 januari 2011. Het lokaal formaat wordt in de systeeminstellingen bepaald. Een REXX programma mag geen veronderstellingen maken omtrent het resultaat omdat het programma op een ander systeem in de prak kan lopen als er een andere land-instelling is gekozen.
Kortom, de meest zinvolle formaten zijn N, B, S, D, M en W. Sorteren kan enkel zinvol gebeuren met het S of B formaat. Het B-formaat is dan weer ideaal om met datums te rekenen zoals we verder zullen leren.
Bij vorige eeuwwisseling werd de functie drastisch uitgebreid om de jaar-2000 problematiek te kunnen aanpakken. De datum kan nu variabel zijn (dus niet enkel die van vandaag). Meer nog, de datum kan via de functie worden omgezet van één formaat in een ander waarbij ook scheidingstekens kunnen gekozen worden.
De input- en outputformaten zijn diegene wie we hierboven al bestudeerden.
Nu zal de datum die als 2de parameter is meegegeven, en wiens formaat is bepaald door de derde parameter, naar het outputformaat worden omgezet. Men kan tevens aangeven welk scheidingsteken er in de input staat, en welke er in de output moet komen. De datum kan in het verleden of de toekomst liggen.
Een moeilijke uitleg, daarom snel over naar voorbeelden om het te begrijpen:
In de veronderstelling dat het vandaag 19 november 2011 is, dan mogen we terecht verwachten dat de functie aan Gisteren de waarde «18 Nov 2011» zal toekennen. Dit is hoe het werkt:
* de eerste parameter bepaalt dat we de datum in het Normaal formaat willen terugkrijgen;
* de tweede parameter is de datum die moet worden omgevormd. In dit geval gebruiken we date('B') om vandaag in Basisdagen te krijgen. Daar kunnen we dan gemakkelijk één van aftrekken om naar gisteren over te gaan;
* de derde parameter geeft aan in welk formaat de tweede parameter is opgegeven, hier dus het B-formaat.
De functie zal dus de uitkomst van de tweede parameter intern omzetten naar het N-formaat. Het voorbeeld onderstreept nogmaals het belang van het B-formaat als er moet gerekend worden. Dagen toevoegen of aftrekken van andere formaten kan niet altijd foutloos of is zelfs onmogelijk. date('N')-1 zal bijvoorbeeld een bad arithmetic operation geven, want date('N') is geen getal.
Uiteraard kan de om te vormen datum uit een variabele gehaald worden. Laten we nog een voorbeeld bekijken:
Het antwoord dat we hier mogen verwachten is «Kris is geboren op Wednesday, 22 Apr 1953».
Mooi is dat echter niet. Willen we het volledig in het Nederlands vertalen, dan moeten we wat meer moeite doen, zo bijvoorbeeld:
Nu wordt het resultaat «Kris is geboren op vrijdag, 22 april 1953». We kunnen dat nog wat inkorten door het zo te schrijven:
Als besluit geven we nog een aantal voorbeelden waarbij scheidingstekens worden gebruikt. De vierde parameter van de functie bepaalt welk teken in het resultaat moet verschijnen, en de vijfde parameter bepaalt welk teken er in de bron staat. Stel dat het nog steeds 19 november 2011 is, dan geven:
In het eerste geval voegen we mintekens toe aan een Sorted datum (jjjjmmdd). REXX weet waar het moet want het is duidelijk waar jaartal, maand en dag in een Sorted datum staan ! In het tweede geval zetten we die IsoDate weer om naar een Basedate, waarbij we duidelijk maken dat de Sorted input datum mintekens bevat. Argument 4 blijft leeg omdat we in de output geen scheidingsteken willen (wat trouwens niet kan voor een Basedate). In het derde geval vragen we mintekens toe te voegen in een Normale datum.
Voor de volledigheid moeten we ook vermelden dat omzetting van tijden in OORexx ook mogelijk is. Bijvoorbeeld:
In het tweede statement worden seconden dus omgezet naar een leesbare tijd. Deze mogelijkheid bestaat dus niet in alle REXX implementaties en behoort niet tot de ANSI standaard.

# TRANSLATE - karakters vertalen

Translate is eigenlijk een eenvoudige functie, maar kan zo krachtig gebruikt worden dat ze dan voor beginnelingen wat moeilijker onder de knie te krijgen is. Het zal trouwens snel duidelijk worden waarom we ze nu, net na de date functie bespreken.
In de eenvoudige vorm, waarbij enkel een string als parameter wordt meegegeven is het nog gemakkelijk: de string wordt naar hoofdletters vertaald.
Uit deze voorbeelden volgt duidelijk dat enkel de normale karakters a tot z naar hoofdletters worden vertaald.
Maar de naam van de functie laat vermoeden dat we er nog andere vertalingen kunnen mee maken. Willen we bijvoorbeeld accent-karakters vertalen, dan moeten we het als volgt schrijven:
De tweede en derde parameter kunnen als tabellen worden gezien. Nu wordt niet meer naar hoofdletters vertaald ! REXX gaat nu namelijk voor elk karakter in de string na of het ook voorkomt in de derde parameter, de zogenaamde inputtabel. Indien wel, dan wordt het vervangen door het overeenkomstig karakter uit de tweede parameter, de zogenaamde outputtabel. Indien het niet voorkomt in de inputtabel, dan blijft het karakter onveranderd. Dit verklaart waarom nu nog enkel de "à" en "é" worden vertaald.
Nog een voorbeeld:
Nu worden enkel de karakters van a tot i naar hoofdletters vertaald.
Willen we toch het hele alfabet vertalen, moeten we het dan volledig uitschrijven ? Nee, het kan eenvoudiger door middel van de xrange functie. Deze functie retourneert alle karakters binnen een bepaald bereik.
Dit wetende kunnen we dus nu schrijven:
om toch «IK HAD EEN DEJA VU ERVARING» te bekomen.
Noteer dat xrange('A','Z') niet uitgaat van het alfabet, maar van de hexadecimale waarden van deze letters in de huidige karaktertabel (codepage). Voor ASCII maken we dus een tabel van '41'x t.e.m. '5A'x, terwijl het in EBCDIC een tabel is van 'C1'x t.e.m 'E9'x.
Indien geen inputtabel, outputtabel noch een pad karakter wordt opgegeven, dan wordt de string naar hoofdletters vertaald zoals we al zagen. Indien geen inputtabel is opgegeven doch wel een pad karakter, dan wordt de inputtabel verondersteld gelijk te zijn aan xrange("00"x,"FF"x). De outputtabel wordt dan verondersteld een nullstring te zijn waaraan een pad karakters zijn toegevoegd. In dit laatste geval wordt dus alles naar het pad karakter vertaald als er geen outputtabel is.
Pas op, het minteken staat in de vierde parameter !
We zijn echter nog niet uitgepraat over de translate functie. Ze kan namelijk ook dienen om de karakters in de string te verplaatsen. Nu wordt het wel wat moeilijker, daarom beginnen we met een eenvoudig voorbeeld:
De eerste drie karakters worden van plaats verwisseld.
In tegenstelling tot het "gewone" gebruik van de translate functie, staat de string die we willen bewerken nu als tweede parameter. De functie werkt echter nog steeds op dezelfde manier. In ons voorbeeld betekent dat dus:
* Het eerste karakter uit de eerste parameter is "7". Staat dit in de inputtabel ? Ja, en wel op de derde plaats. Dus, we moeten die 7 vertalen naar het derde karakter van de outputtabel, dus C;
* Het tweede karakter is "8". Het staat op de tweede plaats in de inputtabel, dus we moeten het tweede karakter van de outputtabel nemen, dus "B";
* Het derde karakter "9" wordt op dezelfde manier naar "A" vertaald;
* Het laatste karakter "0" wordt "D".
Kortom, onze string "7890" wordt "CBAD", maar voor ons lijkt het alsof de tweede parameter wordt dooreengeschud.
Dit voorbeeld was goed om het principe uit te leggen, maar van weinig praktisch nut. Laten we nu overgaan tot een meer zinvol gebruik van deze techniek. Daarbij gaan we datums van één formaat in een ander omzetten. Dit is een eerste voorbeeld:
Hiermee bereiken we dat de mintekens uit de datum verdwijnen. Of we nu cijfers gebruiken of andere tekens heeft geen belang. Het volgende statement geeft exact hetzelfde resultaat, maar is er daarom niet echt beter leesbaar op geworden:
Toch zullen we met deze manier verderwerken, maar onze vertaaltabellen op een intelligentere manier opbouwen. Als we nu schrijven:
dan wordt het plots duidelijker hoe het werkt. De posities voor de dag stellen we voor d.m.v. "Dd", die voor maand door "Mm" en die voor het jaartal door "EeJj" (E voor eeuw). Schrijf niet "dd" i.p.v. "Dd" want dan treedt er dubbelzinnigheid op zoals we hier kunnen zien:
Bij een zuivere permutatie, moeten de tekens van de eerste parameter uniek zijn, anders worden tekens uit tweede parameter meermaals herhaald.
In voorgaande voorbeelden haalden we tekens weg. Maar het is ook mogelijk tekens toe te voegen of te veranderen. In volgend voorbeeld maken we een ISO-datum (zie ISO 8601 voor meer informatie):
Hier is het belangrijk dat de toegevoegde tekens niet in het derde argument voorkomen.
Laten we de werking bij permutaties nog eens duidelijk formuleren:
* Parameter 1 bevat het gewenste formaat van het resultaat;
* Parameter 2 bevat de te permuteren gegevens;
* Parameter 3 beschrijft het formaat van de te permuteren gegevens.
Om te eindigen geven we een nog iets langer voorbeeld:
Daar we al "Mm" gebruiken als aanduiding voor de maand moeten we voor de minuten spijtig genoeg iets anders kiezen om de dubbelzinnigheid te vermijden. Het werd dan maar "Nn".
Translate is zeker niet de gemakkelijkste functie. Het is daarom goed om dit deel nog eens te herlezen om na te gaan of alles is begrepen.
* Met deze functie kunnen we namelijk veel korter coderen. Kijk maar eens hoeveel SUBSTR e.d. men nodig heeft om bovenstaande translate te vervangen.
* datumTijd='16/11/1949 12:14:03'
* say substr(datumTijd,7,4)||substr(datumtijd,4,2)||left(datumtijd,2)||'.'||,
* substr(datumtijd,12,2)||substr(datumtijd,15,2)||right(datumtijd,2)
Vergeet ook niet een commentaartje toe te voegen om te zeggen wat je bereiken wil, bv. /* hervorm datum naar jjjjmmdd.uummss */

# INTERPRET - tweemaal interpreteren

Dank zij het interpret bevel kan een REXX programma een uit te voeren statement dynamisch aanmaken en dan laten uitvoeren. Het komt er in feite op neer dat nieuwe statements tijdens uitvoering aan het programma worden toegevoegd.
De uitdrukking moet uiteraard resulteren in geldige statements. Alle bevelen zijn toegelaten, maar de DO- en SELECT-blokken moeten volledig zijn, dus hun END bevatten. Het "RexxTry" hulpmiddel dat we in het begin van dit boek bespraken doet niets anders dan elke lijn die de gebruiker intoetst interpreteren.
Een paar voorbeelden zullen snel alles duidelijk maken:
Dit is wat er zal gebeuren:
* de uitdrukking data "= 4" resulteert in de string «Dmitri = 4»
* REXX interpreteert deze string als een statement
* het resultaat is dat variabele Dmitri de waarde «4» krijgt.
Het resultaat zal zijn dat de zin «Gegroet allemaal !» driemaal zal worden afgedrukt. Hier zien we dat het DO-blok volledig moet zijn.
De grote moeilijkheid voor een beginnend REXX programmeur is te weten welk deel van de uitdrukking als constante moet worden geschreven. Bekijk deze voorbeelden:
Interpret werkt in 2 stappen. Na de eerste stap wordt de uitdrukking «SAY Hello, de zon is rond». Het bevel say krijgt geen constante als input, en die komma stoort dan ook geweldig...
Dit helpt niet veel, het probleem ligt niet bij say. Dit is wat het moet zijn:
Dus, de eerste stap moet leiden tot een geldig statement. Daarom moet alles wat niet variabel is als constante worden geschreven. De volgende oplossing zou ook werken...
...tot zolang iemand het in zijn hoofd krijgt een variabele say te initialiseren...
Kortom, de levensbelangrijke regel die we al meermaals tegenkwamen geldt hier evenzeer.
Opmerkingen:
* Labels kunnen niet gebruikt worden in de uitdrukking;
* Door "Trace R" of "Trace I" te gebruiken zal het sneller duidelijk worden welk resultaat men zal bekomen;

# VALUE - waarde van een veranderlijke variabele

De functie zal de waarde van het - dikwijls dynamisch opgebouwd - symbool teruggeven, en er optioneel daarna een nieuwe waarde aan toekennen.
Daar waar interpret een interpretatie in twee stappen toelaat zal de value functie een waardebepaling in twee stappen doen.
Het symbool moet dus duidelijk resulteren in een geldige naam voor een variabele. Kleine karakters worden wel omgezet naar hoofdletters.
Laten we onmiddellijk een aantal voorbeelden bestuderen, waarbij we veronderstellen dat:
Nu voeren we deze statements na elkaar uit:
Bespreking lijn per lijn:
* In een eerste stap interpreteert REXX de parameter, en dat levert de waarde a3 op. Value zoekt nu naar de waarde van variabele a3, maar die is niet geïnitialiseerd en dus is het eindresultaat «A3»;
* Elke k wordt nu een 3, en alles samen wordt het symbool dus a33 waarvan value ontdekt dat het de waarde «7» heeft;
* De constante "Dmitri" verwijst inderdaad naar een variabele met waarde «K»;
* Nu interpreteert REXX eerst de variabele dmitri tot haar waarde "K" en value maakt er uiteindelijk «3» van;
* Zelfde operatie, maar nu krijgt de variabele "K" ook nog een waarde 5. Het toekennen van een nieuwe waarde zouden we niet aanraden omdat het de dingen nodeloos compliceert;
* Als gevolg van het toewijzen van de nieuwe waarde in vorig statement, retourneert de functie dus nu «5».
* Vermits variabele k de waarde 5 heeft moet de functie de waarde van variabele "lijst.5" teruggeven, en die is inderdaad «Hallo».
Met het derde en vierde statement maken we weer eens duidelijk dat het belangrijk is te weten wat variabel is in de parameter en wat niet. Het is niet anders dan voor andere functies waar de uitdrukkingen die als parameter worden gegeven eerst door REXX worden geïnterpreteerd alvorens de functie zelf op te roepen. Het verwarrende hier is dat de functie a.h.w. nogmaals een interpretatie doet.
Wanneer komt deze functie nu van pas ? We maken het duidelijk met een voorbeeld:
Men kan nu deze waarden als volgt opvragen en bewerken:
Onmiddellijk met een stem werken is uiteraard wel eenvoudiger:
Er zijn echter minder voor de hand liggende gevallen, zoals dit:
Nu moeten we iets schrijven in de aard van:
Maar weer eens, we hadden ook een stem kunnen gebruiken, waardoor we value hadden kunnen vermijden.
Dat werkt perfect, tot iemand het in zijn hoofd krijgt om te zeggen straat='LAND' zodat een Say adres.straat "België" op het scherm laat zien. Daarom gebruiken kenners speciale staarten, die nooit geldig variabele kunnen zijn:
Kortom, indien we een variabele naam dynamisch opbouwen laat de functie value toe de waarde op te vragen. Indien we echter een volledig statement dynamisch opbouwen dan biedt interpret de mogelijkheid dat statement te interpreteren.