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?
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?
Réponses:
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:
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 rustc
le compilateur du langage Rust .
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 10
provient 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.
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:
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.
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:
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 .
Vous devez maintenant ajouter de nouvelles fonctionnalités. Supposons que vous while
n'ayez implémenté que -loops, mais que for
vous 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 while
boucles 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.
while
et for
n'é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.
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.
Comme la version Ruby du compilateur CoffeeScript existait déjà, elle a été utilisée pour créer la version CoffeeScript du compilateur CoffeeScript.
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.
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).
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).
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.
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.
self-hosting
compilateur. Voir programmers.stackexchange.com/q/263651/6221