Ta wersja generuje kod asemblerowy dla 64-bitowych PC-tow (architektura x86_64). Polecenie 'make' buduje kompilator. Potem np. ./komp < ttt1.pl0 > ttt1.s daje plik asemblerowy. Następnie: gcc -g -o ttt1 ttt1.s uruchamia asembler i łączy z bibliotekami. Na razie nie na żadnej możliwiści wyświetlania wyników. Ale gdb ttt1 break main run pozdala uruchomic program pod kontrola debugera (tzn. gdb) i śledzić jego prace. Np. polecemie 'disassemble' pokazuje aktualnie wykonywana funkcję. Polecenie 'stepi' wykonuje jedną instrukcję maszynową. Polecenie 'nexti' wykonuje instukcje aż do dojścia do następnej. 'nexti' używamy gdy chemy np. by debuger wykonał cała wołaną proceduę i zatrzymał się dopiero po jej zakończeniu. W przykładzie 'ttt1' kilkakroten wykonanie 'nexti' doprowadzi do wykonania procedury 'multiplay'. Ta procedura mnoży wartość zmiennych 'x' i 'y' i zostawia wynik w 'z'. Polcenie print z pozwala wypisac wartość zmiennej i w ten sposób sprawdzić czy wynik jest poprawny. -------------------------------------------------------------- W porównaniu z wersją 4 są dwa dodatkowe pliki .c, tzn. 'pl0_gen.c' i 'symtab.c'. 'symtab.c' zawiera bardzo naiwną implementację tabeli symboli używającą wyszukiwanie liniowe w tablicy o ustalonym rozmiarze. 'pl0_gen.c' zawiera procedure generujące kod. Generacja kodu jest rozbita na trzy warstwy. Najwyższa warstwa przechodzi po drzewie rozbiory i dla każdej procedury generuje jej reprezentację jako listy insturukcji abstrakcyjnych. Najwyższa warstwa jest odpowiedzialna za komunikację z tablicą symboli. W szczególności umieszcza deklaracje w tablici symboli a przy użyciu sprawdza czy symbol był odpowienio zadeklarowany. W szczególności zadeklarowane stałe są zastępowane przez ich wartości i niższe warstwy pracują tylko ze stałymi w postaci liczb. Tu też obsługujemy zagnieżdżanie leksykalne: po przetworzeniu procedury odtwarzamy stan tabeli symboli z momentu po napotkaniu nagłówka procedury. Tzn. po przetworzeniu procedury w tabeli symboli zostaje deklaracja procedury zaś zapomina się o obiektach (stałych. zmiennych i procedurach definiowanych wewnątrz niej). Do odtwarzania poprzedniego stanu używamy listę odtwarzania zawierającą poprzednie deklaracje. Ponieważ potencjalnie procedury mogą się wielokrotnie zagnieżdżać to w trakcie przetwarzania procedury musimy zapamiętać listę odtwarzania procedury zewnętrznej. Do tego celu używamy stosu przechowywanego w tablicy 'stara_lista_odtwarzania'. Za obsługę odtwarzania są odpowiedzialne procedury 'popdecls' (odtwarzająca), 'pushdecls' i 'get_new_declaration' (ta ostatnia umieszcza deklaracje na liście odtwarzania). Aby łatwo umieszczać deklaracje na liście struktura Pośrednia warstwa zawiera procedury gromadzące instrukcje w listę. Przy okazji wykonuja ona parę prostych przekształceń by ograniczyć liczbę potrzebnych instrukcji. Trzecia warstwa jest odpowiedzialna za generację kodu. Stosujemy tu bardzo naiwną metodę: wszystkie operacje produkują wynik w pamięci. Dla każdego podwyrażenia wprowadzamy zmienną tymczasową. Ponieważ instukcje procesora mogą mieć tylko jeden argument w pamięci i dodatkowo dla instukcji obliczeniowych jeden z argumentów musi być wynikim przyjeliśmy zasadę że jeden z argumentów jest ładownay do rejestru rax, również w rax pojawia się wynik instrujcji obliczeniowych a dopiero na koniec przepisujemy wynik z rejestru rax do pamięci. Zaletą jest prostota generatora. Wadą słaba wydajność generowanego kodu. Aby ułatwić operacje listowe na instrukcjach i deklaracjach odpowiednie struktury zawierają pole 'nastepny' pozwalające na tworzenie z nich list. Dla zmiennych i etykiet używamy tą samą strukturę co dla instrucji co ogranicza nieco rozmiar kodu kompilatora.