9. Parsing
   ==========

 A questo punto,  possiamo  considerare  la  possibilita'  di
lanciare un  programma  REXX  con degli argomenti sulla linea
di comando.  Vediamo un esempio:

   /* Legge i propri argomenti */
   parse arg a.1, a.2, a.3, a.4
   do i = 1 to 4
    say "L'argomento " i "vale:"  a.i
   end

 Eseguitelo, aggiungendo  per esempio "alfa beta gamma delta"
dopo  il nome  del programma  (per esempio,  parse  alfa beta
gamma delta).  Il programma stam- pera' come risultato

   L'argomento 1 vale:  alfa
   L'argomento 2 vale:  beta
   L'argomento 3 vale:  gamma
   L'argomento 4 vale:  delta

 L'argomento complessivo,  la frase  "alfa beta gamma delta",
e' stato  suddiviso in  quattro componenti,  separate  tra di
loro  all'altezza degli spazi.  Se provate a inserire meno di
quattro argomenti noterete che  le ultime componenti verranno
stampate  come vuote,  e  se scrivete piu' di  quattro compo-
nenti, l'ultima frase conterra'  tutti i dati in eccesso.  Se
inoltre  c'e' piu' di uno spazio tra i vari argomenti, questi
spazi extra  verranno cancellati,  eccetto quelli dell'ultimo
componente  l'argomento  completo.  Questo  comportamento  e'
chiamato "Tokenisation".

 Oltre agli argomenti,  e' possibile elaborare in questo modo
anche  l'ingresso:  provate  a  rimpiazzare  "parse arg"  con
"parse pull" nell'esempio pre- cedente.  Noterete che al lan-
cio, l'ingresso (una singola stringa) verra' "tokenizzato".
Se rimpiazzate  "parse pull" con  "parse upper",  l'ingresso,
oltre alla tokenizzazione, verra' trasportato tutto in maius-
colo (dove possibile, ovviamente).

Anche altri dati possono subire il processo di parsing.
"parse source" elabora nello stesso modo informazioni su come
il programma e' stato lanciato,  mentre "parse version"  ela-
bora le informazioni di versione dell'interprete REXX.
Comunque,  i due usi  piu' importanti  dell'istruzione  parse
sono  senza dubbio  "parse var  [variabile]"  e  "parse value
[espressione] with...".  Queste  permettono l'elaborazione di
dati arbitrari forniti dal progrmma.  Esempio:

   /* Acquisisce informazioni su data e ora */
   d = date ()
   parse var d giorno mese anno
   parse value time () with ora ":" minuti ":"  secondi

 Vediamo  qui  due modi  di  parsing  differenti.  Invece  di
tokenizzare  tutto il risultato  della funzione "time ()", lo
dividiamo in tre pezzi all'altezza del carattere ":".  Cosi',
per esempio, "17:44:11" viene diviso, dopo il parsing, in 17,
44, e 11.

Il comando "parse" accetta la presenza di una stringa, che fa
da maschera (template) di ricerca.  Questa stringa  e' speci-
ficata tra virgolette, per esempio in

   parse arg primo "beta" secondo "alfa"

 Questa frase  assegna alla variabile "primo"  qualsiasi cosa
appaia PRIMA della scritta "beta", e alla variabile "secondo"
tutto cio' che appare tra le scritte "beta" e "alfa".
Ovviamente se la scritta  "beta"  non appare nella stringa di
argomenti,  tutta  la  stringa   stessa  verra'  assegnata  a
"primo":  a "secondo" competera' invece la stringa vuota.  Se
"beta" e' presente e "alfa" no, tutto quello che segue "beta"
verra' assegnato a "secondo".

 E' poi possibile tokenizzare pezzi di input che appaiono tra
maschere diverse:  per esempio,

   parse arg "alfa" prima seconda "beta"

tokenizza  tutto cio' che appare tra "alfa" e "beta" e piazza
i token, secondo le regole viste, nelle due variabili "prima"
e  "seconda".  Piazzare  un punto  al  posto  di  un  nome di
variabile  durante  la  tokenizzazione comporta  la rimozione
di quel particolare token;

   parse pull a . c . e

