Newsgroup: it.comp.lang.c
Titolo: macro crash-course

Ho preferito aprire un altro thread perché ho intenzione di approfittare 
del quesito posto nel thread "Preprocessore - piccolo quiz" per spiegare 
un paio di cose che ho, ahimè, dovuto imparare da solo con non poca fatica.
La prima cosa da fare è riuscire a visualizzare il codice emesso dal 
preprocessore.
Col gcc si usa il comande -E:
     gcc -E main.c > main.o
Questo è un passo INDISPENSABILE.
Purtroppo sono pochi i preprocessori che seguono lo standard.
Lo stesso accadde in C++ per i template. Solo un uso intensivo di questi 
ultimi (vedi librerie Boost, Loki, e altre) ha messo in luce le mancanze 
dei vari compilatori.
Con le macro il discorso non cambia.
Fortunatamente tutti i compilatori possono essere suddivisi in grandi 
gruppi quindi le modifiche da apportare al codice sono solitamente 
minime (qualche #if è sufficiente).
La versione che ho creato col Visual C++ 2005 (VC8) per es. non funziona 
col gcc, quindi ne ho creata una col gcc per l'occasione.
La differenza più grande tra VC8 e gcc è la seguente.
Il codice
     #define ID(x)     x
     ID(x)ID(y)
produce
     xy
in VC8
e
     x y
in gcc.
La differenza è notevole.
Questo fa sì che la funzione CAT sia diversa.
---
Ok, ora procediamo con ordine.
Prima cerchiamo di implementare una macro che concateni il VALORE di due 
simboli.
Un primo tentativo potrebbe essere
#define SYMBOL1    parte1
#define SYMBOL2    parte2
#define CAT(x,y)   x##y
CAT(SYMBOL1,SYMBOL2)
ma il risultato è
SYMBOL1SYMBOL2
Noi avremmo voluto
parte1parte2
Come risolvere?
Solitamente i parametri vengono sostituiti prima di essere applicati, ma 
## concatena proprio le "stringhe".
Proviamo a frapporre un passaggio extra:
#define CAT(x,y)        CATSTR(x,y)
#define CATSTR(x,y)     x##y
CAT(SYMBOL1,SYMBOL2)
produce
parte1parte2
Può risultare comoda anche una CAT3:
#define CAT3(x,y,z)     CAT(CAT(x,y),z)
CAT3(SYMBOL1, SYMBOL2, SYMBOL1)
produce
parte1parte2parte1
Adesso ci serve (fidatevi) un modo per ignorare il primo argomento di 
una lista
#define PL_IGNORE1(x,...)	__VA_ARGS__
PL_IGNORE1(x,y,z,1,2,3)
produce
y,z,1,2,3
Lo so che alcuni compilatori non supportano le variadic macro, ma è una 
funzionalità troppo utile e comoda. E' stata introdotta (ufficialmente) 
con il C99 e sarà presente pure nel C++09.
Iniziamo a scrivere la PL_FIND (PL = Preprocessor Library):
#define PL_FIND(d,x)		CAT3(d,_SYMBOL_,x)
PL_FIND(MYDICT,abcde)
produce
MYDICT_SYMBOL_abcde
Aggiungiamo due funzioni utili:
#define PL_SET_SYMBOL(x)	_, x
#define PL_SET_RESULT(x)	x
L'utente deve definire un simbolo in questo modo:
#define	MYDICT_RESULT_		 PL_SET_RESULT(not_found)		// valore di 
default/errore
#define MYDICT_SYMBOL_CASA       PL_SET_SYMBOL(CASA)
#define MYDICT_RESULT_CASA	 PL_SET_RESULT(risultato_casa)
La prima define definisce cosa restituire in caso di errore (simbolo non 
trovato).
Se scriviamo
PL_FIND(MYDICT,CASA)
otteniamo
_, CASA
Perché questa scelta? Lo scoprirete presto :)
Aggiungiamo un altro pezzo alla PL_FIND:
#define PL_FIND(d,x)				PL_IGNORE1(CAT3(d,_SYMBOL_,x))
Adesso
PL_FIND(MYDICT,CASA)
produce
                     // assolutamente niente
