BUDOWA PROGRAMU W ATARI-BASIC
1. Wstęp.
Chyba prawie każdy, kto przez jakiś czas pisał programy w BASIC-u, jest ciekawy, w jakiej postaci program taki jest przechowywany w pamięci komputera, a następnie na taśmie lub dyskietce. Informacja o tym może nie tylko zaspokoić ciekawość programującego, ale także pomóc w pisaniu bardziej zwartych programów (zajmujących mniej pamięci), oraz w odzyskiwaniu programów straconych (np. przez uszkodzenie sektora dyskietki).
2. Wpisywanie programu.
Program wpisywany z klawiatury składa się z poszczególnych wierszy, opatrzonych numerem i zawierających jedną lub więcej instrukcji. Wiersz wpisujemy jako ciąg znaków ATASCII. Naciśnięcie klawisza RETURN powoduje, że wpisany wiersz programu zostaje zapamiętany jako część programu, ale już nie w postaci kodów ATASCII, ale w postaci tzw. półskompliowanej. W tej postaci wszystkie użyte nazwy i symbole funkcji, operatorów i instrukcji zostają zamienione na jednobajtowe tzw. tokeny. W jednolity sposób zostają też zapamiętane użyte zmienne, liczby i teksty. W czasie pisania programu są tworzone tabele nazw zmiennych i wartości zmiennych, a także uaktualniane są zawartości komórek zawierających adresy poszczególnych części programu.
3. Adresy części programu.
W osiemnastu kolejnych komórkach pamięci BASIC umieszcza adresy charakteryzujące program:
128-129 ($80-81) LOMEM - początek pamięci dostępnej dla BASIC-a
130-131 ($82-83) VNTP - początek tabeli nazw zmiennych
132-133 ($84-85) VNTD - koniec tabeli nazw zmiennych
134-135 ($86-87) WTP - początek tabeli wartości zmiennych
136-137 ($88-89) STMTAB - początek właściwego programu
138-139 ($8A-8B) STMCUR - początek bieżącego wiersza BASIC
140-141 ($8C-8D) STARP - początek tabeli zmiennych indeksowanych (tekstów i tablic)
142-143 ($8E-8P) RUNSTK - początek stosu dla instrukcji GOSUB i FOR/NEXT
144-145 ($90-91) MEMTOP - koniec obszaru zajętego przez BASIC
4. Tabela nazw zmiennych.
Tuż za LOMEM interpreter BASIC-a zostawia sobie jedną wolną stronę na bufor wydawniczy tokenów, a następnie rozpoczyna się tabela nazw zmiennych. Są w niej umieszczane nazwy zmiennych w takiej kolejności, w jakiej pojawiały się przy pisaniu programu. Nazwy zmiennych tekstowych jako ostatni znak mają $, a nazwy tablic pamiętane są do nawiasu otwierającego włącznie.
Jeżeli mamy w pamięci dowolny program w BASIC-u, to możemy obejrzeć tabelę nazw zmiennych, np. w następujący sposób:
FOR A=PEEK(130)+256*PEEK(131) TO PEEK(132)+
256*PEEK(133): ?CHR$(PEEK(A));:NEXT A
Widzimy, że ostatni znak każdej zmiennej jest w negatywie, tzn. ma ustawiony najstarszy bit. Np. jeżeli w programie występują zmienne: A, B, C1, WILK, KOZA$, KAPUSTA(1,1), to tabela wygląda następująco:
5. Tabela wartości zmiennych.
Tuż za tabelą nazw zmiennych rozpoczyna się tabela wartości zmiennych. Zmienne występują w tej samej kolejności, co w tabeli nazw zmiennych. Każda zmienna zajmuje w tabeli 8 bajtów:
Bajt 1 - typ zmiennej:
$00 - zmienna liczbowa
$40 lub 41 - tablica
$80 lub 81 - zmienna tekstowa.
Bajt 2 - numer kolejny zmiennej. Numery liczone są od 0, maksymalny numer zmiennej - $7F (127).
Bajty 3-8 - dla zmiennej liczbowej zawierają wartość zmiennej, a dla pozostałych - 3 kolejne pary bajtów zawierają parametry tekstu lub tablicy, a mianowicie kolejno:
dla tablicy: położenie w tabeli zmiennych indeksowanych, pierwszy i drugi wymiar tablicy;
dla zmiennej tekstowej: położenie w tablicy zmiennych indeksowanych, aktualna i zadeklarowana długość tekstu.
Obejrzyjmy sobie tabelę wartości zmiennych. Najlepiej wyświetlić ją w kodzie szesnastkowym, co można zrobić najprościej w TURBO BASIC-u XL:
POKE 83,26: FOR A=DPEEK(134) TO DPEEK(136)-
1:?HEX$(PEEK(A)); " ";:NEXT A: POKE 83,39
Jeżeli program nie był uruchomiony, zmienne liczbowe będą miały wartość 0 (6 bajtów zerowych). Jeżeli był uruchomiony i został zatrzymany, możemy zobaczyć aktualne wartości zmiennych. Aby je odczytać, trzeba znać sposób reprezentacji liczb w ATARI. Jest to podane w skrócie w dodatku A.
6. Budowa wiersza programu.
Zaraz za tabelą wartości zmiennych umieszczone są kolejne wiersze programu. W każdym wierszu pierwsze dwa bajty oznaczają numer wiersza. Jest to liczba binarna w zakresie od 0 do 32767 (kolejność bajtów:
młodszy, starszy). Trzeci bajt oznacza długość wiersza w bajtach (maks. 255), liczoną łącznie z dwoma pierwszymi bajtami. Czwarty bajt oznacza liczbę bajtów do końca instrukcji. Jeżeli wiersz zawiera jedną instrukcję, to trzeci i czwarty bajt są jednakowe. Jeżeli wiersz zawiera więcej instrukcji, to pierwszy bajt po każdym dwukropku oznacza liczbę bajtów od początku wiersza do końca bieżącej instrukcji. Na podstawie 3. i 4. bajtu można zorientować się, gdzie się zaczynają i kończą kolejne instrukcje.
W następnych bajtach zakodowana jest instrukcja. Symbole funkcji, operatorów, instrukcji i znaków pomocniczych zakodowane są w postaci jednobajtowych tokenów (patrz dodatek B).
Każda występująca w instrukcji nazwa zmiennej jest reprezentowana przez jednobajtowy numer zmiennej, z dodatkowo ustawionym najstarszym bitem (np. zmienna nr 4 ma kod $84). Każda stała liczbowa zajmuje 7 bajtów: pierwszy bajt $0E, następne 6 bajtów liczba w postaci zmiennoprzecmkowej (patrz dodatek A). Stała tekstowa zaczyna się od bajtu $0F, drugi bajt oznacza liczbę znaków tekstu, od trzeciego bajtu zaczyna się sam tekst w kodach ATASCII. Ciągi tekstowe, występujące po instrukcjach REM i DATA mają postać kodów ATASCII.
Na końcu wiersza występuje bajt o kodzie $16 lub (np. na końcu wiersza z instrukcją DATA) - $9B (RETURN). Na końcu instrukcji, która nie jest ostatnią w wierszu występuje dwukropek o kodzie $14.
7. Zakończenie programu.
Po zakończeniu ostatniej instrukcji programu następuje bieżący wiersz programu. Jest to wiersz, który był ostatnio wpisywany. Jeżeli był to wiersz pisany w trybie bezpośrednim (bez numeru linii), to jako numer linii występuje liczba 32768 (bajty: $00, $80).
Bezpośrednio za bieżącym wierszem jest miejsce na tabelę wartości zmiennych indeksowanych, tzn. zmiennych tekstowych i tablic. Każdy element zmiennej tekstowej zajmuje 1 bajt, każdy element tablicy - 6 bajtów.
Za tą tabelą BASIC umieszcza stos przeznaczony do zapamiętywania adresów powrotnych Instrukcji GOSUB i FOR/NEXT. Każda czynna instrukcja GOSUB zajmuje na tym stosie 4 bajty, a FOR/NEXT - 16 bajtów. W przypadku GOSUB pierwszym bajtem jest 0 jako kod Instrukcji, następne 2 bajty mieszczą numer wiersza zawierającego tę instrukcję, a czwarty bajt położenie tej instrukcji w wierszu. Dla pętli FOR/NEXT pierwsze 6 bajtów zawiera wartość końcową licznika (to co jest po TO), następne 6 bajtów - krok inkrementacji (STEP), następny bajt - numer zmiennej, kolejne dwa - numer wiersza programu, a ostatni - położenie instrukcji FOR w wierszu.
8. Zapis programu.
Program może być zapisany na taśmie lub dyskietce w dwóch postaciach:
1. Postać źródłowa (listing) - zapis instrukcjami LIST "C:", LIST "D:NAZWA".
2. Postać "półskompilowana" - zapis instrukcjami CSAVE, SAVE "C:", SAVE "D:NAZWA".
Przy zapisie w postaci listingu zapisywane są na taśmie lub dysku w postaci kodów ATASCII kolejne bajty listingu, tak jak to widzimy na ekranie po napisaniu LIST. Przy zapisie w postaci półskompilowanej program zapisywany jest w takiej postaci, w jakiej jest pamiętany w pamięci komputera, od początku tabeli nazw zmiennych do końca bieżącego wiersza. Dodatkowo na samym początku zapisywane jest 14 bajtów zawierających 7 adresów poszczególnych części programu. Adresy te pochodzą z komórek 128-141, przy czym od każdego adresu odejmowana jest wartość LOMEM (zawartość komórek 128-129). Tak więc pierwsze dwa bajty są zerowe, następne dwa zawierają liczbę $100 (kolejno $00, $01).
Przy pomocy prostego triku możemy obejrzeć cały stokenizowany program (w postaci znaków ATASCII). Napiszmy:
SAVE "S:"
a zobaczymy cały program w takiej postaci, jak jest zapisywany na dysku lub kasecie.
Panuje powszechne przekonanie, że zapis w postaci listingu jest dłuższy. Przy zapisie na taśmie jest to prawda, ale przede wszystkim dlatego, że zapis odbywa się z długimi przerwami. Natomiast liczba zapisanych bajtów na ogół jest mniej więcej taka sama, a często nawet mniejsza w przypadku listingu. Dlaczego tak jest, wyjaśnię na przykładzie. Napiszmy wiersz programu:
10 SOUND 0,0,0,0
Listing zawiera 17 znaków (łącznie ze spacjami i znakiem RETURN). Ten sam wiersz w postaci półskompilowanej ma długość 37 bajtów - ponad 2 razy więcej niż listing. Co prawda instrukcja SOUND zajmuje tylko 1 bajt, ale za to każda liczba zajmuje 7 bajtów.
9. Efektywne pisanie programu.
Jeżeli chcemy, żeby program zajmował mało miejsca w pamięci i na taśmie lub dysku (np. program jest bardzo długi, lub potrzebuje dużo pamięci na tablice, albo też chcemy jak najbardziej skrócić czas wczytywania), to przede wszystkim trzeba często występujące stałe liczbowe zastąpić zmiennymi. Wiersz programu, podany jako przykład w poprzednim rozdziale, po zastąpieniu stałej 0 zmienną X, której wcześniej przypisano wartość 0, będzie miał postać:
10 SOUND X,X,X,X
i zajmie 13 bajtów zamiast 37.
Inne sposoby skracania programu, np. pisanie wielu instrukcji w jednym wierszu, stosowanie krótkich nazw zmiennych, zmniejszanie liczby zmiennych - są także skuteczne, ale skracają program w znacznie mniejszym stopniu, niż sposób podany wyżej.
Przy skracaniu programu trzeba mieć na uwadze, że może się nieco pogorszyć czytelność listingu, a także zmieni się czas wykonywania programu, co często bywa ważne.
Operacją, którą powinno się wykonać po napisaniu programu, jest pozbycie się niepotrzebnych zmiennych. Chodzi o to, że w czasie pisania programu dokonuje się licznych zmian, poprawek, popełnia się błędy, a wiele z wprowadzanych przy okazji napisów ląduje w tabeli zmiennych, nie występując w samym programie. Co prawda, jak podano w [3], najnowsza wersja interpretera ATARI-BASIC eliminuje zmienne, które powstały po wpisaniu błędnego wiersza, ale i tak dużo "śmieci" pozostaje. Przeglądałem wiele programów różnych autorów (m.in. programy nadawane w audycji RADIOKOMPUTER). Tabele zmiennych niektórych z tych programów zawierały ponad 40 niepotrzebnych zmiennych! Oznacza to kilkaset bajtów niepotrzebnie zajętych w tabelach nazw i wartości zmiennych, a w konsekwencji na taśmie lub dysku. Ciekawostką jest, że w wielu programach występuje nazwa ADY, która jest resztką napisu READY.
Aby pozbyć się zmiennych nie występujących w programie, należy zapisać program na taśmę lub dysk instrukcją LIST, usunąć program z pamięci instrukcją NEW, odczytać instrukcją ENTER i dopiero zapisać przez SAVE lub CSAVE. Przy LIST tabela zmiennych nie jest zapisywana, a przy ENTER jest tworzona na nowo tylko z tych zmiennych, które występują w programie.
10. Zabezpieczanie programów.
Sposobów zabezpieczania programów przed listowaniem lub zatrzymaniem jest wiele i nie jest to tematem niniejszego artykułu. Tutaj wspomnę tylko o paru sprawach związanych z budową programu.
Ponieważ w programie używane są numery zmiennych, a nie ich nazwy, więc postać tabeli nazw zmiennych nie est ważna dla działania programu. Gdy program jest gotowy, do tabeli nazw zmiennych można wpisać zupełnie dowolne kody. Program będzie działał, ale listingi mogą wyglądać dziwnie. Spróbujmy np. wypełnić tabelę nazw zmiennych bajtami o kodzie znaku RETURN (155=$9B). Jest to wykorzystywane w zabezpieczaniu przed listowaniem.
Innymi sposobami są zmiany niektórych adresów zawartych w komórkach 128-141 lub np. wpisanie liczby 0 do bajtu oznaczającego długość jednego z wierszy.
Aby dokładnie przyjrzeć się programowi i ewentualnie dokonać w nim jakichś wymyślnych zmian, wygodnie jest posłużyć się dowolnym programem typu monitor.
Mam nadzieję, że informacje podane w tym artykule przyczynią się do lepszego poznania komputera przez użytkowników.
Literatura
1. ATARI PEEKs and POKEs
2. The ATARI BASIC Source Book, (COMPUTE! Publications Inc.)
3. Zientara W. - Mapa pamięci ATARI XL/XE. Procedury interpretera BASIC-a
Dodatek A
Liczby zmiennoprzecinkowe
Każda liczba w ATARI zajmuje 6 bajtów. W pierwszym bajcie mieści się znak liczby (najstarszy bit: 0 - liczba dodatnia) i wykładnik (pozostałe 7 bitów: wykładnik potęgi liczby 100 powiększony o 64). W pozostałych pięciu bajtach mieści się 10-cyfrowa mantysa w kodzie BCD. Pierwszy bajt mantysy jest niezerowy i uważa się, że po nim występuje kropka dziesiętna. Wyjątkiem jest liczba 0, którą reprezentuje 6 bajtów zerowych.
Przykłady reprezentacji liczb w ATARI:
1 |
40 |
01 |
00 |
00 |
00 |
00 |
46 |
40 |
46 |
00 |
00 |
00 |
00 |
32768 |
42 |
03 |
27 |
68 |
00 |
00 |
0.0003=3*100^-2 |
3E |
03 |
00 |
00 |
00 |
00 |
-65535=6.5535*100^2 |
C2 |
06 |
55 |
35 |
00 |
00 |
-1 |
C0 |
01 |
00 |
00 |
00 |
00 |
Dodatek B
Tokeny ATARI-BASIC
Tokeny są jednobajtowymi odpowiednikami instrukcji, operatorów, funkcji, zmiennych, zgodnie z następującym podziałem (wszystkie liczby - w zapisie szesnastkowym):
1. Stałe, zmienne, operatory i funkcje:
00-0D nieużywane
0E oznaczenie stałej liczbowej
0F oznaczenie stałej tekstowej
10-3C operatory
3D-54 funkcje
55-7F nieużywane
80-FF nazwy zmiennych
2. Nazwy instrukcji: 00-36
Oto szczegółowe tabele tokenów:
OPERATORY:
, |
12 |
|
$ |
13 |
|
: |
14 |
rozdzielenie instrukcji w wierszu |
; |
15 |
|
CR |
16 |
koniec wiersza |
GOTO |
17 |
po ON |
GOSUB |
18 |
po ON |
TO |
19 |
|
STEP |
1A |
|
THEN |
1B |
|
# |
1C |
|
<= |
1D |
relacje między liczbami |
<> |
1E |
relacje między liczbami |
>= |
1F |
relacje między liczbami |
< |
20 |
relacje między liczbami |
> |
21 |
relacje między liczbami |
= |
22 |
relacje między liczbami |
^ |
23 |
operacje arytmetyczne |
* |
24 |
operacje arytmetyczne |
+ |
25 |
operacje arytmetyczne |
- |
26 |
operacje arytmetyczne |
/ |
27 |
operacje arytmetyczne |
NOT |
28 |
operacje logiczne |
OR |
29 |
operacje logiczne |
AND |
2A |
operacje logiczne |
( |
2B |
|
) |
2C |
|
= |
2D |
operacja przyporządkowania (dla liczb) |
= |
2E |
operacja przyporządkowania (dla tekstów) |
<= |
2F |
relacje między tekstami |
<> |
30 |
relacje między tekstami |
>= |
31 |
relacje między tekstami |
< |
32 |
relacje między tekstami |
> |
33 |
relacje między tekstami |
= |
34 |
relacje między tekstami |
+ |
35 |
znak liczby |
- |
36 |
znak liczby |
( |
37 |
nawias długości tekstu |
( |
38 |
lewy nawias tablicy |
( |
39 |
lewy nawias DIM tablicy |
( |
3A |
lewy nawias funkcji |
( |
3B |
lewy nawias DIM tekstu |
, |
3C |
oddzielenie wymiaru tablicy |
STR$ |
3D |
REM |
00 |
POINT |
1C |
CHR$ |
3E |
DATA |
01 |
XIO |
1D |
USR |
3F |
INPUT |
02 |
ON |
1E |
ASC |
40 |
COLOR |
03 |
POKE |
1F |
VAL |
41 |
LIST |
04 |
PRINT |
20 |
LEN |
42 |
ENTER |
05 |
RAD |
21 |
ADR |
43 |
LET |
06 |
READ |
22 |
ATN |
44 |
IF |
07 |
RESTORE |
23 |
COS |
45 |
FOR |
08 |
RETURN |
24 |
PEEK |
46 |
NEXT |
09 |
RUN |
25 |
SIN |
47 |
GOTO |
0A |
STOP |
26 |
RND |
48 |
GO TO |
0B |
POP |
27 |
FRE |
49 |
GOSUB |
0C |
? |
28 |
EXP |
4A |
TRAP |
OD |
GET |
29 |
LOG |
4B |
BYE |
0E |
PUT |
2A |
CLOG |
4C |
CONT |
0F |
GRAPHICS |
2B |
SQR |
4D |
COM |
10 |
PLOT |
2C |
SGN |
4E |
CLOSE |
11 |
POSITION |
2D |
ABS |
4F |
CLR |
12 |
DOS |
2E |
INT |
50 |
DEG |
13 |
DRAWTO |
2F |
PADDLE |
51 |
DIM |
14 |
SETCOLOR |
30 |
STICK |
52 |
END |
15 |
LOCATE |
31 |
PTRIG |
53 |
NEW |
16 |
SOUND |
32 |
STRIG |
54 |
OPEN |
17 |
LPRINT |
33 |
|
|
LOAD |
18 |
CSAVE |
34 |
|
|
SAVE |
19 |
CLOAD |
35 |
|
|
STATUS |
1A |
DOMYSLNE |
|
|
|
NOTE |
1B |
LET |
36 |
Lech Bogusz
|