Я досліджую CoffeeScript на веб-сайті http://coffeescript.org/ , і він містить текст
Компілятор CoffeeScript сам написаний на CoffeeScript
Як компілятор може скласти себе, або що означає це твердження?
Я досліджую CoffeeScript на веб-сайті http://coffeescript.org/ , і він містить текст
Компілятор CoffeeScript сам написаний на CoffeeScript
Як компілятор може скласти себе, або що означає це твердження?
Відповіді:
Перше видання компілятора не може бути машиногенеровано із специфічної для нього мови програмування; ваша плутанина зрозуміла. Пізніша версія компілятора, що має більше мовних особливостей (з джерелом, переписаним у першій версії нової мови), може бути побудована першим компілятором. Потім ця версія могла б скласти наступний компілятор тощо. Ось приклад:
Примітка. Я не впевнений, як саме прошифровані версії CoffeeScript, це був лише приклад.
Цей процес зазвичай називають завантажувальним . Іншим прикладом компілятора завантаження є rustc
компілятор для мови Rust .
У статті Роздуми про довіру довіри Кен Томпсон, один із авторів Unix, пише захоплюючий (і легко читабельний) огляд того, як компілятор C складається з себе. Подібні поняття можна застосувати до CoffeeScript або будь-якої іншої мови.
Ідея компілятора, який компілює власний код, нечітко схожа на quine : вихідний код, який при виконанні видає вихідний вихідний код. Ось один із прикладів квітки CoffeeScript. Томпсон наводив цей приклад С-квінки:
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);
}
Далі, вам може бути цікаво, як компілятор вчить, що послідовність евакуації на зразок '\n'
являє собою код ASCII 10. Відповідь полягає в тому, що десь у компіляторі C є звичайна програма, що інтерпретує букве символів, що містить деякі умови на зразок цього для розпізнавання послідовностей зворотної косої риси:
…
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 */
…
Отже, ми можемо додати до коду одну умову вище ...
if (c == 'n') return 10; /* '\n' is a newline */
… Створити компілятор, який знає, що '\n'
представляє ASCII 10. Цікаво, що цей компілятор і всі наступні компілятори, складені ним , «знають» це відображення, тому в наступному поколінні вихідного коду ви можете змінити цей останній рядок на
if (c == 'n') return '\n';
… І це зробить правильно! 10
Приходить від компілятора, і більше не повинно бути явно визначені у вихідному коді компілятора. 1
Це один приклад функції мови C, яка була реалізована в коді C. Тепер повторіть цей процес для кожної окремої мовної функції, і у вас є компілятор "самохостингу": компілятор C, написаний на C.
1 Скрут сюжету, описаний у статті, полягає в тому, що оскільки компілятору можна «викладати» такі факти, це також може бути неправильним способом генерування троянських виконуваних файлів способом, який важко виявити, і такий акт саботажу може зберігатися. у всіх компіляторах, що виробляються підручним компілятором.
Ви вже отримали дуже гарну відповідь, проте я хочу запропонувати вам іншу точку зору, яка, сподіваюся, буде освічуючою для вас. Давайте спочатку встановимо два факти, з якими ми можемо погодитися:
Я впевнений, що ви можете погодитись, що і №1, і №2 є правдою. А тепер подивіться на два твердження. Чи бачите ви, що цілком нормально компілятор CoffeeScript мати можливість компілювати компілятор CoffeeScript?
Компілятору не важливо, що він компілює. Поки це програма, написана в CoffeeScript, вона може компілювати її. І сам компілятор CoffeeScript, як буває, є такою програмою. Компілятор CoffeeScript не хвилює, що саме компілятор CoffeeScript він компілює. Все, що він бачить, це якийсь код CoffeeScript. Період.
Як компілятор може скласти себе, або що означає це твердження?
Так, саме це означає це твердження, і я сподіваюся, що ви зараз зможете побачити, наскільки це твердження істинне.
Як компілятор може скласти себе, або що означає це твердження?
Це означає саме це. Перш за все, деякі речі. Ми повинні переглянути чотири об'єкти:
Тепер має бути очевидним, що ви можете використовувати створену збірку - виконавчий файл - компілятора CoffeScript для компіляції будь-якої довільної програми CoffeScript та генерування збірки для цієї програми.
Тепер сам компілятор CoffeScript є лише умовною програмою CoffeScript, і, таким чином, її можна скласти компілятором CoffeScript.
Здається , що ваша плутанина виникає з того факту , що , коли ви створюєте свій власний нову мову, ви не маєте компілятор ще ви можете використовувати для компіляції компілятора. Це напевно виглядає як проблема з курячим яйцем , правда?
Введіть процес, який називається завантажувальним .
Тепер вам потрібно додати нові функції. Скажімо, ви реалізували лише while
-loops, але також хочете for
-loops. Це не проблема, оскільки ви можете переписати будь-яку for
петлю таким чином, щоб вона була while
-loop. Це означає, що ви можете використовувати while
-loops у вихідному коді вашого компілятора, оскільки збірка, яка є у вас під рукою, може компілювати лише їх. Але ви можете створити функції всередині вашого компілятора, які можуть виправляти і компілювати for
з ним. Потім ви використовуєте збірку, яка вже є, і компілюєте нову версію компілятора. А тепер у вас є збірка компілятора, яка також може розбирати і компілювати for
-loops! Тепер ви можете повернутися до вихідного файлу вашого компілятора та переписати будь-які while
петлі, які вам не потрібні, у for
-loops.
Промийте та повторіть, доки всі бажані функції мови не зможуть скласти компілятор.
while
і for
очевидно, були лише приклади, але це працює для будь-якої нової мовної функції, яку ви хочете. І тоді ви опинилися в ситуації, в якій зараз знаходиться CoffeScript: Компілятор компілює себе.
Там багато літератури. Роздуми про довіру довіри - класика, яку кожен, хто цікавиться цією темою, повинен прочитати хоча б раз.
Тут компілятор терміна переслідує те, що в ньому задіяні два файли. Один є виконуваним файлом, який приймає вхідні файли, написані в CoffeScript, і створює як його вихідний файл інший виконуваний файл, об'єктний файл або спільну бібліотеку. Інший - вихідний файл CoffeeScript, який просто описує процедуру компіляції CoffeeScript.
Ви застосовуєте перший файл до другого, виробляючи третій, який здатний виконувати той самий акт компіляції, що і перший (можливо більше, якщо другий файл визначає функції, не реалізовані першим), і таким чином може замінити перший, якщо ви так бажання.
Оскільки версія Ruby компілятора CoffeeScript вже існувала, вона була використана для створення версії CoffeeScript компілятора CoffeeScript.
Це відомо як компілятор самохостингу .
Це надзвичайно часто і зазвичай є результатом бажання автора використовувати власну мову, щоб підтримувати її зростання.
Тут не справа компіляторів, а питання виразності мови, оскільки компілятор - це лише програма, написана якоюсь мовою.
Коли ми кажемо, що "мова написана / реалізована", ми фактично маємо на увазі, що компілятор або інтерпретатор цієї мови реалізований. Існують мови програмування, на яких можна писати програми, що реалізують мову (є компіляторами / перекладачами для однієї мови). Ці мови називаються універсальними мовами .
Для того, щоб можна було це зрозуміти, подумайте про токарний верстат для металу. Це інструмент, який використовується для формування металу. Можна, використовуючи саме цей інструмент, створити інший, однаковий інструмент, створивши його частини. Таким чином, цей інструмент є універсальною машиною. Звичайно, перший був створений за допомогою інших засобів (інших інструментів) і, ймовірно, був нижчої якості. Але перший був використаний для побудови нових з більшою точністю.
3D-принтер - це майже універсальна машина. Ви можете надрукувати весь 3D-принтер за допомогою 3D-принтера (ви не можете побудувати наконечник, який плавить пластик).
N + 1-я версія компілятора написана в X.
Таким чином, він може бути складений п ятою версією компілятора (також написаною в X).
Але перша версія компілятора, написана на X, повинна бути складена компілятором для X, який написаний мовою, відмінною від X. Цей етап називається завантаженням компілятора.
Компілятори приймають специфікацію високого рівня і перетворюють її на реалізацію низького рівня, таку, яку можна виконати на апаратному забезпеченні. Тому між форматом специфікації та фактичним виконанням немає сеансу, окрім семантики мови, на яку орієнтується.
Перехресні компілятори переходять з однієї системи в іншу, міжмовні компілятори складають одну мовну специфікацію в іншу мовну специфікацію.
В основному компіляція - це справедливий переклад, і рівень, як правило, є мовою вищого рівня до мови нижчого рівня, але існує багато варіантів.
Зрозуміло, що компілятори завантаження є найбільш заплутаними, звичайно, тому що вони складають мову, на якій вони написані. Не забувайте про початковий крок завантаження, який вимагає принаймні мінімальної існуючої версії, яка виконується. Багато завантажуваних компіляторів працюють над мінімальними можливостями мови програмування і додають додаткові складні мовні функції вперед, поки нова функція може бути виражена за допомогою попередніх функцій. Якщо це не так, потрібно було б заздалегідь розробити цю частину "компілятора" іншою мовою.
self-hosting
компілятор. Дивіться програмісти.stackexchange.com