[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [volume] [parte]


Capitolo 425.   PostScript: espressioni e funzioni

Il linguaggio PostScript è nato per essere interpretato in modo veloce da stampanti realizzate appositamente. In questo senso, la sua logica segue vagamente quella di un linguaggio assemblatore.

425.1   La pila

Per poter scrivere codice PostScript un po' più complesso, diventa necessario l'utilizzo di istruzioni che realizzano delle espressioni, fino ad arrivare alla costruzione di funzioni (procedure) che possono essere richiamate successivamente. Purtroppo, le espressioni realizzate con questo linguaggio, diventano un po' complicate da leggere. Infatti, queste funzioni ricevono i loro argomenti prelevandoli da una pila (stack) ed emettono risultati inserendoli nella stessa pila.

dato... funzione

Osservando il modello, le informazioni che non sono riconducibili a nomi di funzione, vengono inserite in questa pila, che poi la prima funzione inizia a leggere. Si osservi l'istruzione seguente:

1 2 3 4 5 6 7 8 9 moveto lineto

I valori da uno a nove, vengono inseriti così come sono nella pila, poi ogni funzione preleva dalla pila la quantità di argomenti che la riguarda. In questo caso, moveto preleva gli ultimi due valori a essere stati inseriti, precisamente la coppia otto e nove, spostando le coordinate correnti in 8 , 9; successivamente è il turno di lineto, che preleva altri due valori, precisamente il sei e il sette, tracciando una linea fino al punto 6 , 7. Pertanto, tutto è come se fosse scritto nel modo seguente:

8 9 moveto 6 7 lineto

Tuttavia, rimangono ancora altri valori nella pila, per altre funzioni successive, ammesso che vogliano usarli, perché se si inseriscono altri valori, questi vengono poi estratti per primi.

Dato questo meccanismo, diventano importanti alcune funzioni che consentono di intervenire su questa pila: clear svuota completamente la pila; pop preleva ed elimina l'ultimo valore inserito. A fianco di pop si potrebbe immaginare la presenza di una funzione con il nome push, ma in questo caso non serve, perché l'azione di inserimento nella pila avviene in modo implicito.

Sono un po' più difficili da comprendere le funzioni exch e roll. La prima scambia l'ordine degli ultimi due valori inseriti nella pila; la seconda esegue uno scorrimento, verso sinistra o verso destra di una certa quantità di questi valori:

n_elementi_da_scorrere scorrimento roll

Per esempio, se nella pila ci fossero i valori 1, 2, 3, 4, 5, 6 e 7, in questo ordine, per cui il primo a essere prelevato sarebbe il numero 7, l'istruzione 3 2 roll trasformerebbe questa sequenza in 1, 2, 3, 4, 6, 7 e 5; al contrario, l'istruzione 3 -2 roll trasformerebbe questa sequenza in 1, 2, 3, 4, 7, 5 e 6. In pratica, il primo valore indica quanti elementi prendere in considerazione, a partire dall'ultimo, mentre il secondo indica quante volte scorrere e in quale direzione.

Figura 425.3. Esempio di funzionamento dell'istruzione roll, con uno scorrimento verso destra.

pila iniziale:          1 2 3 4 5 6 7

    3 2 roll                    7 5 6  (primo scorrimento verso destra)
                                6 7 5  (secondo scorrimento verso destra)
pila finale:            1 2 3 4 6 7 5

Figura 425.4. Esempio di funzionamento dell'istruzione roll, con uno scorrimento verso sinistra.

pila iniziale:          1 2 3 4 5 6 7

    3 -2 roll                   6 7 5  (primo scorrimento verso sinistra)
                                7 5 6  (secondo scorrimento verso sinistra)
pila finale:            1 2 3 4 7 5 6

Quando una funzione restituisce un valore, lo fa inserendolo implicitamente nella pila. In questo modo, l'assegnamento a una variabile, così come si è abituati nei linguaggi di programmazione comuni, non c'è. Al massimo si definisce una funzione che restituisce un valore, inserendolo nella pila.

Anche le funzioni exch e roll prelevano dei valori dalla pila e poi ne inseriscono degli altri nella stessa. In pratica, exch preleva due valori, li scambia e li inserisce nella pila; roll preleva due valori, li interpreta, quindi preleva un gruppo di altri valori, li fa scorrere in un verso o nell'altro, quindi li inserisce nuovamente nella pila.

A questo punto si può cominciare a comprendere che i dati inseriti nella pila, quando ciò non avviene per mezzo di una funzione che restituisce qualcosa, devono avere una rappresentazione formale. Può trattarsi di: valori numerici, che si scrivono come sono, utilizzando il punto per separare la parte decimale; stringhe, che sono delimitate da parentesi tonde e possono contenere delle sequenze di escape; espressioni, che sono delimitate tra parentesi graffe (si ricordi il caso della funzione repeat). I valori logici, Vero e Falso, non hanno una rappresentazione particolare e si indicano espressamente solo attraverso le funzioni true e false.

Tabella 425.5. Rappresentazione dei dati e gestione della pila.

Istruzione Descrizione
intero[.decimale]
Inserisce il valore numerico nella pila.
(stringa)
Inserisce la stringa nella pila.
{espressione}
Inserisce le istruzioni nella pila.
clear
Svuota la pila.
oggetto pop
Preleva dalla pila l'ultimo valore inserito.
oggetto_1 oggetto_2 exch
Scambia gli ultimi due valori nella pila.
m n roll
Fa scorrere gli ultimi m elementi della pila di n posizioni verso destra.
m -n roll
Fa scorrere gli ultimi m elementi della pila di n posizioni verso sinistra.
oggetto dup
Preleva l'ultimo valore e ne inserisce due copie nella pila.

425.2   Funzioni comuni

Alcune funzioni operano su valori numerici, restituendo un risultato che, secondo la logica del linguaggio PostScript, viene inserito nella pila. Per esempio, la funzione add riceve due valori restituendo la somma di questi:

10 20 add 40 moveto

In questo caso, vengono sommati i valori 10 e 20, inserendo nella pila il valore 30. Così, si ottiene lo spostamento nelle coordinate 30 , 40, attraverso la funzione moveto.

I valori logici, come accennato, si indicano attraverso le funzioni true e false, che si limitano rispettivamente a inserire nella pila il valore corrispondente. Possono generare risultati logici anche alcune funzioni di confronto e i valori logici possono essere rielaborati attraverso funzioni booleane. Infine, in base a un valore logico è possibile eseguire o meno un gruppo di espressioni. Si osservino gli esempi seguenti.

10 20 lt
La funzione lt confronta due valori e restituisce Vero se il primo (secondo la lettura umana) è minore. In questo caso, viene restituito Vero.
10 20 lt 45 34 gt and
La funzione and restituisce Vero se riceve due valori Vero simultaneamente. In questo caso, la funzione lt inserisce il valore Vero nella pila e anche la funzione gt inserisce un altro valore Vero, dal momento che il confrontando i valori 45 e 34 si vede che il primo è maggiore del secondo.
10 20 lt {45 50 moveto} if
La funzione if preleva un valore logico e un gruppo di istruzioni. Se il valore logico è Vero viene eseguito il raggruppamento di istruzioni. In questo caso, dato che la funzione lt inserisce il valore Vero nella pila, così può essere eseguito lo spostamento nelle coordinate 45 , 50.
10 20 lt {45 50 moveto} \
  \{50 45 moveto} ifelse
La funzione ifelse preleva un valore logico e due gruppi di istruzioni. Se il valore logico è Vero viene eseguito il primo gruppo di istruzioni, altrimenti viene eseguito il secondo. In questo caso, dato che la funzione lt inserisce il valore Vero nella pila, così viene eseguito lo spostamento nelle coordinate 45 , 50.

Queste funzioni vengono descritte brevemente nella tabella 425.8.

Tabella 425.8. Espressioni matematiche, logiche e condizionali.

Istruzione Descrizione
n neg
Inverte il segno del valore.
m n add
Somma i due valori.
m n sub
Sottrae n da m.
m n mul
Moltiplica i valori.
m n div
Divide m per n.
m n mod
Il resto della divisione intera di m per n.
n round
Arrotonda n.
n abs
Calcola il valore assoluto di n.
n sin
Calcola il seno di n.
n cos
Calcola il coseno di n.
m n min
Restituisce il minimo tra due valori.
m n max
Restituisce il massimo tra due valori.
true
Vero.
false
Falso.
m n gt
Vero se m è maggiore di n.
m n ge
Vero se m è maggiore o uguale a n.
m n lt
Vero se m è minore di n.
m n le
Vero se m è minore o uguale a n.
m n eq
Vero se i valori sono uguali.
m n ne
Vero se i valori sono diversi.
bool {istruzioni} if
Esegue le istruzioni se il valore logico è Vero.
bool {istr_1} {istr_2} ifelse
Esegue il primo o il secondo gruppo di istruzioni in base al valore logico.

425.3   Operazioni sulle stringhe

A causa della struttura del linguaggio, la gestione delle stringhe non è affatto intuitiva: bisogna tradurre tutto nell'ottica della pila. Tanto per cominciare, la cosa più semplice che si può fare con una stringa è misurarne la lunghezza con l'aiuto della funzione stringwidth. Per la precisione, si tratta di determinare la posizione finale di una stringa collocata a partire dalle coordinate 0 , 0:

stringa stringwidth

Se si osserva la figura 425.9, si può vedere la stringa composta dalla parola «Ciao», scritta con il carattere Helvetica, avente un corpo di 12 punti. Come si vede, la sua lunghezza è di 24,672 punti.

Figura 425.9. Lunghezza di una stringa.

lunghezza

Quando c'è la necessità di convertire un valore in una stringa, si pone il problema dell'allocazione di memoria per la stringa stessa. Per esempio, la funzione cvs converte un valore in stringa, ma per farlo deve avere già una stringa da prelevare dalla pila:

valore stringa cvs

Volendo convertire il valore 23,45 in stringa, bisogna preparare prima una stringa di almeno cinque caratteri:

23.45 (     ) cvs

Per allocare una stringa, composta da caratteri <NUL>, ovvero 0008, si può usare la funzione string, che richiede l'indicazione della quantità di caratteri. Pertanto, la stessa cosa avrebbe potuto essere scritta nel modo seguente:

23.45 5 string cvs

Naturalmente, la funzione cvs si può usare per visualizzare la stringa generata, per esempio nel modo seguente:

10 10 moveto
23.45
5 string cvs
show

Si osservi che con cvs, anche se si alloca una stringa più grande del necessario, questa viene ridotta alla dimensione richiesta dalla conversione.

Tabella 425.13. Espressioni relative a stringhe.

Istruzione Descrizione
n string
Alloca una stringa di n caratteri <NUL>.
stringa stringwidth
Inserisce le coordinate finali della stringa nella pila.
valore stringa cvs
Restituisce una stringa corrispondente al valore.
valore n string cvs
Restituisce una stringa corrispondente al valore, con un massimo di n caratteri.

425.4   Funzioni

Una funzione si definisce attraverso la sintassi seguente:

/nome {istruzioni} def

In pratica, si vuole fare in modo che usando il nome indicato, si faccia riferimento automaticamente al gruppo di istruzioni contenuto tra parentesi graffe. Si noti che, eccezionalmente, se si tratta di definire una costante o se si vuole ridefinire il nome di un'altra funzione, non sono necessarie le parentesi graffe.

Come per qualunque altra funzione normale, anche le funzioni definite in questo modo ricevono gli argomenti delle chiamate dalla pila. Per esempio, la funzione quadrilatero che si potrebbe dichiarare nel modo seguente, va usata mettendo davanti, ordinatamente gli argomenti per le varie funzioni utilizzate:

/quadrilatero { newpath moveto lineto lineto lineto closepath stroke } def

Per esempio, volendo disegnare un quadrato con gli angoli nelle coordinate 0,0, 0,10, 10,10 e 10,0, si deve usare la funzione quadrilatero nel modo seguente:

10 0 10 10 0 10 0 0 quadrilatero

È importante osservare che la prima coppia di coordinate è quella presa in considerazione dall'ultima funzione lineto contenuta nel raggruppamento di quadrilatero.

Così come si definisce una funzione, si può attribuire a un nome un valore costante. In questi casi eccezionali, è consentita l'eliminazione delle parentesi graffe:

/nome costante def

Con la definizione di costanti, si può stabilire una volta per tutte il valore di qualcosa, come nell'esempio seguente:

/Margine_Sinistro 80 def
/Margine_Destro 80 def
/Margine_Superiore 100 def
/Margine_Inferiore 100 def

425.4.1   Variabili

Nel linguaggio PostScript non è prevista la gestione di variabili: tutto viene elaborato attraverso la pila. Tuttavia, esiste un trucco per ottenere qualcosa che assomigli a delle variabili; si tratta di sfruttare opportunamente la definizione di funzioni. È già stato visto l'assegnamento di un valore costante a un nome:

/nome costante def

Oppure:

/nome { costante } def

Se si vuole attribuire a una funzione un valore diverso, occorre un trucco, che si può schematizzare come segue:

/nome_1 { /nome_2 exch def } def

Si tratta di una funzione che ne dichiara un'altra, ma si osservi con attenzione: la parola chiave exch non è racchiusa tra parentesi graffe e non può esserlo, se si vuole che il meccanismo funzioni.

Per assegnare un valore alla funzione nome_2, si utilizza una chiamata alla funzione nome_1:

n nome_1

Per leggere il valore, si fa riferimento alla funzione nome_2, come nell'esempio seguente in cui si utilizza questo dato come coordinata Y per uno spostamento:

n nome_1
m nome_2 moveto

425.5   Dizionari

La dichiarazione delle funzioni può essere inserita in un dizionario, da richiamare quando serve e da sostituire eventualmente con altre di un altro dizionario, quando la situazione lo richiede. In pratica, la definizione di dizionari di funzioni consente di fare riferimento a gruppi di funzioni solo nell'ambito di un certo contesto, ripristinando un utilizzo differente delle stesse subito dopo.

/dizionario n dict def
dizionario begin
    dichiarazione_di_funzione
    ...
    ...
end

Il modello sintattico mostra in che modo procedere alla dichiarazione di un dizionario. Si può osservare che prima della parola chiave dict occorre indicare un numero, allo scopo di definire una quantità di memoria da allocare per il dizionario stesso. Per abilitare l'uso delle funzioni dichiarate nel dizionario, si deve dichiarare espressamente:

dizionario begin
    istruzione
    ...
    ...
end

Eventualmente, la dichiarazione di utilizzo di un dizionario si può annidare; quando si raggiunge la parola chiave end, termina il campo di azione dell'ultimo dizionario ancora aperto.

In generale, potrebbe essere conveniente inserire la dichiarazione dei dizionari nell'ambito delle istruzioni speciali %%BeginProlog e %%EndProlog. L'esempio seguente mostra un estratto di un file PostScript ipotetico, in cui si dichiara un dizionario (molto breve) e lo si utilizza immediatamente nell'ambito della pagina (i puntini di sospensione indicano una parte mancante del file che non viene mostrata).

%!PS-Adobe-2.0
%%DocumentPaperSizes: a4
%%EndComments
%%BeginProlog
/Mio_Dizionario 50 dict def
Mio_Dizionario begin
    /quadrilatero
    {
        newpath moveto lineto lineto lineto closepath stroke
    } def
    /Margine_Sinistro 80 def
    /Margine_Destro 80 def
    /Margine_Superiore 100 def
    /Margine_Inferiore 100 def
end     % Fine della dichiarazione del dizionario «Mio_Dizionario»
%%EndProlog
Mio_Dizionario begin
%%Page: 1 1
...
...
...
showpage
%%Page: 2 2
...
...
...
showpage
end     % Fine del campo di azione del dizionario «Mio_Dizionario»
%%Trailer
%%EOF

425.6   Riferimenti

Appunti di informatica libera 2007.02 --- Copyright © 2000-2007 Daniele Giacomini -- <daniele (ad) swlibero·org>


Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome postscript_espressioni_e_funzioni.htm

[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico]

Valid ISO-HTML!

CSS validator!