Qualcosa non va. Ma cosa?
Ci saremmo aspettati questo:
PL_FIND(MYDICT,CASA) ->
PL_IGNORE1(CAT3(d,_SYMBOL_,x)) ->
PL_IGNORE1(MYDICT_SYMBOL_CASA) ->
PL_IGNORE1(PL_SET_SYMBOL(CASA)) ->
PL_IGNORE1(_, CASA) ->
CASA
L'idea è buona, ma il problema è che avviene questo:
PL_FIND(MYDICT,CASA) ->
PL_IGNORE1(CAT3(d,_SYMBOL_,x)) ->
                     // assolutamente niente
Questo accade poiché PL_IGNORE1 viene espanso prima del suo argomento. 
Questo fa sì che PL_IGNORE1 riceva di fatto un solo argomento, che viene 
prontamente scartato.
Questa è una fine che vogliamo riservare SOLO ai simboli non presenti 
nel dizionario.
Quello che vogliamo è che prima venga eseguita l'espansione di 
CAT3(d,_SYMBOL_,x) e che poi il risultato venga passato a PL_IGNORE1.
Come fare?
Semplice! Ritardiamo l'applicazione di PL_IGNORE1:
#define PL_FIND(d,x)				CAT(PL_IGNORE1,(CAT3(d,_SYMBOL_,x)))
Adesso
PL_FIND(MYDICT,CASA)
produce
CASA
mentre
PL_FIND(MYDICT,sparisci!)
produce
                          // assolutamente niente
Veramente notevole, direi!
Riassumendo, nel primo caso arriviamo a
PL_IGNORE1(MYDICT_SYMBOL_CASA) ->
PL_IGNORE1(PL_SET_SYMBOL(CASA)) ->
PL_IGNORE1(_, CASA) ->
CASA
mentre nel secondo a
PL_IGNORE1(MYDICT_SYMBOL_sparisci!)->
                          // assolutamente niente
