Sviluppo

Corso di programmazione in REXX - Lezione 5 - nozioni di base

Alessandro Cantatore
 

Nota: i sorgenti descritti in questo numero sono stati raccolti nel file rxsource.zip (3386 byte).

Istruzioni di controllo dell'esecuzione del programma
Un programma può essere una semplice lista di istruzioni o l'insieme di più liste connesse da istruzioni che determinano quale lista elaborare e quante vote elaborarla.
Le istruzioni che determinano il flusso di esecuzione di un programma sono dette istruzioni di controllo. Le operazioni compiute da tali istruzioni includono:

diramazione (branching)
selezione di una lista di istruzioni da elaborare da un gruppo di diverse liste. Le istruzioni di diramazione sono IF e SELECT.
iterazione (looping)
ripetizione di una lista di istruzioni, o per un dato numero di volte o finché non si verifica una determinata condizione.
Le istruzioni di iterazione sono DO - UNTIL e DO - WHILE.
terminazione (exiting)
Un programma costituito da una semplice lista di istruzioni termina dopo aver eseguito l'ultima istruzione. Le istruzioni EXIT e RETURN permettono di terminare esplicitamente un programma senza che sia necessario aver raggiunto l'ultima istruzione.

I blocchi di istruzioni
Una caratteristica comune delle diverse istruzioni di controllo è quella di raggruppare una serie di istruzioni in un unico blocco che agisce come una singola istruzione.
Il modo più semplice di raggruppare una serie di istruzioni è tramite la parola chiave DO seguita da END per indicare la fine del blocco:

DO
   espressione 1
   espressione 2
   espressione 3
END
In questo caso la serie di istruzioni racchiusa dalla coppia DO - END viene considerata dall'interprete del REXX come un'unica istruzione.

E' consigliabile indentare il blocco di istruzioni di alcuni spazi a destra per rendere il programma più leggibile, mostrando chiaramente che la serie di istruzioni costituisce un unico blocco.

Condizioni di comparazione
Abbiamo già accennato nelle precedenti lezioni alle espressioni di comparazione che determinano se una determinata condizione è vera o falsa. Gli operatori comunemente usati sono "=" (uguale a), "<" (minore di) e ">" (maggiore di).
Il risultato dell'espressione di comparazione può essere solo 1 (vero) o 0 (falso). Per esempio:

/* comp.cmd */
say 5 = 5                   /* mostra '1' - vero    */
say 5 < 4                   /* mostra '0' - falso   */
say 5 = 4                   /* mostra '0' - falso   */
say 5 > 4                   /* mostra '1' - vero    */

reply = "sì"                /* assegna la stringa "sì"
                               alla variabile REPLY */

say reply                   /* mostra "sì"          */
say reply = "FORSE"         /* mostra '0' - falso   */
say reply = "sì"            /* mostra '1' - vero    */

Diramazione semplice
Per effetuare una decisione relativa all'esecuzione di una singola istruzione si usa IF - THEN:

if espressione
   then istruzione
L'istruzione viene eseguita solo se l'espressione è vera. Per esempio:
/* richiesta di conferma */
say " digita YES per continuare"
pull reply
if reply = "YES"
   then say "OK!"
L'istruzione say "OK!" viene eseguita solo se nella variabile reply è memorizzato il valore "YES".
L'istruzione IF introduce una nuova branca di istruzioni da eseguire se l'espressione che segue IF è vera.
I programmatori spesso visualizzano l'azione di decisione di quale branca di istruzioni tramite un diagramma chiamato diagramma di flusso.

L'espressione di decisione è rappresentata dal rombo. Se l'espressione (reply = "YES") è vera, il flusso di esecuzione del programma prende una deviazione per un ulteriore istruzione (say "OK!") prima di riprendere il flusso normale.

L'uso di DO...END per il raggruppamento di più espressioni

Per eseguire una serie di istruzioni dopo il THEN che segue IF è necessario raggrupparle in modo che l'interprete del REXX possa valutarle come un'unica espressione. A tale scopo si usa l'istruzione DO per iniziare la lista delle istruzioni e la parola chiave END per chiuderla. Per esempio:

IF espressione THEN
DO
   istruzione 1
   istruzione 2
   istruzione 3
   ...eccetera
END
Se espressione è VERA tutte le istruzioni comprese tra DO e END vengono eseguite come se fossero un'unica istruzione.
Se l'espressione è FALSA si passa invece direttamente all'istruzione successiva a END.
if sole = "splende"
then
   do
     say "Svegliati!"
     say "Alzati!"
     say "Esci!"
   end
Il diagramma di flusso è il seguente:

