• Non ci sono risultati.

Come funziona \newif in L A TEX e in Plain

Nel documento Appunti di programmazione in L (pagine 170-173)

La macro\newif, in entrambi i formati, definisce tre comandi: un condizionale e due macro per modificarne il senso. Dal momento che\newifnon compare nel manuale di\LaTeX, gli sviluppatori non si sono preoccupati di rendere obbligatorio che il condizionale definito abbia un nome che comincia per ‘if’, fidandosi del buon senso di chi lo usa per i propri pacchetti o definizioni.

Vediamo come lo si definisce in LATEX, sarà sufficiente qualcosa in più per capire il modo più complicato con cui è scritto in Plain:

\def\newif#1{% \count@\escapechar \escapechar\m@ne \let#1\iffalse \@if#1\iftrue \@if#1\iffalse \escapechar\count@} \def\@if#1#2{% \expandafter\def\csname\expandafter\@gobbletwo\string#1% \expandafter\@gobbletwo\string#2\endcsname {\let#1#2}}

Abbiamo già detto che\escapecharè un registro di tipo\countche contiene il numero del byte che viene premesso al risultato di\stringse l’argomento di questo comando è un token complesso. Il valore usuale è, naturalmente, 92, cioè il codice ASCII di ‘\’, ma la macro\newifpotrebbe essere chiamata in un contesto in cui il valore è diverso; perciò si usa il trucco di memorizzare il valore attuale del parametro nel contatore\count@per poi ripristinarlo alla fine.

Con\texttt{\string\xyz}si ottiene ‘\xyz’, mentre\texttt{\string a}dà ‘a’. Come si vede l’argomento di\stringè il token immediatamente seguente e non va messo tra graffe. Se il valore di\escapecharè diverso, anche il risultato sarà diverso:

\escapechar=‘\?␣\string\xyz→?xyz \escapechar=-1␣\string\xyz→ xyz

Lo spazio dopo\?viene ignorato perché segue una costante, se non ci fosse\string sarebbe espanso prima che l’assegnazione sia completa. Se non vale 0 ≤ x ≤ 255 per il valore x di\escapechar, non verrà aggiunto alcun carattere prima del nome di un token complesso, come si vede dal secondo esempio. Non è necessario che il token dopo\stringsia definito.

La definizione di\newifcomincia dando −1 come valore di\escapechar, dopo aver ricordato in\count@il valore attuale. L’argomento di\newifviene reso equiva-lente a\iffalse; nella spiegazione assumeremo che l’argomento dato sia\ifxyz: la prima chiamata è dunque\let\ifxyz\iffalse. Seguono le due chiamate

\@if\ifxyz\iftrue \@if\ifxyz\iffalse

e la definizione viene completata ripristinando il valore di\escapecharusando quello memorizzato in\count@. Ora vediamo che, come ci si aspettava,\@ifè una macro con due argomenti. Ecco l’espansione della prima chiamata (come sempre, %>indica continuazione):

\expandafter\def\csname\expandafter\@gobbletwo\string\ifxyz %> \expandafter\@gobbletwo\string\iftrue\endcsname %>

{\let\ifxyz\iftrue}

Il primo\expandaftercausa l’espansione di\csnameche, come sappiamo, espan-de tutto quanto trova fino a\endcsname. Il primo token espanso è\expandafter che causa l’espansione di\string, in modo che\@gobbletwotrovi dopo di sé la stringaifxyz(perché\escapecharè negativo);\@gobbletwoè una macro con due argomenti che semplicemente scarta e qui i due argomenti sono i tokeni12ef12(di categoria 12 perché prodotti da\string). Perciò i caratteri che rimangono sonoxyz. Analogamente il successivo\expandafterfa rimanere la stringatrue; dunque il tutto è equivalente alla chiamata

\def\xyztrue{\let\ifxyz\iftrue}

e analogamente funziona\@if\ifxyz\iffalseportando a

\def\xyzfalse{\let\ifxyz\iffalse}

Se però seguiamo ciò che succede dando\newif\abc, scopriamo che i comandi definiti sono\abc,\ctruee\cfalse. Meglio dunque che il nostro condizionale abbia un nome che comincia perif, per non incorrere in stranezze che potrebbero ridefinire comandi importantissimi.

*Peggio ancora se dessimo\newif\?perché la lista di token dentro\csnamediventerebbe \expandafter\@gobbletwo\string\?\expandafter\@gobbletwo\string\iftrue Quando il primo\expandafterviene espanso si ha

\@gobbletwo ?\expandafter\@gobbletwo\string\iftrue

e\@gobbletwofa il suo mestiere ingoiando?12e\expandafter; adesso anche il secondo comando\@gobbletwofa il suo mestiere e ingoia gli ultimi due token, quindi fra\csnamee \endcsnamenon rimane nulla e avremmo definito il token con nome vuoto. In effetti, dando \newif\?otteniamo la seguente risposta a\show:

Infatti la macro ‘vuota’ viene definita due volte, una con la chiamata\@if\?\iftrue, l’altra con la chiamata\@if\?\iffalse.

*La macro vuota potrebbe essere definita con uno fra \expandafter\def\csname\endcsname{...} \@namedef{}{...} o anche con \count@=\endlinechar \endlinechar=-1 \def\ {...} \endlinechar=\count@

