0992 - Język FORTH cz.8

W numerze 2 Tajemnic Atari ogłosiłem mały konkurs na zapis pewnego wyrażenia w języku FORTH. Ponieważ konkurs został już rozstrzygnięty, jestem winien czytelnikom przedstawienie zwycięzcy. Został nim Grzegorz Wysocki w Warszawy. Prawidłowy zapis powinien wyglądać następująco :

2 3 * 1 + 1 2 + 2 3 + * + . RETURN

Gratuluję wygranej i dziękuję czytelnikom za liczny udział w konkursie. W niedługim czasie zajmiemy się tworzeniem nowych słów w języku FORTH. Mam nadzieję, że wielu czytelników przedstawi swoje własne rozwiązania. Aby tego dokonać należy dołączyć do posiadanej wersji Extended fig-FORTH, programy zamieszczone w 6/7 i 8 numerze Tajemnic Atari.

Niestety, w zamieszczonym wcześniej listingu edytora wprowadzania jakaś tajemna siła usunęła dwa słowa, co uniemożliwia uruchomienie tego programu. Słowo K0 zadeklarowane wewnątrz edytora powinno wyglądać następująco :

 : K0 1 *K +! C@ *K @ * DUP EN +! + 1024 MOD ;

Dla właścicieli magnetofonów zmieniamy ostatnią deklarację :

 : EDW-COMP AD0 0 WARNING ! BEGIN EL 1024 + CD @ UNTIL DROP ; RETURN

Po wprowadzeniu (kompilacji) całego edytora musimy wpisać jeszcze następującą sekwencję :

 71 ' E1 6 - C! RETURN

Także w zamieszczonym fragmencie próbnym zmieniamy linię z deklaracją słowa ZEGAR@:

 04O1 : ZEGAR 20 C@ 19 C@ 256 * + ; RETURN

Za powstałe błędy przepraszam wszystkich wielbicieli FORTH-a

ASEMBLER, ciąg dalszy

W tym odcinku będziemy w dalszym ciągu poznawać możliwości asemblera FORTH-a. Przypominam, że listing tego programu został zamieszczony w poprzednim numerze Tajemnic Atari. Asembler FORTH-a zajmuje ok. 1.5 kB pamięci, a mimo to w połączeniu ze słowami FORTH-a daje programiście duże możliwości działania.

Wszystkie asemblery procesora 6502 mają w swoim zestawie instrukcje skoków warunkowych takich jak BCC, BCS, BEQ, BPL, BMI. Nasz asembler nie posiada takich słów w swoim zestawie. W jaki więc sposób wykonać skoki warunkowe, będące podstawą programowania ?

Odgałęzienia

Otrzymujemy do dyspozycji struktury zapożyczone z języków wyższego rzędu. Pierwsza z nich to :

IF, ... ELSE, ... THEN,

Każde z prezentowanych słów ma na końcu nazwy przecinek, aby nie zostało pomylone przez kompilator ze słowami FORTH-a o podobnej nazwie. Kropkami zostały oznaczone miejsca, w których możemy umieścić dowolne rozkazy asemblera. Słowo IF, (podobnie jak w języku FORTH) sprawdza warunek i jeżeli jest on spełniony wykonywane są rozkazy znajdujące się między IF, a ELSE. W przeciwnym wypadku między ELSE. a THEN, . W języku FORTH sprawa była prosta, gdyż IF. pobierało znacznik ze szczytu stosu. Co jednak ma sprawdzać słowo IF, w asemblerze ? Przed zapisem tego słowa należy podać jeden z następujących symboli :

CS >= 0= 0<

Symbol CS oraz >= sprawdza znacznik przeniesienia C. Gdy jest on ustawiony oznacza to prawdę, gdy nie - fałsz. Dziwić może występowanie symbolu >= oznaczającego sprawdzanie znacznika C. Użycie go po rozkazie porównania, np. CMP, znacznie polepsza czytelność zapisu. Kolejnym symbolem jest 0= i oznacza sprawdzenie znacznika Z czyli zera. Słowo IF, odczyta prawdę, gdy wynik z działania ostatniego rozkazu wyniósł 0. Ostatnim symbolem jest 0< i oznacza sprawdzenie znacznika N, czyli wyniku ujemnego. Słowo IF, odczyta prawdę gdy wynik ostatniej operacji przybrał wartość ujemną.