Se la condizione sole = "splendente" è vera tutte le 3 istruzioni SAY vengono eseguite, se invece è falsa non ne viene eseguita nessuna.

E' bene indentare il testo racchiuso tra DO e END in modo che sia immediatamente comprensibile che esso rappresenta un gruppo di istruzioni. Il DO può essere anche scritto sulla stessa riga di seguito a THEN. Un diverso stile di formattazione è ad esempio:

if sole = "splendente" then do
   say "Svegliati!"
   say "Alzati!"
   say "Esci!"
   end

La parola chiave ELSE
Abbiamo visto come attraverso IF...THEN è possibile controllare quali istruzioni eseguire se una determinata condizione è VERA.
La parola chiave ELSE permette di eseguire una differente serie di istruzioni quando invece la condizione è FALSA. Per esempio:

IF espressione
THEN istruzione 1
ELSE istruzione 2
Quando IF è usato in questo modo il REXX esegue solo una delle due istruzioni: istruzione 1 se espressione è VERA, istruzione 2 se invece è FALSA.

Anche nel primo esempio di questo corso si era usato IF...THEN...ELSE:

/* CIAO.CMD : questo è il mio primo programma REXX */
say "Ciao! Come ti chiami?"
pull nome
if nome = "" then say "Ciao sconosciuto!"
else say "Ciao" nome
Il diagramma di flusso è il seguente:

Un altro esempio di IF...THEN...ELSE è BACKITUP.CMD che crea una copia di backup del file introdotto:

/* BACKITUP.CMD: fa una copia di backup di un programma REXX             */
arg fname"."ext          /* attraverso questo costrutto viene effettuato */
                         /* il "parsing" dell'argomento scomponendolo    */
                         /* nelle due parti: "nome file" e "estensione"  */

if fname = "" then do    /* se non si è introdotto il nome del file      */
   say "Scrivi il nome del file"         /* lo chiede all'utente         */
   pull fname"."ext
   end

if ext = "" then ext = "CMD"             /* se non c'è l'estensione usa  */
                                         /* ".CMD" come estensione       */

"@dir" fname"."ext       /* esegue il comando "DIR" sul nome del file    */
                         /* introdotto per controllare che sia valido    */

if rc \= 0 then do                       /* se il file non esiste mostra */
   say "Impossibile eseguire il backup!" /* un messaggio di errore e     */
   say "Programma terminato."            /* termina                      */
   exit
   end
else do                                  /* altrimenti copia il file in  */
   say "Backing up" fname"."ext          /* un nuovo file con estensione */
   "@copy" fname"."ext  fname".BKP"      /* ".BKP"; mostrandone i dati   */
   "@dir" fname".BKP"                    /* tramite il comando "DIR"     */
   say "Programma terminato."
   exit
   end
Come si può notare anche con ELSE si è usato DO...END per raggruppare in un blocco unico diverse istruzioni.

Note:

  • In questo esempio abbiamo introdotto anche l'istruzione EXIT che indica all'interprete del REXX di terminare l'esecuzione del programma.
  • il punto racchiuso tra virgolette, dopo le istruzioni ARG e PULL chiamato modello di parsing letterale, dice all'interprete del REXX di rimuovere la stringa racchiusa tra le virgolette, se presente, e di separare quanto rimane in due parti assegnandone i valori alle variabili fname e ext.
    L'argomento del parsing sarà approfondito in una delle prossime lezioni.

L'istruzione SELECT
Il controllo del flusso di esecuzione dei programmi non è limitato alla scelta di esecuzione tra solo due blocchi, ma attraverso l'istruzione SELECT permette di eseguire uno tra molti diversi blocchi di istruzioni:

SELECT
   WHEN espressione1 THEN istruzione1
   WHEN espressione2 THEN istruzione2
   WHEN espressione3 THEN istruzione3
   ...

   OTHERWISE
      istruzione
      istruzione
      istruzione
      ...

END
Se espressione1 è VERA viene eseguita istruzione1 e l'elaborazione poi prosegue con le istruzioni che seguono END.
Se espressione1 è FALSA viene controllata la validità di espressione2: se questa è VERA viene eseguita istruzione2 e l'elaborazione riprende con l'istruzione successiva a END, altrimenti vengono testate le rimanenti espressioni finché non se ne trovi una VERA. Nel caso nessuna espressione risulti VERA viene eseguito il blocco di istruzioni compreso tra OTHERWISE (che in italiano significa altrimenti) e END.
Il diagramma di flusso per l'istruzione SELECT è il seguente:

Anche con l'istruzione SELECT è possibile raggruppare una lista di istruzioni tramite DO...END:

