Czy są jakieś kompilatory, które próbują samodzielnie naprawić błędy składniowe? [Zamknięte]

15

Jakiś czas temu słyszałem, że istniał kompilator, który próbował naprawić błędy składniowe poprzez analizę kontekstu i wywnioskowanie, co było zamierzone.

Czy taki kompilator naprawdę istnieje? Oczywiście ma niewielką wartość praktyczną, ale bardzo interesująca byłaby zabawa i nauka.

Nathan Osman
źródło
3
Czy IntelliSense należy do tej kategorii? Wiele kompilatorów ma błędy podobne do oczekiwanych [średników].
Robert Harvey
1
@Robert: Nie, ale to dobra uwaga.
Nathan Osman,
1
Mój przyjaciel sporo zhakował preprocesor C, na przykład „inlcude -> include”, i trochę pracy nad próbą ustalenia, gdzie powinny być zamknięte otwarte warunki warunkowe. To była praca magisterska, którą szybko porzucił dla czegoś łatwiejszego. Wciąż dość interesujące pytanie!
Tim Post
3
Kompilator AC # kończy się niepowodzeniem z BARDZO przydatnymi komunikatami o błędach. To w połączeniu z dobrą dokumentacją dostępną online dla każdego kodu błędu działa raczej dobrze. Nie jest dobrym pomysłem automatyczne poprawianie składni, chociaż tłumacze HTML (np. Przeglądarki) często tak robią.
Job
1
Kompilator, o którym mówisz, był oryginalnym PL / I. Zakładał, że wszystko, co napisał programista, musiało coś znaczyć, i próbował zgadnąć, co to może być. Z mojego doświadczenia wynika, że ​​bardzo źle to zgadło!
david.pfx

Odpowiedzi:

28

W pewnym sensie kompilacja wnioskuje, do czego służy pewna składnia, a zatem błąd składniowy występuje, gdy kompilator nie jest w stanie tego zrozumieć. Możesz dodać więcej „zgadywania”, aby kompilator mógł wyciągać dalsze wnioski i być bardziej elastyczny ze składnią, ale musi to robić na podstawie określonego zestawu reguł. Zasady te stają się częścią języka i nie są już błędami.

Nie, nie ma takich kompilatorów, bo pytanie nie ma sensu. Zgadywanie, co mają robić błędy składniowe zgodnie z pewnym zbiorem reguł, staje się po prostu częścią składni.

W tym sensie istnieje dobry przykład kompilatora, który to robi: Dowolny kompilator C. Często po prostu wypisują ostrzeżenie o czymś, co nie jest tak, jak powinno, a następnie zakładają, że masz na myśli X, i kontynuuj. Jest to w rzeczywistości „zgadywanie” niejasnego kodu (chociaż w większości nie jest to składnia per se), coś, co równie dobrze mogłoby zatrzymać kompilację z błędem, a zatem kwalifikować się jako błąd.

Lennart Regebro
źródło
4
To jest właściwa odpowiedź. Gdy kompilator może odzyskać po błędzie, nie jest to już tak naprawdę błąd. Perl jest (in?) Znany z tego zachowania „Zrób co mam na myśli”, wybierając to, co programista najprawdopodobniej miał na myśli podając niejednoznaczne źródło.
Jon Purdy,
Perl poświęca gadatliwość dla rozmiaru kodu źródłowego.
Nathan Osman
@George Edison: To albo tautologia, albo sprzeczność.
Jon Purdy,
Lub głęboki wgląd. :)
Lennart Regebro
23

Brzmi naprawdę niebezpiecznie. Jeśli kompilator spróbuje wywnioskować twoją intencję, źle ją wyliczy, naprawi kod, a następnie nie powie ci (lub ostrzeże, że jak wszyscy ignorujesz), masz zamiar uruchomić kod, który może poważnie wyrządzić szkody.

Taki kompilator jest prawdopodobnie czymś, co bardzo celowo NIE zostało utworzone.

nganju
źródło
5
Wiem to. Taki kompilator nie miałby zastosowania do kompilacji, ale koncepcja jest dość interesująca i ma potencjał edukacyjny.
Nathan Osman
2
prawie wszystkie najnowsze IDE zawierają sugestie dotyczące składni i są naprawdę pomocne. a przez resztę części zgadzam się z nganju
Jigarem Joshi
Nie użyłbym takiego kompilatora. Jest objęty hasłem „czarnej magii”.
Michael K
Hmmm, gdzie oceniasz wnioskowanie typu Scali na tej skali? Po wypróbowaniu powiedziałby, że to duży wkład w zwięzły kod. Z drugiej strony od czasu do czasu postrzelił mnie w stopę (np. Ponieważ myślałem, że mam do czynienia z listami, ale tak naprawdę nadal miałem do czynienia z setami).
dnia
Mamy takie rzeczy jak autoskop w OMP, więc trochę z tego można zrobić. Oczywiście kod, nad którym pracuję, wyłączył automatyczne skalowanie, ponieważ mu nie ufamy. Widziałem interaktywny kompilator, który pyta „czy chodziło Ci o XXX?”. Tak daleko, jak bym chętnie poszedł. I nawet to jest prawdopodobnie zbyt niebezpieczne.
Omega Centauri,
12

