0492 - Język FORTH cz.4

Na Początku tego odcinka chciałbym zacytować fragment listu, jaki otrzymałem od pewnego właściciela Amigi. Była to odpowiedź na moje pytanie o język FORTH na ten Komputer. Zacytuję : "Chciałam Pana poinformować, że języki FORTH, FORTRAN nie istnieją na Amigę. Myślę że nie ma sensu programować w języku FORTH, jeżeli on praktycznie wychodzi z użytku". Mam nadzieję, że użytkownicy komputerów ATARI XL/XE nie podzielają poglądów zawartych w tych słowach.

Aby rozwiać całkowicie wszelkie wątpliwości, postanowiłem skomentować powyższy fragment listu. Po pierwsze, języki FORTH i FORTRAN łączą cztery rzeczy : F, O, R i T. Poza tym, są to języki całkowicie odmienne. Po drugie. Sądzę, że język FORTH nie zdążył jeszcze dobrze wejść do użytku, aby miał już z niego wychodzić. A swoją drogą : oczywiście, język FORTH istnieje w wersji na Amigę. Przystąpmy jednak do kolejnej odsłony, ukazującej tajniki naszego języka.

ZADANIE

Przed poznaniem kolejnych słów postawmy przed sobą następujące zadanie : Napisać słowo o nazwie PAUSE, zatrzymujące program na ustalony czas. Parametrem wejściowym tego słowa będzie liczba określająca czas postoju. Większość czytelników domyśla się zapewne, że słowo to stworzymy w oparciu o wewnętrzny zegar naszego komputera.

PĘTLE

Aby wykonać postawione zadanie, musimy poznać podstawową strukturę każdego języka programowania. Jest nią pętla. Zacznijmy od tej najprostszej, a przy tym - najmniej pożytecznej. Ma ona następującą postać :

BEGIN ... AGAIN

Pętla ta wykonuje bez końca wszystkie słowa zawarte między BEGIN a AGAIN. Uwaga ! Wszystkie pętle w języku FORTH muszą występować wewnątrz zadeklarowanego słowa. Niedopuszczalne jest użycie takiej pętli w trybie bezpośrednim, np. BEGIN 1 .AGAIN RETURN.

Każda pętla musi mieć swój początek i koniec wewnątrz jednego słowa, np :

 : PET BEGIN 1 . AGAIN ; RETURN

Wykonajmy teraz nasze słowo pisząc :

 PET RETURN

Słowo to będzie się wykonywało w nieskończoność i nie pomoże nam naciskanie klawisza BREAK. Jedynym ratunkiem jest niezastąpiony RESET, po którym FORTH wróci do stanu początkowego.

Następną pętlą, którą chciałbym przedstawić jest tzw. pętla liczona. Ma ona postać :

n2 n1 DO ... LOOP

n1 - to liczba określająca wartość początkową pętli, znajdująca się na szczycie stosu,

n2 - wartość końcowa licznika pętli, znajdująca się na stosie pod n1,

DO - otwiera pętle liczoną,

... - tu znajdują się dowolne słowa wewnątrz pętli,

LOOP - zamyka pętlę liczoną.

Oczywiście, pętla ta może występować tylko wewnątrz zadeklarowanego słowa. Przykład :

 : PEL 20 0 DO 1 . LOOP ; RETURN

I sprawdzamy :

 PEL RETURN

Na ekranie ukazało się 20 jedynek. Aby uzyskać dostęp do licznika pętli wprowadzono słowo I. Wywołanie tego słowa (tylko wewnątrz pętli liczonej) zostawia na stosie aktualny stan licznika pętli. Przykład :

 : PEI 20 0 DO I . LOOP ; RETURN

i wywołujemy :

 PEI RETURN

Na ekranie ukazały się liczby od 0 do 19, a nie do 20, jakbyśmy się spodziewali.

Pętla była wykonywana, dopóki licznik nie osiągnął wartości końcowej, czyli w  naszym przypadku 20. Wartość licznika jest zwiększana w słowie LOOP, tam też jest sprawdzany warunek końcowy. W przypadku osiągnięcia przez licznik wartości końcowej pętla zakończy się.

FORTH rozporządza także bardziej skomplikowaną odmianą pętli liczonej. Ma ona następującą postać :

n2 n1 DO ... n +LOOP

