Pagina principale faq Amiga Life a Pianeta Amiga La redazione
Galleria Indice generale
Enigma Amiga Life

AmigaLife 121 ?

AmigaDev
ARexx
di Alfonso Ranieri

Parte nona: Alberi di macro e condivisione di dati

Un programmatore ARexx smaliziato viene prima o poi a contatto con un grave deficit di ARexx: la mancanza di qualsiasi struttura di controllo di alberi di macro e l'impossibilità di condividere i dati tra macro. Dedichiamo questa intera puntata ad illustrare come risolvere entrambi i problemi.

Progetti non banali sono caratterizzati dalla seguente struttura: una macro detta padre, lancia una o più macro dette figlie. Il padre deve potere controllare l'esecuzione delle macro figlie, sincronizzandole o, più frequentemente, interrompendole quando desidera. Il padre deve potere passare alle macro figlie un insieme di dati su cui operare o, addirittura, esiste un insieme di dati che deve essere a disposizione e modificabile da qualsiasi macro figlia. Ebbene, l'interprete ARexx non fornisce alcun metodo per realizzare tutto ciò. L'unico modo di lanciare macro è di chiamarle attraverso il call od eseguirle come camando shell con shell command 'rx nome_macro'. L'unico modo di condividere dati è la lista delle clips; purtroppo non esiste alcun meccanismo per cancellare le clips create da una macro alla sua uscita, il che significa che, dopo un po', la lista delle clips è talmente sporca e la memoria allocata talmente grande da non permetterne un uso pulito. Dopo ampia riflessione, sono giunto alla conclusione che nuovi metodi e concetti fossero necessari: il risultato sono MacroNotify, NamedSpace e variabili locali.

MacroNotify

Un MacroNotify è un handler trasparente al programmatore, che identifica uno spazio di macro. Con spazio di macro si intende un club, che ha un proprietario e regole precise. L'esistenza di un MacroNotify è strettamente legata all'esecuzione della macro in cui è stato creato: all'uscita della macro il MacroNotify cessa di esistere.
Le funzioni che operano su un MacroNotify sono elencate in Tabella 1. Un MacroNotify viene creato da una macro (che in quel momento si identifica come macro padre) con


msn = MacroNotifyCreate(name)

name deve essere un nome unico: per ottenerlo esistono molti metodi, ad esempio name=Pragma("ID").

Il risultato della funzione, mns, è un segnale Exec da attendere per ricevere la notifica "un evento è accaduto sul MacroNotify". In una macro, possono essere creati un qualsiasi numero di MacroNotify, anche se nella maggior parte dei casi, se ne creerà soltanto uno. Quando una macro figlia viene lanciata dalla macro padre, deve unirsi esplicitamente al club con


call MacroNotifyJoin(name). 

Poiché tutto ciò abbia senso, occorre che la macro figlia sia stata lanciata dal padre in modalità asincrona. Infatti se lanciassimo macro in modalità sincrona, non potremmo avere alcun controllo su di esse, per la semplice ragione che saremmo bloccati ad attendere la loro esecuzione. Nella pratica ciò si traduce nell'impossibilità di usare i comandi call e shell command per lanciare le macro figlie e nell'esigenza di trovare un meccanismo più raffinato. Fortunatamente, RxSocket mette a disposizione la funzione RxsCall() che permette, tra le altre cose, di lanciare macro in modalità asincrona.

Finalmente unitasi al club, una macro figlia è sotto controllo da parte della macro padre, perché:

  1. Quando la macro padre termina, qualsiasi MacroNotify da essa creato viene distrutto;
  2. Quando un MacroNotify viene distrutto, tutti i suoi membri, ovvero tutte le macro che hanno usato al loro interno la funzione MacroNotifyJoin(name), vengono segnalate con un ctrl_d;
  3. In qualsiasi momento, la macro padre può mandare un ctrl_d a tutte le macro figlie attraverso la funzione MacroNotifySync(name).
