Comment un compilateur peut-il se compiler?

168

Je recherche CoffeeScript sur le site Web http://coffeescript.org/ , et il contient le texte

Le compilateur CoffeeScript est lui-même écrit en CoffeeScript

Comment un compilateur peut-il se compiler, ou que signifie cette déclaration?

AlexanderRD
la source
14
Un autre terme pour un compilateur qui peut se compiler est un self-hostingcompilateur. Voir programmers.stackexchange.com/q/263651/6221
oɔɯǝɹ
37
Pourquoi un compilateur ne devrait-il pas être capable de se compiler?
user253751
48
Il y a au moins deux copies du compilateur impliqués. Un préexistant compile une nouvelle copie. Le nouveau peut être identique ou non à l'ancien.
bdsl
12
Vous pouvez également être intéressé par Git: son code source est bien sûr suivi dans un référentiel Git.
Greg d'Eon
7
Cela revient à demander "Comment une imprimante Xerox pourrait-elle imprimer les schémas sur elle-même?" Les compilateurs compilent le texte en code octet. Si le compilateur peut compiler vers n'importe quel code d'octet utilisable, vous pouvez écrire le code du compilateur dans le langage respectif, puis passer le code via le compilateur pour générer la sortie.
RLH

Réponses:

219

La première édition d'un compilateur ne peut pas être générée automatiquement à partir d'un langage de programmation qui lui est spécifique; votre confusion est compréhensible. Une version ultérieure du compilateur avec plus de fonctionnalités de langage (avec la source réécrite dans la première version du nouveau langage) pourrait être construite par le premier compilateur. Cette version pourrait alors compiler le prochain compilateur, et ainsi de suite. Voici un exemple:

  1. Le premier compilateur CoffeeScript est écrit en Ruby, produisant la version 1 de CoffeeScript
  2. Le code source du compilateur CS est réécrit dans CoffeeScript 1
  3. Le compilateur CS d'origine compile le nouveau code (écrit en CS 1) dans la version 2 du compilateur
  4. Des modifications sont apportées au code source du compilateur pour ajouter de nouvelles fonctionnalités de langage
  5. Le deuxième compilateur CS (le premier écrit en CS) compile le nouveau code source révisé dans la version 3 du compilateur
  6. Répétez les étapes 4 et 5 pour chaque itération

Remarque: je ne sais pas exactement comment les versions de CoffeeScript sont numérotées, ce n'était qu'un exemple.

Ce processus est généralement appelé bootstrapping . Un autre exemple de compilateur d'amorçage est rustcle compilateur du langage Rust .

Ben N
la source
5
L'autre méthode pour démarrer un compilateur est d'écrire un interpréteur pour (un sous-ensemble) de votre langage.
Aron
Comme alternative de plus à l'amorçage avec un compilateur ou un interpréteur écrit dans un autre langage, la voie très ancienne serait d'assembler manuellement les sources du compilateur. Chuck Moore explique comment faire cela pour un interpréteur Forth dans le chapitre 9, «Programmes qui bootstrap», à la fin de Programming a Problem-Oriented Language ( web.archive.org/web/20160327044521/www.colorforth.com/POL .htm ), sur la base de l'avoir fait deux fois auparavant à la main. La saisie de code ici se fait via un panneau avant qui permet le stockage direct des valeurs vers des adresses mémoire contrôlées par des commutateurs à bascule pour les bits.
Jeremy W. Sherman
60

Dans l'article Reflections on Trusting Trust , Ken Thompson, l'un des initiateurs d'Unix, écrit un aperçu fascinant (et facilement lisible) de la façon dont le compilateur C se compile. Des concepts similaires peuvent être appliqués à CoffeeScript ou à tout autre langage.

L'idée d'un compilateur qui compile son propre code est vaguement similaire à un quine : code source qui, lorsqu'il est exécuté, produit en sortie le code source d'origine. Voici un exemple de quine CoffeeScript. Thompson a donné cet exemple de 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);
}

Ensuite, vous pourriez vous demander comment le compilateur apprend qu'une séquence d'échappement comme '\n'représente le code ASCII 10. La réponse est que quelque part dans le compilateur C, il existe une routine qui interprète les caractères littéraux, contenant certaines conditions comme celle-ci pour reconnaître les séquences de barres obliques inverses:

…
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 */
…

Nous pouvons donc ajouter une condition au code ci-dessus…

if (c == 'n')  return 10;       /* '\n' is a newline */

… Pour produire un compilateur qui sait que '\n'représente ASCII 10. Fait intéressant, ce compilateur, et tous les compilateurs suivants compilés par lui , "connaissent" ce mappage, donc dans la prochaine génération du code source, vous pouvez changer cette dernière ligne en