IDE dla języka programowania zwykle w dzisiejszych czasach ma kompilator działający w tle, dzięki czemu może zapewniać usługi analizy, takie jak kolorowanie składni, IntelliSense, błędy i tak dalej. Oczywiście taki kompilator musi być w stanie zrozumieć sens głęboko uszkodzonego kodu; przez większość czasu podczas edycji kod jest nieprawidłowy. Ale nadal musimy to zrozumieć.

Jednak zwykle funkcja odzyskiwania po błędzie jest używana tylko podczas edycji; nie ma sensu zezwalać na faktyczną kompilację w scenariuszach „głównych”.

Co ciekawe, wbudowaliśmy tę funkcję w kompilator JScript.NET; w zasadzie możliwe jest przełączenie kompilatora w tryb, w którym zezwalamy kompilatorowi na kontynuowanie nawet w przypadku napotkania błędu, jeśli środowisko IDE by z niego wyszło. Możesz wpisać kod Visual Basic , uruchomić na nim kompilator JScript.NET i mieć uzasadnioną szansę, że działający program wyjdzie z drugiej strony!

To zabawne demo, ale z wielu powodów okazuje się, że nie jest to bardzo dobra funkcja w scenariuszach „głównych”. Pełne wyjaśnienie byłoby dość długie; krótkie wyjaśnienie jest takie, że powoduje, że programy działają nieprzewidywalnie i przypadkowo , a także utrudnia uruchomienie tego samego kodu przez wiele kompilatorów lub wiele wersji tego samego kompilatora. Duże wydatki, które dodaje funkcja, nie są uzasadnione małymi korzyściami.

Peter Torr, który w przeszłości napisał tę funkcję, omawia ją krótko w blogu z 2003 roku .

Chociaż udostępniamy tę funkcję za pośrednictwem interfejsów API skryptów silnika JScript .NET, nie znam żadnych prawdziwych klientów, którzy kiedykolwiek z niej korzystali.

