Szukam CoffeeScript na stronie http://coffeescript.org/ i ma on tekst
Kompilator CoffeeScript jest sam napisany w CoffeeScript
W jaki sposób kompilator może sam się skompilować lub co oznacza to stwierdzenie?
Szukam CoffeeScript na stronie http://coffeescript.org/ i ma on tekst
Kompilator CoffeeScript jest sam napisany w CoffeeScript
W jaki sposób kompilator może sam się skompilować lub co oznacza to stwierdzenie?
Odpowiedzi:
Pierwsza edycja kompilatora nie może być wygenerowana maszynowo z określonego dla niego języka programowania; twoje zamieszanie jest zrozumiałe. Późniejsza wersja kompilatora z większą liczbą funkcji językowych (z przepisanym kodem źródłowym w pierwszej wersji nowego języka) mogłaby zostać zbudowana przez pierwszy kompilator. Ta wersja mogłaby następnie skompilować następny kompilator i tak dalej. Oto przykład:
Uwaga: nie jestem pewien, jak numerowane są wersje CoffeeScript, to był tylko przykład.
Ten proces jest zwykle nazywany ładowaniem początkowym . Innym przykładem kompilatora ładującego jest rustc
kompilator języka Rust .
W artykule Reflections on Trusting Trust , Ken Thompson, jeden z twórców Uniksa, pisze fascynujący (i łatwy do odczytania) przegląd tego, jak kompilator C kompiluje się sam. Podobne koncepcje można zastosować do CoffeeScript lub dowolnego innego języka.
Pomysł kompilatora, który kompiluje swój własny kod, jest nieco podobny do quine : kodu źródłowego, który po wykonaniu generuje na wyjściu oryginalny kod źródłowy. Oto jeden przykład quine CoffeeScript. Thompson podał ten przykład C quine:
char s[] = {
'\t',
'0',
'\n',
'}',
';',
'\n',
'\n',
'/',
'*',
'\n',
… 213 lines omitted …
0
};
/*
* The string s is a representation of the body
* of this program from '0'
* to the end.
*/
main()
{
int i;
printf("char\ts[] = {\n");
for(i = 0; s[i]; i++)
printf("\t%d,\n", s[i]);
printf("%s", s);
}
Następnie możesz się zastanawiać, w jaki sposób kompilator jest nauczany, że sekwencja ucieczki, taka jak '\n'
reprezentuje kod ASCII 10. Odpowiedź jest taka, że gdzieś w kompilatorze C istnieje procedura, która interpretuje literały znakowe, zawierając takie warunki, aby rozpoznawać sekwencje z ukośnikiem odwrotnym:
…
c = next();
if (c != '\\') return c; /* A normal character */
c = next();
if (c == '\\') return '\\'; /* Two backslashes in the code means one backslash */
if (c == 'r') return '\r'; /* '\r' is a carriage return */
…
Więc możemy dodać jeden warunek do powyższego kodu…
if (c == 'n') return 10; /* '\n' is a newline */
… Aby stworzyć kompilator, który wie, że '\n'
reprezentuje ASCII 10. Co ciekawe, ten kompilator i wszystkie kolejne kompilatory przez niego skompilowane „znają” to mapowanie, więc w następnej generacji kodu źródłowego można zmienić tę ostatnią linię na
if (c == 'n') return '\n';
… I zrobi to dobrze! 10
Pochodzi z kompilatora, i nie musi być wyraźnie zdefiniowane w kodzie źródłowym kompilatora. 1
To jeden z przykładów funkcji języka C, która została zaimplementowana w kodzie C. Teraz powtórz ten proces dla każdej funkcji języka, a otrzymasz kompilator „samowystarczający”: kompilator C napisany w języku C.
1 Spójność opisana w artykule polega na tym, że skoro kompilator można „nauczyć” takich faktów, można go również błędnie nauczyć generowania trojanów plików wykonywalnych w sposób trudny do wykrycia, a taki akt sabotażu może się utrzymywać we wszystkich kompilatorach utworzonych przez skażony kompilator.
Otrzymałeś już bardzo dobrą odpowiedź, ale chcę zaproponować Ci inną perspektywę, która, miejmy nadzieję, będzie dla Ciebie pouczająca. Najpierw ustalmy dwa fakty, co do których oboje możemy się zgodzić:
Jestem pewien, że możesz się zgodzić, że oba punkty 1 i 2 są prawdziwe. Teraz spójrz na te dwa stwierdzenia. Czy widzisz teraz, że kompilator CoffeeScript może całkowicie skompilować kompilator CoffeeScript jest całkowicie normalnym zjawiskiem?
Kompilator nie dba o to , co kompiluje. Dopóki jest to program napisany w CoffeeScript, może go skompilować. Tak się składa, że sam kompilator CoffeeScript jest właśnie takim programem. Kompilator CoffeeScript nie przejmuje się tym, że kompiluje sam kompilator CoffeeScript. Wszystko, co widzi, to kod CoffeeScript. Kropka.
W jaki sposób kompilator może sam się skompilować lub co oznacza to stwierdzenie?
Tak, dokładnie to oznacza to stwierdzenie i mam nadzieję, że teraz widzisz, jak to stwierdzenie jest prawdziwe.
W jaki sposób kompilator może sam się skompilować lub co oznacza to stwierdzenie?
To dokładnie oznacza. Przede wszystkim kilka kwestii do rozważenia. Są cztery obiekty, którym musimy się przyjrzeć:
Teraz powinno być oczywiste, że można użyć wygenerowanego zestawu - pliku wykonywalnego - kompilatora CoffeScript do skompilowania dowolnego programu CoffeScript i wygenerowania zestawu dla tego programu.
Teraz sam kompilator CoffeScript jest po prostu dowolnym programem CoffeScript, a zatem może być skompilowany przez kompilator CoffeScript.
Wygląda na to, że twoje zamieszanie wynika z faktu, że kiedy tworzysz swój własny nowy język, nie masz jeszcze kompilatora, którego możesz użyć do skompilowania swojego kompilatora. To z pewnością wygląda na problem z jajkiem kurzym , prawda?
Przedstaw proces zwany ładowaniem początkowym .
Teraz musisz dodać nowe funkcje. Powiedzmy, że zaimplementowałeś tylko while
-loops, ale chcesz także for
-loops. Nie stanowi to problemu, ponieważ możesz przepisać dowolną for
-loop w taki sposób, aby była while
-loopem. Oznacza to, że możesz używać while
-loops tylko w kodzie źródłowym swojego kompilatora, ponieważ zestaw, który masz pod ręką, może tylko je skompilować. Ale możesz tworzyć funkcje wewnątrz swojego kompilatora, które mogą z nim pase i kompilować for
pętle. Następnie używasz zestawu, który już masz, i kompilujesz nową wersję kompilatora. A teraz masz zestaw kompilatora, który może również analizować i kompilować for
-loops! Możesz teraz wrócić do pliku źródłowego swojego kompilatora i przepisać wszystkie while
niechciane for
pętle do -loops.
Przepłucz i powtarzaj, aż wszystkie żądane funkcje językowe będą mogły zostać skompilowane za pomocą kompilatora.
while
i for
oczywiście były to tylko przykłady, ale to działa dla każdej nowej funkcji językowej, którą chcesz. A potem jesteś w sytuacji, w której jest teraz CoffeScript: kompilator kompiluje się sam.
Jest tam dużo literatury. Refleksje na temat zaufania do zaufania to klasyka, którą każdy zainteresowany tym tematem powinien przeczytać przynajmniej raz.
Tutaj termin kompilator” wyjaśnia fakt, że w grę wchodzą dwa pliki. Jeden z nich to plik wykonywalny, który przyjmuje pliki wejściowe zapisane w CoffeScript i tworzy jako plik wyjściowy inny plik wykonywalny, plik obiektowy z możliwością łączenia lub bibliotekę współdzieloną. Drugi to plik źródłowy CoffeeScript, który przypadkiem opisuje procedurę kompilacji CoffeeScript.
Stosujesz pierwszy plik do drugiego, tworząc trzeci, który jest w stanie wykonać tę samą czynność kompilacji co pierwszy (być może więcej, jeśli drugi plik definiuje funkcje nie zaimplementowane w pierwszym), a więc może zastąpić pierwszy, jeśli tak pragnienie.
Ponieważ kompilator CoffeeScript w wersji Ruby już istniał, został użyty do utworzenia wersji kompilatora CoffeeScript dla języka CoffeeScript.
Jest to znane jako kompilator samoobsługowy .
Jest to niezwykle powszechne i zwykle wynika z chęci autora do używania własnego języka, aby utrzymać jego rozwój.
Nie chodzi tutaj o kompilatory, ale o wyrazistość języka, ponieważ kompilator to tylko program napisany w jakimś języku.
Kiedy mówimy, że „język jest napisany / zaimplementowany”, w rzeczywistości mamy na myśli, że zaimplementowano kompilator lub interpreter tego języka. Istnieją języki programowania, w których można pisać programy, które implementują język (są kompilatorami / tłumaczami dla tego samego języka). Te języki nazywane są językami uniwersalnymi .
Aby móc to zrozumieć, pomyśl o metalowej tokarce. Jest to narzędzie służące do kształtowania metalu. Możliwe jest, używając tylko tego narzędzia, stworzyć inne, identyczne narzędzie, tworząc jego części. Tym samym narzędzie to jest maszyną uniwersalną. Oczywiście pierwsza została stworzona innymi środkami (innymi narzędziami) i prawdopodobnie była niższej jakości. Ale pierwszy był używany do budowania nowych z większą precyzją.
Drukarka 3D to prawie uniwersalna maszyna. Możesz wydrukować całą drukarkę 3D za pomocą drukarki 3D (nie możesz zbudować końcówki, która topi plastik).
Wersja n + 1 kompilatora jest napisana w X.
W ten sposób może być skompilowany przez n-tą wersję kompilatora (również napisaną w X).
Ale pierwsza wersja kompilatora napisana w X musi zostać skompilowana przez kompilator dla X, który jest napisany w języku innym niż X. Ten krok jest nazywany bootstrapowaniem kompilatora.
Kompilatory pobierają specyfikację wysokiego poziomu i przekształcają ją w implementację niskiego poziomu, taką jak może być wykonywana na sprzęcie. Dlatego nie ma żadnego związku między formatem specyfikacji a rzeczywistym wykonaniem, poza semantyką docelowego języka.
Kompilatory krzyżowe przenoszą się z jednego systemu do innego, kompilatory międzyjęzykowe kompilują specyfikację jednego języka na specyfikację innego języka.
Zasadniczo kompilacja jest zwykłym tłumaczeniem, a poziom jest zwykle od wyższego poziomu do niższego poziomu języka, ale istnieje wiele wariantów.
Kompilatory bootstrapowe są oczywiście najbardziej zagmatwane, ponieważ kompilują język, w którym są napisane. Nie zapomnij o początkowym etapie ładowania, który wymaga przynajmniej minimalnej istniejącej wersji, która jest wykonywalna. Wiele kompilatorów uruchomionych najpierw pracuje nad minimalnymi funkcjami języka programowania i dodaje w przyszłości dodatkowe złożone funkcje językowe, o ile nowa funkcja może być wyrażona przy użyciu poprzednich funkcji. Gdyby tak nie było, wymagałoby to wcześniejszego opracowania tej części „kompilatora” w innym języku.
self-hosting
kompilator. Zobacz programmers.stackexchange.com/q/263651/6221