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?
compilation
AlexanderRD
źródło
źródło
self-hosting
kompilator. Zobacz programmers.stackexchange.com/q/263651/6221Odpowiedzi:
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 .źródło
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:
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:Więc możemy dodać jeden warunek do powyższego kodu…
… 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… I zrobi to dobrze!
10
Pochodzi z kompilatora, i nie musi być wyraźnie zdefiniowane w kodzie źródłowym kompilatora. 1To 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.
źródło
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.
Tak, dokładnie to oznacza to stwierdzenie i mam nadzieję, że teraz widzisz, jak to stwierdzenie jest prawdziwe.
źródło
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żefor
-loops. Nie stanowi to problemu, ponieważ możesz przepisać dowolnąfor
-loop w taki sposób, aby byławhile
-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ć wszystkiewhile
niechcianefor
pętle do -loops.Przepłucz i powtarzaj, aż wszystkie żądane funkcje językowe będą mogły zostać skompilowane za pomocą kompilatora.
while
ifor
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.
źródło
Małe, ale ważne wyjaśnienie
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.
źródło
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.
źródło
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).
źródło
Dowód przez indukcję
Krok indukcyjny
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).
Podstawa
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.
źródło
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.
źródło