SELECT
   WHEN espressione1 THEN DO
      istruzione_a
      istruzione_b
      istruzione_c
      END
   WHEN espressione2 THEN istruzione2
   WHEN espressione3 THEN istruzione3
   ...

   OTHERWISE
      istruzione
      istruzione
      istruzione
      ...

END
Non è necessario invece usare DO...END per raggruppare più istruzioni dopo la parola chiave OTHERWISE.

Nel seguente esempio, INFO.CMD, viene mostrato l'uso di SELECT.

/* INFO.CMD */
say "cosa vuoi sapere? (data/ora/giorno/mese/adesso)"
pull reply
select
   when reply = "DATA"   then say date()
   when reply = "ORA"    then say time()
   when reply = "GIORNO" then say date("W")
   when reply = "MESE"   then say date("M")
   when reply = "ADESSO" then do
      say time("H")
      say time("M")
      say time("S")
      end
   otherwise
      say "gli argomenti validi sono: DATA"
      say "                           ORA"
      say "                           GIORNO"
      say "                           MESE"
      say "                           ADESSO"
   end
exit
E' essenziale che SELECT abbia un corrispondente END!
E' inoltre opportuno, come per i costrutti che abbiamo visto in precedenza, indentare le istruzioni che costituiscono un gruppo unico per facilitare la lettura del codice sia per correzioni che per espansioni future.

