Una Chiave per il Desktop Publishing

Corso di PostScript - Marco A. Calamari

Seconda Puntata


La struttura del linguaggio

Come promesso, in questa seconda parte illustreremo il funzionamento del programma realizzato nella prima puntata. Prima di ciò, è però necessario fare parentesi (non breve) per introdurre le caratteristiche principali e la struttura del linguaggio PostScript; questo permetterà a chi ha familiarità con altri linguaggi simili di utilizzare le conoscenze che possiede, e sarà una fatica necessaria ma redditizia, per chi non ha molta esperienza di programmazione.

Il linguaggio PostScript possiede le seguenti principali caratteristiche:

I programmi PostScript

Un programma PostScript è una sequenza di caratteri ASCII stampabili, e può contenere, oltre ai caratteri di controllo elencati la volta scorsa, i caratteri alfabetici maiuscoli e minuscoli, le cifre e i seguenti caratteri speciali

/ (slash),
% (percento),
( (parentesi tonda aperta),
) (parentesi tonda chiusa),
[ (parentesi quadra aperta)
] (parentesi quadra chiusa)
{ (parentesi graffa aperta)
} (parentesi graffa chiusa)
< (parentesi angolata aperta)
> (parentesi angolata chiusa)
\ (backslash)

Questi ultimi sono caratteri riservati, nel senso che non possono essere usati per formare nomi; tutti gli altri caratteri speciali stampabili sono in realtà utilizzabili nei nomi, ma, per rendere il programma più leggibile (e in PostScript è vitale scrivere programmi leggibili !) vi consigliamo di limitare l'utilizzo di questi caratteri ai soli - (meno) e _ (sottolineatura). Sempre per migliorare la leggibilità, anticipiamo qui una serie di convenzioni universalmente utilizzate in PostScript:

- I nomi devono essere formati solo da caratteri alfabetici e numerici, e, dato che l'interprete considera diverse le lettere maiuscole e minuscole, particolare cura dovrà essere posta nell'evitare di scrivere cose come Nome, NOME e nome, che sono considerate tre oggetti diversi.

I nomi di variabili devono sempre iniziare con una lettera maiuscola, e quelli di procedure con lettera minuscola.

I nomi composti di più parole saranno formati scrivendo maiuscole le lettere iniziali di ogni parola oltre la prima; ad esempio

nomeMoltoLungoDiProcedura

NomeMoltoLungoDiVariabile.

In PostScript non è esatto parlare di programma nel senso classico del termine, poiché in questo linguaggio non vi è una separazione netta fra programma e dati; è più semplice pensare che, quando sottoponiamo un file di comandi all'interprete PostScript, esso lo consumi carattere per carattere, producendo oggetti e azioni all'interno della memoria della LaserWriter. Quando l'interprete PostScript riceve i caratteri, li immagazzina in un buffer; all'arrivo del primo carattere separatore (spazio, return, ecc.) lo scarta, considera terminata la stringa e la interpreta, eseguendo il comando PostScript corrispondente, od operando su di essa mediante le regole predefinite e trasformandola in un oggetto PostScript. Questa interpretazione immediata implica che non esiste nessuna precedenza fra gli operatori del PostScript, a differenza di quello che accade nella maggior parte degli altri linguaggi.

Gli oggetti PostScript

I tipi di dati del PostScript, che d'ora in poi indicheremo con il nome di oggetti, sono 13, e possono essere raggruppati come segue:

Oggetti numerici

Oggetti nominali

Oggetti speciali

Oggetti composti

Tutti gli oggetti PostScript possiedono alcune caratteristiche comuni, che sono tipo, valore ed attributi. Normalmente, per programmare in PostScript, è necessario considerare solo il tipo ed il valore di un oggetto, essendo gli attributi principalmente di interesse dell'interprete PostScript; solo in casi molto particolari, che di rado si presentano, può essere necessario intervenire sugli attributi con gli opportuni operatori.

La suddivisione più importante fra i tipi di oggetti è quella fra oggetti semplici e composti. La maggior parte degli oggetti PostScript sono oggetti semplici; questo significa che tutte le loro caratteristiche (tipo,valore ed eventuali attributi) sono strettamente legate assieme, e non possono essere modificate singolarmente. Ciò è necessario, ad esempio, per evitare che l'oggetto numerico intero 12 possa avere il valore di 13. L'unico metodo disponibile per cambiare le caratteristiche di un oggetto semplice è quello di copiarlo in un nuovo oggetto che abbia le caratteristiche desiderate.