n2 i n1 - to liczby na stosie mające identyczne znaczenie jak w poprzedniej pętli,

DO - otwiera pętlę,

... - słowa wewnątrz pętli,

n - liczba określająca skok licznika pętli. Musi zawsze występować przed +LOOP a może przyjmować dowolne wartości, także ujemne,

+LOOP - słowo kończące pętlę.

Podczas korzystania z tej pętli należy uważać, aby licznik zbliżał się do górnej granicy, a nie oddalał.

Błędny byłby zapis :

: PEM 20 0 DO I . -1 +LOOP ; RETURN

Wartość początkowa licznika wynosi 0, a z każdym krokiem pętli licznik jest zwiększany o -1, czyli zmniejszany. Licznik zamiast zbliżać się do górnej granicy, oddala się od niej. Pętla ta nie zostanie nigdy zakończona.

Poprawny będzie jednak następujący zapis :

 : PEM 0 20 DO I . -1 +LOOP ; RETURN

Wartością początkową jest 20, a końcową 0. Słowo to po wywołaniu wyświetli liczby od 20 do 1.

Skoro poznaliśmy już pętle liczone, przeprowadźmy test sprawdzający szybkość języka FORTH na naszym komputerze. Deklarujemy słowo TEST :

 : TEST 30000 0 DO LOOP ; RETURN

Wywołując słowo ze stoperem w ręku, możemy określić czas jego wykonania. Gdy ukaże się napis OK, oznacza to zakończenie działania słowa. Aby uzyskać większą dokładność pomiaru czasu, możemy wykorzystać stworzone w poprzednich odcinkach słowa : 0ZEGAR! i ZEGAR@.

Piszemy sekwencję :

 0ZEGAR! TEST ZEGAR@ . RETURN

Na ekranie pojawiła się liczba, która podzielona przez 50 da nam czas w sekundach. Tak, to nie przywidzenie, pętla wykonywała się około 3 sekund. Dla porównania, podobna pętla (FOR NEXT) w języku ATARI BASIC jest liczona 58 sekund.

PORÓWNANIA

Aby przedstawić inne rodzaje pętli, muszę zapoznać czytelników ze słowami wykonującymi porównania. Zacznijmy od słowa "=". Operator ten pobiera i porównuje dwie liczby będące najbliżej szczytu stosu. Na ich miejscu zostawia jedną liczbę będącą tzw. znacznikiem porównania. Znacznik ten przybiera wartość 0 w przypadku fałszu i 1 dla prawdy.

W języku FORTH każda liczba niezerowa jest znacznikiem prawdy. Należy o tym pamiętać, gdyż jak przekonamy się dalej. Jako znacznik można wykorzystać wynik z dowolnego działania matematycznego.

Kolejnymi słowami porównania będą operatory :

< - mniejszości,

> - większości.

Słowa te działają na stosie bardzo podobnie do operatora równości, lecz zostawiany wskaźnik jest obliczany następująco :

Dla < znacznik przybiera wartość 1 (czyli prawdy), gdy liczba znajdująca się niżej na stosie jest mniejsza od liczby będącej na jej szczycie.

Dla > znacznik wyniesie 1 (prawda), gdy liczba w głębi stosu jest większa od liczby na szczycie.

Sprawdźmy to w praktyce pisząc na komputerze :

 10 5 = . RETURN

Otrzymamy wynik 0. Jeszcze raz :

 10 10 = . RETURN

Tym razem otrzymamy oczywiście 1.

Dla nierówności :

 10 5 > . RETURN

jest 1, a dla :

 10 5 < . RETURN

Oczywiście 0.

Zauważmy, że znaki porównywania wystąpiły w zapisie za literami, a nie miedzy nimi. Wynika to z zastosowanej notacji zapisu działania (tzw. Odwrotnej Notacji Polskiej).

Poznane dotychczas porównania interpretują dane wejściowe jako liczby ze znakiem. Aby umożliwić porównanie liczb bez znaku, należy zastosować słowo "U<". Działa ono podobnie jak <, lecz nie uwzględnia znaku liczby. Pamiętajmy o tym, że każda liczba większa od 32767 jest interpretowana przez większość słów jako ujemna. Była o tym mowa w poprzednich odcinkach tego cyklu. Spróbujmy :

20000 45000 < . RETURN

