Przeczytałem kilka artykułów, artykułów i sekcji 4.1.4, rozdział 4 kompilatorów: Zasady, techniki i narzędzia (2. wydanie) (aka „The Dragon Book”), które wszystkie omawiają temat odzyskiwania błędów kompilatora składniowego. Jednak po eksperymentach z kilkoma nowoczesnymi kompilatorami zauważyłem, że odzyskują one również po błędach semantycznych , a także błędach składniowych.
Rozumiem dość dobrze algorytmy i techniki kryjące się za kompilatorami odzyskującymi na podstawie błędów pokrewnych, ale nie do końca rozumiem, w jaki sposób kompilator może odzyskać dane po błędzie semantycznym.
Obecnie używam niewielkiej zmiany wzorca odwiedzającego, aby wygenerować kod z mojego abstrakcyjnego drzewa składni. Rozważ mój kompilator kompilujący następujące wyrażenia:
1 / (2 * (3 + "4"))
Kompilator wygeneruje następujące abstrakcyjne drzewo składniowe:
op(/)
|
-------
/ \
int(1) op(*)
|
-------
/ \
int(2) op(+)
|
-------
/ \
int(3) str(4)
Faza generowania kodu użyłaby następnie wzorca odwiedzającego, aby rekurencyjnie przechodzić przez abstrakcyjne drzewo składniowe i przeprowadzać sprawdzanie typu. Drzewo abstrakcyjnej składni będzie przechodzić, dopóki kompilator nie znajdzie się w najbardziej wewnętrznej części wyrażenia; (3 + "4")
. Kompilator sprawdza następnie każdą stronę wyrażeń i widzi, że nie są one semantycznie równoważne. Kompilator zgłasza błąd typu. Tutaj leży problem. Co powinien teraz zrobić kompilator ?
Aby kompilator mógł zresetować się po tym błędzie i kontynuować sprawdzanie typu zewnętrznych części wyrażeń, musiałby zwrócić pewien typ ( int
lub str
) z oceny najbardziej wewnętrznej części wyrażenia, do następnej najbardziej wewnętrznej części wyrażenia. Ale po prostu nie ma typu do zwrócenia . Ponieważ wystąpił błąd typu, nie wydedukowano żadnego typu.
Jednym z możliwych rozwiązań, które postulowałem, jest to, że jeśli wystąpi błąd typu, błąd powinien zostać podniesiony, a specjalna wartość, która oznacza, że wystąpił błąd typu, powinna zostać zwrócona do poprzednich wywołań abstrakcyjnego drzewa przejścia składni. Jeśli poprzednie wywołania przechodzenia napotkają tę wartość, wiedzą, że błąd typu wystąpił głębiej w abstrakcyjnym drzewie składni i powinni unikać próby wywnioskowania typu. Chociaż ta metoda wydaje się działać, wydaje się bardzo nieefektywna. Jeśli najbardziej wewnętrzna część wyrażenia znajduje się głęboko w abstrakcyjnym drzewie składni, wówczas kompilator będzie musiał wykonać wiele rekurencyjnych wywołań tylko po to, aby zdać sobie sprawę, że nie można wykonać żadnej prawdziwej pracy, i po prostu powrócić z każdego z nich.
Czy zastosowałem metodę, którą opisałem powyżej (wątpię). Jeśli tak, to czy nie jest wydajne? Jeśli nie, jakie dokładnie metody są stosowane, gdy kompilatory odzyskują po błędach semantycznych?