Posłużymy się GhostScriptem jak kalkulatorem, by przećwiczyć postfiksowy zapis wyrażeń. Przy okazji przedstawimy też podzbiór Postscriptu umożliwiający użycie go jak "prawdziwego" języka programowania.
Prosimy o uruchomienie programu Ghostscript poleceniem gs. Otworzy się okno z graficznym podglądem strony, a na konsoli będziemy mogli wprowadzać polecenia. Kończąc pracę, wpiszemy quit.
Zaczniemy od:
0 0 moveto 595 842 lineto stroke
rysującego przekątną strony, by pokazać, że interpreter działa.
Po tym wprowadzimy polecenie:
0 842 595 0 moveto lineto stroke
które narysuje drugą przekątną. Wyjaśnijmy, co tu się stało.
Prosimy o wpisanie ciągu liczb:
9 7 5 3 1
Po wprowadzeniu każdej z nich, w tekście zachęty wyświetli się informacja o liczbie wartości na stosie. Po ostatniej liczbie wprowadzimy polecenie:
stack
które wyświetli aktualną zawartość stosu, nie zmieniając jej.
Następnie wpiszemy:
add sub mul div =
i ręcznie prześledzimy ciąg obliczeń. = zdejmuje i wyświetla wartość z czubka stosu a pozostałe polecenia wykonują odpowiednie operacje arytmetyczne.
Prosimy o przetłumaczenie jakiegoś przykładu wyrażenia w zapisie infiksowym (z nawiasami) na zapis posfiksowy i sprawdzenie w Ghostscripcie, czy wynik wychodzi zgodny z oczekiwaniami.
Następnie przechodzimy do prezentacji PostScriptu jako języka programowania. Zaczniemy od pętli for:
1 2 10 {=} for
Polecenie for ma cztery argumenty - wartość startową, krok, wartość końcową i kod treści pętli. Treść pętli jest wykonywana z wartościa "zmiennej sterującej" na czubku stosu. Powyższy przykład wypisze więc nieparzyste liczby od 1 to 10.
By to przećwiczyć, jeszcze jeden przykład, liczący silnię z 10:
1 2 1 10 {mul} for =Pętle oczywiście można zagnieżdżać. Oto przykład rysujący odcinki między parami punktów na dolnej i górnej krawędzi strony:
0 35 595 {
0 35 595 {0 moveto dup 842 lineto stroke} for
pop
} for
Przed jego wykonaniem warto oczyścić okno podglądu strony poprzez showpage.
W przykładzie mamy dwa nowe polecenia: dup powielające wartość na czubku stosu i pop zdejmujące wartość z czubka stosu.
Kolejny przykład wypisuje tekstowo choinkę (bez pnia). Mamy tu prostszą formę pętli - "repeat":
0 1 20 {
dup 20 exch sub {( ) print} repeat {(**) print} repeat (*\n) print
} for
repeat wykonuje podany na czubku stosu kod liczbę razy wskazaną wartością pod czubkiem stosu. Mamy tu też użycie polecenia exch zamieniającego miejscami dwie wartości z czubka stosu i print wypisującego napis.
W kolejnym przykładzie pokażemy, jak można definiować odpowiedniki Pascalowych procedur/funkcji:
/Pionowa {dup 0 moveto 842 lineto stroke} def
/Pionowa jest tu nazwą, którą wiążemy z podanym kodem za pomocą polecenia def. Dostajemy w ten sposób procedurę, w tym przypadku jednoparametrową, rysującą dla zadanej współrzędnej x pionową kreskę przez całą stronę. Możemy ją wywołać np. poleceniem:
100 Pionowa
albo:
200 5 400 {Pionowa} forKolejny przykład, tym razem z funkcją:
/Silnia {dup 1 gt {dup 1 sub Silnia mul} if} def
Jest tu polecenie gt sprawdzające, czy między dwiema wartościami zachodzi relacja >, oraz if dostające na czubku stosu kod i wykonujące go, jeśli pod czubkiem stosu jest wartość logiczna "prawda".
Przykład uruchomimy poleceniem:
5 Silnia =
Jeszcze jedna funkcja rekurencyjna:
/Fib {dup 1 gt {dup 1 sub Fib exch 2 sub Fib add} if} defuruchamiana np. tak:
0 1 10 {Fib =} for
Żonglowanie wartościami na stosie bywa niewygodne i łatwo się pomylić. Alternatywą jest posłużenie się zmiennymi, którym nadajemy wartość znanym już polecenien def. Wpiszmy:
/NWD {
/a exch def
/b exch def
{a b eq {exit} if a b sub dup 0 gt {/a} {/b} ifelse exch abs def} loop
a
} defi przetestujmy:
234 432 NWD =
W tym przykładzie jest kilka nowych poleceń - eq badające równość, abs liczące wartość bezwzględną, loop realizujące potencjalnie nieskończoną pętlę i exit przerywające pętlę.
Następny przykład rysuje odcinki od środka strony do punktów na okręgu:
/szerokosc 595 def
/wysokosc 842 def
/NaSrodek {szerokosc 2 div wysokosc 2 div moveto} def
0 5 360 {dup sin 250 mul exch cos 250 mul NaSrodek rlineto} for stroke
Tym razem za pomocą def zdefiniowaliśmy nie tylko pomocniczą procedurę, ale i dwie stałe. Korzystamy tu też z sin i cos.
W kolejnym przykładzie - implementacji sita Eratostenesa - posłużymy się tablicą:
/n 100 def
/t n 1 add array def
2 1 n sqrt {dup dup mul exch n {t exch 1 put} for} for
2 1 n {dup t exch get 1 ne {=} {pop} ifelse} for
array tworzy tablicę o rozmiarze podanym na czubku stosu. Tablice w PostScripcie są indeksowane od 0, a wartością początkową poszczególnych elementów jest null. Wartość z tablicy odczytujemy za pomocą get, które na czubku stosu dostaje indeks a pod nim tablicę. Zapis do tablicy realizuje put z trzema argumentami (w kolejności od czubka stosu): wartość, indeks i tablica.
Mamy tu też sqrt, czyli pierwiastek kwadratowy i ne sprawdzające, czy wartości są różne.
W ostatnim przykładzie skorzystamy z generatora liczb pseudolosowych i wprowadzimy do rysunku kolor:
/Losowa {rand 2 31 exp div} def
/LosowyPunkt {Losowa szerokosc mul Losowa wysokosc mul} def
10 setlinewidth
1000 {
Losowa Losowa Losowa setrgbcolor
LosowyPunkt moveto LosowyPunkt lineto stroke
} repeat
rand daje losową liczbę całkowitą od 0 do 2^31-1, exp to potęgowanie, setlinewidth ustala
szerokość kreski w punktach (domyślnie jest 1) a setrgbcolor wskazuje aktualny kolor na podstawie trzech liczb rzeczywistych z przedziału [0 .. 1] określających składową czerwoną, zieloną i niebieską.
Przejdźmy do kolejnego programu przykładowego (cesaro.pas):
program cesaro; const SZEROKOSC_STRONY = 595; WYSOKOSC_STRONY = 842; MARGINES = 5; DLUGOSC_BOKU = SZEROKOSC_STRONY - 2 * MARGINES; ZMIANA_KATA = PI / 2.1; PODZIAL_DLUGOSCI = 2 * (1 + cos(ZMIANA_KATA)); N = 5; procedure r(dlugosc, kat : Real; krok : Integer); begin if krok = N then writeln(dlugosc * cos(kat), ' ', dlugosc * sin(kat), ' rlineto') else begin r(dlugosc / PODZIAL_DLUGOSCI, kat, krok + 1); r(dlugosc / PODZIAL_DLUGOSCI, kat + ZMIANA_KATA, krok + 1); r(dlugosc / PODZIAL_DLUGOSCI, kat - ZMIANA_KATA, krok + 1); r(dlugosc / PODZIAL_DLUGOSCI, kat, krok + 1) end end; begin writeln('%!PS'); {writeln('[2] 0 setdash');} writeln(MARGINES, ' ', (WYSOKOSC_STRONY - DLUGOSC_BOKU) / 2, ' moveto'); r(DLUGOSC_BOKU, 0, 0); r(DLUGOSC_BOKU, PI / 2, 0); r(DLUGOSC_BOKU, PI, 0); r(DLUGOSC_BOKU, 3 * PI / 2, 0); writeln('stroke showpage') end.
Kompilujemy go i uruchamiamy, przekierowując wynik do pliku wynik.ps, a następnie otwieramy ten plik programem Ghostscriptem.
Program ten rysuje fraktal Cesaro. Zmieniając wartość stałej N na 0, 1, 2 itd. powinniśmy zrozumieć rekurencyjny proces budowy krzywej. Wartość stałej "ZMIANA_KATA" można modyfikować w zakresie [O, π / 2]. W szczególności, dla π / 3 dostaniemy "cztery-trzecie" płatka (a właściwie antypłatka) Kocha.
Przykład korzysta z polecenia rlineto ("r" od "relative"), które jest odpowiednikiem lineto, ale z argumentami wskazującymi wektor, a nie współrzędne docelowe. Do kompletu w Postscripcie jest też rmoveto.
Dodatkowo, korzystamy tu też z faktu, że w skład rysowanej "ścieżki" może wchodzić więcej niż jeden odcinek. Kolejne polecenia lineto czy rlineto rysują odcinki od miejsca, w którym zakończył się odcinek poprzedni.
Odkomentujmy instrukcję writeln('[2] 0 setdash'). Powoduje ona, że linia rysowania staje się przerywana. Pierwszy argument to tablica określająca cykliczną sekwencję kresek i przerw. W tym wypadku będzie kreska długości dwóch punktów, następnie dwa punkty przerwy itd. Drugi argument mówi, w którym miejscu tej sekwencji zaczynamy - 0 oznacza, że od
początku.