mantiene  il  primo,  il  terzo e  il  quinto token; perde il
secondo  e il  quarto.  Il  punto  e'  un ottimo sistema  per
evitare  argomenti  in eccesso:  piazzandolo  alla fine delle
variabili su cui effettuare il parsing, per esempio in

   parse arg a b c d e .

ci si assicura che i token in eccesso non vengano usati, e in
piu' cancella tutti gli spazi relativi all'ultimo token (dato
che  solo  l'ultimo token  puo' contenere spazi,  il punto ci
assicura che questi spazi non verranno MAI considerati).

 Infine, e' possibile effettuare il parsing per posizioni nu-
meriche  nella stringa,  anziche' tramite i template:  queste
posizioni partono da 1 per il primo carattere.

   parse var a 6 alfa + 3 beta + 5 gamma

 Il valore  di  "alfa"  sara' dato  dai tre caratteri di "a",
partendo  dal  carattere  n.6:  "beta"  sara' riempito  con i
cinque caratteri  successivi, "gamma"  invece conterra' tutta
la parte finale di "a".


   10.  Interpret
   ==============

 Supponiamo  di  avere una variabile  "istr" che contenga una
stringa come,  per esempio, "a = a + 1",  ossia un'istruzione
REXX.  Questa  stringa  puo' essere eseguita come  una istru-
zione tramite il comando Interpret:

   interpret istr

informera' l'interprete REXX di eseguire la stringa contenuta
in  "istr".  L'istruzione  "interpret"  puo' essere usata per
accettare istruzioni REXX dall'utente, o per assegnare valori
a  variabili  i  cui  nomi  non siano noti  in anticipo.  Per
esempio:

   /* Inserisce il nome di una variabile e assegna */
   /* a quella variabile il valore 42 */
   parse pull var
   interpret var "=" 42

 Un esempio lampante di "interpret"  e' nel file REXXTRY.CMD,
che permette di provare  in maniera interattiva le istruzioni
REXX:  REXXTRY.CMD e'  presente  nella distribuzione base  di
OS/2 ed eCS, alla directory \OS2.  Nel file troviamo,  per la
precisione  nel ciclo principale  individuato  dall'etichetta
"main"  (vedremo in altra sede  cosa sono  le etichette),  le
seguenti due istruzioni

   call set2 ; trace (trace) /* Need these on same line.  */
   interpret inputrx /* Try the user's input.  */

con le quali,  tra le altre cose,  viene appunto interpretato
cio' che l'utente inserisce.


   11.  Lo stack
   =============

 Il  REXX  ha una "catasta"  (o stack)  per i dati, che viene
accessata  dalle  istruzioni  "push",  "queue" e  "pull".  In
particolare abbiamo gia' visto che "pull"  (o la sua versione
completa  "parse pull")  viene usata  per l'input dei dati da
utente:  se  e'  presente  qualcosa  di valido  nello  stack,
"pull" prelevera' dallo stack questa entita'.  Esempio:

   /* Accesso allo stack */
   queue "Ciao!"  parse pull a /* preleva "Ciao!"  dallo  */
                               /* stack e lo immette in a */
   parse pull b                /* stack vuoto:  chiede    */
                               /* l'input all'utente      */
   push "67890" push "12345"   /* due entita', stringhe,  */
                               /* vengono inserite nello  */
                               /* stack */
   parse pull c                /* c contiene "12345" */
   /* E' rimasto un elemento nello stack !  */

 La differenza tra "queue" e "push" sta nel fatto che, quando
gli  elementi  vengono  estratti  dallo stack,  gli  elementi
accodati tramite  "queue"  escono nello stesso ordine  in cui
sono stati inseriti (FIFO o First In First Out,  come la coda
dei messaggi  delle  applicazioni PM):  quelli  spinti  nello
stack  dalla direttiva  "push"  appaiono  in  ordine inverso,
dando uno stack di tipo LIFO (Last In First Out).


   12.  Subroutines e nuove funzioni
   =================================

 Definiamo una funzione e diamole un nome: questa funzione e'