ma in effetti non serve a nulla. Questa è solo un’interessante applicazione delle regole di for-mazione dei token: se\endlinecharha valore fuori dall’intervallo [0, 255], TEX non inserisce nulla quando decide che una riga dell’input è finita. Perciò sono vuoti sia il nome della macro da definire che il testo 〈parametri 〉.

Il succo del discorso è definire\ifxyzin modo che inizialmente equivalga a \iffalse. Con le macro\xyztruee\xyzfalsepossiamo modificare il significato di\ifxyz; si noti che le macro usano sempre\lete non\def, in modo che TEX ‘veda’ sempre un condizionale quando incontra\ifxyz.

*La macro\newifin Plain è definita in modo molto simile, ma esegue la verifica che l’argomento sia una sequenza di controllo il cui nome cominci perif. C’è un piccolo problema: i token che vengono prodotti dall’espansione di\stringhanno tutti categoria 12. Vediamo la definizione: \outer\def\newif#1{\count@\escapechar \escapechar\m@ne \expandafter\expandafter\expandafter \def\@if#1{true}{\let#1=\iftrue}% \expandafter\expandafter\expandafter \def\@if#1{false}{\let#1=\iffalse}%

\@if#1{false}\escapechar\count@} % the condition starts out false \def\@if#1#2{\csname\expandafter\if@\string#1#2\endcsname}

% ‘if’ is required

{\uccode‘1=‘i \uccode‘2=‘f \uppercase{\gdef\if@12{}}}

Consideriamo ancora il caso in cui l’argomento di\newifsia\ifxyze seguiamo che succede con l’espansione delle prime due righe:

\expandafter\expandafter\expandafter\def\@if\ifxyz{true} {\let\ifxyz=\iftrue}

Il primo\expandafterespande il terzo che, a sua volta, espande\@if; la lista di token che risulta è

\expandafter\def\csname\expandafter\if@\string\ifxyztrue\endcsname {\let\ifxyz=\iftrue}

perché\@ifdeve usare i suoi due argomenti che sono proprio\ifxyzetrue(le graffe spariscono perché delimitano un argomento). Ora\expandaftercausa l’espansione di \csname; vediamo che c’è fino a\endcsname:

\expandafter\if@\string\ifxyztrue e questo lascia la lista di token

sempre perché\escapecharha valore −1. Ma l’espansione deve continuare, perché siamo dentro\csname.

La definizione di\if@è data dentro un gruppo in cui i codici\uccodedi1e di2sono rispettivamente i codici ASCII diie dif. Ciò significa che\uppercasetrasforma112ini12e 212inf12, senza modificare il codice di categoria! Perciò la definizione di\if@richiede che questa macro sia seguita dai12ef12: esattamente ciò che si trova\if@adesso. Se avessimo dato\newif\abcqui ci sarebbe un errore. Invece nel nostro caso\if@è contento e sparisce insieme ai due tokeni12ef12, perché la sua espansione è vuota. Ciò che rimane è

\def\xyztrue{\let\ifxyz\iftrue} o, più precisamente,

\expandafter\def\csname xyztrue\endcsname{\let\ifxyz\iftrue}

ma il codice di categoria dei caratteri dopo\csnameè irrilevante e non quindi importa se sono di categoria 12. Analogamente funzionano le due righe successive per la definizione di\xyzfalse. Per finire viene eseguita la macro\xyzfalseappena definita e, anche qui, il condizionale è inizialmente falso.

Il trucco con\uppercasefunziona perché questo comando è eseguito nello ‘stomaco’ di TEX, come tutte le assegnazioni. Va ricordato che\uppercasee\lowercasecambiano solo il codice ASCII dei token carattere usando le tabelle dei codici\uccodee\lccode, lasciano immutato il loro codice di categoria e non toccano in alcun modo i token complessi.

Il pacchettoifthendà queste definizioni, che evitano il problema di\newifin LATEX: \def\newboolean#1{% \expandafter\@ifdefinable\csname if#1\endcsname{% \expandafter\newif\csname if#1\endcsname}} \def\provideboolean#1{% \@ifundefined{if#1}{% \expandafter\newif\csname if#1\endcsname}\relax}

Dando\newboolean{xyz}, LATEX controlla se il comando\ifxyzè definito e, se non lo è, esegue\newif\ifxyztramite\csname. Simile è\provideboolean{xyz}che esegue\newif\ifxyzsolo se\ifxyznon è definito. Di solito però non è necessario prendere troppe cautele: se si usa un preciso schema per i nomi dei comandi la probabilità di beccare un condizionale già definito è molto bassa.

*Si può usare il sistema di\if@anche per macro da usare a livello utente. Può risultare seccante dover scrivere ogni volta{\TeX}in un manuale su TEX; se diamo la definizione

\def\tex/{\TeX}

possiamo scrivere\tex/ è bellosenza doverci preoccupare degli spazi dopo il comando: secondo le regole sui parametri, il comando\texdeve essere seguito da/e questo evita il problema della terminazione del nome del comando: lo spazio nell’esempio non è il primo token di categoria diversa da 11 dopo il nome del comando che è sempre\texe quindi non è ignorato; il carattere/non comparirà perché viene eliminato durante l’espansione.

Nel documento Appunti di programmazione in L (pagine 170-173)