Programmeren in REXX/Bevelen, deel 2
In dit hoofdstuk leren we nog andere REXX bevelen te gebruiken.
Werken met de programmabuffer of stack
bewerkenVolgende instructies laten toe de programmabuffer of stack te gebruiken.
QUEUE - Een lijn achteraan toevoegen aan de stack
bewerkenHet bevel queue voegt een lijn toe aan de programmastack. De lijn wordt achteraan de wachtrij (queue=staart) toegevoegd en zal dus als laatste terug worden gelezen. Hier is de term FIFO (First In, First Out) van toepassing. Wie eerst aan het loket komt zal dus eerst bediend worden.
queue 'Klaas' queue 'Jan' parse pull woord ; say woord «Klaas» parse pull woord ; say woord «Jan»
PUSH - Een lijn vooraan toevoegen aan de stack
bewerkenMet push voegen we ook een lijn toe aan de stack, maar de lijn wordt nu vooraan in de wachtrij geplaatst (de andere worden a.h.w. achteruit gepusht). De nieuwe lijn zal dus als eerste terug worden gelezen. Men heeft het over LIFO (Last In, First Out).
push 'Klaas' push 'Jan' parse pull woord ; say woord «Jan» parse pull woord ; say woord «Klaas»
PARSE PULL - Lezen van de stack
bewerkenMet het parse pull bevel kunnen we nu lijn voor lijn uit de stack gaan halen. Indien de stack leeg is, dan gaat het programma wachten op data die men zal inbrengen aan het klavier (en lijkt dit equivalent aan het gebruik van parse linein).
parse pull input say 'Je hebt "'input'" ingetoetst.'
PULL - Lezen van de stack
bewerkenHet pull bevel is een verkorte vorm voor parse upper pull. Hetgeen wordt ingelezen wordt dus tevens omgezet naar hoofdletters. Omdat die omzetting niet duidelijk wordt aangegeven in de instructie verkiezen we parse upper pull.
Voor de volledigheid moeten we hier ook nog de queued() functie bespreken:
QUEUED - Bepaal het aantal lijnen in de stack
bewerkenDeze functie heeft geen parameters en retourneert het aantal lijnen dat staat te wachten in de programmabuffer.
Indien een programma de stack wil ledigen is het goed eerst na te gaan of er nog een lijn staat te wachten. Zoniet stopt het programma en wacht het op input van de gebruiker, wat misschien niet de bedoeling is. Queued() is daarvoor geschikt.
do while queued()>0 parse pull line /* verdere verwerking hier */ end
Werken met subroutines of inwendige functies
bewerkenSubroutines en inwendige functies beginnen beide aan een label en eindigen met een return bevel. Het label bepaalt de naam van de functie of routine.
Een functie kan op twee manieren worden opgeroepen. Expliciet met een call bevel, of impliciet als een klassieke functie [functie(parm1,parm2,...)]. Wanneer deze laatste vorm wordt gebruikt moet de functie een resultaat teruggeven, anders treedt er een fout op in de aard van Error 44: Function or message did not return data. Het resultaat kan desnoods een nullstring zijn, als er maar iets wordt teruggegeven.
We bestuderen nu het bevel call.
CALL - Oproepen van een routine of functie
bewerkencall naam [parameter1][,parameter2][,parameter3]...
Met dit bevel kunnen we zowel
- een interne routine,
- een externe routine of programma, als
- een interne functie
oproepen. De externe routine of programma is meestal een ander REXX programma. Sommige programmapakketten kunnen REXX als macro-taal gebruiken en dan kan met een call ook een functie van het pakket worden opgeroepen.
De naam verwijst naar wat wordt opgeroepen. Parameters worden van elkaar gescheiden door een komma.
In dit eenvoudig voorbeeld roepen we een interne routine op:
/* Programma met routine */ a=10 call MijnRoutine a say a exit /****** onze routine begint hier ******/ MijnRoutine: a=a+10 return
Op het scherm zullen we het getal «20» zien verschijnen. De variabele a is in dit voorbeeld inderdaad globaal aan het hoofdprogramma en de subroutine. De routine kan dus de waarde van a lezen en/of aanpassen. Later zullen we zien hoe we dit kunnen vermijden.
Indien we van de subroutine een functie zouden willen maken, dan moeten we dus zorgen dat er een resultaat wordt teruggegeven bij de return. Ons voorbeeld wordt dan:
/* Programma met interne functie */ a=10 ; b=20 say MijnFunctie(a,b) exit /****** onze functie ******/ MijnFunctie: parse arg w1 , w2 return w1*w2
Het resultaat dat return teruggeeft wordt natuurlijk eerst geïnterpreteerd door REXX.
We kunnen deze functie echter ook met een call oproepen:
/* Functie als routine */ a=10 ; b=20 call MijnFunctie a,b say result exit MijnFunctie: parse arg w1 , w2 return w1*w2
Het antwoord dat het bevel return dan teruggeeft is terug te vinden in de gereserveerde variabele result. Indien we vergeten een resultaat terug te geven zal de routine toch nog werken, maar de variabele result zal dan niet opgevuld zijn.
Soms hebben we namelijk geen resultaat nodig, of niet altijd nodig. Een functie kan bijvoorbeeld een host-commando (commando van het besturingssysteem) oproepen. Dit commando zal normaal gezien een returncode produceren. Die code kunnen we aan de oproeper van de routine teruggeven. Maar het staat de oproeper vrij deze code wél of niet op te pikken uit de variabele result.
Merk op dat ook in deze laatste voorbeelden de variabelen a en b zichtbaar en manipuleerbaar zijn door de subroutine of functie. Men zegt dan dat de variabelen globaal zijn. Dit kan natuurlijk leiden tot onverwachte toestanden in het hoofdprogramma.
Om die problemen te vermijden kunnen we gebruik maken van het procedure bevel.
PROCEDURE - Beschermen van variabelen
bewerkenprocedure [EXPOSE [naam1] [naam2] ...]
Een procedure bevel stelt dat alle variabelen vanaf nu lokaal moeten zijn. Dit bevel moet onmiddellijk ná het label volgen.
De variabelen van de oproeper blijven dus onbekend en onbereikbaar. Gebruiken we dezelfde naam voor een variabele in de hoofd- en subroutine, dan betreft het twee verschillende variabelen. De variabelen van de subroutine gaan verloren bij het return bevel.
Willen we sommige variabelen van het hoofdprogramma toch wel kunnen zien of aanpassen, dan moeten we ze exposeren. Op die manier worden het terug globale variabelen. Men kan zowel afzonderlijke variabelen als hele stems exposeren. Dit is een voorbeeld:
/* Programma met lokale en globale variabelen */ a=10 ; b=20 mystem.0=2 ; mystem.1=5 ; mystem.2=8 say a "-" b "-" mystem.1 "-" mystem.2 call subroutine a say a "-" b "-" mystem.1 "-" mystem.2 exit /**** onze subroutine ****/ Subroutine: procedure expose b a=43234 say a "-" b "-" mystem.1 b=a*b return
Dit zien we bij uitvoering op het scherm verschijnen:
10 - 20 - 5 - 8 43234 - 20 - MYSTEM.1 10 - 200 - 5 - 8
De eerste resultaatlijn hoeft weinig uitleg, het toont de waarden zoals we ze net hebben toegekend in het hoofdprogramma.
Vervolgens roepen we de subroutine op die via het bevel procedure de variabelen van de oproeper afschermt. Er is wel een uitzondering gemaakt voor variabele b, wie wordt namelijk geëxposeerd. De tweede lijn in het resultaat bewijst dat de variabele a lokaal is voor de subroutine. De variabele b is wel degelijk die van de hoofdroutine, dus met waarde «20». De variabele mystem.1 tenslotte, is niet geïnitialiseerd in de subroutine.
Nadat de subroutine is uitgevoerd wordt de derde lijn afgedrukt. We zien daarbij dat enkel de geëxposeerde variabele b nu een andere waarde «200» heeft gekregen, als gevolg van de aanpassing in de subroutine.
Indien we ook de mystem. hadden willen publiek maken, dan moesten we eenvoudig dit toevoegen aan de expose:
Subroutine: procedure expose b mystem.
We moeten gelukkig dus niet alle elementen afzonderlijk gaan exposeren !
RETURN - Verlaten van een subroutine of functie
bewerkenMet een return bevel verlaat men dus een subroutine of functie. In het geval van een functie moet een parameter zorgen voor het teruggeven van een resultaat. De parameter wordt zoals steeds, eerst door REXX geïnterpreteerd.
SIGNAL - Springen naar een label
bewerkensignal label
Met signal springen we naar het label dat als parameter is opgegeven. Terugkeren zou enkel kunnen als we net ná het signal bevel een terugkeer-label voorzien. Dit is het belangrijke verschil met een call.
Signal gebruiken is een weinig elegante manier van programmeren. Signal wordt dan ook enkel gebruikt ingeval er nooit meer moet teruggekeerd worden. Typisch zijn de routines die fouten afhandelen en dan het programma beëindigen. We leren hier meer over in het hoofdstuk over Debugging.
Bijzonderheden en valkuilen met CALL en functies
bewerkenEXIT in de subroutine
bewerken/* */ call subrout say 'Goodbye' exit subrout: say 'Hello' exit
In de subroutine hebben we het bevel exit geschreven en niet return. REXX heeft hier geen probleem mee, maar we zullen nooit een «Goodbye» op het scherm zien verschijnen omdat we niet meer terugkeren naar de oproeper. We stoppen het programma.
Het kan zijn dat we in een subroutine beslissen niet langer verder te gaan met de rest van het programma, omdat er bijvoorbeeld een probleem is opgetreden. Dit is echter geen mooie manier van programmeren, en soms moeilijk te debuggen. Beter is een foutcode met een return-bevel terug te geven en het hoofdprogramma te laten beslissen of het programma moet stoppen.
Recursieve functie
bewerkenEen recursieve functie is een functie die zichzelf telkens weer oproept. Om te vermijden dat men er nooit meer uitgeraakt moet er ergens een "uitgang" voorzien worden. Het volgend voorbeeld berekent de faculteit van een getal (x!).
/* Recursieve interne function */ parse arg getal say getal'! =' facculteit(getal) exit /* Functie ter berekening van de faculteit */ Faculteit: procedure arg n /* nieuwe n bij elke iteratie */ if n=0 then return 1 /* Bij 1 stoppen we */ return faculteit(n-1) * n
Dank zij het bevel procedure wordt bij elke recursieve oproep een nieuwe variabele n aangemaakt. Door te testen of "n=0" zullen we ook recursief uit alle subroutines terug naar boven zullen kruipen.
Externe functies of routines
bewerkenOm een externe functie of routine op te roepen gaat men op dezelfde manier te werk, doch de variabelen van het oproepend programma zijn dan altijd onbereikbaar. Om gegevens door te spelen zal men bij het oproepen parameters moeten meegeven, want een expose heeft hier geen zin.
De externe routine haalt de parameters dan op met een ARG- of PARSE ARG-bevel of een ARG-functie. Meer daarover in Oproepen van host commando's.
Tweelingen
bewerkenNiets belet ons om een interne functie te schrijven die dezelfde naam heeft als een standaard REXX functie of een bestaande externe functie. Op die manier kunnen we bijvoorbeeld de werking van een functie beïnvloeden. REXX zoekt namelijk eerst intern en dan extern. De zoekvolgorde om externe functies te vinden is afhankelijk van het besturingssysteem. Er kunnen bijvoorbeeld nog externe functiepakketten meespelen.
Willen we met zekerheid de standaardfunctie oproepen, dan moeten we de naam als een constante, en met hoofdletters schrijven.
Hier hebben we een voorbeeld:
say "LEFT"('Europeaan',6) say left('Europeaan',6) ... Left: Procedure parse arg string , lengte return right(string,lengte)
We zien dan het volgende op het scherm verschijnen:
Europe opeaan