Gli oggetti di tipo vettore, stringa e dizionario sono invece oggetti composti; ciò significa che questi tipi di oggetti possiedono una struttura interna formata da componenti, i quali sono visibili, accessibili e modificabili singolarmente. Maggiori dettagli su questi oggetti saranno forniti quando ne introdurremo l'uso.

Gli oggetti numerici in PostScript possono essere interi o reali. I numeri interi possono variare fra limiti superiori ed inferiori che dipendono dall'implementazione; i numeri reali sono rappresentati in virgola mobile, ed hanno quindi una precisione che dipende dall'implementazione. Nessuna di queste caratteristiche variabili è di solito significativa nella normale programmazione in PostScript, posto che si evitino situazioni in cui l'errore di arrotondamento è decisivo. Un caso è quello in cui in un salto condizionato si confronta un intero con un reale; si tratta di situazioni da evitare indipendentemente dal linguaggio di programmazione utilizzato.

Malgrado non si tratti propriamente di numeri, considereremo i valori logici come appartenenti a questa categoria di oggetti. Un valore logico può avere solo due possibili valori, vero o falso; benché siano equivalenti ai valori 1 e 0, in PostScript essi hanno rappresentazioni interne particolari, quindi 0 non è uguale a falso ed 1 non è uguale a vero, come avviene di solito nel Basic. Per questa ragione il PostScript fornisce due operatori appositi, true e false, che restituiscono i relativi valori e devono essere utilizzati per i confronti con valori logici.

Gli oggetti nominali sono simboli indivisibili, definiti da una stringa di caratteri.

Un oggetto di tipo nome, non è la stringa di caratteri che lo identifica; i caratteri che formano questa stringa non sono infatti elementi del nome, ma devono essere presi nel loro insieme solo come il suo identificatore. Gli oggetti di tipo nome (che nel seguito indicheremo semplicemente come nome) non hanno un valore nel senso in cui lo possiedono in altri linguaggi di programmazione; essi sono invece associati con dei valori mediante l'uso del meccanismo dei dizionari. Per il momento accontentiamoci di questa definizione; ritorneremo su questo argomento più avanti.

Un oggetto di tipo operatore è un comando primitivo del PostScript; ogni volta che un operatore viene incontrato, l'interprete PostScript esegue l'azione corrispondente. Possiamo per ora pensare che un operatore ed il suo nome coincidano, in attesa di chiarire anche questo punto quando esamineremo in dettaglio il meccanismo dei dizionari.

Un oggetto di tipo file è una sequenza di caratteri che può essere letta o scritta, e viene utilizzato per trasferire informazioni fra il PostScript e l'ambiente esterno. L'interprete PostScript possiede due oggetti di tipo file predefiniti; lo standard input e lo standard output. Il primo è la normale sorgente da cui l'interprete riceve i programmi, i comandi ed i dati, il secondo è la normale destinazione dei messaggi di status e di errore. Si noti che lo standard output non ha nulla a che fare con l'output finale che si vuole ottenere, cioè la pagina stampata; infatti i messaggi dell'interprete PostScript (ad esempio lo %%[Status: idle]%% che si ottiene battendo CONTROL-T) non finiscono sulla pagina stampata ma ritornano al terminale da cui sono stati battuti. Nel caso di un collegamento seriale con la LaserWriter possiamo quindi dire che lo standard input è la tastiera del computer e lo standard output è il relativo video.

Gli oggetti speciali sono particolari tipi di oggetti che il PostScript usa internamente per i suoi reconditi scopi; una minima conoscenza di essi è comunque necessaria per completare il panorama (molto sintetico) degli oggetti PostScript.

Il primo oggetto di questo tipo è il null. Come il suo nome suggerisce esso non contiene niente; viene usato ad esempio per occupare le posizioni di oggetti compositi quando vengono creati ma non sono ancora stati inizializzati. Un oggetto null può essere generato usando l'omonimo operatore null.

