[inizio] [indice generale] [precedente] [successivo] [indice analitico] [contributi]

158. M4: introduzione

M4 è un elaboratore di macro, nel senso che la sua elaborazione consiste nell'espandere le macro che incontra nell'input. In altri termini, si può dire che copia l'input nell'output, espandendo man mano le macro che incontra.

La logica di funzionamento di M4 è completamente diversa dai linguaggi di programmazione comuni, e in più, le sue potenzialità richiedono molta attenzione da parte del programmatore. Detto in maniera diversa, si tratta di un linguaggio macro molto potente, ma altrettanto difficile da gestire.

L'obbiettivo di questo capitolo è solo quello di mostrarne i principi di funzionamento, per permettere la comprensione, parziale, del lavoro di altri. Per citare un caso significativo, la configurazione di Sendmail (capitolo 173) viene gestita attualmente attraverso una serie di macro di M4, con le quali si genera il file /etc/sendmail.cf.

158.1 Principio di funzionamento

M4 è costituito in pratica dall'eseguibile m4, la cui sintassi per l'avvio può essere semplificata nel modo rappresentato dallo schema seguente:

m4 [<opzioni>] [<file-da-elaborare>]

Il file da elaborare può essere fornito come argomento, oppure attraverso lo standard input; il risultato viene emesso attraverso lo standard output, e gli errori eventuali vengono segnalati attraverso lo standard error.

Per iniziare a comprendere il funzionamento di M4, si osservi il testo seguente:

Ciao, come stai ? dnl Che domanda!

# Questo è un commento ? dnl Sì.

Oggi è una giornata stupenda.

Supponendo di avere scritto questo in un file, precisamente prova.m4, lo si può rielaborare con M4 in uno dei due modi seguenti (sono equivalenti).

m4 prova.m4

m4 < prova.m4

In entrambi i casi, quello che si ottiene attraverso lo standard output è il testo seguente:

Ciao, come stai ?
# Questo è un commento ? dnl Sì.

Oggi è una giornata stupenda.

Tutto ciò che M4 non riesce a interpretare come una macro rimane inalterato. Anche se il simbolo di commento è previsto, e corrisponde a # (a meno che siano state usate opzioni o istruzioni particolari), i commenti non vengono eliminati: servono solo a evitare che il testo sia interpretato da M4.

L'unico commento che funzioni in modo simile a quello dei linguaggi di programmazione comuni è la macro dnl (è stata usata nella prima riga), con la quale viene eliminato il testo a partire da quel punto fino al codice di interruzione di riga successivo. Dal momento che viene eliminato anche il codice di interruzione di riga (ovvero il codice newline), si può vedere dall'esempio che la seconda riga, quella vuota, viene inghiottita; invece, il «dnl» contenuto nella riga di commento non è stato considerato da M4.

158.2 Convenzioni generali

L'analisi di M4 sull'input viene condotta separando tutto in «elementi» (token), i quali possono essere classificati fondamentalmente in tre tipi: nomi, stringhe tra virgolette, e caratteri singoli che non hanno significati particolari.

I nomi sono sequenze di lettere (compreso il simbolo di sottolineatura) e numeri, dove il primo carattere è una lettera. Una volta che M4 ha delimitato un nome, se questo viene riconosciuto come una macro, questa viene espansa (sostituendola al nome).

Le stringhe delimitate da virgolette richiedono l'uso di un apice di apertura e di uno di chiusura (` e '). Il risultato dell'elaborazione di una stringa di questo tipo è ciò che si ottiene eliminando il livello più esterno di apici. Per esempio:

`'

corrisponde alla stringa vuota;

`la mia stringa'

corrisponde al testo la mia stringa;

``tra virgolette''