if (c == 'n')  return '\n';

… Et il fera la bonne chose! Le 10provient du compilateur et n'a plus besoin d'être explicitement défini dans le code source du compilateur. 1

C'est un exemple d'une fonctionnalité de langage C qui a été implémentée dans du code C. Maintenant, répétez ce processus pour chaque fonctionnalité du langage, et vous avez un compilateur "auto-hébergé": un compilateur C qui est écrit en C.


1 La torsion de l'intrigue décrite dans l'article est que puisque le compilateur peut être "enseigné" des faits comme celui-ci, il peut également être mal appris pour générer des exécutables de chevaux de Troie d'une manière qui est difficile à détecter, et un tel acte de sabotage peut persister dans tous les compilateurs produits par le compilateur corrompu.

200_succès
la source
7
Bien que ce soit une information intéressante, je ne pense pas que cela réponde à la question. Vos exemples supposent que vous avez déjà un compilateur bootstrap, ou bien dans quel langage le compilateur C est-il écrit?
Arturo Torres Sánchez
9
@ ArturoTorresSánchez Différentes explications fonctionnent bien pour différentes personnes. Je n'ai pas l'intention de réitérer ce qui a été dit dans d'autres réponses. Au contraire, je trouve que les autres réponses parlent à un niveau plus élevé que ce que j'aime penser. Personnellement, je préfère une illustration concrète de la façon dont une seule fonctionnalité est ajoutée, et laisser le lecteur extrapoler à partir de cela, au lieu d'un aperçu superficiel.
200_success
5
OK, je comprends votre point de vue. C'est juste que la question est plus «comment un compilateur peut-il se compiler si le compilateur pour compiler le compilateur n'existe pas» et moins «comment ajouter de nouvelles fonctionnalités à un compilateur bootstrap».
Arturo Torres Sánchez
17
La question elle-même est ambiguë et ouverte. Il semble que certaines personnes l'interprètent comme signifiant "comment un compilateur CoffeeScript peut-il se compiler?". La réponse désinvolte, comme donnée dans un commentaire, est "pourquoi ne devrait-il pas être capable de se compiler, comme il compile n'importe quel code?" Je l'interprète comme signifiant "comment un compilateur auto-hébergé peut-il exister?", Et j'ai donné une illustration de la façon dont un compilateur peut être enseigné sur l'une de ses propres fonctionnalités de langage. Il répond à la question d'une manière différente, en fournissant une illustration de bas niveau de la manière dont il est mis en œuvre.
200_success
1
@ ArturoTorresSánchez: "[I] n dans quelle langue le compilateur C est-il écrit?" Il y a longtemps, j'ai maintenu le compilateur C original noté dans l'ancienne annexe K&R (celle d'IBM 360.) Beaucoup de gens savent qu'il y avait d'abord BCPL, puis B, et que C était une version améliorée de B.En fait, il y en avait beaucoup parties de cet ancien compilateur qui étaient encore écrites en B, et qui n'avaient jamais été réécrites en C. Les variables étaient de la forme lettre / chiffre unique, l'arithmétique du pointeur n'était pas supposée être automatiquement mise à l'échelle, etc. Cet ancien code témoignait du bootstrapping de B à C. Le premier compilateur "C" a été écrit en B.
Eliyahu Skoczylas
29

Vous avez déjà obtenu une très bonne réponse, mais je veux vous offrir une perspective différente, qui, espérons-le, vous éclairera. Établissons d'abord deux faits sur lesquels nous pouvons tous deux nous entendre:

  1. Le compilateur CoffeeScript est un programme qui peut compiler des programmes écrits en CoffeeScript.
  2. Le compilateur CoffeeScript est un programme écrit en CoffeeScript.

Je suis sûr que vous pouvez convenir que les deux numéros 1 et 2 sont vrais. Maintenant, regardez les deux déclarations. Voyez-vous maintenant qu'il est tout à fait normal que le compilateur CoffeeScript puisse compiler le compilateur CoffeeScript?

Le compilateur ne se soucie pas de ce qu'il compile. Tant qu'il s'agit d'un programme écrit en CoffeeScript, il peut le compiler. Et le compilateur CoffeeScript lui-même se trouve être un tel programme. Le compilateur CoffeeScript ne se soucie pas que ce soit le compilateur CoffeeScript lui-même qu'il compile. Tout ce qu'il voit, c'est du code CoffeeScript. Période.

Comment un compilateur peut-il se compiler, ou que signifie cette déclaration?

Oui, c'est exactement ce que signifie cette déclaration, et j'espère que vous pouvez voir maintenant comment cette déclaration est vraie.

Jörg W Mittag
la source
2
Je ne sais pas grand-chose sur le script coffee, mais vous pouvez clarifier le point 2 en déclarant qu'il a été écrit en script coffee mais qu'il a été compilé depuis et qu'il est alors du code machine. Et de toute façon, pourriez-vous s'il vous plaît expliquer le problème de la poule et de l'œuf. Si le compilateur a été écrit dans un langage pour lequel un compilateur n'a pas encore été écrit, comment le compilateur peut-il même s'exécuter ou être compilé?
barlop
6
Votre déclaration 2 est incomplète / inexacte et très trompeuse. car comme le dit la première réponse, la première n'a pas été écrite en écriture café. C'est tellement pertinent pour sa question. Et quant à "Comment un compilateur peut-il se compiler, ou que signifie cette déclaration?" Vous dites "Oui", je suppose (bien que mon esprit soit un peu petit), je vois qu'il est utilisé pour compiler des versions antérieures de lui-même, plutôt que de lui-même. Mais sert-il aussi à se compiler? J'ai supposé que ce serait inutile.
barlop
2
@barlop: Remplacez l'instruction 2 par " Aujourd'hui , le compilateur CoffeeScript est un programme écrit en CoffeeScript." Cela vous aide-t-il à mieux le comprendre? Un compilateur est "juste" un programme qui traduit une entrée (code) en une sortie (programme). Donc, si vous avez un compilateur pour le langage Foo, alors écrivez le code source d'un compilateur Foo dans le langage Foo lui-même, et alimentez cette source vers votre premier compilateur Foo, vous obtenez un deuxième compilateur Foo en sortie. Ceci fait par beaucoup de langages (par exemple, tous les compilateurs C que je connais sont écrits en… C).
DarkDust
3
Le compilateur ne peut pas se compiler. Le fichier de sortie n'est pas la même instance que le compilateur qui produit le fichier de sortie. J'espère que vous pouvez voir maintenant comment cette déclaration est fausse.
pabrams
3
@pabrams Pourquoi supposez-vous cela? Le résultat pourrait bien être identique au compilateur utilisé pour le produire. Par exemple, si je compile GCC 6.1 avec GCC 6.1, j'obtiens une version de GCC 6.1 compilée avec GCC 6.1. Et puis si j'utilise ça pour compiler GCC 6.1, j'obtiens également une version de GCC 6.1 compilée avec GCC 6.1, qui devrait être identique (en ignorant des choses comme les horodatages).
user253751
9

Comment un compilateur peut-il se compiler, ou que signifie cette déclaration?

Cela signifie exactement cela. Tout d'abord, certaines choses à considérer. Il y a quatre objets que nous devons examiner:

  • Le code source de tout programme CoffeScript arbitraire
  • L'assemblage (généré) de tout programme CoffeScript arbitraire
  • Le code source du compilateur CoffeScript
  • L'assemblage (généré) du compilateur CoffeScript

Maintenant, il devrait être évident que vous pouvez utiliser l'assembly généré - l'exécutable - du compilateur CoffeScript pour compiler n'importe quel programme CoffeScript arbitraire et générer l'assembly pour ce programme.

Maintenant, le compilateur CoffeScript lui-même n'est qu'un programme CoffeScript arbitraire, et donc, il peut être compilé par le compilateur CoffeScript.

Il semble que votre confusion provient du fait que lorsque vous créez votre propre nouvelle langue, vous n'avez un compilateur mais vous pouvez utiliser pour compiler votre compilateur. Cela ressemble sûrement à un problème d'oeuf de poule , non?

Présentez le processus appelé bootstrapping .

  1. Vous écrivez un compilateur dans un langage déjà existant (dans le cas de CoffeScript, le compilateur d'origine a été écrit en Ruby) qui peut compiler un sous-ensemble du nouveau langage
  2. Vous écrivez un compilateur qui peut compiler un sous-ensemble du nouveau langage dans le nouveau langage lui-même. Vous ne pouvez utiliser que les fonctionnalités de langage que le compilateur de l'étape ci-dessus peut compiler.
  3. Vous utilisez le compilateur de l'étape 1 pour compiler le compilateur de l'étape 2. Cela vous laisse avec un assembly qui a été écrit à l'origine dans un sous-ensemble du nouveau langage et qui est capable de compiler un sous-ensemble du nouveau langage.

Vous devez maintenant ajouter de nouvelles fonctionnalités. Supposons que vous whilen'ayez implémenté que -loops, mais que forvous vouliez également -loops. Ce n'est pas un problème, puisque vous pouvez réécrire n'importe quelle for-loop de telle sorte que ce soit une while-loop. Cela signifie que vous ne pouvez utiliser while-loops que dans le code source de votre compilateur, puisque l'assembly que vous avez sous la main ne peut les compiler. Mais vous pouvez créer des fonctions dans votre compilateur qui peuvent passer et compiler for-loops avec lui. Ensuite, vous utilisez l'assembly que vous avez déjà et compilez la nouvelle version du compilateur. Et maintenant, vous avez un assembly d'un compilateur qui peut également analyser et compiler for-loops! Vous pouvez maintenant revenir au fichier source de votre compilateur et réécrire toutes les whileboucles que vous ne voulez pas dans for-loops.

Rincez et répétez jusqu'à ce que toutes les fonctionnalités du langage souhaitées puissent être compilées avec le compilateur.

whileet forn'étaient évidemment que des exemples, mais cela fonctionne pour toute nouvelle fonctionnalité de langage que vous souhaitez. Et puis vous êtes dans la situation dans laquelle CoffeScript est maintenant: Le compilateur se compile.

Il y a beaucoup de littérature là-bas. Reflections on Trusting Trust est un classique que tous ceux qui s'intéressent à ce sujet devraient lire au moins une fois.

Polygnome
la source
5
(La phrase «Le compilateur CoffeeScript est lui-même écrit en CoffeeScript» est vraie, mais «Un compilateur peut se compiler» est fausse.)
pabrams
4
Non, c'est tout à fait vrai. Le compilateur peut se compiler. Cela n'a tout simplement pas de sens. Disons que vous avez l'exécutable qui peut compiler la version X du langage. Vous écrivez un compilateur qui peut compiler la version X + 1, et le compilez avec le compilateur que vous avez (qui est la version X). Vous vous retrouvez avec un exécutable capable de compiler la version X + 1 du langage. Vous pouvez maintenant utiliser ce nouvel exécutable pour recompiler le compilateur. Mais à quelle fin? Vous disposez déjà de l'exécutable qui fait ce que vous voulez. Le compilateur peut compiler n'importe quel programme valide, donc il peut se compiler complètement!
Polygnome
1
En effet, il n'est pas rare de construire plusieurs fois, iirc modern freepascal construit le compilateur un total de 5 fois.
plugwash
1
@pabrams Ecrire "Ne pas toucher" et "Objet chaud. Ne pas toucher" ne change rien au message voulu de la phrase. Tant que le public visé par le message (les programmeurs) comprend le message voulu de la phrase (une version du compilateur peut compiler sa source) quelle que soit la façon dont elle est écrite, cette discussion est inutile. Dans l'état actuel des choses, votre argument est invalide. À moins que vous ne soyez en mesure de montrer que le public visé par le message est constitué de non-programmeurs, alors, et alors seulement, vous avez raison.
DarkDestry
2
@pabrams «Good english» est un anglais qui communique clairement des idées au public visé, et de la manière que l'écrivain ou le conférencier a voulu. Si le public visé est constitué de programmeurs et que les programmeurs le comprennent, c'est un bon anglais. Dire "La lumière existe à la fois sous forme de particules et d'ondes" est fondamentalement équivalent à "La lumière existe à la fois sous forme de photons et d'ondes électromagnétiques". Pour un physicien, ils signifient littéralement la même chose. Cela signifie-t-il que nous devrions toujours utiliser la sentance la plus longue et la plus claire? Non! Parce que cela complique la lecture lorsque le sens est déjà clair pour le public visé.
DarkDestry
7

Une petite mais importante clarification

Ici, le terme compilateur passe sous silence le fait qu'il y a deux fichiers impliqués. L'un est un exécutable qui prend comme fichiers d'entrée écrits en CoffeScript et produit comme fichier de sortie un autre exécutable, un fichier objet pouvant être lié ou une bibliothèque partagée. L'autre est un fichier source CoffeeScript qui se trouve juste pour décrire la procédure de compilation de CoffeeScript.

Vous appliquez le premier fichier au second, produisant un troisième qui est capable d'effectuer le même acte de compilation que le premier (éventuellement plus, si le second fichier définit des fonctionnalités non implémentées par le premier), et peut donc remplacer le premier si vous alors le désir.

nbro
la source
4
  1. Le compilateur CoffeeScript a d'abord été écrit en Ruby.
  2. Le compilateur CoffeeScript a ensuite été réécrit en CoffeeScript.

Comme la version Ruby du compilateur CoffeeScript existait déjà, elle a été utilisée pour créer la version CoffeeScript du compilateur CoffeeScript.

entrez la description de l'image ici C'est ce qu'on appelle un compilateur auto-hébergé .

C'est extrêmement courant et résulte généralement du désir d'un auteur d'utiliser sa propre langue pour maintenir la croissance de cette langue.

Trevor Hickey
la source
3

Ce n'est pas une question de compilateurs ici, mais une question d'expressivité du langage, puisqu'un compilateur n'est qu'un programme écrit dans un langage.

Quand nous disons qu '"un langage est écrit / implémenté", nous voulons dire en fait qu'un compilateur ou un interpréteur pour ce langage est implémenté. Il existe des langages de programmation dans lesquels vous pouvez écrire des programmes qui implémentent le langage (il s'agit de compilateurs / interprètes pour le même langage). Ces langues sont appelées langues universelles .

Pour pouvoir comprendre cela, pensez à un tour à métaux. C'est un outil utilisé pour façonner le métal. Il est possible, en utilisant uniquement cet outil, de créer un autre outil identique, en créant ses pièces. Ainsi, cet outil est une machine universelle. Bien sûr, le premier a été créé en utilisant d'autres moyens (d'autres outils), et était probablement de qualité inférieure. Mais le premier a été utilisé pour en construire de nouveaux avec une plus grande précision.

Une imprimante 3D est presque une machine universelle. Vous pouvez imprimer toute l'imprimante 3D à l'aide d'une imprimante 3D (vous ne pouvez pas construire la pointe qui fait fondre le plastique).

Paul92
la source
J'aime l'analogie du tour. Contrairement à l'analogie du tour, cependant, les imperfections de la première itération du compilateur sont transmises à tous les compilateurs suivants. Par exemple, une réponse ci-dessus mentionne l'ajout d'une fonctionnalité de boucle for où le compilateur d'origine utilise uniquement des boucles while. La sortie comprend les boucles for, mais l'implémentation est avec des boucles while. Si l'implémentation de la boucle while d'origine est défectueuse ou inefficace, elle le sera toujours!
@ Physics-Compute c'est tout simplement faux. En l'absence de malveillance, les défauts ne se propagent généralement pas lors de la compilation d'un compilateur.
plugwash
Les traductions d'assembly sont certainement transmises d'itération en itération jusqu'à ce que la traduction d'assembly soit corrigée. Les nouvelles fonctionnalités qui s'appuient sur d'anciennes fonctionnalités ne modifient pas l'implémentation sous-jacente. Penses-y pendant un moment.
@plugwash Voir "Reflections on Trusting Trust" de Ken Thompson - ece.cmu.edu/~ganger/712.fall02/papers/p761-thompson.pdf
3

Preuve par induction

Étape inductive

La version n + 1e du compilateur est écrite en X.

Ainsi, il peut être compilé par la nième version du compilateur (également écrite en X).

Cas de base

Mais la première version du compilateur écrite en X doit être compilée par un compilateur pour X qui est écrit dans un langage autre que X. Cette étape est appelée amorçage du compilateur.

Guy Argo
la source
1
Le tout premier compilateur pour le langage X peut facilement être écrit en X. Comment cela est possible, c'est que ce premier compilateur peut être interprété . (Par un interprète X écrit dans une langue autre que X).
Kaz
0

Les compilateurs prennent une spécification de haut niveau et la transforment en une implémentation de bas niveau, telle qu'elle peut être exécutée sur du matériel. Il n'y a donc pas de relation entre le format de la spécification et l'exécution réelle en dehors de la sémantique du langage ciblé.

Les compilateurs croisés passent d'un système à un autre, les compilateurs multilingues compilent une spécification de langage dans une autre spécification de langage.

Fondamentalement, la compilation est une traduction juste, et le niveau est généralement du niveau supérieur de la langue au niveau inférieur de la langue, mais il existe de nombreuses variantes.

Les compilateurs d'amorçage sont les plus déroutants, bien sûr, car ils compilent le langage dans lequel ils sont écrits. N'oubliez pas l'étape initiale du bootstrap qui nécessite au moins une version minimale existante exécutable. De nombreux compilateurs bootstrap travaillent d'abord sur les fonctionnalités minimales d'un langage de programmation et ajoutent des fonctionnalités de langage complexes supplémentaires à l'avenir tant que la nouvelle fonctionnalité peut être exprimée en utilisant les fonctionnalités précédentes. Si ce n'était pas le cas, il faudrait que cette partie du "compilateur" soit développée au préalable dans un autre langage.

nbro
la source