LINGUAGGI
CORSO DI
AMIGA BASIC
Seconda puntata del corso di programmazione sull'AmigaBasic,
l'interprete Basic dell'Amiga
di Paolo Russo
La strutturazione è una componente molto importante
di ogni linguaggio e la trattazione di tale argomento, già iniziata
in precedenza, viene adesso conclusa con la presentazione di ulteriori
informazioni di carattere eminentemente pratico, più che teorico,
riguardanti sia le strutture di controllo che i subprogram. Viene poi discusso
il problema della gestione della memoria dell'Amiga dal punto di vista
del programmatore AmigaBasic; infatti, al contrario della maggior parte
delle macchine a otto bit e di molte di quelle a sedici bit, che non soffrono
del problema della condivisione delle risorse tra più programmi,
l’Amiga possiede un sistema operativo multitasking che consente sì
a più di un programma di girare contemporaneamente sulla macchina
ma obbliga i programmi stessi ad attenersi a una serie di regole per evitare
che due di loro tentino di usufruire della stessa zona di memoria. Protocolli
simili esistono anche per quanto riguarda la condivisione della tastiera,
del drive e di parecchie altre risorse, ma l'interprete AmigaBasic o il
sistema operativo solitamente si occupano autonomamente di questi dettagli
senza scomodare il programmatore. La gestione della RAM è invece
lasciata agli esseri umani, essendo spesso la quantità di memoria
disponibile un fattore critico per il funzionamento di un programma, dal
momento che, al contrario di altre risorse come la tastiera e il drive,
presenta spesso la seccante tendenza a esaurirsi.
Le strutture di controllo
L'AmigaBasic ha in comune con il Basic standard il classico
e universalmente conosciuto ciclo FOR-NEXT, sul quale non è opportuno
soffermarsi a causa della sua eccessiva fama. Il ciclo WHILE-WEND, molto
di moda recentemente e spesso impiegato anche a sproposito, è una
generalizzazione del ciclo FOR-NEXT dal quale differisce nella condizione
di uscita dal loop; mentre il ciclo FOR-NEXT si interrompe solo quando
il valore di una certa variabile supera un certo limite, il ciclo WHILE-WEND
prosegue fintantoché la condizione (formalmente simile a quella
che segue un IF) specificata dopo la parola WHILE si mantiene vera. Ciò
significa che la linea WHILE 1 = 1: PRINT "X": WEND scriverà un
numero infinito di X mentre la linea WHILE 1 =2: PRINT "X": WEND non ne
stamperà nemmeno una. La coppia WHILE-WEND consente anche di emulare
quella FOR-NEXT; le seguenti due linee sono infatti equivalenti:
FOR I = 1 TO 10: NEXT I = 1: WHILE I<=10:
I=I+1: WEND
S'intende che il contrario non è possibile: un generico
WHILE-WEND non è sostituibile con un FOR-NEXT. Qualcuno si chiederà
che scopo abbia tentare di usare una struttura al posto di un'altra. Naturalmente,
come risulta evidente dall'esempio riportato poco sopra, il rimpiazzamento
di un ciclo FOR-NEXT con uno WHILE-WEND non è mai conveniente in
termini sia di praticità che di leggibilità, ma un rispettabile
numero di persone in vena di semplificazioni ritengono che due strutture
al posto di una siano ridondanti ed evitano sistematicamente il FOR-NEXT
nei loro programmi, tentando nel contempo con tutte le loro energie di
convincere chiunque altro che ciò sia indice di buona programmazione.
Il lettore è consigliato di non lasciarsi trasportare ciecamente
dai flussi e riflussi della moda, esistenti nella programmazione come in
molte altre attività umane, ed è altresì vivamente
incoraggiato alla creazione di uno stile personale, soprattutto se non
desidera che l'eccitante attività programmatoria si trasformi rapidamente
in una grigia routine.
Al classico IF-THEN(-ELSE) è stato affiancato il
più flessibile IF-THEN (-ELSEIFTHEN) ... (-ELSEIF-THEN) (-ELSE)
-END IF che consente di eseguire in maniera condizionata interi blocchi
di linee alla volta, che possono a loro volta contenere altri IF. L'interprete
identifica la variante di IF (classica o moderna) che il programmatore
intende usare basandosi sulla presenza o meno di istruzioni in linea subito
dopo il THEN; se infatti tale parola chiave è seguita da una o più
istruzioni l’interprete decide che si tratta di un IF classico, a linea
singola; se al contrario dopo il THEN non si scrive niente deve evidentemente
trattarsi della variante evoluta multilinea e l’AmigaBasic parte dal presupposto
che le linee successive debbano essere eseguite solo se la condizione specificata
risulta vera. Naturalmente è necessaria l'esistenza di una parola
chiave per contrassegnare la fine del blocco di linee da eseguire condizionatamente;
tale parola, che deve occupare un'intera linea, è END IF. La parola
ELSE, che esige anch'essa di non essere preceduta o seguita da alcunché,
marca invece l'inizio delle linee che devono essere eseguite solo se la
condizione è falsa. Al contrario delle parole IF, THEN ed END IF,
la cui presenza è obbligatoria, l'uso di un blocco introdotto da
ELSE è opzionale, e altrettanto può dirsi per ELSEIF, utile
in caso di scelte multiple. La parola ELSEIF, che richiede una condizione
e un THEN dopo di sè, introduce un blocco di istruzioni che verranno
eseguite solo se l'ultima condizione specificata è vera e tutte
le precedenti sono false. Eccone un esempio:
IF A=1 THEN
PRINT "UNO"
ELSEIF A= 2 THEN
PRINT "DUE"
ELSEIF A=3 THEN
PRINT "TRE"
ELSE
PRINT "NON È UNO, DUE
O TRE"
END IF
L'indentazione, da qualcuno chiamata dentellatura, ossia
l'abitudine di lasciare all'inizio di ogni riga uno spazio variabile in
modo da evidenziare i blocchi, non è obbligatoria ma molti la consigliano
per motivi stilistici, trovandola evidentemente molto estetica o particolarmente
giovevole alla chiarezza del listato. Purtroppo quando si esce dall'ambito
dei programmi banali accade che all'interno di ogni singola procedura il
numero di IF, FOR e WHILE concatenati sia solitamente abbastanza elevato
da far sì che alcune parti del listato dimostrino una spiccata tendenza
a uscire dall'area visibile dello schermo a causa della spaziatura iniziale.
L'AmigaBasic possiede anche il GOTO, ma tale direttiva
è stata implementata nella presente release dell'interprete in una
forma non troppo flessibile; è infatti proibito saltare dentro e
fuori da una struttura tramite GOTO, cosa invece consentita da qualche
altro computer. Non è quindi possibile saltar fuori neppure da un
banalissimo ciclo FOR-NEXT prima del suo esaurimento: se si desidera farlo
occorre assegnare il valore limite alla variabile indice e saltare al NEXT,
cosa questa fattibile ma indubbiamente alquanto scomoda.
I dati dei subprogram
È stato detto nella scorsa puntata che le variabili
di un subprogram esistono solo al suo interno, eccezion fatta per quelle
dichiarate come SHARED che sono condivise con il programma principale.
La parola STATIC che compare nella prima linea di ogni subprogram ha il
seguente significato: tutte le variabili locali al subprogram sono statiche,
ossia il loro contenuto non viene perso tra una chiamata e l'altra del
subprogram stesso.
In realtà la presenza della parola STATIC nella
definizione di un subprogram è obbligatoria e ciò significa
che L’AmigaBasic non supporta le variabili locali non statiche. Quest'ultimo
tipo di variabile è utile per limitare l'occupazione di memoria
e per implementare algoritmi ricorsivi, cioè per consentire che
un subprogram possa richiamarsi da solo; ciò in AmigaBasic non è
quindi possibile, a meno di ideare un meccanismo che salvi le variabili
prima di ogni chiamata. Esiste tuttavia un ulteriore inconveniente legato
alla staticità delle variabili: com'è possibile utilizzare
degli array locali in un subprogram? Se dimensioniamo l’array in questione
nel programma principale perdiamo il vantaggio della località, oltre
a essere costretti a dichiararlo SHARED; se il DIM compare invece all'inizio
del subprogram i guai iniziano quando il suddetto viene richiamato per
la seconda volta e l’interprete si trova nella seccante situazione di dover
dimensionare un array già esistente. Ciò è illegale
in AmigaBasic e provoca il blocco del programma con segnalazione di errore.
Come risolvere questo problema?
Sfogliando il manuale qualcuno avrà notato l'esistenza
di un utile comando chiamato ERASE che cancella un array eliminandolo dalla
memoria come se non fosse mai esistito. Si può quindi pensare di
inserire questo comando alla fine di un subprogram in modo che alla successiva
chiamata l’array, assente, possa essere ridimensionato. Per qualche strano
motivo questo trucco non sembra funzionare molto bene; probabilmente il
comando ERASE è stato creato appositamente per gli array globali
e non funziona sempre su quelli locali. Comunque, anche se funzionasse
regolarmente sarebbe un metodo troppo inefficiente, in quanto la cancellazione
e successivo dimensionamento sono operazioni alquanto lente, ed eseguirle
ogni volta che il subprogram viene richiamato non sembra essere la soluzione
migliore. Un trucco più valido consiste nell'inserzione all'inizio
del subprogram di una linea come la seguente:
IF FLAG=0 THEN FLAG=1:DIM AR(100)
La prima volta che il subprogram viene richiamato le sue
variabili non esistono ancora; FLAG vale quindi zero, come tutte le variabili
non ancora inizializzate. Alla suddetta variabile viene quindi assegnato
il valore uno e l’array viene dimensionato. A ogni successiva chiamata
del subprogram la condizione espressa dopo l’IF risulterà falsa
e non sarà quindi effettuato alcun tentativo di dimensionare nuovamente
l’array. Se poi all'interno del programma principale viene eseguito un
CLEAR tutte le variabili spariscono, array compreso, ma anche FLAG torna
a zero e il trucco continua a funzionare.
Lo statement CLEAR
L'istruzione CLEAR in ogni versione di Basic provoca la
cancellazione di tutte le variabili, ma in alcuni dialetti essa assume
un significato più ampio. Nel caso in questione la suddetta istruzione
consente di cambiare il modo in cui la RAM viene ripartita tra le sue tre
aree fondamentali: lo heap, lo stack e il segmento del Basic. Lo heap (cumulo)
è una sorta di magazzino pubblico di memoria inutilizzata al quale
tutti i programmi attingono secondo le loro necessità, dopo aver
rivolto una regolare richiesta al sistema operativo (a Exec, per la precisione);
ormai nemmeno i programmi per computer riescono a salvarsi dalla burocrazia.
Se non altro Exec non insabbia mai le pratiche e si preoccupa di sbrigarle
nel giro di qualche millisecondo, respingendole, in un tempo altrettanto
breve, solo se la memoria è ormai esaurita o non possiede le caratteristiche
richieste: un traguardo di efficienza del tutto fantascientifico per la
burocrazia italiana. Purtroppo certe richieste che devono essere rivolte
alle sezioni competenti del sistema operativo per ottenere determinati
risultati prevedono la compilazione, da parte del programma richiedente,
di un vero e proprio modulo di complessità tale da far invidia al
tristemente noto 740; ma di questo si tratterà meglio in futuro.
Il numero che compare nella barra superiore dello Workbench indica appunto
la quantità di memoria ancora disponibile nello heap. Ogni programma
che gira sull'Amiga possiede uno stack (pila, catasta) il cui scopo è
la memorizzazione temporanea di indirizzi e variabili locali e la cui dimensione
varia solitamente tra uno e quattro kappa di RAM; l’interprete AmigaBasic
è esso stesso un programma e possiede di conseguenza uno stack.
Il segmento Basic è quella zona di memoria che l’interprete ha sottratto
allo heap per destinarla alla memorizzazione del programma Basic e delle
sue variabili. La sintassi completa del comando è CLEAR (,segmento
(,stack)), dove le parentesi indicano, come già in precedenza, un
parametro che può essere omesso.
L'esperienza insegna che praticamente nessuno trova utile
alterare la dimensione dello stack, mentre molti programmatori desiderano
aumentare l'esiguo spazio a disposizione del Basic, che normalmente ammonta
a circa 25K. Bisogna innanzitutto premettere che non è buona norma
assegnare troppa RAM al segmento lasciando al verde lo heap, non solo perché
risulterebbe impossibile il funzionamento in multitasking di un qualunque
altro programma assieme all'interprete, ma anche perché determinate
azioni intraprese da un programma Basic assorbono memoria dallo heap; il
comando SCREEN per esempio definisce un nuovo schermo e preleva dallo heap
la memoria necessaria alla relativa pagina grafica. Quando inoltre vengono
eseguite certe operazioni, come lo spostamento di una finestra con il mouse,
il computer necessita temporaneamente di una certa quantità di RAM
per portare a termine il compito richiesto e se non la trova possono accadere
stranezze di vario genere.
Il modo più semplice per riservare al segmento
una certa quantità di RAM consiste nell'eseguire uno statement come
CLEAR , 100000 (se si desiderano circa 100K; notate comunque la curiosa
quanto obbligatoria presenza della virgola). Questo metodo presenta qualche
inconveniente; il primo consiste nell'impossibilità di allocare
ripetutamente più di metà (nel migliore dei casi) della memoria
disponibile. Supponiamo per esempio che su di un Amiga inespanso si desideri
portare il segmento a 120K; basta digitare in modo diretto CLEAR ,120000
e si è poi liberi di creare programmi con array di dimensioni veramente
notevoli. Quando poi il programma viene salvato e in seguito ricaricato,
magari dopo un reset, si scoprirà che non funziona più perché
l'effetto del comando CLEAR è svanito, dal momento che anche l’interprete
è stato ricaricato da disco, e non c'è più posto per
le variabili. Evidentemente è opportuno inserire il CLEAR in testa
al programma stesso, in modo che venga eseguito a ogni caricamento; così
facendo, tuttavia, tale comando verrà eseguito anche a ogni run
e ciò crea un altro problema, connesso al modo in cui l’interprete
porta a termine un CLEAR. L'AmigaBasic richiederà i 120K prima di
restituire i vecchi 25K allo heap, altrimenti il programma Basic verrebbe
perso per strada. Per un attimo quindi l’interprete possiederà 145K.
Non c'è niente di male in ciò, ma al successivo run l’AmigaBasic
tenterà di ottenere altri 120K prima di mollare i vecchi 120, con
esito infelice non essendo disponibili 240K di memoria. Si può allora
ricorrere a un doppio CLEAR; supponendo che il programma in sè (variabili
escluse) non richieda più di 10K, si può eseguire un CLEAR
,10000:CLEAR ,120000. In tal modo la massima occupazione di RAM in ogni
dato istante non supera 130K. Esiste poi un ulteriore inconveniente: se
l'abbondanza di memoria è necessaria non solo per le variabili,
ma per il programma stesso, risulta impossibile caricarlo in memoria prima
di aver eseguito il CLEAR, che a sua volta entrerebbe in azione solo dopo
il caricamento: il classico serpente che si morde la coda. L'unica via
di uscita consiste nella creazione di un programma di lancio come il seguente:
CLEAR ,5000
CLEAR ,120000
LOAD "nomeprogramma" ,R
Questo microscopico programmino, se lanciato in esecuzione,
riserva al Basic la quantità desiderata di RAM e subito dopo carica
il programma vero e proprio, attivandolo al termine del caricamento (l’opzione
R significa Run). Naturalmente non è piu necessario includere in
quest'ultimo programma istruzioni per allocare memoria, essendo questo
compito già svolto dal programma di lancio.
La funzione FRE(x) consente di conoscere in ogni momento
il modo in cui la RAM è ripartita; il classico FRE(0) fornisce la
quantità di memoria ancora libera all'interno del segmento Basic,
FRE(-1) la dimensione dello heap, FRE(-2) la quantità di RAM nello
stack che non è mai stata usata. Ogni volta che la funzione FRE
viene invocata l’interprete esegue il cosiddetto garbage collecting (alla
lettera, raccolta di immondizia); tale operazione riordina le stringhe
presenti in memoria in modo da riutilizzare gli interstizi che si formano
di solito durante la loro manipolazione.
Una piccola curiosità: anche il comando CLEAR del
vecchio ZX Spectrum ha la capacità di variare la porzione di RAM
riservata al Basic! Chissà quanti altri computer possiedono questa
caratteristica così assolutamente non standard associata al comando
CLEAR?
|