Ciò vuol dire che:
  1. Quando il padre termina, le figlie terminano;
  2. In qualsiasi momento il padre può terminare tutte le sue figlie.
Tiriamo un bel respiro. Fatto? Suvvia, a parole suona complicato, ma nella pratica è tutto molto semplice. C'è una macro che si dichiara come proprietaria di un club. Un socio del club è una macro che viene lanciata dal proprietario e sottostà alle sue regole. Le linee ARexx per creare il tutto sono pochissime:

padre:

name = Pragma("ID")

msn = MacroNotifyCreate(name)

…

call rxscall(macro_figlia name)

…



figlia:

…

call MacroNotifyJoin(name)

…

Ad un MacroNotify sono associati gli eventi:

  • STARTED: una macro figlia si è unita al club
  • ENDED: una macro figlia è uscita dal club
  • INFO: una macro figlia ci invia un messaggio
  • ATTEMPT: qualcuno ha tentato di creare un MacroNotify di nome name
Per ricevere eventi, si attende il segnale mns ritornato da MacroNotifyCreate(nome), quindi si prendono, uno per uno, tutti gli eventi con MacroNotifyNextEvent(name). Ciò si realizza con:

…

mask = or(mns,…)

…

recv=wait(mask)

if and(recv,mns)>0 then call HandleMAcroNotifyEvents

…

MacroNotifyEvents:

    do forever

        ev=MacroNotifyNexEvent(name)

        if ev="" then return

        parse var ev ev id more

        select

            when ev="STARTED" then do

                /* macro id started */

            end

            when ev="ENDED" then do

                /* macro id ended */

            end

            when ev="INFO" then do

                /* macro id sends us infos, more is an info string */

            end

            when ev="ATTEMPT" then do

                /* macro id tried to create the MacroNotify */

            end

	end

Il formato dei vari eventi è:
  • STARTED (id è lo id della macro, alla pragma("ID"))
  • ENDED
  • INFO
  • ATTEMPT
Ciò ha senso sia nel caso in cui il padre lancia una ed una solo una macro figlia, sia nel caso in cui il padre lancia una o più macro figlie. Non c'è limitazione al numero di macro figlie da poter lanciare e c'è piena libertà all'uso dei segnali. Il ctrl_d segnalato dal padre può avere sempre significato muori, ed allora andrebbe intrappolato nella macro figlia con:

…

Signal on break_d

…

break_d:

	exit

Oppure può avere la prima volta il significato di ok, vai! e la seconda di muori e così via.
Faccio notare che anche se MacroNotifySync() permette soltanto di "brekkare" tutte le macro figlie, conservando gli id tornati dall'evento STARTED, potrete in qualsiasi momento "brekkare" selettivamente qualsiasi macro con call Signal(x2d(id),2**12). Si rammenti che una macro figlia fa parte del club solo nel momento in cui il padre riceve l'evento STARTED, e non al suo lancio. I MacroNotify permettono di creare alberi di macro di qualsiasi natura e di avere totalmente sotto controllo la dinamica con cui questi si sviluppano. L'unica pecca dei MacroNotify è la seguente: uno schema molto diffuso è quello in cui la macro padre lancia la figlia e si mette in attesa di ricevere sul MacroNotify l'evento STARTED; se la macro figlia viene interrotta dall'esterno (ad esempio perché riceve un halt), prima di aver usato MacroNotifyJoin(), la macro padre è destinata ad attendere per sempre. Il rimedio a ciò è il seguente: la macro figlia deve intrappolare qualsiasi interruzione (break_c halt e così via) e chiamare MacroNotifyJoin() al ricevimento di un'interruzione, se non ancora fatto.

Condivisione dati

I casi che si presentano sono svariati, ma riconducibili ai due seguenti:

  1. Una macro padre crea un insieme di dati da passare ad una o più macro figlie. Queste a loro volta usano i dati come vogliono ma qualsiasi modifica ai dati è soltanto locale;
  2. Una macro padre crea un insieme di dati da passare ad una o più macro figlie, le quali modificano i dati localmente e globalmente.