Otrzymaliśmy wynik 0, a powinno być 1. Tak, lecz słowo < interpretuje liczby jako ujemne o całkowicie innych właściwościach. Sprawdźmy, jak widzi te liczby słowo <. Piszemy :

20000 . RETURN

i

45000 . RETURN

Widzimy, że nasze słowo zachowało się całkowicie poprawnie. Ten sam warunek powinien wyglądać następująco :

 20000 45000 U< . RETURN

Teraz otrzymaliśmy wartość 1. Słowo U< znajduje szerokie zastosowanie przy porównywaniu adresów, które jak wiemy - są liczbami dodatnimi.

Operatory przedstawione poniżej wymagają tylko jednego argumentu wejściowego. Są nimi :

0= i 0<

Wykonują one porównanie liczby będącej na szczycie stosu z zerem.

Operator 0= pobiera liczbę ze szczytu stosu i zostawia na jej miejsce znacznik równy 1 (prawda), gdy liczba równa się 0, a 0 (fałsz), gdy jest inaczej.

Operator 0< zostawia na stosie znacznik prawdy (1) w przypadku, gdy liczba na stosie była ujemna.

Słowo 0= pełni w języku FORTH także funkcję operatora NOT, znanego nam z innych języków. Umożliwia ono zamianę znacznika (prawda) 1 na (fałsz) 0 lub odwrotnie.

Czytelnicy zauważyli zapewne, że nie ma wśród standardowych słów języka FORTH słowa U> lub 0>. Co zrobić w przypadku, gdy zaistnieje konieczność wykonania takiego porównania ? Nie ma z tym żadnych kłopotów, gdyż FORTH jest językiem umożliwiającym samodzielne tworzenie brakujących słów. Przykład :

 : 0> 1 - 0< 0= ; RETURN

Zapewne zrozumiałe jest dla czytelników zastosowanie w prezentowanej deklaracji słowa 0<, a następnie zaprzeczenie znacznika przez 0=. Po co jednak odjęliśmy 1 od liczby na stosie ? Gdyby opuścić tę operację, powstałoby następujące słowo :

: 0>= 0< 0= ; - Słowo to zostawiałoby znacznik prawdy także w przypadku, gdy liczba ze stosu równałaby się 0.

Mam nadzieję, że se słowem U> czytelnicy poradzą sobie sami.

Kończąc opisywanie porównań, chciałbym zwrócić uwagę, że porównania wymagają zawsze parametrów na stosie. Niedopuszczalny byłby zapis : "10 5 < =", gdyż dla słowa = zabraknie jednego parametru. W takim przypadku należy zastosować :

 10 5 > 0= RETURN

Aby zwiększyć czytelność możemy stworzyć odpowiednie słowo :

 : <= > 0= ; RETURN

i zapisać :

 10 5 <= RETURN

PĘTLE, CIĄG DALSZY...

Skoro poznaliśmy już operatory porównań, powróćmy do pętli. Kolejna z nich będzie miała postać :

BEGIN ... ƒ UNTIL

BEGIN - Początek pętli,

... - jak w poprzednich przykładach,

ƒ - znacznik logiczny zostawiony na stosie,

UNTIL - Koniec pętli.

Pamiętajmy, że znacznik ƒ musi być zostawiony na stosie po każdym przebiegu pętli, gdyż słowo UNTIL pobiera go ze stosu.

Pętla ta jest wykonywana tak długo, aż znacznik ƒ przybierze wartość różną od zera (prawda). Słowa będące wewnątrz pętli muszą ten znacznik odpowiednio modyfikować. Przykład :

 : ES 255 764 C! BEGIN 764 
 C@ 28 = UNTIL ; RETURN

Zadeklarowaliśmy słowo o nazwie ES. Co ono wykonuje ? Sprawdźmy :

 ES RETURN

Nic się nie dzieje, dopiero naciśnięcie klawisza ESC powoduje opuszczenie słowa. Jedyną jego funkcją jest właśnie czekanie na naciśnięcie klawisza.

Ostatnia z prezentowanych pętli ma postać :

BEGIN ... ƒ WHILE ... REPEAT

BEGIN - Początek pętli,

... - ciąg słów,

ƒ - znacznik logiczny na stosie,

WHILE - Słowo pobierające i sprawdzające znacznik. Gdy jest on równy 0 (fałsz), pętla jest opuszczana (skok za słowo REPEAT),

