0892 - Język FORTH cz.7

W poprzednim odcinku przedstawiłem Edytor Wprowadzania. Dzięki niemu będę mógł prezentować czytelnikom dłuższe programy w języku FORTH, a ich wprowadzenie do komputera nie powinno sprawiać żadnych trudności. Jeżeli ktoś nie wklepał jeszcze Edytora Wprowadzania, powinien zrobić to natychmiast, gdyż tylko wtedy będzie mógł uzyskać inne programy.

W tym odcinku przedstawiam ASSEMBLER FORTH-a. Na dyskietce zawierającej Extended fig-FORTH znajdują się dwa asemblery. Pierwszy z nich nie zasługuje moim zdaniem na większą uwagę. Natomiast drugi - to bardzo dobry, strukturalny (sic!) asembler. Polecałbym go wszystkim czytelnikom, także tym, którzy stawiają pierwsze kroki w programowaniu. Asembler ten ma bardzo ciekawie rozwiązany sposób zapisu pętli oraz skoków warunkowych. Istnieją w nim struktury znane z języków wyższego rzędu np. BEGIN-UNTIL lub IF-ELSE-THEN. Jest to pewne odstępstwo od formy zapisu w innych implementacjach asemblera, jednak korzyści z tego wynikających jest wiele. Tekst źródłowy staje się bardziej przejrzysty, a początkujący programista zostaje zmuszony do bardziej uporządkowanego sposobu rozwiązywania zadań programowych. Wprowadzenie symboli porównania takich jak np. 0< 0= jest, moim zdaniem, bardziej czytelne od nazw skoków warunkowych stosowanych powszechnie w innych asemblerach. Nie ma jednak róży bez kolców i prezentowany asembler ma pewne wady. Jeżeli ktoś stara się uzyskać większą szybkość działania programu kosztem przejrzystości (np stosując skoki w dowolne miejsca programu), to srodze się na nim zawiedzie.

Przedstawiony na końcu tego artykułu tekst źródłowy nie jest wierną kopią asemblera znajdującego się na dyskietce. Usunąłem z niego pewne błędy (brakowało niektórych instrukcji) oraz rozbudowałem o dwa dodatkowe słowa. Ponieważ prezentowany asembler nie jest mojego autorstwa (poza drobnymi zmianami),przedstawiam go raczej z tzw. mieszanymi odczuciami. Nie mam jednak innej możliwości kontynuowania tego cyklu, a zwłaszcza prezentacji programów korzystających z asemblera. Posiadacze magnetofonów mogą tylko w taki sposób włączyć asembler do swojego FORTH-a. We wszelkich publikacjach dotyczących języka FORTH bardzo często wykorzystuje się listingi z implementacjami; myślę więc, że czytelnicy nie ocenią zbyt surowo tego występku. Po wprowadzeniu (z kodami) tekstu źródłowego asemblera do Edytora Wprowadzania i kompilacji możemy przystąpić do tworzenia nowych słów w asemblerze.

ASSEMBLER FORTH-a

Jeżeli posiadamy już ASSEMBLER w swoim komputerze, możemy przejrzeć słownik z nowymi nazwami. Aby to uczynić, należy napisać ASSEMBLER, a następnie VLIST i zatrzymać w odpowiednim miejscu naciskając spację.

Deklarowanie nowych słów w asemblerze nie różni się zbytnio od deklaracji w Języku FORTH. Zamiast znanego nam dwukropka piszemy słowo CODE. Za nim podajemy nazwę tworzonego słowa, a następnie nazwy rozkazów asemblera. Deklarację kończymy pisząc słowo END-CODE lub C; - jest to znak dla kompilatora, aby przeszedł do FORTH-a. Tak zadeklarowane słowo jest dostępne w języku FORTH po podaniu jego nazwy. Także wewnątrz słów zadeklarowanych w asemblerze przez CODE mamy dostęp do nazw słów zadeklarowanych wcześniej, zarówno w asemblerze jak i FORTH-cie.

Mnemoniczne nazwy rozkazów są w większości wypadków, takie same jak w innych asemblerach (z wyjątkiem skoków). Różnica polega na tym, że na końcu nazwy rozkazu dodajemy przecinek np.

LDA, zamiast LDA

Druga zasadnicza zmiana to, oczywiście, stosowanie Odwrotnej Notacji Polskiej i w związku z tym wszystkie parametry podajemy przed nazwą np.

18 LDA, zamiast LDA 18

Podawanie parametru przed nazwą ma tę zaletę, że można go podać w formie wyrażenia zapisanego w języku FORTH, gdyż wartość parametru jest pobierana ze szczytu stosu. Przykład :