I due casi si risolvono con modalità differenti:
  1. Conviene passare l'insieme dei dati in variabili locale;
  2. Bisogna creare un NamedSpace.
Variabili locali

Ogni processo Amiga conserva una lista di coppie <nome, valore> dette variabili locali. Tali variabili possono essere passate da un processo ad un altro al momento della creazione. Esattamente queste variabili sono, ad esempio, usate dai server HTTP per passare importanti dati alle macro CGI.
Le funzioni per gestire le variabili locali sono riportate in Tabella 2. Di queste, due sono vitali ai nostri scopi: StemToVar() e VarToStem(). Ciò che dobbiamo fare è conservare i dati da condividere in uno stem, ad esempio:


var.Host

var.HostPort

var.Path

var.User

var.Pass

var.Referer

var.Resume

var.IfModifiedSince

Questi sono i tipici dati che una macro per scaricare file da un server HTTP usa. Poiché in qualsiasi momento, nella macro padre, questi dati possono cambiare, dobbiamo sincronizziamo le nostre variabili locali con lo stem var. esattamente prima di chiamare la macro figlia:

call StemToVar("var")

call RxsCall("child_macro")

Appena parte, la macro figlia, copia le sue variabili locali in uno stem con

call VarToStem("var.#?")

e il gioco è fatto, ovvero la macro figlia riceve con un sol colpo (una sola chiamata a VarToStem()) un grande insieme di dati. Pensate che questo "giochino" risolve l'annoso problema di condividire tra macro un intero stem ARexx. Il problema è, al solito, che né call né rx copiano le variabili locali, quindi dovrete sempre usare RxsCall() oppure rxs per lanciare le macro. E' consigliabile creare contemporaneamente un MacroNotify e passare il suo nome in una variabile locale. Anche se una macro figlia ha bisogno di un solo dato, ad esempio var.Host, conviene sempre che copi tutti i campi di var. con VarToStem(), piuttosto che leggere solo var.Host con FindVar("var.host"), poiché il sovraccarico di VarToStem() è minimo.

Il modo più veloce di testare il meccanismo di condivisione dati via variabili locali è la linea:


rx "do i=0 to 99; var.i=i; end; call StemToVar('var');

call RxsCall('call VarToStem(var.#?);do i=0 to 99;say var.i;end'),,'string')"

NamedSpace

Un NamedSpace è un handler trasparente all'utente che rappresenta uno spazio di associazioni <nome, variabile>.

Un NamedSpace viene creato con


res=NamedSpaceCreate(name,opt) 

e la sua esistenza è legata alla vita della macro in cui è stato creato. Come nel caso di un MacroNotify, il nome deve essere unico. Le opzioni sono per ora solo PRIVATE: se specificata il NamedSpace può essere modificato soltanto dal creatore. In qualsiasi momento una macro può leggere una o più variabili e, se il NamedSpace non è privato, modificarne il valore.
Le funzioni che operano su un NamedSpace sono riportate in Tabella 3. Due sono notevoli: NamedSpaceExport() e NamedSpaceImport(), che permettono di sincronizzare i nostri dati con un NamedSpace in entrambi i sensi. Si noti che un NamedSpace è molto potente, ma andrebbe usato soltanto quando il meccanismo di condivisioni dati attraverso le variabili locali non è sufficiente (ciò succede quando molte macro in competizione devono poter liberamente accedere e modificare un grande insieme di dati).

Varie ed eventuali

Correlato agli argomenti trattati è il concetto di ambiente. Quando una macro parte, ha una nutrita serie di dati a sua disposizione, che sarebbe interessantissimo poter ricavare. Ciò è possibile attraverso la funzione MacroEnv() di rmh.library. MacroEnv() deve essere lanciata all'inizio della macro, e il suo scopo è di recuperare e impostare molti parametri legati all'ambiente in cui la macro gira. La sua sintassi è:


call MacroEnv(stem,opt)

,[opt]