Cerchiamo di completare l'opera:
#define PL_FIND(d,x)			 
CAT(MYDICT_RESULT_,CAT(PL_IGNORE1,(CAT3(d,_SYMBOL_,x))))
PL_FIND(MYDICT,CASA)
produce
not_foundCASA
Eh?
Ah, MYDICT_RESULT_ viene prodotto prima di essere concatenato con CASA. 
Come fare?
Proviamo
#define PL_FIND(d,x) 
CAT3(MYDICT,_RESULT_,CAT(PL_IGNORE1,(CAT3(d,_SYMBOL_,x))))
Col compilatore M$ funziona, ma col gcc il risultato non cambia.
Facciamo un passo alla volta.
Con
#define PL_FIND(d,x)	CAT(_RESULT_,CAT(PL_IGNORE1,(CAT3(d,_SYMBOL_,x))))
otteniamo
_RESULT_CASA
Dai che ci siamo!
#define PL_FIND(d,x) 
CAT(MYDICT,CAT(_RESULT_,CAT(PL_IGNORE1,(CAT3(d,_SYMBOL_,x)))))
produce finalmente
risultato_casa
Alleluja!
---
Fortunamente funziona anche in VC8. L'unica diversità si ha con 
l'implementazione
della CAT che in VC8 è:
#define ID(x)     x
#define CAT(x,y)  ID(x)ID(y)
---
Come ci aspettavamo,
PL_FIND(MYDICT,asdfpokasdf)
produce invece
not_found
Ok, tutto bene.
Adesso generializziamo il risultato:
#define PL_FIND(d,x)			 
CAT(d,CAT(_RESULT_,CAT(PL_IGNORE1,(CAT3(d,_SYMBOL_,x)))))
Ho semplicemente messo d al posto di MYDICT :)
Con una simile funzione è perfino possibile contare il numero di 
argomenti passati a una macro.
Tutto ciò che serve è aggiungere una sentinella in fondo alla lista con
#define ADD_SENT(x,...)		x,__VA_ARGS__,END_OF_LIST
e il gioco è fatto. Dato che non è possibile avere una perfetta 
ricorsione con le MACRO, occorre scrivere varie funzioni.
Vi do giusto qualche indizio. In VC8 (non intendo fare una revisione 
come con PL_FIND perché la complessità è alle stelle), ho creato prima 
una PL_IF usando PL_COND (è facile da fare), quindi ho creato una 
funzione PL_CEIL_LOG_NUM_ARGS così descritta:
// PL_CEIL_LOG_NUM_ARGS(x) = ceil(log_2(numero_argmenti(x))).
//
// Es:
//	#define	FOUR_PARAMS				a,b,c,d
//	PL_CEIL_LOG_NUM_ARGS(FOUR_PARAMS)				// restituisce 2
Secondo lo standard, il numero massimo di parametri che una macro può 
accettare è 127 quindi PL_CEIL_LOG_NUM_ARGS riceve un solo parametro:
PL_CEIL_LOG_NUM_ARGS((1,2,3,4,5)).
Esatto, il trucco è quello di passare una lista come parametro unico.
Dato che non è possibile avere una vera ricorsione con le macro 
(dovreste saperlo) allora è necessario usare un piccolo stratagemma.
Se è vero che
#define      ADD1(x)     (ADD2(x)+1)
#define      ADD2(x)     (ADD3(x)+1)
#define      ADD3(x)     (ADD4(x)+1)
#define      ADD4(x)     (ADD5(x)+1)
#define      ADD5(x)     x
ADD1(1)
produce
((((1 +1)+1)+1)+1)
allora è anche vero che
#define      ADD1(x)     ADD2(ADD2(x))
#define      ADD2(x)     ADD3(ADD3(x))
#define      ADD3(x)     ADD4(ADD4(x))
#define      ADD4(x)     ADD5(ADD5(x))
#define      ADD5(x)     (x+1)
ADD1(1)
produce
((((((((((((((((1 +1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)
Non è difficile capire perché.
Allora si potrebbe pensare di sostituire x+1 con qualcosa che usi dei 
PL_IF, ma le cose non sono mai belle come potrebbero essere.
Il preprocessore non è LAZY, cioè espande anche quello che non gli 
serve, così occorre fare salti mortali pazzeschi per ridurre la 
complessità computazionale.
Il risultato è qualcosa del genere:
.
.
.
.
#define LEVEL2_CONTINUE(x)	LEVEL3(x)
#define LEVEL2_STOP			2
#define LEVEL2(x)			CAT(LEVEL2, PL_IF( HEAD(SKIP2(ADD_SENT2(x))), 
_CONTINUE(x), _STOP ))
#define LEVEL1_CONTINUE(x)	LEVEL2(x)
#define LEVEL1_STOP			1
#define	LEVEL1(x)			CAT(LEVEL1, PL_IF( HEAD(SKIP1(ADD_SENT1(x))), 
_CONTINUE(x), _STOP ))
#define LEVEL0_CONTINUE(x)	LEVEL1(x)
#define LEVEL0_STOP			0
#define	LEVEL0(x)			CAT(LEVEL0, PL_IF( HEAD(SKIP0(ADD_SENT0(x))), 
_CONTINUE(x), _STOP ))
Il trucco sta nel fatto che il calcolo prosegue solo se viene generato 
LELVEL?_CONTINUE. Nel modo ingenuo, calcoleremmo tutta l'espressione 
anche se non necessario (crash del compilatore assicurato).
Vi risparmio i dettagli perché il codice completo è piuttosto complesso.
Passandogli una lista di 500'000 elementi impiega circa 2 sec. per 
contarli e restituire il log di 500000.
Perché il log? Per motivi tecnici, ma lasciamo perdere.
E' una libreria che anch'io voglio dimenticare perché stava diventando 
insostenibile. Quando le espressioni diventano molto complesse, anche se 
corrette, i compilatori impazziscono quindi non vale la pena perderci tempo.
Volevo solo mostrarvi quali meraviglie (a seconda dei gusti) è possibile 
ottenere con un semplice preprocessore.
Però sono riuscito a realizzare una libreria IMPERATIVA, quindi più 
vicina allo stile C/C++, di cui vi scrivo solo un esempio d'utilizzo:
#define GUNC_0  (SET, REG0, 500)
#define GUNC_1  (SET, REG1, 0)
#define GUNC_2  (WHILE, REG0 > 0)
#define GUNC_3      (SET, REG0, REG0-1)
#define GUNC_4      (INC, REG1)
#define GUNC_5      (VERBATIM)
#define GUNC_6          int CAT(var, REG0) = REG1;
#define GUNC_7      (ENDV)
#define GUNC_8  (ENDW)
#define GUNC_9  (END)
#include "run.h"
Il risultato è
int var499 = 1;
.
.
.
int var0 = 500;
L'istruzione SET supporta espressioni di complessità arbitraria:
(SET, (REG0+REG32)*REG2 + REG3 + REG(iREG0))
REG(iREG0) è un caso particolare dove il valore di REG0 viene usato per 
costruire l'indice di REG. Se REG0 vale 12, allora REG(iREG0) diventa REG12.
Più avanti rilascerò la libreria (già funzionante, ma da ottimizzare) 
col codice sorgente.
Kiuhnm

 col codice sorgente.
Prima verifica che funzioni con un compilatore standard. Se voui 
aggiungere supporto per altri compilatori diffusi ma non standard, puoi 
farlo. Ma sarebbe un errore se la rilasciassi come codice non standard.
Comunque complimentissimi! Se questo è il livello di approfondimento con 
cui impari le cose, mi sa che il tuo megaprogetto potrebbe essere 
interessante. E che con i funzionali ci fai veramente i numeri!
Un solo consiglio: impara non solo ad approfondire e a macinarti il 
cervello (lo so che è la cosa più bella) ma anche a semplificare. Il 
codice migliore non è quello più veloce o scritto con meno righe, ma 
quello più facile da capire a colpo d'occhio anche senza averlo mai visto.

 un paio di cose che ho, ahimè, dovuto imparare da solo con non poca fatica.
Grazie Kiuhm.
Purtroppo con GCC 3.4.2 sotto MINGW
Espandendo  CAT(PL_IGNORE1,(CAT3(d,_SYMBOL_,x)))
ottengo 'pasting "IGNORE1" and "(" does not give a valid preprocessing 
token'.
Comunque non e' un problema. Se ho capito bene il nucleo è nel ritardare 
l'espansione delle macro in modo che vengano valutati prima i suoi 
argomenti.
Appena ho un attimo provo a risolvere l'errore che da' il gcc.
Grazie ancora.
Remo D.

 quello più facile da capire a colpo d'occhio anche senza averlo mai visto.
Me ne sto rendendo conto sempre più. Stranamente è la cosa più difficile 
da imparare per me. Ma adesso sono costretto altrimenti finisco tra 20 anni.
Kiuhnm

 token'.
Eppure ho usato proprio il gcc 3.4.2 (mingw-special).
Kiuhnm

 Eppure ho usato proprio il gcc 3.4.2 (mingw-special).
Hai ragione. Ho sudato freddo quando ho capito il problema!
Il preprocessore fa il suo lavoro e il risultato (main.o) contiene il 
risultato corretto, ma poi segnala ugualmente l'errore dicendoti che non 
puoi incollare una parentesi '(' (simbolo speciale) a un altro token.
Non me ne ero accorto perché guardavo esclusivamente il risultato (che 
compilatore traditore!).
Usa questa:
#define PL_FIND(d,x)		 
CAT(d,CAT(_RESULT_,CAT(PL_IGN,ORE1(CAT3(d,_SYMBOL_,x)))))
e nota l'espediente: non ho fatto altro che spostare la virgola.
Il file completo è:
#define CATSTR(x,y)     x##y
#define CAT(x,y)        CATSTR(x,y)
#define CAT3(x,y,z)     CAT(CAT(x,y),z)
#define PL_IGNORE1(x,...)			__VA_ARGS__
#define PL_SET_SYMBOL(x)         _, x
#define PL_SET_RESULT(x)		 x
#define	MYDICT_RESULT_			 PL_SET_RESULT(not_found)		// valore di 
default/errore
#define MYDICT_SYMBOL_CASA       PL_SET_SYMBOL(CASA)
#define MYDICT_RESULT_CASA		 PL_SET_RESULT(risultato_casa)
#define PL_FIND(d,x)		 
CAT(d,CAT(_RESULT_,CAT(PL_IGN,ORE1(CAT3(d,_SYMBOL_,x)))))
int main()
{
     int PL_FIND(MYDICT, CASA);
     int PL_FIND(MYDICT, sdijgsdofij);
     risultato_casa = 0;
     not_found = 0;
}
Kiuhnm