FORTH część 7W 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 FORTHa. 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 FORTHa. We wszelkich publikacjach dotyczących języka FORTH bardzo często wykorzystuje się listingi z implementacji; 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 stów w asemblerze. ASSEMBLER FORTHaDeklarowanie 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 FORTHa. 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 FORTHcie. 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 FORTHa 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 FORTHa. Czytelnicy, którzy programują w języku Basic i próbowali kiedykolwiek wykorzystać wstawki asemblerowe, wiedzą dobrze, że 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 FORTHa umożliwia bezpośredni dostęp do stosu FORTHa. 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 : # l MODE ! ; : 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 <BUILDS C, DOES> 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 CPY 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 <BUILDS C, , DOES> DUP 1E28 1+ @ 80 AND IF 10 MODE +! THEN 1F59 OVER FF00 AND UPMODE UPMODE 1G04 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 Cl 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 l ; 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 2Q9O : 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 1N3P : MAC -FIND 0= 0 ?ERROR 3O4V DROP 32 C, , ; IMMEDIATE 3PD2 ( END ) Roland Pantoła
|