Eric Lippert
źródło
Żałuję, że mój pracodawca nie miał środków na takie eksperymenty; nawet nie przeprowadzamy testów jednostkowych w nocy, ponieważ jest tyle funkcji do dodania i błędów do naprawienia :(
Job
1
Jest to odpowiedź, na którą liczyłem ... jak już wspomniałem - oczywiście taka funkcja ma niewielkie praktyczne zastosowanie, ale byłaby świetnym sposobem na poznanie niektórych technik, które można by zastosować do innych rzeczy. (
Nathan Osman
1
@Job: Ogólna mądrość jest taka, że ​​jeśli nie przeprowadzasz regularnie testów jednostkowych, będziesz mieć dużo więcej błędów do naprawienia .
Eric Lippert,
Wiem już, co muszę zrobić z moją pracą zamiast narzekać. W niektórych firmach programistycznych ludzie na szczycie nie rozumieją naprawdę różnicy między prototypem a gotowym produktem. W końcu w pikselach często nie ma dużej różnicy. Rozsądnie jest nie zaczynać od prototypu, aby nie marnować czasu. Ale okropna odpowiedź „wygląda dobrze, ile dni można wprowadzić do produkcji?”. Są to te same osoby, które byłyby podejrzane, gdyby inżynierowie powiedzieli im, że muszą spędzić czas na infrastrukturze lub refaktoryzacji. Słyszę, że nawet Spolskyemu się to nie podoba.
Job
10

Pierwszą rzeczą, jaka przychodzi mi do głowy, jest automatyczne wstawianie średników w JavaScript . Okropna, okropna cecha, która nigdy nie powinna była przedostać się do języka.

Nie oznacza to, że nie byłby w stanie wykonać lepszej pracy. Jeśli spojrzy w przyszłość po linii, może lepiej odgadnąć zamiary programisty, ale pod koniec dnia, jeśli istnieje wiele prawidłowych sposobów, w jakie mogła pójść składnia , to tak naprawdę nie ma substytutu dla programisty.

Dean Harding
źródło
1
Zgadzam się z funkcją wstawiania średników JavaScript - całkowicie bezużyteczną.
Nathan Osman
7

Wydaje mi się, że jeśli kompilator mógłby naprawić niepoprawną składnię, to należy ją udokumentować w języku.

Przyczyną błędów składniowych jest to, że parser nie mógł utworzyć abstrakcyjnego drzewa składni z programu. Dzieje się tak, gdy token jest nie na miejscu. Aby odgadnąć, gdzie powinien być ten token, czy należy go usunąć lub dodać jakiś inny token w celu naprawienia błędu, potrzebny byłby komputer, który mógłby odgadnąć zamiar programisty. Jak maszyna może zgadywać, że:

int x = 5 6;

Miało być:

int x = 5 + 6;

To może równie dobrze być dowolna z poniższych sytuacji: 56, 5 - 6, 5 & 6. Kompilator nie może wiedzieć.

Ta technologia jeszcze nie istnieje.

jjnguy
źródło
1
Taka technologia nie może istnieć. Czytanie w myślach nie jest dozwolone; wszystkie instrukcje muszą jednoznacznie pochodzić z kodu.
Job
To prawda, ale tak naprawdę miałem na myśli: „Czy są jakieś kompilatory, które próbują poprawić niepoprawną składnię, zgadując na podstawie kontekstu”. Fakt, że kompilator poprawia niepoprawną składnię, nie powoduje, że jest ona poprawna. Zdaję sobie również sprawę, że takie narzędzie byłoby bezużyteczne przy tworzeniu kodu.
Nathan Osman
6

Chociaż nie do końca to samo, właśnie dlatego HTML zmienił się w katastrofę. Przeglądarki tolerowały złe znaczniki i następna rzecz, o której wiesz, że przeglądarka A nie mogła renderować w taki sam sposób, jak przeglądarka B (tak, są inne powody, ale była to jedna z niewielu, szczególnie około 10 lat temu, zanim niektóre zasady dotyczące luźności stały się konwencją ).

Jak podaje Eric Lippert, wiele z tych rzeczy najlepiej obsługiwać IDE, a nie kompilator. To pozwala zobaczyć, co automatyczne bity próbują dla ciebie zepsuć.

Myślę, że obecnie dominującą strategią jest ciągłe doskonalenie języka zamiast rozluźniania kompilatora: jeśli naprawdę jest to coś, co kompilator może automatycznie zrozumieć, to wprowadź wokół niego dobrze zdefiniowaną konstrukcję językową.

Bezpośrednim przykładem, który przychodzi mi na myśl, są właściwości automatyczne w języku C # (nie jest to jedyny język, który ma coś podobnego): Biorąc pod uwagę, że większość programów pobierających / ustawiających w dowolnej aplikacji to tak naprawdę opakowania wokół pola, po prostu pozwól programistom wskazać ich i niech kompilator wstrzykuje resztę.

Co skłoniło mnie do myślenia: większość języków w stylu C już to robi. W przypadku rzeczy, które można ustalić automatycznie, po prostu popraw składnię:

 if (true == x)
 {
    dothis();
 }
 else
 {
    dothat();
 }

Można zredukować do:

if (true == x)
    dothis();
else
    dothat();

Ostatecznie myślę, że sprowadza się to do tego: trend polega na tym, że nie czynisz kompilatora „inteligentniejszym” ani „luźniejszym”. To jest język, który jest mądrzejszy lub luźniejszy.

Poza tym zbyt duża „pomoc” może być niebezpieczna, na przykład klasyczny błąd „jeśli”:

if (true == x)
    if (true == y)
       dothis();
else
    dothat();
MIA
źródło
Należy zauważyć, że XHTML dostarczył rozwiązanie problemu bałaganu spowodowanego złymi specyfikacjami HTML.
Nathan Osman
2
if (x && y) dothis(); else dothat();wyglądałby nieco lepiej.
Job
1
Kot umiera za każdym razem, gdy ktoś porównuje się z truelub false.
JensG
2

Kiedy kodowałem FORTRAN i PL / I na przełomie lat 80. i 90. na minikomputerze DEC i IBM, wydaje mi się, że pamiętam, że kompilatory regularnie wylogowują komunikaty typu „bla bla bla; zakładając bla bla i kontynuując .. . ” Wówczas było to dziedzictwo (jeszcze wcześniej, przed moim czasem) dni przetwarzania wsadowego i kart dziurkowanych, kiedy prawdopodobnie istniało ogromne oczekiwanie między przesłaniem kodu do uruchomienia a odzyskaniem wyników. Dlatego kompilator miał dużo sensu, aby odgadnąć programistę i kontynuować, zamiast przerywać pierwszy napotkany błąd. Pamiętaj, że nie pamiętam, aby „poprawki” były szczególnie wyrafinowane. Kiedy ostatecznie przeniosłem się na interaktywne stacje robocze Unix (Sun, SGI itp.),

czas
źródło
2
Te kompilatory byłyby kontynuowane, ale kontynuowałyby TYLKO w celu znalezienia dalszych błędów, więc można (potencjalnie) naprawić kilka rzeczy przed ponownym przesłaniem. Nowoczesne komputery PC są wystarczająco szybkie, aby „interaktywny” kompilator mógł zatrzymać się przy pierwszym błędzie składni i przenieść cię do edytora. (I tak naprawdę oryginalny Turbo Pascal na początku lat 80. działał dokładnie w ten sposób. Było miło.)
John R. Strohm,
1
Tak, pamiętam, że kompilator optymalizujący IBM PL / I od czasu do czasu dostarczałby brakujące instrukcje BEGIN i END, a ISTR również dostarczałby brakujące średniki.
TMN
1

Celem kompilatora jest tworzenie plików wykonywalnych, które zachowują się zgodnie z potrzebami. Jeśli programista napisze coś, co jest nieważne, nawet jeśli kompilator może z prawdopodobieństwem 90% odgadnąć, co było zamierzone, ogólnie lepiej byłoby wymagać od programisty naprawy programu, aby wyraził zamiar, niż pozwolić kompilatorowi na wykonanie pliku wykonywalnego co miałoby znaczną szansę na ukrycie błędu.

Oczywiście języki powinny być ogólnie zaprojektowane w taki sposób, aby kod, który wyraźnie wyraża intencję, był legalny, a kod, który nie wyraża jasno intencji, powinien być zabroniony, ale to nie znaczy, że są. Rozważ następujący kod [Java lub C #]

const double oneTenth = 0.1;
const float  oneTenthF = 0.1f;
...
float f1 = oneTenth;
double d1 = oneTenthF;

f1Pomocne byłoby dodanie przez kompilator niejawnej rzutówki dla przypisania , ponieważ jest tylko jedna logiczna rzecz, którą programista mógłby chcieć f1zawrzeć ( floatwartość najbliższa 1/10). Jednak zamiast zachęcać kompilatory do akceptowania niewłaściwych programów, lepiej byłoby, gdyby specyfikacja zezwalała na niejawne konwersje typu double-float w niektórych kontekstach. Z drugiej strony, przypisanie do d1może być lub nie być tym, co naprawdę zamierzał programista, ale nie ma żadnej reguły językowej zabraniającej tego.

Najgorsze rodzaje reguł językowych to te, w których kompilatory dokonują wnioskowania w przypadkach, w których coś nie mogłoby legalnie skompilować inaczej, ale gdzie program może „przypadkowo” być prawidłowy w przypadku, gdy zamierzono wnioskować. Wiele sytuacji związanych z domniemanym zakończeniem deklaracji należy do tej kategorii. Jeśli programista, który zamierza napisać dwie oddzielne instrukcje, pomija terminator instrukcji, kompilatorowi zwykle udaje się wywnioskować granicę instrukcji, ale czasami może uważać za jedną instrukcję coś, co powinno być przetwarzane jako dwa.

supercat
źródło
0

Błędy składniowe są szczególnie trudne do poprawienia. Weźmy przypadek brakującego prawa ): my wiemy, że możemy naprawić kod, wstawiając jeden, ale zwykle jest wiele miejsc, w których moglibyśmy wstawić jeden i uzyskać poprawny składniowo program.

Znacznie łatwiejszym punktem są błędnie napisane identyfikatory (należy jednak pamiętać, że nie są to błędy składniowe). Można obliczyć odległość edycji między nierozwiązywalnym identyfikatorem a wszystkimi identyfikatorami w zakresie, a poprzez zastąpienie nierozwiązywalnego słowa słowem, które użytkownik najprawdopodobniej miał na myśli, w wielu przypadkach można znaleźć odpowiedni program. Okazuje się jednak, że nadal lepiej jest zgłosić błąd i pozwolić IDE zasugerować prawidłowe zastąpienie.

Ingo
źródło
-1

Taki kompilator byłby po prostu swobodną, ​​niestandardową implementacją dowolnego kompilowanego języka.

Rei Miyasaka
źródło
-2

Próbowano go wiele razy, ale często nie osiągnął pożądanego efektu: pomyśl HAL 9000 lub GlaDOS.

cbrandolino
źródło
-3

W C nie można przekazywać tablic według wartości, ale kompilator pozwala pisać:

void foo(int array[10]);

który jest następnie cicho przepisywany jako:

void foo(int* array);

Jakie to głupie? Wolę tutaj twardy błąd zamiast cichego przepisywania, ponieważ ta specjalna reguła doprowadziła wielu programistów do przekonania, że ​​tablice i wskaźniki są w zasadzie tym samym. Oni nie są.

fredoverflow
źródło