interna al programma, ne fa cioe' parte integrante.

   /* Definizione di una funzione */
   say "I risultati sono:" square (3) square (5) square (9)
   exit

   square:  /* Torna il quadrato del suo argomento */
   parse arg in
   return in * in    /* o anche in**2 */


 L'uscita del programma e' la seguente:

   I risultati sono:  9 25 81

Quando l'interprete trova la chiamata a "square (...)", cerca
lungo il programma  un'ETICHETTA,  chiamata appunto "square".
In questo caso  la trova  alla riga 5,  seguita dal due punti
che appunto la distingue come etichetta.  L'interprete esegue
le istruzioni  successive  fino  ad  arrivare  all'istruzione
"return ...": intanto che queste istruzioni vengono eseguite,
gli argomenti  della funzione  possono essere recuperati  con
l'istruzione  "parse arg"  esattamente come  gli argomenti al
lancio  di  un  programma:  quando  si arriva  all'istruzione
"return", il valore specificato  viene valutato  e usato come
valore di ritorno della funzione (nel nostro caso "square").

 L'istruzione  "exit"  che appare  alla terza riga  causa  il
termine dell'esecuzione del programma,  che altrimenti prose-
guirebbe eseguendo (scorrettamente) la funzione.

 Una funzione che usa  piu' di un argomento puo' essere defi-
nita semplicemente  separando  gli argomenti con una virgola:
come ad esempio

   /* Funzione che accetta tre argomenti */
   say "I risultati sono:"
   condizione (5, "Si'", "No") condizione (10, "X", "Y")
   exit

   condizione:  /* Se il primo argomento e' minore di 10  */
                /* viene ritornato il secondo, altrimenti */
                /* il terzo */
   parse arg c,x,y
   if c < 10 then
    return x
   else
    return y

 Una  subroutine  (in Pascal, una procedura)  e' simile a una
funzione, eccettuato il fatto che non ritorna alcun valore.
Le subroutines  vengono  chiamate  tramite l'istruzione "call
subroutine...":  per esempio,

   /* Definisce una subroutine che stampa una stringa */
   /* in una cornice e la chiama */
   call box "Questa frase e' incorniciata"
   call box "Questa domanda e' incorniciata?"
   exit

   box:  /* Questa e' la procedura */
   parse arg text
   say "+--------------------------------+"
   say "|"centre (text, 32)"|"        /* centra il testo */
   say "+--------------------------------+" return

Nota bene:  la funzione "centre" e' predefinita in REXX!  Una
funzione,  anche una predefinita,  puo' essere chiamata  come
una  subroutine.  Il risultato  ritornato  dalla  funzione e'
piazzato in una variabile chiamata "result".

   /* Stampa la data usando l'istruzione "call" */
   call date "N"
   say result

 Se una funzione o una subroutine  non necessita delle varia-
bili usate dal chiamante, o se al contrario usa variabili che
non interessano il chiamante,  la funzione  puo' iniziare con
l'istruzione  "procedure".  Questo ripulisce  tutte le varia-
bili esistenti  non viste,  e prepara un nuovo set  di varia-
bili.  Il nuovo insieme  verra'  distrutto  all'uscita  della
funzione (in pratica verranno create delle variabili locali).
Questo programma di esempio calcola il fattoriale di un nume-
ro intero, in maniera ricorsiva  (per cui, la ricorsivita' e'
ammessa in REXX!):

   /* Calcola il fattoriale di x, 1*2*3*4*...*x */
   parse pull x
   say "x!  =" factorial (x)
   exit

   factorial:  procedure
   parse arg p
   if p < 3
   then
    return p
   else
    return factorial (p-1) * p

 La variabile  "p",  che conserva l'argomento  della funzione
fattoriale,  non  e'  modificata  dalla  funzione  (factorial
(p-1)), in quanto viene nascosta dall'istruzione "procedure".
Ad ogni chiamata della funzione vengono create  variabili lo-
cali,  che cioe' esistono  e sono viste  SOLO  all'interno di
QUELLA chiamata alla funzione.
Se la subroutine o la funzione ha bisogno pero' di un accesso
ad  alcune variabili,  e'  possibile  estendere la  direttiva
"procedure" usando  "procedure expose" seguito dalla lista di
nomi delle variabili che  dovranno essere esposte, cioe' rese
visibili.  Per questa particolare caratteristica consiglio la
consultazione della  Guida in Linea  "Informazioni sul REXX",
che qui e' abbastanza esauriente.

 Funzioni e subroutines possono essere contenute in programmi