Un altro tipo di oggetto speciale è il mark. Viene largamente usato durante la programmazione per poter ritrovare particolari posizioni sugli stack quando vengono usati gli operatori di manipolazione degli stack (un attimo di pazienza, ne parleremo fra poco).

Il tipo di oggetto save viene utilizzato in momenti particolari dall'interprete PostScript per memorizzare e ripristinare il suo stato grafico; ne parleremo in esteso quando introdurremo i relativi operatori.

Il tipo fontID viene invece usato durante la creazione dei font di carattere; si tratta di una operazione abbastanza rara, perché i font vengono di solito acquisiti già pronti, ed al massimo modificati. La creazione di un font sarà forse oggetto di una intera puntata di questo corso.

Gli oggetti compositi sono, come abbiamo già ricordato, quelli dotati di struttura interna.

Il tipo stringa è formato da una sequenza di caratteri ASCII, inclusi quelli speciali e quelli non stampabili; i singoli caratteri vengono immagazzinati come byte, i quali possono essere interpretati anche come interi da 0 a 255. È possibile eseguire sulle stringhe le classiche operazioni che tutti i linguaggi consentono, ed accedere ai singoli caratteri per mezzo del loro indice. Una stringa viene definita racchiudendone i caratteri fra parentesi tonde (le parentesi tonde sono i primi caratteri speciali del PostScript che utilizziamo); si consideri però che gli spazi bianchi eventualmente presenti fanno parte integrante della stringa, quindi le stringhe


(pippo)
( pippo)
(pippo )
(pi ppo)


sono tutte diverse tra loro.

Il tipo vettore è formato da un insieme arbitrario di altri oggetti PostScript, inclusi altri oggetti vettore. Benché i vettori del PostScript possano avere elementi di tipo diverso, essi sono limitati ad una dimensione; nel caso che sia necessario utilizzare matrici, esse possono essere realizzate con qualunque numero di dimensioni semplicemente creando vettori di vettori .... di vettori. L'indice di un vettore PostScript parte da 0, quindi un vettore di n elementi ha il primo con indice 0 e l'ultimo con indice n-1. Un vettore viene definito racchiudendone gli elementi fra parentesi quadre; quindi un vettore di tre elementi che abbia come primo elemento la stringa pippo, come secondo il numero intero 2 e come terzo il valore di pi greco si ottiene scrivendo


[ (pippo) 2 3.1415 ]



Il tipo di oggetto dizionario verrà trattato in dettaglio più avanti.

Gli stack del PostScript

Quelli di voi che conoscono il linguaggio Forth si saranno già accorti delle notevoli somiglianze che esso presenta col PostScript. Come quest'ultimo infatti il Forth obbliga ad utilizzare gli stack per manipolare i numeri, e permette di definire nuovi comandi utilizzando quelli preesistenti. La struttura interna dell'interprete PostScript ed il suo modo di funzionamento rivelano però significative differenze rispetto al Forth; infatti uno stack del Forth può contenere solo un tipo di dati (numeri interi o numeri reali).

Ma che cos'è uno stack? Uno stack non è niente altro che una pila di oggetti (eventualmente vuota), in cui questi possono essere messi o tolti solo partendo dalla cima, proprio come se si trattasse di una pila di libri. Nel PostScript esistono quattro distinti stack:

Tutti questi stack sono indipendenti l'uno dall'altro; le operazioni che avvengono su uno di essi non hanno nessuna influenza diretta sugli altri. Ogni stack ha un modo di accesso particolare, ma nelle linee generali, il funzionamento è identico

Lo stack di esecuzione è sotto l'esclusivo controllo dell'interprete PostScript, e può essere solo interrogato ma non alterato; il suo utilizzo è riservato a programmi molto sofisticati, e per i nostri scopi attuali ci basta sapere che si tratta dello stack su cui l'interprete pone gli oggetti che devono essere eseguiti, come le procedure.

Lo stack degli stati grafici contiene esclusivamente oggetti di tipo save; ne parleremo diffusamente quando introdurremo il concetto di stato grafico.

Lo stack dei dizionari contiene esclusivamente oggetti di tipo dizionario; anche di questo stack parleremo quando introdurremo l'uso dei dizionari.

