Я исследую CoffeeScript на веб-сайте http://coffeescript.org/ , и в нем есть текст
Компилятор CoffeeScript сам написан на CoffeeScript
Как компилятор может компилировать себя сам или что означает это утверждение?
Я исследую CoffeeScript на веб-сайте http://coffeescript.org/ , и в нем есть текст
Компилятор CoffeeScript сам написан на CoffeeScript
Как компилятор может компилировать себя сам или что означает это утверждение?
Ответы:
Первое издание компилятора не может быть сгенерировано машиной из специфического для него языка программирования; Ваше замешательство понятно. Более поздняя версия компилятора с большим количеством языковых возможностей (с переписанным исходным кодом в первой версии нового языка) могла быть создана первым компилятором. Эта версия может затем скомпилировать следующий компилятор и так далее. Вот пример:
Примечание: я не уверен, как именно нумеруются версии CoffeeScript, это был только пример.
Этот процесс обычно называется начальной загрузкой . Другим примером загрузочного компилятора является rustc
компилятор для языка Rust .
В статье « Размышления о доверии» Кен Томпсон, один из создателей Unix, пишет увлекательный (и легко читаемый) обзор того, как компилятор C сам компилируется. Подобные понятия могут быть применены к CoffeeScript или любому другому языку.
Идея компилятора, который компилирует свой собственный код, в некоторой степени похожа на quine : исходный код, который при выполнении выдает на выходе исходный исходный код. Вот один из примеров стиля CoffeeScript. Томпсон привел этот пример 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);
}
Далее вам может быть интересно, как компилятору учат, что escape-последовательность, подобная '\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.
1 Поворот сюжета, описанный в статье, заключается в том, что поскольку компилятору можно «обучать» таким фактам, его также могут неправильно учить генерировать троянские исполняемые файлы способом, который трудно обнаружить, и такой акт саботажа может продолжаться во всех компиляторах, созданных испорченным компилятором.
Вы уже получили очень хороший ответ, однако я хочу предложить вам другую точку зрения, которая, будем надеяться, будет для вас полезной. Давайте сначала установим два факта, с которыми мы оба можем согласиться:
Я уверен, что вы можете согласиться с тем, что и № 1, и № 2 верны. Теперь посмотрим на два утверждения. Теперь вы понимаете, что компилятор CoffeeScript вполне нормально может компилировать компилятор CoffeeScript?
Компилятору все равно, что он компилирует. Пока это программа, написанная на CoffeeScript, она может компилировать ее. И сам компилятор CoffeeScript просто оказывается такой программой. Компилятору CoffeeScript не важно, что это компилятор самого CoffeeScript. Все, что он видит, это некоторый код CoffeeScript. Период.
Как компилятор может компилировать себя сам или что означает это утверждение?
Да, это именно то, что означает это утверждение, и я надеюсь, что вы теперь видите, насколько это утверждение верно.
Как компилятор может компилировать себя сам или что означает это утверждение?
Это означает именно это. Прежде всего, некоторые вещи, чтобы рассмотреть. Нам нужно рассмотреть четыре объекта:
Теперь должно быть очевидно, что вы можете использовать сгенерированную сборку - исполняемый файл - компилятора CoffeScript для компиляции любой произвольной программы CoffeScript и генерации сборки для этой программы.
Теперь сам компилятор CoffeScript - это просто произвольная программа CoffeScript, и, следовательно, он может быть скомпилирован компилятором CoffeScript.
Кажется , что ваша путаница проистекает из того факта , что , когда вы создаете свой собственный новый язык, вы не имеете компилятор еще вы можете использовать для компиляции компилятора. Это, безусловно, выглядит как проблему куриного яйца , верно?
Введите процесс, называемый начальной загрузкой .
Теперь вам нужно добавить новые функции. Скажем, вы реализовали только while
-loops, но также хотите for
-loops. Это не проблема, поскольку вы можете переписать любой for
-loop таким образом, чтобы он был while
-loop. Это означает, что вы можете использовать только while
-loops в исходном коде вашего компилятора, поскольку имеющаяся у вас сборка может только компилировать его. Но вы можете создавать функции внутри вашего компилятора, которые могут создавать и компилировать for
циклы с ним. Затем вы используете уже имеющуюся сборку и компилируете новую версию компилятора. И теперь у вас есть сборка компилятора, который также может анализировать и компилировать for
-loops! Теперь вы можете вернуться к исходному файлу вашего компилятора и переписать любые while
-loops, которые вам не нужны, в for
-loops.
Промойте и повторяйте, пока все требуемые языковые функции не будут скомпилированы с помощью компилятора.
while
и, for
очевидно, были только примеры, но это работает для любой новой языковой функции, которую вы хотите. И тогда вы попадаете в ситуацию, в которой находится CoffeScript: компилятор сам компилируется.
Там много литературы. Размышления о доверии - это классика, которую каждый, интересующийся этой темой, должен прочитать хотя бы один раз.
Здесь термин компилятор скрывает тот факт, что задействованы два файла. Одним из них является исполняемый файл, который принимает в качестве входных файлов, написанных на CoffeScript, и создает в качестве выходного файла другой исполняемый файл, объектный файл с возможностью связи или общую библиотеку. Другой - это исходный файл CoffeeScript, в котором описывается процедура компиляции CoffeeScript.
Вы применяете первый файл ко второму, создавая третий, который способен выполнять тот же акт компиляции, что и первый (возможно, больше, если второй файл определяет функции, не реализованные первым), и поэтому может заменить первый, если вы так хочется.
Поскольку версия компилятора CoffeeScript на Ruby уже существует, она использовалась для создания версии CoffeeScript компилятора CoffeeScript.
Это известно как самодостаточный компилятор .
Это чрезвычайно распространено и обычно является результатом желания автора использовать свой собственный язык для поддержания роста этого языка.
Здесь дело не в компиляторах, а в выразительности языка, поскольку компилятор - это просто программа, написанная на каком-то языке.
Когда мы говорим, что «язык написан / реализован», мы фактически имеем в виду, что реализован компилятор или интерпретатор для этого языка. Существуют языки программирования, на которых вы можете писать программы, которые реализуют язык (являются компиляторами / интерпретаторами для одного и того же языка). Эти языки называются универсальными языками .
Чтобы понять это, подумайте о металлическом токарном станке. Это инструмент, используемый для придания формы металлу. Возможно, используя только этот инструмент, создать другой, идентичный инструмент, создав его части. Таким образом, этот инструмент является универсальной машиной. Конечно, первый был создан с использованием других средств (других инструментов) и, вероятно, был более низкого качества. Но первый использовался для создания новых с более высокой точностью.
3D-принтер - это почти универсальная машина. Вы можете распечатать весь 3D-принтер, используя 3D-принтер (вы не можете создать наконечник, который плавит пластик).
N + 1-я версия компилятора написана на X.
Таким образом, он может быть скомпилирован n-й версией компилятора (также написанной на X).
Но первая версия компилятора, написанная на X, должна быть скомпилирована компилятором для X, написанным на языке, отличном от X. Этот шаг называется начальной загрузкой компилятора.
Компиляторы берут высокоуровневую спецификацию и превращают ее в низкоуровневую реализацию, которую можно выполнить на оборудовании. Следовательно, нет никакой связи между форматом спецификации и фактическим исполнением, кроме семантики целевого языка.
Кросс-компиляторы перемещаются из одной системы в другую, кросс-языковые компиляторы компилируют одну языковую спецификацию в другую языковую спецификацию.
По сути, компиляция - это просто перевод, и уровень, как правило, от уровня языка выше уровня языка ниже, но есть много вариантов.
Само собой разумеется, что компиляторы начальной загрузки являются наиболее запутанными, поскольку они компилируют язык, на котором они написаны. Не забудьте начальный шаг начальной загрузки, который требует как минимум минимальной существующей версии, которая является исполняемой. Многие загрузочные компиляторы сначала работают с минимальными функциями языка программирования и добавляют дополнительные сложные языковые функции, если новая функция может быть выражена с использованием предыдущих функций. Если бы это было не так, потребовалось бы, чтобы эта часть «компилятора» была заранее разработана на другом языке.
self-hosting
компилятор. См programmers.stackexchange.com/q/263651/6221