Esecuzione ripetitiva: i loop
Una parte essenziale di ogni linguaggio di programmazione sono le istruzioni di loop attraverso le quali l'esecuzione di una serie di istruzioni viene ripetuta:

  • un determinato numero di volte,
  • finché una determinata condizione è vera,
  • finché non venga soddisfatta una determinata condizione,
  • per sempre (finché l'utente non decide di interrompere l'esecuzione.
Il costrutto per ripetere una serie di istruzioni è:
DO espressione
   istruzione1
   istruzione2
   istruzione3
   ....
END
dove il valore di espressione è un numero intero che rappresenta il numero di volte che la serie di istruzione viene eseguita.
Il seguente esempio, SUONA.CMD esegue una serie di suoni un determinato (5) numero di volte:
/* SUONA.CMD */
do 5
   call beep 440, 200
   call beep 880, 200
   call beep 1760, 200
   call beep 3520, 200
   end
Il seguente programma chiede di specificare la base e l'altezza e disegna un rettangolo sullo schermo:
/* RETT.CMD */
/* chiede di introdurre la dimensione della base del rettangolo */
say "Introduci la base del rettangolo (3 - 60)"
pull base
/* controlla la validità della grandezza introdotta             */
select
   when \datatype(base, "WHOLE") then                    /* (1) */
      say base "non è un numero valido!"
   when base < 3 then                                    /* (2) */
      say "è troppo stretto!"
   when base > 60 then
      say "è troppo largo!"
   otherwise
      say "Introduci l'altezza del rettangolo (3 - 15)"
      pull altezza
      select
      when \datatype(altezza, "WHOLE") then              /* (1) */
         say altezza "non è un numero valido!"
      when altezza < 3 then                              /* (2) */
         say "è troppo basso!"
      when altezza > 15 then
         say "è troppo alto!"
      otherwise
         say
         say
         do altezza                                      /* (3) */
            call charout , "          "
            do base                                      /* (3) */
               call charout , "db"x                      /* (4) */
               end /* do base */
            call charout , "0d0a"x            /* ritorno a capo */
            end /* do altezza */
         say
         say
      end
   end
exit
La funzione dell'interprete del REXX DATATYPE (1) viene usata con il parametro "WHOLE" per controllare che il dato introdotto sia un numero intero. Viene inoltre controllato che il valore introdotto sia compreso entro i limiti stabiliti (2) (nell'esempio 3 - 60 per la base e 3 - 15 per l'altezza.
Le istruzioni comprese tra DO e END vengono eseguite tante volte quanto è il valore delle variabili che seguono DO (3), nell'esempio altezza e base.
La funzione CHAROUT (4) viene qui usata per visualizzare un singolo carattere sullo schermo (un rettangolo se la codepage usata è 850).
La visualizzazione viene ripetuta tante volte quanta è la larghezza della base (do base ... end) e il ciclo viene ripetuto tante volte quanto il valore dell'altezza ( do altezza ... end).

Loop condizionali
Nei cicli condizionali, la lista di istruzioni compresa tra DO ... END viene eseguita finché non viene soddisfatta una determinata condizione.
Secondo la posizione dell'espressione condizionale ci sono tre diversi tipi di loop:

  • DO FOREVER (con LEAVE)
  • DO WHILE
  • DO UNTIL

Nota: nello scrivere programmi in REXX può capitare di creare inavvertitamente dei cicli infiniti, la cui esepressione condizionale è sempre valutata vera. Per interrompere l'esecuzione dello script in tali casi è sufficiente premere i tasti Control e C.

Il ciclo DO FOREVER e l'istruzione LEAVE
Il modo più semplice di creare un loop condizionale è tramite le istruzioni DO FOREVER e LEAVE.
L'istruzione DO FOREVER (letteralmente "esegui per sempre") crea un ciclo infinito. All'interno di tale tipo, di solito, si colloca un'espressione condizionale che se verificata termina il ciclo tramite l'istruzione LEAVE.

Il seguente programma di esempio somma i numeri introdotti da tastiera.
Ogni nuovo numero introdotto viene sommato al precedente. Per terminare il programma è sufficiente introdurre un carattere non numerico.

/* somma.cmd */
totale = 0
do forever
   say "Introduci un numero"
   pull numero
   if \datatype(numero,"N")     /* se non si è introdotto un numero */
   then leave                   /* termina il ciclo */
   totale = totale + numero
   say "Totale = "totale
end
say "'"numero"' non è un numero. Termine del programma."

Il ciclo DO WHILE
Per creare un ciclo che ripeta una lista di istruzioni finché una determinata condizione sia VERA si usa l'istruzione DO WHILE:

DO WHILE espressione
   istruzione1
   istruzione2
   istruzione3
   . . .
END
dove espressione deve dare come risultato 0 (FALSO) o 1 (VERO).
Come si vede dal sottostante diagramma di flusso, la condizione viene testata all'inizio del ciclo, prima dell'esecuzione della lista di istruzioni. Ciò implica che se la condizione non viene soddisfatta nella fase iniziale la lista di istruzioni non viene mai eseguita.
Al contrario nel ciclo DO UNTIL la condizione viene verificata solo a fine ciclo, perciò, anche nel caso che non venga sodisfatta dall'inizio, la lista di istruzioni viene eseguita almeno una volta.

La stessa funzione del ciclo DO WHILE può essere ottenuta anche da DO FOREVER. Infatti:

DO FOREVER
   IF \ espressione THEN LEAVE
   istruzione1
   istruzione2
   istruzione3
   . . .
END
equivale a:
DO WHILE espressione
   istruzione1
   istruzione2
   istruzione3
   . . .
END
Per esempio il ciclo DO WHILE può essere usato per richiedere all'utente dei parametri quando non li ha immessi da riga di comando all'avvio del programma:
/* ottiene l'argomento */
arg filename
do while filename = ""    /* viene eseguito se non ci sono argomenti */
   say "Introduci il nome di un file o '*' per terminare il programma:"
   pull filename
   if filename = "*" then exit
   end
.
.
.
Se l'utente scrive l'argomento quando avvia il programma la lista di istruzioni compresa nel ciclo DO WHILE non viene mai eseguita.
Nel caso contrario il ciclo viene eseguito finché non viene immesso il nome di un file.

Il ciclo DO UNTIL
Per ripetere una serie di istruzioni finché non sia verificata una determinata condizione si usa il ciclo DO UNTIL:

DO UNTIL espressione
   istruzione1
   istruzione2
   istruzione3
   . . .
END
dove espressione quando viene valutata dall'interprete del REXX deve dare come risultato o 0 o 1.
La condizione viene verificata solo a fine ciclo, cioè dopo ogni volta che la lista di istruzioni viene eseguita. Ciò implica che la lista di istruzioni viene eseguita anche se espressione è VERA dall'inizio.
Paragonate il seguente diagramma di flusso con quello del ciclo DO WHILE:

Come anche per DO WHILE si può ottenere la stessa funzione di DO UNTIL usando DO FOREVER:

DO FOREVER
   istruzione1
   istruzione2
   istruzione3
   . . .
   IF espressione THEN LEAVE
END
equivale a:
DO UNTIL espressione
   istruzione1
   istruzione2
   istruzione3
   . . .
END
L'istruzione DO UNTIL può convenientemente usata per controllare la validità dei dati immessi dall'utente. Nell'esempio seguente la funzione DATATYPE è usata per controllare che il dato immesso sia un numero. Il ciclo continua finché l'utente non immette un numero o introduce un argomento nullo premendo semplicemente Invio:
/* accetta solo dati numerici */
do until datatype(input, "N")
   say "Introduci un numero o premi 'Invio' per terminare."
   pull input
   if input = "" then exit
   end
.
.
.
Il ciclo viene sempre eseguito almeno una volta, anche se nella variabile input è già memorizzato un numero valido.

Nell'esempio del paragrafo precedente era possibile terminare il loop immettendo il carattere '*', mentre in questo esempio basta premere Invio. E' importante, quando si impiegano dei loop per richiedere dati all'utente, prevedere dei comandi di terminazione del loop stesso.

Cicli controllati da contatori
E' possibile usare un ciclo per incrementare automaticamente il valore di una variabile:

DO variabile = espressione_i
   istruzione1
   istruzione2
   istruzione3
   . . .
END
oppure
DO variabile = espressione_i TO espressione_t
   istruzione1
   istruzione2
   istruzione3
   . . .
END
dove:
variabile
è la variabile di controllo, detta anche contatore. Può essere usata anche all'interno del loop. Il suo valore viene incrementato di 1 ad ogni ciclo.
espressione_i
rappresenta il valore iniziale assegnato alla variabile di controllo ed è il valore che essa ha durante la prima esecuzione del loop.
espressione_t
è l'espressione per il valore finale (TO) che la variabile assumerà durante l'ultima esecuzione del loop. Cioè il ciclo termina quando il valore della variabile di controllo supera quello di espressione_t.
Il seguente diagramma di flusso mostra come viene incrementato il contatore e come viene eseguita la decisione di terminare il loop:

Il seguente esempio mostra come sia possibile disegnare un triangolo sullo schermo usando un contatore:

/* triangle.cmd: viene richiesto all'utente di specificare l'altezza */
/*               del triangolo e il triangolo viene disegnato sullo  */
/*               schermo */
say "Introduci l'altezza del triangolo (un numero intero tra 3 e 15):"
pull altezza
select
   when  \ datatype(altezza,"WHOLE") then      /* se non è un numero */
      say "Il dato introdotto non è valido!"
   when  altezza < 3 then                      /* se è < 3           */
      say "Il dato introdotto è troppo piccolo!"
   when  altezza > 15 then                     /* se è > 15          */
      say "Il dato introdotto è troppo grande!"
   otherwise             /* se il dato è valido disegna il triangolo */
      do contatore = 1 to altezza
         say copies("db"x, 2 * contatore - 1)
         end /* contatore = 1 to altezza */
   end /* select */
exit
Al termine del loop è ancora possibile usare la variabile impiegata per il contatore tenendo però presente che il suo valore sarà maggiore di quello indicato nell'espressione dopo il TO.

Nei cicli visti precedentemente il contatore viene aumentato di 1 ad ogni esecuzione del ciclo. Con l'aggiunta di alcune parole chiave è possibile eseguire incrementi diversi da 1, negativi e non interi:

DO variabile = espressione_i BY espressione_b
   istruzione1
   istruzione2
   istruzione3
   . . .
END
oppure
DO variabile = espressione_i BY espressione_b TO espressione_t
   istruzione1
   istruzione2
   istruzione3
   . . .
END
dove BY espressione_b rappresenta l'incremento (o decremento) desiderato.
Il seguente semplice esempio mostra come per l'incremento sia possibile usare amche un valore negativo e/o non intero: il valore iniziale viene decrementato e il loop viene eseguito fino a che il valore del contatore non scende al di sotto del valore che segue TO.
/* decr.cmd */
i = 0
do contatore = 60 by -5.2 TO 10
   i = i + 1
   say "ciclo n. "i" - contatore = "contatore
   end
say "Al termine del loop il valore del contatore è:" contatore
exit
Un contatore può essere implementato anche con gli altri tipi di loop visti in precedenza essendo equivalente per esempio a:
variabile = espressione_i
DO WHILE variabile < espressione_t
   istruzione1
   istruzione2
   istruzione3
   . . .
   espressione_i = espressione_i + espressione_b
END
Ovviamente nel caso di decremento (espressione_i = espressione_i - espressione_b) si dovrà usare ">" nella comparazione iniziale.

L'istruzione EXIT
Come accennato in uno dei precedenti paragrafi l'istruzione EXIT permette di terminare immediatamente il programma REXX tornando al prompt dei comandi di OS/2.
L'istruzione EXIT può anche essere seguita da un espressione di valore intero che viene restituito all'interprete dei comandi.
Per esempio:

/* esempio di programma REXX */

/* elabora alcune istruzioni */
. . .
. . .
. . .

/* se si verifica un errore restituisce "2" all'interprete dei comandi */
if errore then exit 2

/* elabora altre istruzioni  */
. . .
. . .
. . .

/* termina senza errori      */
exit 0


[Pagina precedente] [Sommario] [Pagina successiva]