Lo stack degli operandi, è di gran lunga il più importante, infatti la maggior parte degli operatori PostScript prelevano da qui i dati e vi depositano gli eventuali risultati; d'ora in poi ci riferiremo a lui chiamandolo semplicemente stack, mentre specificheremo sempre per esteso i nomi degli altri.

Lo stack degli operandi è, come gli altri, uno stack di tipo LIFO (Last In First Out - ultimo entrato primo uscito); esaminiamo il funzionamento con un semplice programma che calcola la somma di due numeri e la stampa sullo standard output.

Collegatevi via seriale alla LaserWriter, resettatela con CONTROL-D, e, senza entrare in modalità executive, battete la seguente riga di caratteri

12 37 add == flush

senza battere RETURN dopo l'ultimo carattere. Battete ora uno spazio bianco; la LaserWriter risponderà

49

In figura 1 possiamo osservare cosa avviene nello stack degli operandi durante l'esecuzione di questo brevissimo programma (si suppone che all'inizio lo stack sia vuoto) :

  1. l'interprete PostScript comincia ad accumulare i primi caratteri, cioè 12;
  2. quando arriva lo spazio bianco seguente il carattere 2, il PostScript cessa di accettare caratteri in input e tenta di interpretare la stringa 12 appena ricevuta;
  3. il primo tentativo è quello di interpretarla come un numero, poiché 12 corrisponde per il PostScript ad un numero intero valido, la stringa viene cancellata dal buffer di input, e viene generato un oggetto di tipo numero intero e con valore 12;
  4. l'oggetto appena generato viene messo sullo stack, il quale assume la configurazione 1a;
  5. le operazioni precedenti vengono ripetute per la sequenza 37, e lo stack assume la configurazione 1b;
  6. viene ricevuta la stringa add, la sua interpretazione come numero fallisce, quindi il PostScript tenta di interpretarla come nome; poiché add è uno degli operatori predefiniti del PostScript, nessun oggetto viene generato, ma viene eseguito l'operatore di addizione;
  7. l'operatore add preleva gli ultimi due numeri dallo stack, ne calcola la somma, genera un oggetto di tipo numero intero e di valore pari alla somma dei due addendi, cioè 49, ed infine lo pone sullo stack, il quale assume la configurazione 1c;
  8. viene letta la sequenza ==, di nuovo essa viene riconosciuta come un operatore del PostScript, che viene eseguito
  9. l'operatore == preleva l'oggetto in cima allo stack e ne stampa la rappresentazione sullo standard output (in questo caso il valore del numero intero); lo stack assume quindi la configurazione 1d.
  10. l'operatore flush ha lo scopo di svuotare il buffer di output della LaserWriter, che non viene normalmente svuotato fino al riempimento, facendo cosi trasmettere i caratteri al computer.

Gli operatori del PostScript

Gli operatori primitivi del PostScript sono, nella release 38.0 oltre 240; per fortuna molti di essi sono di uso raro e si può utilmente lavorare in PostScript conoscendone solo qualche decina. Non lamentatevi mai comunque del numero eccessivo di operatori; buona parte della potenza del PostScript risiede proprio nella presenza di questi operatori estremamente specializzati e potenti, che se ne stanno buoni da una parte in attesa che ne abbiate bisogno.

Gli operatori del PostScript possono essere classificati in cinque gruppi principali:

Questa ultima classe è ovviamente device dependent; gli esempi che faremo durante il corso riguarderanno, salvo avviso contrario, quelli implementati sulla LaserWriter.

Il programma della prima parte

Commentiamo adesso il programma che è stato eseguito nella prima parte di questo corso; ci limiteremo però solo ad accennare al funzionamento degli operatori che incontreremo, riservandoci di definirli completamente in seguito. Il listato era


newpath
72 72 moveto
432 0 rlineto
0 720 rlineto
-432 0 rlineto
closepath
12 setlinewidth
stroke
/Times-Bold findfont
72 scalefont setfont
144 432 moveto
(PostScript) show
showpage


Commentiamolo adesso linea per linea.

Si conclude così questa seconda parte del corso, che vi dovrebbe aver messo in grado di compiere le prime prove utilizzando gli operatori fin qui illustrati; nella prossima parte apprenderemo l'uso di un set di base degli operatori PostScript e ci impadroniremo dell'utilizzo di procedure e dizionari.


Copyright © 1985: Marco A. Calamari