3 4 * 12 + 6 / LDA,

W skład wyrażenia mogą wchodzić dowolne słowa wcześniej skompilowane (także stworzone przez użytkownika). Uwaga ! Słowa nie wchodzące w skład asemblera, a znajdujące się między CODE a C; lub END-CODE , będą wykonywane podczas kompilacji, a nie zostaną skompilowane.

Czas wreszcie na przykład deklaracji nowego słowa :

 0 VARIABLE WA RETURN
 CODE WA+ WA LDY, INY, WA STY, NEXT JMP, C; RETURN

Stworzyliśmy w asemblerze słowo WA+ , na początku jednak zadeklarowaliśmy zmienną WA , która będzie wykorzystana do przekazywania wartości z FORTH-a do asemblera i z powrotem. Przed zakończeniem deklaracji słowa WA+ , występuje sekwencja NEXT JMP, - jest to skok do interpretera języka FORTH. Skok ten musi występować zawsze, gdy zadeklarowane słowo będziemy wywoływać z FORTH-a.

Czytelnicy, którzy programują w języku BASIC i próbowali kiedykolwiek wykorzystać wstawki asemblerowe, wiedzą dobrze, ile sprawia to kłopotów. Także inne języki na naszą "maszynkę" nie posiadają w pełni rozwiązanego problemu włączania kodu maszynowego. Wiele problemów występuje także przy przekazywaniu parametrów z języka do kodu i z powrotem. W języku FORTH nie istnieje problem przekazywania parametrów, gdyż jak łatwo zauważyć, te same struktury danych są dostępne zarówno w asemblerze jak i w FORTH. Co wykonuje nasze słowo ? Sprawdźmy :

 10 WA ! RETURN
 WA+ RETURN
 WA ? RETURN

Słowo to zwiększyło wartość zmiennej WA o 1 , należy jednak pamiętać, że zmienna w języku FORTH ma 2 bajty i dla liczb większych od 254 należy je rozbudować. To samo zadanie można było zapisać prościej, wykorzystując rozkaz INC, .

W utworzonym przez nas słowie wykorzystaliśmy adresowanie absolutne. Aby umożliwić inne tryby adresowania, należy wstawić między parametr (operand) a nazwę rozkazu asemblera odpowiedni znaczek określający rodzaj adresowania. Uwaga : wszystkie części rozdzielamy spacją.

Poszczególne tryby mają następujące symbole :

  • # - tryb natychmiastowy,
  • .A - akumulatora,
  • X) - pośredni X,
  • )Y - pośredni Y,
  • ) - pośredni,
  • ,X - strony zerowej lub absolutny X,
  • ,Y - strony zerowej lub absolutny Y

Brak znaczka oznacza tryb absolutny lub strony zerowej, w zależności od wartości operandu. Przykłady :

10 # LDA,

załaduje liczbę 10 do akumulatora (tryb natychmiastowy).

0 ,X LDA,

załaduje liczbę znajdującą się na stronie zerowej w komórce, której wartość zawiera rejestr X (tryb strony zerowej X).

Ostatni z wymienionych trybów nie jest może zbyt często wykorzystywany przez programistów, jednak w asemblerze FORTH-a umożliwia bezpośredni dostęp do stosu FORTH-a.