stem - stem ARexx dove la funzione scrive alcuni dati

opt - opzioni

MacroEnv() scrive in stem:
  • FullName - percorso completo della macro; ad esempio se la macro si trova in Work:ARexx e si chiama test.rexx, FullName sarà Work:ARexx/test.rexx
  • Path - percorso completo alla directory dove si trova la macro; ad esempio se la macro si trova in Work:ARexx, Path sarà Work:ARexx
  • Name - nome della macro; ad esempio se la macro si chiama test.amirx, Name sarà test.amirx
  • NoExtName - nome della macro senza estensione; ad esempio se la macro si chiama test.rexx, NoExtName sarà test
  • Prg - sinonimo di NoExtName
  • Language - il linguaggio di default settato nelle preferenze del Workbench (ad esempio italiano o english)
  • Parent- il nome completo della macro che ci ha lanciato, una stringa vuota altrimenti
  • WB? - 1 se la macro è stata lanciata da Workbench, 0 altrimenti (ha senso se e solo se si usa rxs come tool; se si usa rx è sempre 0)
  • Socket? - 1 se c'è uno stack TCP/IP in funzione, 0 altrimenti
  • Sock? - 1 se qualcuno ci ha passato un socket (ottenibile con LastSocket()), 0 altrimenti
  • Sock - se Sock? è 1, il socketfd
Inoltre la funzione accetta le opzioni:
  • PROGDIR - setta la ProgDir alla directory in cui si trova la macro, permettendo da ARexx l'uso di PROGDIR: come path assoluta.
  • CD - Setta CurrentDir alla directory dove si trova la macro
  • STDERR - crea un file di nome STDERR; esattamente come se nella macro si eseguisse
    If ~Open("SDTERR","CONSOLE:","W") then STDERR="STDOUT"
Salutoni.

Tabella 1: Funzioni che operano su MacroNotify
FUNZIONE ED ARGOMENTI Note Chi la usa
call MacroNotifyFree(name) padre
call MacroNotifyInsertEvent(name,type,more)
,,[more]
type è: INFO figlia
call MacroNotifyJoin(nane) figlia
call MacroNotifySync(name) padre
Ev = MacroNotifyNextEvent(name) Se ev è "" gli eventi sono finiti figlia
sig = MacroNotifyCreate(name) Se sig è 0, esiste già un MacroNotify di nome name padre

Tabella 2: Funzioni che operano su variabili locali
FUNZIONE ED ARGOMENTI Note Chi la usa
call DeleteVar(name,opt) ,[opt] opt può assumere altri valori padre e figlia
call SetVar(name,value,"LOCALE") ,,[opt] opt può assumere altri valori padre e figlia
call StemToVar(stem) Sincronizza le variabili locali con i dati ARexx padre
call VarToStem(pattern) Legge le variabili locali. pattern è una pattern AmigaDOS figlia
value = FindVar(name) padre e figlia
value = GetVar(name,"LOCALE") ,[opt] opt può assumere altri valori padre e figlia

Tabella 3: Funzioni che operano su NamedSpace
FUNZIONE ED ARGOMENTI Note Chi la usa
res = NamedSpaceCreate(name,opt) ,[opt] opt è: PRIVATE creatore
res = NamedSpaceExport(name,pattern,opt) ,,[opt] pattern è una pattern AmigaDOS
opt è uno tra:
LOCAL (esporta solo le var locali)
GLOBAL (esporta solo le var globali)
BOTH (esporta entrambe)
creatore, se il NamedSpace è privato
chiunque, altrimenti
res = NamedSpaceFree(name) creatore
res = NamedSpaceImport(name,pattern) , pattern è una pattern AmigaDOS chiunque
value = NamedSpaceGetVar(name,var) , chiunque

Torna al sommario

Copyright (C) 1999-2002, la redazione di AmigaLife.
Il logo e le copertine della rivista sono tratti dal sito Pluricom e sono Copyright (C) 1992-2001 Pluricom S.r.l.