corrisponde a `tra virgolette'.

È importante tenere presente che anche i simboli usati per delimitare le stringhe possono essere modificati attraverso istruzioni di M4.

Tutto ciò che non rientra nella classificazione di nomi e stringhe delimitate tra virgolette, sono elementi sui quali non si applica alcuna trasformazione.

I commenti per M4 rappresentano solo una parte di testo che non deve essere analizzato alla ricerca di macro. Quello che si ottiene è la riproduzione di tale testo senza alcuna modifica. In linea di principio, i commenti sono delimitati dal simbolo # fino alla fine della riga, cioè fino al codice di interruzione di riga. M4 permette di modificare i simboli usati per delimitare i commenti, o di annullarli del tutto.

È il caso di soffermarsi un momento su questo concetto. Quando si utilizza M4, spesso lo si fa per generare un file di configurazione o un programma scritto in un altro linguaggio. Questi tipi di file potrebbero utilizzare dei commenti, e può essere conveniente generare nel risultato dei commenti il cui contenuto cambia in funzione di situazioni determinate. Si immagini di voler realizzare uno script di shell, in cui notoriamente il commento si introduce con lo stesso simbolo #, e di volere comporre il commento in base a delle macro; diventa necessario fare in modo che M4 non consideri il simbolo # come l'inizio di un commento.

L'unico tipo di dati che M4 può gestire sono le stringhe alfanumeriche, e questo indipendentemente dal fatto che si usino gli apici per delimitarle. Naturalmente, una stringa contenente un numero può avere un significato particolare che dipende dal contesto.

158.2.1 Macro

M4 è un linguaggio di programmazione il cui scopo principale è quello di gestire opportunamente la sostituzione di testo in base a delle macro. Tuttavia, alcune macro potrebbero servire a ottenere qualche funzione in più rispetto alla semplice sostituzione di testo. In generale, per uniformità, si parla sempre di macro anche quando il termine potrebbe essere improprio; per la precisione si distingue tra macro interne (builtin), che pur non essendo dichiarate fanno parte di M4, e macro normali, dichiarate esplicitamente.

Una macro può essere «invocata» attraverso due modi possibili:

<nome>

<nome>(<parametro-1>, <parametro-2>, ... <parametro-N>)

Nel primo caso si tratta di una macro senza parametri (ovvero senza argomenti); nel secondo si tratta di una macro con l'indicazione di parametri. È importante osservare che, quando si utilizzano i parametri, la parentesi aperta iniziale deve seguire immediatamente il nome della macro (senza spazi aggiuntivi); inoltre, se una macro non ha parametri, non si possono utilizzare le parentesi aperta e chiusa senza l'indicazione di parametri, perché questo sarebbe equivalente a fornire la stringa nulla come primo parametro.

La cosa più importante da apprendere è il modo in cui viene trattato il contenuto che appare tra parentesi, che serve a descrivere i parametri di una macro; infatti, prima di espandere la macro, viene espanso il contenuto che appare tra parentesi. Una volta espansa anche la macro con i parametri ottenuti, viene eseguita un'altra analisi del risultato, con il quale si possono eseguire altre espansioni di macro, oppure si può ottenere la semplice eliminazione delle coppie di apici dalle stringhe delimitate. Le operazioni svolte da M4 per espandere una macro sono elencate dettagliatamente di seguito.

  1. Vengono suddivisi gli elementi contenuti tra parentesi ignorando gli spazi iniziali, e includendo quelli finali. Per esempio,

    miamacro(a mio, d)
    

    è equivalente a

    miamacro(a mio,d)
    

  2. Vengono espanse le macro contenute eventualmente tra i parametri. Continuando l'esempio precedente, supponendo che mio sia una macro che si espande nella stringa

    , b, c
    

    a causa della sostituzione di mio, si ottiene in pratica quanto segue:

    miamacro(a , b, c,d)
    

    Infine, tutto si riduce a

    miamacro(a ,b,c,d)
    

    dove i parametri sono esattamente una a seguita da uno spazio, e poi le altre lettere b, c e d.

  3. Una volta risolti i parametri, viene espansa la macro.

  4. Il risultato dell'espansione viene rianalizzato alla ricerca di stringhe delimitate a cui togliere gli apici esterni e di altre macro da espandere.

In un certo senso si potrebbe dire che le stringhe, delimitate come previsto da M4, siano delle macro che restituiscono il contenuto in modo letterale, perdendo quindi la coppia di apici più esterni. Questo significa che ciò che appare all'interno di una tale stringa non può essere interpretato come il nome di una macro, e nemmeno i commenti vengono presi in considerazione come tali. La differenza fondamentale rispetto alle macro normali sta nel fatto che l'espansione avviene una volta sola.

Quando si usano le stringhe delimitate tra le opzioni di una macro normale, è necessario tenere presente che queste vengono trattate la prima volta nel modo appena descritto, allo scopo di fornire i parametri effettivi alla macro, ma dopo l'espansione della macro avviene un'ulteriore elaborazione del risultato.

In generale sarebbe conveniente e opportuno indicare i parametri di una macro sempre utilizzando le stringhe delimitate, a meno di voler indicare esplicitamente altre macro. Ciò facilita la lettura umana di un linguaggio di programmazione già troppo complicato. In ogni caso, non si deve dimenticare il ruolo degli spazi finali che vengono sempre inclusi nei parametri. Per esempio, la macro miamacro mostrata sotto,

miamacro(`a' , `b', `c', `d')

