Intensywnie studiuję tematy kompilatorów i tłumaczy. Chcę sprawdzić, czy moje podstawowe zrozumienie jest prawidłowe, więc załóżmy, że:
Mam język o nazwie „Foobish”, a jego słowami kluczowymi są
<OUTPUT> 'TEXT', <Number_of_Repeats>;
Więc jeśli chcę drukować na konsoli 10 razy, napiszę
OUTPUT 'Hello World', 10;
Witaj plik World.foobish.
Teraz piszę tłumacza w wybranym przeze mnie języku - C # w tym przypadku:
using System;
namespace FoobishInterpreter
{
internal class Program
{
private static void Main(string[] args)
{
analyseAndTokenize(Hello World.foobish-file)//Pseudocode
int repeats = Token[1];
string outputString = Token[0];
for (var i = 0; i < repeats; i++)
{
Console.WriteLine(outputString);
}
}
}
}
Na bardzo łatwym poziomie interpretera, tłumacz analizowałby plik skryptu itp. I wykonywał foobish-język na drodze implementacji interpretera.
Czy kompilator stworzyłby język maszynowy działający bezpośrednio na fizycznym sprzęcie?
Czyli interpreter nie tworzy języka maszynowego, ale czy kompilator robi to dla swoich danych wejściowych?
Czy mam jakieś nieporozumienia w podstawowy sposób działania kompilatorów i tłumaczy?
źródło
Odpowiedzi:
Terminy „interpreter” i „kompilator” są znacznie bardziej rozmyte niż kiedyś. Wiele lat temu kompilatory częściej produkowały kod maszynowy do późniejszego wykonania, a tłumacze mniej więcej „wykonywali” kod źródłowy. Te dwa terminy były wtedy dobrze zrozumiane.
Ale dziś istnieje wiele odmian używania „kompilatora” i „interpretera”. Na przykład VB6 „kompiluje” bajtowy kod (forma języka pośredniego ), który jest następnie „interpretowany” przez środowisko wykonawcze VB. Podobny proces zachodzi w języku C #, który wytwarza plik CIL, który jest następnie wykonywany przez kompilator Just-In-Time (JIT), który w dawnych czasach byłby uważany za tłumacza. Możesz „zamrozić” dane wyjściowe JIT do rzeczywistego pliku binarnego za pomocą NGen.exe , którego produkt byłby w przeszłości kompilatorem .
Odpowiedź na twoje pytanie nie jest już tak prosta, jak kiedyś.
Dalsze czytanie
Kompilatory kontra tłumacze na Wikipedii
źródło
Podsumowanie, które podaję poniżej, oparte jest na „Kompilatorach, zasadach, technikach i narzędziach”, Aho, Lam, Sethi, Ullman, (Pearson International Edition, 2007), strony 1, 2, z dodatkiem moich własnych pomysłów.
Dwa podstawowe mechanizmy przetwarzania programu to kompilacja i interpretacja .
Kompilacja pobiera jako dane wejściowe program źródłowy w danym języku i generuje program docelowy w języku docelowym.
Jeśli językiem docelowym jest kod maszynowy, można go wykonać bezpośrednio na niektórych procesorach:
Kompilacja obejmuje skanowanie i tłumaczenie całego programu wejściowego (lub modułu) i nie wymaga jego wykonania.
Interpretacja przyjmuje jako dane wejściowe program źródłowy i jego dane wejściowe, i tworzy wynik programu źródłowego
Interpretacja zazwyczaj polega na przetwarzaniu (analizie i wykonywaniu) programu pojedynczo.
W praktyce wiele procesorów językowych stosuje połączenie obu tych podejść. Np. Programy Java są najpierw tłumaczone (kompilowane) na program pośredni (kod bajtowy):
dane wyjściowe z tego kroku są następnie wykonywane (interpretowane) przez maszynę wirtualną:
Aby jeszcze bardziej skomplikować sytuację, JVM może wykonać kompilację just-in-time w czasie wykonywania, aby przekonwertować kod bajtowy na inny format, który jest następnie wykonywany.
Ponadto, nawet podczas kompilacji do języka maszynowego, plik binarny jest uruchamiany przez interpreter, który jest implementowany przez procesor. Dlatego nawet w tym przypadku używasz hybrydy kompilacji + interpretacji.
Tak więc rzeczywiste systemy używają mieszanki tych dwóch, więc trudno powiedzieć, czy dany procesor językowy jest kompilatorem, czy tłumaczem, ponieważ prawdopodobnie użyje obu mechanizmów na różnych etapach przetwarzania. W takim przypadku prawdopodobnie bardziej odpowiednie byłoby użycie innego, bardziej neutralnego terminu.
Niemniej kompilacja i interpretacja to dwa odrębne rodzaje przetwarzania, jak opisano na powyższych schematach,
Aby odpowiedzieć na wstępne pytania.
Kompilator niekoniecznie tłumaczy program napisany dla maszyny M1 na równoważny program napisany dla maszyny M2. Maszyna docelowa może być zaimplementowana sprzętowo lub być maszyną wirtualną. Koncepcyjnie nie ma różnicy. Ważne jest to, że kompilator patrzy na fragment kodu i tłumaczy go na inny język bez wykonywania go.
Jeśli podczas tworzenia odwołujesz się do wyniku, kompilator tworzy program docelowy, który może być w języku maszynowym, interpreter tego nie robi.
źródło
defined A && !defined B
.Nie Kompilator jest po prostu program, który bierze za swoje wejście program napisany w języku A i generuje jako wyjście do semantycznie równoważne programu w języku B . Język B może być dowolny, nie musi to być język maszynowy.
Kompilator może kompilować z języka wysokiego poziomu na inny język wysokiego poziomu (np. GWT, który kompiluje Javę do ECMAScript), z języka wysokiego poziomu na język niskiego poziomu (np. Gambit, który kompiluje Scheme to C), od języka wysokiego poziomu do kodu maszynowego (np. GCJ, który kompiluje Javę do kodu natywnego), od języka niskiego poziomu do języka wysokiego poziomu (np. Clue, który kompiluje C do Java, Lua, Perl, ECMAScript i Common Lisp), od języka niskiego poziomu do innego języka niskiego poziomu (np. Android SDK, który kompiluje kod bajtowy JVML do kodu bajtowego Dalvik), od języka niskiego poziomu do kodu maszynowego (np. Kompilator C1X, który jest częścią HotSpot, który kompiluje kod bajtowy JVML do kodu maszynowego, kod maszynowy do języka wysokiego poziomu (dowolny tak zwany „dekompilator”, także Emscripten, który kompiluje kod maszynowy LLVM do ECMAScript),kod maszynowy do języka niskiego poziomu (np. kompilator JIT w JPC, który kompiluje kod natywny x86 do kodu bajtowego JVML) i kod rodzimy do kodu natywnego (np. kompilator JIT w PearPC, który kompiluje kod natywny PowerPC do kodu natywnego x86).
Zauważ też, że „kod maszynowy” jest bardzo rozmytym terminem z kilku powodów. Na przykład istnieją procesory, które natywnie wykonują kod bajtowy JVM, i istnieją interpretery programowe dla kodu maszynowego x86. Co zatem sprawia, że jeden „natywny kod maszynowy” jest inny, a drugi nie? Ponadto każdy język jest kodem abstrakcyjnej maszyny dla tego języka.
Istnieje wiele specjalistycznych nazw dla kompilatorów wykonujących funkcje specjalne. Pomimo tego, że są to wyspecjalizowane nazwy, wszystkie z nich są nadal kompilatorami, tylko specjalnymi rodzajami kompilatorów:
Niekoniecznie. Może być uruchomiony w tłumaczu lub na maszynie wirtualnej. Można go dodatkowo skompilować w innym języku.
Tłumacz nic nie produkuje. Po prostu uruchamia program.
Kompilator coś wytwarza, ale niekoniecznie musi to być język maszynowy, może to być dowolny język. Może to być nawet ten sam język, co język wprowadzania! Na przykład Superkompilatory, LLC ma kompilator, który przyjmuje Java jako dane wejściowe i produkuje zoptymalizowaną Javę jako dane wyjściowe. Istnieje wiele kompilatorów ECMAScript, które pobierają ECMAScript jako dane wejściowe i produkują zoptymalizowany, zminimalizowany i zaciemniony ECMAScript.
Możesz być zainteresowanym także tym:
źródło
Myślę, że powinieneś całkowicie porzucić pojęcie „kompilator kontra tłumacz”, ponieważ jest to fałszywa dychotomia.
Wspólne słowo określające użyteczność abstrakcyjnego języka programowania w świecie rzeczywistym to implementacja .
W przeszłości, implementacja języka programowania często składały się tylko kompilator (i CPU to wygenerowany kod) lub po prostu tłumacza - więc może nie wyglądał jak tych dwóch rodzajów narzędzi wzajemnie się wykluczają. Dzisiaj widać wyraźnie, że tak nie jest (i nigdy nie było tak na początku). Podjęcie zaawansowanej implementacji języka programowania i próba przekazania mu nazwy „kompilator” lub „tłumacz” często prowadzi do niejednoznacznych lub niekonsekwentnych rezultatów.
Jedna implementacja języka programowania może obejmować dowolną liczbę kompilatorów i tłumaczy , często w wielu postaciach (samodzielna, w locie), dowolną liczbę innych narzędzi, takich jak analizatory statyczne i optymalizatory , oraz dowolną liczbę kroków. Może nawet obejmować całe implementacje dowolnej liczby języków pośrednich (które mogą być niezwiązane z tym, który jest wdrażany).
Przykłady schematów wdrażania obejmują:
...i tak dalej.
źródło
Podczas gdy linie między kompilatorami i tłumaczami z czasem się rozmyły, nadal można rysować linię między nimi, patrząc na semantykę tego, co program powinien zrobić i co robi kompilator / tłumacz.
Kompilator wygeneruje inny program (zwykle w języku niższego poziomu, takim jak kod maszynowy), który, jeśli ten program zostanie uruchomiony, zrobi to, co powinien zrobić Twój program.
Tłumacz zrobi to, co powinien zrobić twój program.
Dzięki tym definicjom miejsca, w których robi się rozmyte, to przypadki, w których można uznać, że Twój kompilator / interpreter robi różne rzeczy w zależności od tego, jak na to patrzysz. Na przykład Python pobiera kod Pythona i kompiluje go do skompilowanego kodu bajtowego Pythona. Jeśli ten kod bajtowy Pythona jest uruchamiany przez interpreter kodu bajtowego Python , robi to, co powinien zrobić Twój program. Jednak w większości sytuacji programiści Pythona myślą, że oba te kroki są wykonywane w jednym dużym kroku, więc wybierają interpreter CPython jako interpretację swojego kodu źródłowego, a fakt, że został skompilowany po drodze, jest uważany za szczegół implementacji . W ten sposób wszystko zależy od perspektywy.
źródło
Oto prosta konceptualna niejednoznaczność między kompilatorami a tłumaczami.
Rozważ 3 języki: język programowania , P (w jakim program jest napisany); język domeny , D (dla tego, co dzieje się z uruchomionym programem); i język docelowy , T (jakiś trzeci język).
Koncepcyjnie
kompilator tłumaczy P do T, dzięki czemu można ocenić T (d); natomiast
interpreter ocenia P (D) bezpośrednio.
źródło