REXX differenti  (e' buona pratica).  Basta scrivere  le fun-
zioni  e  salvarle  in  un  file  il  cui  nome  possa essere
riconosciuto  dall'interprete:  queste funzioni vengono chia-
mate funzioni ESTERNE (external), in contrasto con le funzio-
ni definite nel programma in  esecuzione, che vengono invece
chiamate INTERNE (internal).

 Per esempio,  vogliamo usare  una funzione esterna  chiamata
"asino ()",  o una subroutine con "call asino":  nel salvarla
le daremo nome  "asino.cmd",  e  la salveremo  in un percorso
contenuto  nella  variabile  di  sistema  PATH  di OS/2 - eCS
(specificata in CONFIG.SYS:  la cosa piu' semplice da fare, e
forse  la migliore,  e' salvarla nella  stessa  directory del
programma principale che la chiamera').

 Per le  funzioni esterne,  l'istruzione  "procedure"  non e'
necessaria,  perche'  viene  eseguita  automaticamente  prima
dell'esecuzione  della  funzione  dall'interprete  REXX:  una
funzione esterna  non e'  in alcun modo  in grado di accedere
all'insieme  delle  variabili  del  chiamante,   a  parte  il
passaggio dei parametri.

 Il ritorno  da una funzione esterna  e' effettuato con l'is-
truzione  "return"  o con l'istruzione  "exit":  "exit"  puo'
essere usata per ritornare dati al chiamante esattamente come
"return",  ma puo'  essere usata per  tornare al chiamante la
funzione  esterna  anche quando questo chiamante  e' una fun-
zione interna (che a sua volta e' in una funzione interna).
"exit" e' usato per uscire da  un normale programma REXX come
abbiamo  gia' visto:  in questo caso  puo' essere passato  un
argomento a  "exit",  che fara' da codice di ritorno del pro-
gramma (per esempio, per scopi di debugging).


   13.  Esecuzione di comandi
   ==========================

 Il REXX  puo' essere usato per  l'esecuzione dei comandi del
sistema, se questo lo permette (e' il caso dell'interprete di
comandi CMD.EXE di OS/2).  Quando  l'interprete incontra  una
linea che non corrisponde a un comando REXX, a un assegnamen-
to  o  alla chiamata  di  una funzione (interna o esterna che
sia) tratta  la linea  come una stringa  che viene valutata e
passata al processore dei comandi.  Un esempio banale e':

   /* Preleva gli argomenti dalla linea di comando */
   /* ed esegue i comandi */
   parse arg comando
   comando "file1"
   comando "file2"
   comando "file3"
   exit

(notate che stavolta abbiamo aggiunto exit alla fine).
Ognuna delle tre linee dopo "parse arg comando" e' un'espres-
sione stringa, che aggiunge il nome dei file "file1", "file2"
e "file3" al nome del comando  dato come parametro al momento
del lancio.  Dopo la fine  dell'esecuzione  di  un comando la
variabile "rc" assume  il valore del codice di uscita dal co-
mando stesso (rc vale 0  se il comando ha avuto successo:  ha
lo stesso significato  del valore posto alla fine del comando
"exit", di cui abbiamo appena parlato).

 L'ambiente a cui un comando viene inviato varia da sistema a
sistema,  ma  nella maggior parte dei casi  (come in OS/2) e'
possibile  selezionare  in un insieme  di possibili  ambienti
tramite il comando  "address",  per il quale  vi rimando alla
Guida in Linea del REXX.


   14.  Signal
   ===========

 L'istruzione di  salto incondizionato in REXX viene chiamata
"Signal":  l'istruzione  "signal  etichetta"  causa  un salto
immediato al punto del programma contrassegnato dall'etichet-
ta specificata  (al solito,  seguita dal  due punti).  Se  il
salto viene invocato all'interno di una struttura di ciclo, o
condizionale (select oppure do),  non sara' possibile tornare
all'interno  della struttura, che verra'  definitivamente ab-
bandonata.  Questo  perche'  l'effettivo scopo  di signal  in
REXX  e' sopratutto quello di  puntare a routines di gestione
di errori, cosi' che  il programma possa  quantomeno abortire
limitando i danni.

 C'e',  pero', una via piu' utile  per signal, ossia l'inter-
cettamento di particolari errori.  Le condizioni  che possono
essere  intercettate  sono  "syntax"  (errore  di  sintassi),
"error" (ogni comando dell'ambiente che, eseguito, da' un co-
dice di uscita diverso da 0),  "halt" (interruzione dell'ese-
cuzione causata dall'utente), e  "novalue"  (ossia l'utilizzo
di un simbolo al quale non e' stato ancora dato un valore).

 Queste condizioni  possono  essere  intercettate  tramite la
direttiva

   signal on 

mentre il rilevamento e' disabilitato con

   signal off 

 Quando  si verifica  una di queste situazioni,  il programma
salta  all'etichetta  che ha  lo stesso nome della condizione
verificatasi.  In questo frangente,  il rilevamento di errori
viene disabilitato:  se  serve,  e'  necessario  riabilitarlo
specificamente.

 Quando si ha un "signal", la variabile  "sigl" viene riempita
con il numero di linea che ha causato il salto:  se  il segna-
le e' dovuto a un errore, la variabile "rc" contiene  il codi-
ce dell'errore.  Notare che  "sigl" e "rc"  non  devono essere
definite dall'utente,  di esse  si  prende cura  l' interprete
REXX.  Vediamo un esempio:

   /* Questo programma prosegue fino a che l'utente */
   /* non lo interrompe */
   say "Premere Ctrl-C per interrompere il programma"
   signal on halt
   do i = 1
    say i
    do 10000
    end
   end

   halt: say "Ahi!"
   say "Esecuzione abortita alla linea" sigl

 In effetti, in REXX  esistono altre opzioni per la direttiva
"signal on/off",  per cui  vi rimando  alla  Guida  in  Linea
"Informazioni  sul  REXX" (e alle prossime  pubblicazioni del
PIDO/2 sul REXX e l'Object REXX).


   15.  Tracciamento
   =================

 Il tracciamento  degli errori  e' descritto ampiamente nella
Guida in Linea "Informazioni sul REXX"  distribuita con OS/2,
per  cui  vi  ci  rimando  per una descrizione completa.  Qui
vediamo un piccolo accenno.

 Se un programma funziona male e vi servono maggiori informa-
zioni per scovare gli errori, il REXX permette di eseguire in
maniera controllata (tracing)  il programma,  per vedere piu'
da vicino cio' che accade.

 La forma di tracing piu' comune e' attivata con l'istruzione

   trace results

che comporta la stampa di ogni istruzione immediatamente pri-
ma della sua esecuzione,  e stampa il risultato di ogni even-
tuale calcolo numerico dopo l'esecuzione.

 Un altro comando utile e'

   trace ?all

che spinge l'interprete a listare  ogni istruzione e fermarsi
prima della sua esecuzione.  L'istruzione puo' essere esegui-
ta premendo il tasto  Invio:  in questo momento  e' possibile
inserire un comando REXX che verra' interpretato, dopo di che
l'interprete si fermera' nuovamente.  Questa opzione e' utile
per esaminare le variabili  definite nel programma in fase di
tracing, o altro.

 Una funzione piu' limitata e' svolta dalla direttiva

   trace ?labels

che porta  l'interprete a fermarsi solo quando incontra un'e-
tichetta:  questo e' utile per impostare dei punti  d'arresto
(breakpoints) nel programma,  oppure per fermare il programma
ad ogni chiamata  di funzione.  Una nota:  e' sufficiente in-
serire solo  l'iniziale dell'opzione di  tracciamento,  cosi'
"trace l" e "trace labels"  hanno la stessa funzione.  Per le
altre  opzioni  del comando  trace,  vi rinvio comunque  alla
Guida in Linea sull'OS/2 REXX fornita nella distribuzione.