A oto wspomniany na początku listing ASSEMBLERA :

 018S ( RAGSDALE ASSEMBLER 1.1 )
 0200
 0371 VOCABULARY ASSEMBLER IMMEDIATE
 04LF ASSEMBLER DEFINITIONS
 05FE BASE @ HEX
 0600
 07MD 0 VARIABLE INDEX -2 ALLOT
 08AM 0909 , 1505 , 0115 , 8011 ,
 09TK 8009 ,
 0AHU 1D0D , 8019 , 8080 , 0080 ,
 0BIN 1404 , 8014 , 8080 , 8080 ,
 0CKO 1C0C , 801C , 2C80 ,
 0D00
 0E9N 2 VARIABLE MODE : .A 0 MODE ! ;
 0FE0 : # 1 MPDE ! ; : MEM 2 MODE ! ;
 0GUA : ,X 3 MODE ! ; : ,Y 4 MODE ! ;
 0HSH : X) 5 MODE ! ; : )Y 6 MODE ! ;
 0I9C : ) F MODE ! ; : BOT ,X 0 ;
 0JLH : SEC ,X 2 ; : RP) ,X 101 ;
 0K42 : UPMODE IF MODE @ 8 AND 0=
 0LI5 IF 8 MODE +! THEN THEN
 0M4I 1 MODE @ F AND -DUP IF 0 DO DUP
 0NG0 + LOOP THEN OVER 1+ @ AND 0= ;
 0OJ4 : CPU  C@ C,
 0PR5 MEM ;
 0Q00
 0R13 00 CPU BRK, 18 CPU CLC,
 0S4B D8 CPU CLD, 58 CPU CLI,
 0T4G B8 CPU CLV,
 0UHL CA CPU DEX, 88 CPU DEY,
 0V2E E8 CPU INX, C8 CPU INY,
 10LT EA CPU NOP, -->
 1100
 1200
 13DA 48 CPU PHA, 08 CPU PHP,
 14HU 68 CPU PLA, 28 CPU PLP,
 155G 40 CPU RTI,
 16EI 60 CPU RTS, 38 CPU SEC,
 17CL F8 CPU SED, 78 CPU SEI,
 186Q AA CPU TAX,
 19EN A8 CPU TAY, BA CPU TSX,
 1AA5 8A CPU TXA, 9A CPU TXS,
 1B5I 98 CPU TYA,
 1C00
 1DTL : MCP  DUP
 1E28 1+ @ 80 AND IF 10 MODE +! THEN
 1F59 OVER FF00 AND UPMODE UPMODE
 1GO4 IF MEM CR LATEST ID. 3 ERROR
 1HNB THEN
 1IEV C@ MODE C@ INDEX + C@ + C,
 1JIV MODE C@ 7 AND IF MODE C@
 1K9I F AND 7 <
 1LGF IF C, ELSE , THEN THEN MEM ;
 1M00
 1NM0 1C6E 60 MCP ADC, 1C6E 20
 1OOL MCP AND, 1C6E C0 MCP CMP,
 1PDS 1C6E 40 MCP EOR, 1C6E A0
 1QFH MCP LDA, 1C6E 00 MCP ORA,
 1R28 1C6E E0 MCP SBC, 1C6C 80
 1SGR MCP STA, 0D0D 01 MCP ASL,
 1T00
 1UA1 -->
 1V00
 2000
 2100
 2200
 2328 0C0C C1 MCP DEC, 0C0C E1
 24SH MCP INC, 0D0D 41 MCP LSR,
 2540 0D0D 21 MCP ROL, 0D0D 61
 261M MCP ROR, 0414 81 MCP STX,
 2707 0486 E0 MCP CPX, 0486 C0
 28PJ MCP CPY, 1496 A2 MCP LDX,
 291P 0C8E A0 MCP LDY, 048C 80
 2AO4 MCP STY, 0480 14 MCP JSR,
 2BCP 8480 40 MCP JMP, 0484 20
 2C7F MCP BIT,
 2D00
 2EM3 : BEGIN, HERE 1 ; IMMEDIATE
 2F00
 2GRK : UNTIL, ?EXEC >R 1 ?PAIRS R>
 2HFP C, HERE 1+ - C, ; IMMEDIATE
 2I00
 2JCJ : IF, C, HERE 0 C, 2 ;
 2K5S IMMEDIATE
 2L00
 2M13 : THEN, ?EXEC 2 ?PAIRS HERE
 2NF8 OVER C@ IF SWAP ! ELSE OVER 1+
 2OM3 - SWAP C! THEN ; IMMEDIATE
 2P00
 2Q90 : ELSE, 2 ?PAIRS HERE 1+ 1 JMP,
 2REO SWAP HERE OVER 1+ - SWAP C! 2 ;
 2S5S IMMEDIATE
 2T00
 2UA1 -->
 2V00
 3000
 3133 ( --- )
 3200
 33MK : NOT 20 + ;
 3400
 35HR 90 CONSTANT CS D0 CONSTANT 0=
 366I 10 CONSTANT 0< 90 CONSTANT >=
 3700
 380L : C; CURRENT @ CONTEXT !
 39LD ?EXEC ?CSP SMUDGE ; IMMEDIATE
 3AHC : END-CODE [COMPILE] C; ;
 3B5S IMMEDIATE
 3CCM FORTH DEFINITIONS DECIMAL
 3D00
 3EAU : CODE ?EXEC CREATE [COMPILE]
 3F36 ASSEMBLER ASSEMBLER MEM !CSP ;
 3GQU IMMEDIATE ' ASSEMBLER CFA
 3H38 ' ;CODE 8 + !
 3I1E LATEST 12 +ORIGIN !
 3J8J HERE 28 +ORIGIN ! HERE 30
 3KFF +ORIGIN ! HERE FENCE !
 3LLF ' ASSEMBLER 6 + 32 +ORIGIN !
 3M1U BASE ! FORTH
 3N3P : MAC  -FIND 0= 0 ?ERROR
 3O4V DROP 32 c, , ; IMMEDIATE
 3PD2 ( END )

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