ha sempre come primo parametro la lettera a seguita da uno spazio; a nulla serve in questo caso l'uso degli apici, o meglio, sarebbe stato più opportuno usarli nel modo seguente:

miamacro(`a ', `b', `c', `d')

È il caso di precisare che le sequenze di caratteri numerici sono comunque delle stringhe per M4, per cui miamacro(123) è perfettamente uguale a miamacro(`123'). Tuttavia, dal momento che un nome non può cominciare con un numero, e di conseguenza non ci possono essere macro il cui nome corrisponda a un numero, si può evitare di utilizzare gli apici di delimitazione perché sarebbe comunque inutile.

Le stringhe delimitate, oltre che per impedire l'espansione di nomi che corrispondono a delle macro, permettono di «unire» due macro. Si osservi l'esempio seguente:

miamacro_x`ciao'miamacro_y

l'intenzione è quella di fare rimpiazzare a M4 le macro miamacro_x e miamacro_y con qualcosa, facendo in modo che queste due parti si uniscano in modo da avere al centro la parola «ciao». Si può intuire che non sarebbe stato possibile scrivere il testo seguente,

miamacro_xciaomiamacro_y

perché in tal modo non sarebbe stata riconosciuta alcuna macro. Nello stesso modo, si può unire il risultato di due macro senza spazi aggiuntivi, utilizzando apici che delimitano una stringa vuota.

miamacro_x`'miamacro_y

L'espansione delle macro pone un problema in più a causa del fatto che dopo l'espansione il risultato viene riletto alla ricerca di altre macro. Si osservi l'esempio seguente, supponendo che la macro miamacro_x restituisca la stringa miama nel caso in cui il suo unico parametro sia pari a 1.

miamacro_x(1)cro_z

Espandendo la macro si ottiene la stringa «miama», ma dal momento che viene fatta una scansione successiva, la parola «miamacro_z» potrebbe essere un'altra macro, e se fosse questo il caso, questa verrebbe espansa a sua volta. Per evitare che accada una cosa del genere si possono usare gli apici in uno dei due modi seguenti.

miamacro_x(1)`'cro_z

miamacro_x(1)`cro_z'

Il problema può essere visto anche in modo opposto, se l'espansione di una macro, quando questa è attaccata a un'altra, può impedire il riconoscimento della seconda. L'esempio seguente, mostra infatti che la seconda macro, miamacro_y, non può essere riconosciuta a causa dell'espansione della prima.

miamacro_x(1)miamacro_y

Una considerazione finale va fatta sulle macro che non restituiscono alcunché, ovvero che si traducono semplicemente nella stringa nulla. Spesso si tratta di macro interne che svolgono in realtà altri compiti, come potrebbe fare una funzione void di un linguaggio di programmazione normale. In questo senso, per una macro che non restituisce alcun valore, viene anche detto che restituisce void, che in questo contesto è esattamente la stringa nulla.

158.2.2 Definizione di una macro

define(<nome-macro>[, <espansione>])

Come si può osservare dalla sintassi mostrata, la creazione di una macro avviene attraverso una macro interna, define, per la quale deve essere fornito un parametro obbligatorio, il nome della macro da creare, e può essere specificato il valore in cui questa si deve espandere. Se non viene specificato in che modo si deve espandere la macro, si intende che si tratti della stringa nulla.

La macro define non restituisce alcun valore (a parte la stringa nulla). Si osservi l'esempio seguente:

1 define(`CIAO', `Ciao a tutti.')
2 CIAO

Se questo file viene elaborato da M4, si ottiene il risultato seguente:

1
2 Ciao a tutti.

Come si era detto, define crea una macro ma non genera alcun risultato, pertanto viene semplicemente eliminata.

Per creare una macro che accetti delle opzioni, occorre indicare, nella stringa utilizzata per definire la sostituzione, uno o più simboli speciali. Si tratta precisamente di $1, $2,... $n. Il numero massimo di parametri gestibili da M4 dipende dalla sua versione. GNU/Linux dispone generalmente di M4 GNU, e questo non ha limiti particolari al riguardo, mentre le versioni presenti in altri sistemi Unix possono essere limitate a nove.

Questa simbologia richiama alla mente i parametri usati dalle shell comuni, e con la stessa analogia, il simbolo $0 si espande nel nome della macro stessa.

1 define(`CIAO', `Ciao $1, come stai?')
2 CIAO(`Tizio')

L'esempio è una variante di quello precedente, in cui si crea la macro CIAO che accetta un solo parametro. Il risultato dell'elaborazione del file appena mostrato è il seguente:

1
2 Ciao Tizio, come stai?

Prima di proseguire è opportuno rivedere il meccanismo dell'espansione di una macro attraverso un caso particolare. L'esempio seguente è leggermente diverso da quello precedente, in quanto vengono aggiunti gli apici attorno alla parola «come». Il risultato dell'elaborazione è però lo stesso.

1 define(`CIAO', `Ciao $1, `come' stai?')
2 CIAO(`Tizio')

Infatti, quando la macro CIAO viene espansa, subisce una rianalisi successiva, e dal momento che viene trovata una stringa, questa viene «elaborata» restituendo semplicemente se stessa senza gli apici. Questo meccanismo ha comunque una fine, dal momento che non ci sono altre macro, per cui, l'esempio seguente,

1 define(`CIAO', `Ciao $1, ``come'' stai?')
2 CIAO(`Tizio')

si traduce in quest'altro risultato:

1
2 Ciao Tizio, `come' stai?

158.2.3 Simboli speciali

All'interno della stringa di definizione di una macro, oltre ai simboli $n, si possono utilizzare altri codici simili, in un modo che assomiglia a quello delle shell più comuni.

158.2.4 Eliminazione di una macro

Una macro può essere eliminata attraverso la macro interna undefine, secondo la sintassi seguente:

undefine(<nome-macro>)

Per esempio,

undefine(`CIAO')

elimina la macro CIAO, per cui, da quel punto in poi, la parola CIAO manterrà il suo valore letterale. undefine non restituisce alcun valore e può essere usata solo con un parametro, quello che rappresenta la macro che si vuole eliminare.

158.3 Istruzioni condizionali, iterazioni e ricorsioni

M4 non utilizza istruzioni vere e proprie, dal momento che tutto viene svolto attraverso delle «macro». Tuttavia, alcune macro interne permettono di gestire delle strutture di controllo.

Dal momento che il risultato dell'espansione di una macro viene scandito successivamente alla ricerca di altre macro da espandere, in qualche modo, è possibile anche la realizzazione di cicli ricorsivi. Resta il fatto che questo sia probabilmente un ottimo modo per costruire macro molto difficili da leggere e da controllare.

158.3.1 ifdef

ifdef(<nome-macro>, <stringa-se-esiste>[, <stringa-se-non-esiste>])

ifdef permette di verificare l'esistenza di una macro. Il nome di questa viene indicato come primo parametro, e il secondo parametro serve a definire la stringa da restituire in caso la condizione di esistenza si avveri. Se viene indicato il terzo parametro, questo viene restituito se la condizione di esistenza fallisce.

L'esempio seguente, verifica l'esistenza della macro CIAO e se questa non risulta già definita, la crea.

ifdef(`CIAO', `', `define(`CIAO', `maramao')')

158.3.2 ifelse

ifelse(<commento>)

ifelse(<stringa-1>, <stringa-2>, <risultato-se-uguali>[, <risultato-se-diverse>])

ifelse(<stringa-1>, <stringa-2>, <risultato-se-uguali>, ... [, <risultato-altrimenti>])

La macro interna ifelse serve generalmente per confrontare una o più coppie di stringhe restituendo un risultato se il confronto è valido o un altro risultato se il confronto fallisce.

Si tratta di una sorta di struttura di selezione (case, switch e simili) in cui, ogni terna di parametri rappresenta rispettivamente le due stringhe da controllare e il risultato se queste risultano uguali. Un ultimo parametro facoltativo serve a definire un risultato da emettere nel caso l'unica o tutte le coppie da controllare non risultino uguali.

Nella tradizione di M4, è comune utilizzare ifelse con un unico parametro; in tal caso, non si può ottenere alcun risultato, e questo viene sfruttato per delimitare un commento.

Esempi

ifelse(`Questo è un commento')

Utilizzando un unico parametro, ifelse non restituisce alcunché.

ifelse(`mio', `mio', `Vero', `Falso')

Questa istruzione restituisce la parola Vero.

ifelse(`mio', `mao', `Vero', `Falso')

Questa istruzione restituisce la parola Falso.

158.3.3 shift

shift(<parametro>[,...])

La macro interna shift permette di eliminare il primo parametro restituendo i rimanenti separati da una virgola. La convenienza di utilizzare questa macro sta probabilmente nell'uso assieme a $* e $#.

Esempi

shift(mio, tuo, suo)

Eliminando il primo parametro si ottiene il risultato seguente:

tuo,suo

158.3.4 forloop

forloop(<indice>, <inizio>, <fine>, <stringa-iterata>)

La macro interna forloop permette di svolgere una sorta di ciclo in cui l'ultimo parametro, il quarto, viene eseguito tante volte quanto necessario a raggiungere il valore numerico espresso dal terzo parametro. Nel corso di questi cicli, il primo parametro viene trattato come una macro che di volta in volta restituisce un valore progressivo, a partire dal valore del secondo parametro, fino al raggiungimento di quello del terzo.

Esempi

forloop(`i', 1, 7, `i; ')

Restituisce la sequenza dei numeri da 1 a 7, seguiti da un punto e virgola.

1; 2; 3; 4; 5; 6; 7;

158.4 Altre macro interne degne di nota

In questa introduzione a M4 ci sono altre macro interne che è importante conoscere per comprendere le possibilità di questo linguaggio. Attraverso queste macro, descritte nelle sezioni seguenti, è possibile eliminare un codice di interruzione di riga, inserire dei file, cambiare i delimitatori dei commenti e deviare l'andamento del flusso di output.

158.4.1 dnl

dnl[<commento>]newline

dnl è una macro anomala nel sistema di M4: non utilizza parametri ed elimina tutto quello che appare dopo di lei fino alla fine della riga, comprendendo anche il codice di interruzione di riga. La natura di M4, in cui tutto è fatto sotto forma di macro, fa sì che ci si trovi spesso di fronte al problema di righe vuote ottenute nell'output per il solo fatto di avere utilizzato macro interne che non restituiscono alcun risultato. Questa macro serve principalmente per questo: eliminando anche il codice di interruzione di riga si risolve il problema delle righe vuote inutili.

Teoricamente, dnl potrebbe essere utilizzata anche con l'aggiunta di parametri (tra parentesi). Il risultato che si ottiene è che i parametri vengono raccolti e interpretati come succederebbe con un'altra macro normale, senza però produrre risultati. Naturalmente, questo tipo di pratica è sconsigliabile.

Esempi

dnl Questo è un commento vero e proprio
define(`CIAO', `maramao')dnl
CIAO

L'esempio mostra i due usi tipici di dnl: come introduzione di un commento fino alla fine della riga, oppure soltanto come un modo per sopprimere una riga che risulterebbe vuota nell'output. Il risultato dell'elaborazione è composto da una sola riga.

maramao

158.4.2 changecom

changecom([<simbolo-iniziale>[, <simbolo-finale>]])

changecom permette di modificare i simboli di apertura e di chiusura dei commenti. Solitamente, i commenti sono introdotti dal simbolo # e sono terminati dal codice di interruzione di riga. Quando si utilizza M4 per produrre il sorgente di un certo linguaggio di programmazione, o un file di configurazione, è probabile che i commenti di questi file debbano essere modificati attraverso le macro stesse. In questo senso, spesso diventa utile cancellare la definizione dei commenti che impedirebbero la loro espansione.

Esempi

changecom(`/*', `*/')

Cambia i simboli di apertura e chiusura dei commenti, facendo in modo di farli coincidere con quelli utilizzati dal linguaggio C.

changecom

Cancella la definizione dei commenti.

158.4.3 include | sinclude

include(<file>)

sinclude(<file>)

Attraverso la macro include è possibile incorporare un file esterno nell'input in corso di elaborazione. Ciò permette di costruire file-macro di M4 strutturati. Tuttavia, è necessario fare attenzione alla posizione in cui si include un file esterno (si immagini un file che viene incluso nei parametri di una macro).

La differenza tra include e sinclude sta nel fatto che il secondo non segnala errori se il file non esiste.

Esempi

include(`mio.m4')

Include il file mio.m4.

158.4.4 divert | undivert

M4 consente l'uso di uno strano meccanismo, detto deviazione, o diversion, attraverso il quale parte del flusso dell'output può essere accantonato temporaneamente per essere rilasciato in un momento diverso. Per ottenere questo si utilizzano due macro interne: divert e undivert.

divert([<numero-deviazione>])

undivert([<numero-deviazione>[,...]])

La prima macro, divert, serve ad assegnare un numero di deviazione alla parte di output generato a partire dal punto in cui questa appare nell'input. Questo numero può essere omesso, e in tal caso si intende lo zero in modo predefinito.

La deviazione zero corrisponde al flusso normale; ogni altro numero positivo rappresenta una deviazione differente. Quando termina l'input da elaborare vengono rilasciati i vari blocchi accumulati di output, in ordine numerico crescente. In alternativa, si può usare la macro undivert per richiedere espressamente il recupero di output deviato; se questa viene utilizzata senza parametri, si intende il recupero di tutte le deviazioni, altrimenti si ottengono solo quelle elencate nei parametri.

Esiste un caso particolare di deviazione che serve a eliminare l'output; si ottiene utilizzando il numero di deviazione -1. Questa tecnica viene usata spesso anche come un modo per delimitare un'area di commenti che non si vuole siano riprodotti nell'output.

Come si può intuire, queste macro non restituiscono alcun valore.

Esempi

divert(1)
Questo testo è deviato
divert
Questo testo segue l'andamento normale

L'esempio si traduce nell'output seguente, dove le righe sono state numerate per facilitare il riconoscimento di quelle vuote che risultano inserite. Come si può notare, al termine del file di input viene rilasciato l'output deviato precedentemente.

1
2 Questo testo segue l'andamento normale
3
4 Questo testo è deviato

---------

divert(1)
Questo testo è deviato
divert
Questo testo segue l'andamento normale
undivert(1)

Questo esempio è una variante di quello precedente, con la dichiarazione esplicita della richiesta di recupero dell'output deviato. Aggiungendo la macro undivert(1), si aggiunge anche un'interruzione di riga aggiuntiva (anche in questo caso vengono numerate le righe solo per facilitare la percezione visiva del risultato).

1
2 Questo testo segue l'andamento normale
3
4 Questo testo è deviato
5

---------

divert(-1)
Quanto qui contenuto non deve dare alcun
risultato nell'output.
Le macro generano regolarmente i loro effetti,
ma il loro output viene perduto.
divert

Questo esempio, mostra l'uso tipico di divert(-1). Dal momento che alla fine appare la macro divert (senza altre righe), dall'elaborazione di questo file si ottiene solo un codice di interruzione di riga, cioè una riga vuota (quella in cui appare la macro divert finale).

divert(1)
Ciao maramao
divert(2)
Ciao Ciao
divert(-1)
undivert

L'uso di divert(-1) seguito da undivert permette di eliminare tutto l'output accumulato nelle varie deviazioni.

158.5 Riferimenti

La documentazione di M4 GNU, cioè quanto distribuito normalmente con GNU/Linux, è disponibile generalmente attraverso la documentazione info: m4.info.

---------------------------

Appunti Linux 1999.09.21 --- Copyright © 1997-1999 Daniele Giacomini --  daniele @ pluto.linux.it


[inizio] [indice generale] [precedente] [successivo] [indice analitico] [contributi]