REPEAT - Kończy pętlę.

Zauważmy, że pętla ta różni się od poprzedniej, nie tylko odmiennym interpretowaniem znacznika, ale także dowolnością umieszczania słowa WHILE sprawdzającego warunek. Jeżeli umieścimy WHILE na początku pętli (zaraz po słowie BEGIN), może ona nie być wykonana ani raz, podczas gdy BEGIN UNTIL musi się wykonać co najmniej raz. Przykład :

 : AA 255 764 C! BEGIN 1 . 764 C@ 255 = WHILE 2 . REPEAT ; RETURN

Nowo stworzone słowo AA wyświetla kolejno pary liczb 1 i 2. Gdy naciśniemy dowolny klawisz (komórka 764 przyjmie wartość różną od 255), pętla zostanie opuszczona. W ostatnim przebiegu pętli, słowa znajdujące się po WHILE nie będą wykonane (liczba 2 nie zostanie wyświetlona).

JESZCZE O STOSIE

Prezentowane w tej części artykułu słowa dotyczą stosu i nie mają w związku z tym odpowiedników w innych językach. Są to :

DUP - Słowo to tworzy duplikat liczby znajdującej się na szczycie stosu i zostawia go nad tą liczbą, powiększając stos,

SWAP - Zamienia miedzy sobą dwie liczby znajdujące się najbliżej szczytu stosu,

DROP - Usuwa liczbę ze stosu,

OVER - Tworzy kopię liczby znajdującej się pod liczbą ze szczytu i umieszcza ją nad liczbą ze szczytu, powiększając odpowiednio stos. Liczby znajdujące się dotychczas na stosie nie ulegają zmianie.

ROT - Ustawia trzy liczby znajdujące się najbliżej sczytu w ten sposób, że trzecią wyciąga na szczyt, a dwie pozostałe obniża o jedno miejsce.

Przetestujmy działanie dwóch ostatnich słów, gdyż wydają się one najtrudniejsze do zrozumienia. Piszemy :

2 3 OVER ... RETURN - Uzyskaliśmy ciąg liczb: 2 3 2. liczba 2 została skopiowana nad dotychczasowym szczytem stosu, czyli liczbą 3. Następny przykład :

1 2 3 ROT ... RETURN - Tym razem zobaczymy 132.

Pamiętajmy, że szczyt stosu przy wprowadzaniu znajduje się po prawej stronie ciagu i jest nim liczba 3. Przy wyświetlaniu - odwrotnie, gdyż najpierw będzie pobrana liczba ze szczytu. Widzimy więc, że liczba 1 przeskoczyła z trzeciego miejsca na szczyt stosu.

Jeżeli twój FORTH posiada słowo S. lub .S to możesz je stosować do przeglądania zawartości stosu.

REALIZACJA ZADANIA

Skoro poznaliśmy już wszystkie niezbędne struktury języka FORTH, możemy przystąpić do rozwiązania problemu postawionego na początku artykułu.

Do napisania słowa PAUSE wykorzystamy wcześniej stworzone słowa 0ZEGAR! i ZEGAR@. Były one opisane w poprzednich odcinkach tego cyklu. Więc do dzieła!

 : PAUSE 0ZEGAR! BEGIN DUP ZEGAR@ = UNTIL DROP ; RETURN

BEGIN - zeruje stan zegara,

DUP - wykonuje duplikat parametru wejściowego,

ZEGAR@ - zostawia na stosie stan zegara,

= - porównuje wartość pobraną z komórek zegara z parametrem wejściowym,

UNTIL - powoduje opuszczenie pętli, gdy znacznik logiczny pozostawiony na stosie przez = ma wartość niezerową (prawda),

Nasze słowo PAUSE wymaga zawsze parametru wejściowego, np;

 50 PAUSE RETURN

Liczba występująca przed PAUSE, określa czas działania słowa. Dla wartości równej 50 będzie on wynosił dokładnie 1 sekundę. Sposób realizacji słowa PAUSE, przedstawiony przeze mnie, nie jest najlepszy, gdyż ingeruje w stan zegara. Proszę więc o wasze rozwiązania słowa PAUSE, oczywiście w języku FORTH. Najlepsze z nich zostaną zaprezentowane na łamach TA.

Roland Pantoła,
Tajemnice Atari, nr. 04/1992r.