Wykład 11 Konstrukcja drzew składniowych
Drzewa składniowe Wykorzystanie drzew składniowych jako reprezentacji pośredniej umożliwia oddzielenie translacji od analizy składniowej; Procedury translacji wywołane w trakcie analizy składniowej mają dwa rodzaje ograniczeń: - gramatyka odpowiednia do analizy składniowej nie musi odpowiadać naturalnej strukturze hierarchicznej konstrukcji języka; - metoda analizy składniowej narzuca kolejność, w której są rozważane węzły drzewa wyprowadzenia. Ta kolejność może nie odpowiadać kolejności, w której informacja o konstrukcjach staje się dostępna;
Drzewa składniowe (Abstrakcyjne) drzewo składniowe jest skondensowaną postacią drzewa wyprowa- dzenia przydatną do reprezentacji konstrukcji języka. Produkcja S -> if B then S 1 else S 2 w drzewie wyprowadzenia może pojawić się jako If-then-else B S 1 S 2
Drzewa składniowe Uproszczenia w drzewach składniowych: - W drzewie składniowym operatory i słowa kluczowe nie pojawiają się jako liście, ale raczej są związane z węzłami wewnętrznymi, które byłby rodzicami tych liści w drzewie wyprowadzenia; - Łańcuchy pojedynczych produkcji mogą być zwinięte; Translacja sterowana składnią może bazować na drzewach składniowych lub drzewach wyprowadzeń. W obu przypadkach podejście jest takie samo i polega na umieszczeniu atrybutów w węzłach drzewa składniowego.
Przypomnienie - drzewo wyprowadzenia z przypisami L W.wart=19 n W.wart=15 + S.wart=4 S.wart=15 C.wart=4 S.wart=3 * C.wart=5 cyfra.lekswart=4 C.wart=3 cyfra.lekswart=5 cyfra.lekswart=3 Drzewo wyprowadzenia z przypisami dla 3*5+4n
Drzewo składniowe Powyższe drzewo wyprowadzenia przyjmuje następującą postać drzewa składniowego: + * 4 3 5 Drzewo składniowe dla 3*5+4n
Konstrukcja drzew składniowych dla wyrażeń Konstrukcja drzew składniowych dla wyrażeń jest podobna do translacji wyrażenia do notacji postfiksowej; Poddrzewa dla podwyrażeń są konstruowane przez stworzenie węzła dla każdego operatora i argumentu; Dziećmi węzła dla operatora są korzenie poddrzew reprezentujących podwyrażenia będące argumentami tego operatora; Każdy węzeł w drzewie składniowym może zostać zaimplementowany jako rekord z kilkoma polami;
Konstrukcja drzew składniowych dla wyrażeń W węźle dla operatora jedno pole identyfikuje operator, a pozostałe pola zawierają wskaźniki do węzłów argumentów tego operatora; Operator często jest nazywany etykietą tego węzła; Jeśli drzewo składniowe jest używane do translacji, to jego węzły mogą zawierać dodatkowe pola przechowujące wartości (lub wskaźniki do wartości) atrybutów związanych z tym węzłem.
Konstrukcja drzew składniowych dla wyrażeń Funkcje użyte przy tworzeniu węzłów w drzewie składniowym dla wyrażeń z operatorami dwuargumentowymi: - twwęzeł(op, lewy, prawy) tworzy węzeł operatora z etykietą op i dwoma polami zawierającymi wskaźniki do lewy i prawy; - twliść(id id,, wpis) tworzy węzeł identyfikatora z etykietą id i polem zawierającym wpis,, czyli wskaźnik do wpisu w tablicy symboli dla tego identyfikatora; - twliść(liczba liczba,, wpis) tworzy węzeł dla liczby z etykietą liczba i polem zawierającym wart,, czyli wartość tej liczby.
Przykład Rozważmy wyrażenie a-4+c Rozważmy ciąg wywołań funkcji p 1,p 2,,p 5, gdzie (1) p 1 :=twliść twliść(id, wpisa); (2) p 2 :=twliść twliść(liczba, 4); (3) p 3 :=twwęzeł twwęzeł( -,p 1,p 2 ); (4) p 4 :=twliść twliść(id,wpisc); (5) p 5 :=twwęzeł twwęzeł( +,p 3,p 4 );
Definicja sterowana składnią dla konstrukcji drzew składniowych Definicja S-atrybutowa do konstrukcji drzewa składniowego dla wyrażenia zawierającego operatory + i -. Produkcja Reguły semantyczne W-> W 1 + S W.wwsk:=twwęzeł( +, W 1.wwsk, S.wwsk) W-> W 1 - S W.wwsk:=twwęzeł( -, W 1.wwsk, S.wwsk) W-> S W.wwsk := S.wwsk S-> ( W ) S.Wwsk := W.wwsk S-> id S.wwsk:=twliść(id, id.wpis) S -> liczba S.wwsk:=twliść(liczba, liczba.wart)
Przypomnienie - drzewo wyprowadzenia z przypisami Drzewo wyprowadzenia z przypisami dla a-4+c
Przypomnienie - drzewo wyprowadzenia z przypisami Interpretacja: 1.Węzły w drzewie wyprowadzenia oznaczone nieterminalami W i S używają atrybutu syntezowanego wwsk do utrzymania wskaźnika do węzła drzewa składniowego dla wyrażenia reprezentowanego przez ten nieterminal; 2.Reguły semantyczne związane z produkcjami S->id i S-> liczba definiują atrybut S.wwsk jako wskaźnik do nowego liścia dla identyfikatora lub liczby; 3.Atrybuty id.wpis i liczba.wart są wartościami leksykalnymi, o których zakłada się, że są dostarczone przez analizator leksykalny podczas zwracania symboli leksykalnych id i liczba; 4.Gdy wyrażenie W jest pojedynczym składnikiem, czyli stosowana była produkcja W->S,, atrybut W.wwsk otrzymuje wartość S.wwsk; 5.Gdy wywoływana jest reguła semantyczna W.wwsk:=twwęzeł( -, W 1.wwsk, S.wwsk) związana z produkcją W->W 1 S wskaźniki W 1.wwsk, S.wwsk mają wartości a i 4 ustawione przez poprzednie reguły semantyczne;
Przypomnienie - drzewo wyprowadzenia z przypisami Uwaga: Warto zauważyć, że powyższe drzewo niższe, utworzone z rekordów, jest prawdziwym drzewem składniowym będącym wyjściem, podczas gdy drzewo wyższe (z linii punktowanych) jest drzewem wyprowa- dzenia, które może istnieć jedynie na rysunku. Drzewo wyższe Drzewo niższe
Acykliczne grafy skierowane dla wyrażeń W acyklicznym grafie skierowanym dla wyrażeń (nazywanym dag) możemy utożsamiać wspólne podwyrażenia; Podobnie jak drzewo składniowe, dag ma węzeł dla każdego podwyrażenia; Węzeł wewnętrzny reprezentuje operator, a jego dzieci reprezentują argumenty tego operatora; Różnica: węzeł w dag reprezentujący wspólne podwyrażenie może mieć więcej niż jednego rodzica w drzewie składniowym; W drzewie składniowym wspólne podwyrażenia są repreentowane jako powielone poddrzewa.
Acykliczne grafy skierowane dla wyrażeń - przykład 1. Rozważmy wyrażenie: a + a * ( b c ) + ( b c ) * d Interpretacja: Liść a ma dwóch rodziców, ponieważ a jest wspólnym podwyrażeniem a i a*(b-c). Podobnie, oba wystąpienia podwyrażenia b-c są reprezentowane przez pojedynczy węzeł i węzeł ten, podobnie, a dwóch rodziców.
Acykliczne grafy skierowane dla wyrażeń - przykład Pytanie: Czy definicja S-atrybutowa do konstrukcji drzewa składniowego dla wyrażenia zawierającego operatory + i, skonstruuje dag zamiast drzewa składniowego? Odpowiedź: tak,, ale po małej modyfikacji; Produkcja W-> W 1 + S W-> W 1 - S W-> S S-> ( W ) S-> id S -> liczba Reguły semantyczne W.wwsk:=twwęzeł( +, W 1.wwsk, S.wwsk) W.wwsk:=twwęzeł( -, W 1.wwsk, S.wwsk) W.wwsk := S.wwsk S.Wwsk := W.wwsk S.wwsk:=twliść(id, id.wpis) S.wwsk:=twliść(liczba, liczba.wart)
Acykliczne grafy skierowane dla wyrażeń - przykład Powyższa definicja sterowana składnią skonstruuje dag zamiast drzewa składniowego, jeśli zostaną zmodyfikowane operacje dla konstrukcji węzłów; Jeśli funkcja konstrukcji węzła będzie najpierw sprawdzać, czy identyczny węzeł nie został już wcześniej utworzony, wówczas otrzymamy graf dag. Przykładowo przed konstrukcją nowego węzła z etykietą op i polami lewy i prawy,, funkcja twwęzeł(op.lewy,prawy) może sprawdzać, czy taki węzeł nie został już wcześniej skonstruowany; Jeśli tak, to twwęzeł(op,lewy,prawy) może zwrócić wska- źnik na poprzednio skonstruowany węzeł.; Funkcje konstrukcji liści twliść mogą zachowywać się podobnie.
Acykliczne grafy skierowane dla wyrażeń - przykład Przy założeniu, że twwęzeł i twliść tworzą węzły tylko wtedy, gdy to konieczne, a kiedy jest to tylko możliwe zwracają wskaźniki do istniejących węzłów z poprawną etykietą i dziećmi, następująca sekwencja instrukcji skonstruuje dag. (1) p 1 :=twliść(id, a); (2) p 2 :=twliść(id, a); (3) p 3 :=twliść(id, b); (4) p 4 :=twliść(id, c); (5) p 5 :=twwęzeł( -, p 3, p 4 ); (6) p 6 :=twwęzeł( *, p 2, p 5 ); (7) p 7 :=twwęzeł( -, p 1, p 6 ); (1) p 8 :=twliść(id, b); (2) p 9 :=twliść(id, b); (3) p 10 :=twwęzeł( -, p 8, p 9 ); (4) p 11 :=twliść(id, d); (5) p 12 :=twwęzeł( -,p 10,p 11 ); (6) p 13 :=twwęzeł( *,p 7,p 12 );
Acykliczne grafy skierowane dla (1) p 1 :=twliść(id, a); (2) p 2 :=twliść(id, a); (3) p 3 :=twliść(id, b); (4) p 4 :=twliść(id, c); (5) p 5 :=twwęzeł( -, p 3, p 4 ); (6) p 6 :=twwęzeł( *, p 2, p 5 ); (7) p 7 :=twwęzeł( -, p 1, p 6 ); wyrażeń - przykład (1) p 8 :=twliść(id, b); (2) p 9 :=twliść(id, b); (3) p 10 :=twwęzeł( -, p 8, p 9 ); (4) p 11 :=twliść(id, d); (5) p 12 :=twwęzeł( -,p 10,p 11 ); (6) p 13 :=twwęzeł( *,p 7,p 12 ); Gdy wywołanie twliść(id id,a) powtarza się w wierszu (2), zwracany jest węzeł skonstruowany w poprzednim wywołaniu twliść(id id,a), a więc p 1 =p 2 ; Podobnie, węzły zwracane w wierszach (8) i (9) są takie same jak zwracane w wierszach (3) i (4); Zatem węzeł zwracany w wierszu (10) musi być ten sam, jak konstruowany przez wywołanie twwęzeł w wierszu (5).
Implementacja W wielu zastosowaniach węzły są implementowane jako rekordy przechowywane w tablicy; Każdy rekord ma pole z etykieta wyznaczającą rodzaj węzła; Do tego węzła można odwoływać się, używając jego indeksu lub pozycji w tablicy. Indeks całkowity węzła, tradycyjnie jest nazwany numerem.
Algorytm tworzenia węzłów dla reprezentacji wyrażeń za pomocą daga Załóżmy, że węzły są przechowywane w tablicy oraz że odwołanie do każdego węzła dokonuje się po jego numerze; Niech sygnaturą węzła operatora będzie trójka <op, l, r> składająca się z etykiety op,, lewego dziecka l i prawego dziecka r. Wejście: etykieta op,, węzły l i r. Wyjście: Węzeł z sygnaturą <op, l, r> Metoda: Przejrzyj tablicę w poszukiwaniu węzła m z etykietą op, lewym dzieckiem l i prawym r.. Jeśli taki węzeł zostanie znaleziony, to zwróć m,, jeśli nie, stwórz nowy węzeł n z etykietą op,, lewym dzieckiem l i prawym r,, po czym zwróć n.
Algorytm tworzenia węzłów dla reprezentacji wyrażeń za pomocą daga Oczywiście sposób ustalenia, czy węzeł m znajduje się już w tablicy, polega na utrzymaniu wszystkich utworzonych do tej pory węzłów na liście i sprawdzaniu, czy każdy węzeł z listy ma potrzebna sygnaturę; Poszukiwanie m może stać się bardziej wydajne przy użyciu k list, zwanych kubełkami oraz funkcji mieszającej h do wyznaczenia, który kubełek ma być przeszukiwany; Wystarczy jakakolwiek struktura danych implementująca słownik zaproponowany przez Aho, Hopcorfta i Ullmana [1983]. Ważną własnością takiej struktury jest to, że dla danego klucza, czyli etykiety op i dwóch węzłów l i r, można szybko otrzymać węzeł m o sygnaturze <op, l, r> lub stwierdzić że taki nie istnieje.
Funkcja mieszająca Funkcja mieszająca h oblicza numer kubełka na podstawie wartości op, l i r; Dla takich samych argumentów funkcja ta zwraca zawsze ten sam numer kubełka; Jeśli m nie znajduje się już w kubełku h(op op, l, r), to nowy węzeł n jest tworzony i umieszczany w tym kubełku, tak aby kolejne wyszukiwania znalazły go tam; Kilka sygnatur może mieć równe kody mieszające i trafiać do tego samego kubełka, lecz w praktyce oczekujemy, że każdy kubełek zawiera małą liczbę węzłów.
Implementacja kubełków Każdy kubełek może zostać zaimplementowany jako lista jednokierunkowa; Każdy element na liście reprezentuje węzeł; Nagłówki kubełków, zawierające wskaźniki do pierwszych elementów list, są przechowywane w tablicy; Numer kubełka, otrzymywany z obliczenia h(op op, l, r), jest indeksem tej tablicy;
Algorytm tworzenia węzłów dla reprezentacji wyrażeń za pomocą daga Algorytm można przystosować dla węzłów, które nie są umieszczane kolejno w tablicy. W wielu kompilatorach węzły są umieszczane w miarę potrzeby aby uniknąć tworzenia tablicy, która może przez większość czasu przechowywać zbyt wiele węzłów, a przez część za mało węzłów; W takim przypadku nie możemy założyć, że węzły znajdują się w pamięci sekwencyjnej, więc trzeba używać wskaźników do odwoływania się do węzłów; Jeśli funkcja mieszająca może zostać użyta do obliczenia numeru kubełka z etykiety i dwóch wskaźników do węzłów dzieci, to można również użyć wskaźników do węzłów zamiast ich numerów. W przeciwnym przypadku węzły możemy ponumerować dowolnie i używać tej liczby jako numeru węzła.
Grafy dag Uwagi: 1.Grafów dag można również używać do reprezentacji zbiorów wyrażeń, ponieważ dag może mieć więcej niż jeden korzeń. 2.Grafy dag znalazły swoje zastosowanie do generowania kodu i optymalizacji kodu, gdzie obliczania wykonywane przez ciąg instrukcji przypisania są reprezentowane właśnie za pomocą grafów dag.
Koniec wykładu jedenastego