Po tych rozważaniach teoretycznych przejdźmy do praktyki. Wcześniej jednak usuńmy lewy margines, wpisując 0 82 C! RETURN, a posiadacze magnetofonów obowiązkowo 0 WARNING ! RETURN. Przykład :

 0 VARIABLE WA RETURN
 CODE ZMW WA LDA, 100 # CMP, >= IF, WA DEC, RETURN
 ELSE, WA INC, THEN, NEXT JMP, C; RETURN

Nasze słowo ZMW będzie działało na zmiennej WA. Co ono wykonuje ? Wprowadźmy do zmiennej liczbę 10 :

 10 WA ! RETURN

I wykonajmy :

 ZMW (RE) WA ? (RE) RETURN

Widzimy, że słowo ZMW (napisane w asemblerze) zwiększyło zmienną o 1. Wykonajmy teraz następującą sekwencję :

 150 WA ! RETURN
 ZMW RETURN
 WA ? RETURN

Tym razem wartość zmiennej została zmniejszona o 1.

Analizując tekst źródłowy słowa ZMW łatwo zauważyć, że rozkaz WA DEC, będzie wykonywany tylko wtedy, gdy wartość zmiennej WA jest większa lub równa 100. W przeciwnym wypadku wykonany będzie rozkaz WA INC, czyli zwiększenie wartości zmiennej o 1. Instrukcja porównania (100 # CMP,) jest konieczna, gdyż wpływa ona na odpowiednie ustawienie znacznika C określonego przez symbol >= dla słowa IF, W asemblerze FORTH-a występuje także symbol NOT . Możemy go używać tylko w połączeniu z innymi wcześniej poznanym symbolem. Przykład :

0= NOT IF, ... THEN,

Rozkazy znajdujące się między IF, a THEN, będą wykonane tylko wtedy, gdy znacznik zera Z nie będzie ustawiony. Oznacza to, że symbol NOT neguje wartość logiczną poprzedniego symbolu. Spróbujmy sprawdzić działanie NOT i utworzyć słowo działające odwrotnie niż ZMW.

 0 VARIABLE WA RETURN
 CODE ZM< WA LDA, 100 # CMP, >= NOT IF, WA
 DEC, ELSE, WA INC, THEN, NEXT JMP, C; RETURN

Widzimy, że wszystko jest takie samo, lecz NOT zmieniło wartość logiczną symbolu >= na przeciwną. Sprawdzenie działania tego słowa pozostawiam czytelnikom. Uwaga : symbolu NOT należy używać zawsze za innym symbolem logicznym, nigdy odwrotnie.

Symbole logiczne występujące przed IF, nie zostaną skompilowane oddzielnie, informują one jedynie kompilator jaki skok warunkowy (BNE, BEQ itp.) znajduje się na miejscu słowa IF, po kompilacji

Kompilacją słów napisanych przez nas w asemblerze zajmuje się standardowy kompilator FORTH-a. Program nazwany przez nas asemblerem to jedynie zestaw słów informujących kompilator FORTH-a w jaki sposób ma tego dokonać.

Widzimy więc, że można by względnie łatwo stworzyć zestaw słów umożliwiających kompilację programu w PASCAL-u lub C. Ambitny programista może się pokusić o stworzenie kompilatora swojego własnego języka. Realizacja tego zadania w języku FORTH, byłaby o wiele prostsza od podobnego przedsięwzięcia w asemblerze. Niestety, czas kompilacji programu byłby zawsze kilkukrotnie dłuższy. Powróćmy jednak do naszego asemblera.

PĘTLE

Poznaliśmy odgałęzienia warunkowe, jak jednak zrealizować pętlę, gdy nie mamy bezpośredniego dostępu do skoków warunkowych. Jak w przypadku odgałęzienia, tak i tu mamy dostęp do struktury zaczerpniętej z języków wyższego rzędu. Aby zbudować pętlę, należy użyć następującej sekwencji :

BEGIN, ... UNTIL,

Także w tym przypadku słowo rozpoczynające i kończące pętlę ma w nazwie przecinek. W miejscu kropek mogą występować dowolne instrukcje asemblera. Pętla będzie wykonywana tak długo, aż słowo UNTIL, odczyta wartość "prawda" z odpowiedniego wskaźnika C, Z lub N. Aby określić wskaźnik  podający znacznik logiczny dla UNTIL, należy użyć przed tym słowem jeden z poznanych wcześniej symboli : CS >= 0= lub 0<. Zasada postępowania jest identyczna, jak w przypadku odgałęzienia warunkowego, lecz tym razem prawda spowoduje opuszczenie wnętrza pętli. Także w tym przypadku można zanegować wartość logiczną symbolu przez NOT. Przykład :

 CODE C$ 119 # LDY, 4 # LDA, BEGIN, 88 )Y 
 STA, DEY, 0< UNTIL, NEXT JMP, C; RETURN

Sprawdźmy działanie naszego słowa :

 C$  RETURN

Widzimy, że słowo to wypełniło w mgnieniu oka trzy pierwsze linie ekranu znakiem $.

W deklaracji słowa symbol 0< występuje bezpośrednio po słowie DEY,. brak jest instrukcji porównania CPY,. Nie jest ona jednak potrzebna, gdyż rozkaz DEY, oddziaływuje bezpośrednio na znacznik N. dający wartość logiczną dla słowa UNTIL,.

Istnienie elementów strukturalnych w asemblerze wpływa korzystnie na czytelność programu. Programista zmuszony zostaje do zachowania większego porządku. Nasz asembler posiada więc cechy języka edukacyjnego, wpływającego korzystnie na formę rozwiązania algorytmów przez początkujących adeptów programowania. Mimo swej prostoty jest językiem pozwalającym rozwiązywać także bardzo skomplikowane zadania.

PODPROGRAMY

Program pisany w asemblerze FORTH-a może składać się z podprogramów już wcześniej skompilowanych. Możliwe jest zatem budowanie programu z gotowych klocków, co jest cechą wszystkich języków strukturalnych. Do asemblera FORTH-a dodałem słowo MAC , umożliwia ono wywołanie gotowego podprogramu przez podanie jego nazwy w odpowiednim miejscu tworzonego słowa. Jako przykład wpiszmy do komputera :

 0 VARIABLE WA RETURN
 CODE WA+ CLC, WA ADC, CS IF, WA 1+ INC,
 THEN, WA STA, RTS, C; RETURN

Widzimy, że tym razem przed słowem C; (kończącym deklarację) występuje rozkaz RTS, a nie NEXT JMP, Zmiana ta jest konieczna, gdyż nasze słowo będziemy wywoływać z asemblera, a nie z FORTH-a.

Uwaga : wywołanie tak zadeklarowanego słowa z języka FORTH, spowoduje zawieszenie komputera, gdyż brak jest w nim skoku powrotnego (NEXT JMP,). Co robi nasz podprogram ? Jak łatwo zauważyć zwiększa on zmienną WA o wartość znajdującą się w akumulatorze. Jako elementu przekazującego parametr wejściowy, używać będziemy więc akumulatora. Piszemy dalej :

 CODE WA5+ 5 # LDA, MAC WA+ NEXT JMP, C; RETURN

Tym razem zadeklarowaliśmy słowo, które będzie wywoływane z języka FORTH, dlatego też kończy się ono skokiem NEXT JMP,. Wewnątrz deklaracji widzimy niespotykany w asemblerze zapis - MAC WA+, który jest wywołaniem podprogramu o nazwie WA+.

Wydaje się, że prościej byłoby użyć wywołania podprogramu pisząc WA+ JSR,. Nie można jednak tego zrobić w takiej formie, gdyż słowo WA+ jest słowem FORTH-a i podanie jego nazwy (wspomniałem o tym wcześniej) spowoduje jego wykonanie podczas kompilacji.

Roland Pantoła
Tajemnice Atari Nr.09/1992r.