Как компилятор может скомпилировать себя?
Я исследую CoffeeScript на веб-сайте http://coffeescript.org/, и в нем есть текст
компилятор CoffeeScript сам написан в CoffeeScript
как компилятор может компилировать себя, или что означает это утверждение?
9 ответов:
первое издание компилятора не может быть сгенерировано машиной из специфического для него языка программирования; ваша путаница понятна. Более поздняя версия компилятора с большим количеством языковых функций (с исходным кодом, переписанным в первой версии нового языка) может быть построена первым компилятором. Эта версия может затем скомпилировать следующий компилятор и так далее. Вот пример:
- первый компилятор CoffeeScript написан на Ruby, производя версию 1 CoffeeScript
- исходный код компилятора CS переписывается в CoffeeScript 1
- исходный компилятор CS компилирует новый код (написанный в CS 1) в версию 2 компилятора
- в исходный код компилятора вносятся изменения для добавления новых языковых функций
- второй компилятор CS (первый написанный на CS) компилирует пересмотренный новый исходный код в версию 3 компилятора
- повторите шаги 4 и 5 для каждого итерация
примечание: Я не уверен точно, как версии CoffeeScript нумеруются, это был просто пример.
этот процесс обычно называется bootstrapping. Другим примером компилятора начальной загрузки является
rustcкомпилятор для язык Руст.
в статье размышления о доверии доверие, Кен Томпсон, один из создателей Unix, пишет увлекательный (и легко читаемый) обзор того, как компилятор C компилирует себя. Подобные понятия могут быть применены к 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. Теперь повторите этот процесс для каждой функции языка, и у вас есть компилятор "self-hosting": компилятор C, написанный на C.
1 поворот сюжета, описанный в статье, заключается в том, что, поскольку компилятор может быть "обучен" таким фактам, его также можно неправильно научить генерировать троянские исполняемые файлы таким образом, что их трудно обнаружить, и такой акт саботажа может сохраняться во всех компиляторах, созданных испорченным компилятором.
вы уже получили очень хороший ответ, однако я хочу предложить вам другую точку зрения, которая, надеюсь, будет интересно вам. Давайте сначала установим два факта, с которыми мы оба можем согласиться:
- компилятор CoffeeScript-это программа, которая может компилировать программы, написанные в CoffeeScript.
- компилятор CoffeeScript-это программа, написанная на языке CoffeeScript.
Я уверен, что вы можете согласиться, что и #1 и #2 верны. А теперь взгляните два утверждения. Теперь вы видите, что это совершенно нормально для компилятора CoffeeScript, чтобы иметь возможность компилировать компилятор CoffeeScript?
компилятору все равно что он компилирует. Пока это программа, написанная в CoffeeScript, она может ее скомпилировать. И компилятор CoffeeScript сам по себе просто оказывается такой программой. Компилятор CoffeeScript не заботится о том, что это сам компилятор CoffeeScript, который он компилирует. Все, что он видит, это какой-то CoffeeScript код. Период.
как компилятор может компилировать себя, или что означает это утверждение?
Да, это именно то, что означает это утверждение, и я надеюсь, что теперь вы можете видеть, как это утверждение верно.
как компилятор может компилировать себя, или что означает это утверждение?
это означает именно это. Прежде всего, некоторые вещи, чтобы рассмотреть. Есть четыре объекта, которые нам нужно посмотреть:
- исходный код любой произвольной программы CoffeScript
- (сгенерированная) сборка любой произвольной программы CoffeScript
- исходный код компилятора CoffeScript
- (сгенерированная) сборка компилятор CoffeScript
теперь должно быть очевидно, что вы можете использовать сгенерированную сборку - исполняемый файл - компилятора CoffeScript для компиляции любой произвольной программы CoffeScript и генерировать сборку для этой программы.
теперь компилятор CoffeScript сам по себе является просто произвольной программой CoffeScript, и, таким образом, он может быть скомпилирован компилятором CoffeScript.
кажется, что ваше недоумение связано с тем, что когда вы создайте свой собственный новый язык, вы не есть компилятор еще можно использовать для компиляции компилятор. Это, конечно, выглядит как проблема куриного яйца, да?
представьте процесс под названием bootstrapping.
- вы пишете компилятор на уже существующем языке (в случае CoffeScript исходный компилятор был написан на Ruby), который может скомпилировать подмножество нового язык
- вы пишете компилятор, который может компилировать подмножество нового языка в самом новом языке. Вы можете использовать только языковые функции, которые компилятор из вышеприведенного шага может скомпилировать.
- вы используете компилятор из Шага 1 для компиляции компилятора из шага 2. Это оставляет вам сборку, которая была первоначально написана в подмножестве нового языка, и которая может скомпилировать подмножество нового языка.
теперь вам нужно добавить новые особенности. Скажем, вы только реализовали
while-петли, но и хочу!--1-->-петли. Это не проблема, так как вы можете переписать любойfor-петли таким образом, что этоwhile-петли. Это означает, что вы можете использовать толькоwhile-циклы в исходном коде вашего компилятора, так как сборка, которую вы имеете под рукой, может только компилировать их. Но вы можете создать функции внутри вашего компилятора, которые могут pase и compilefor-петли с ним. Затем вы используете уже имеющуюся сборку и компилируете новую версия компилятора. И теперь у вас есть сборка компилятора, который также может анализировать и компилироватьfor-петли! Теперь вы можете вернуться к исходному файлу вашего компилятора и переписать любойwhile-петли, которые вы не хотите вfor-петли.промыть и повторять, пока все языковые особенности, которые могут быть скомпилированы с помощью компилятора.
whileиforочевидно, были только примеры, но это работает для любой новой функции языка. И тогда вы находитесь в ситуация CoffeScript находится в настоящее время: компилятор компилирует себя.есть много литературы там. размышления о доверии доверие это классика, которую каждый, кто интересуется этой темой, должен прочитать хотя бы один раз.
небольшое, но важное уточнение
здесь компилятор замалчивает тот факт, что есть два файлы. Один из них-это исполняемый файл, который принимает в качестве входных файлов, записанных в CoffeScript, и создает в качестве выходного файла другой исполняемый файл, связываемый объектный файл или общую библиотеку. Другой-исходный файл CoffeeScript, который просто описывает процедуру компиляции CoffeeScript.
вы применяете первый файл во-вторых, создание Третьего, который способен выполнять тот же акт компиляции, что и первый (возможно, больше, если второй файл определяет функции, не реализованные первым), и поэтому может заменить первый, если вы этого хотите.
- компилятор CoffeeScript был впервые написан на Ruby.
- компилятор CoffeeScript был затем переписан в CoffeeScript.
поскольку Ruby-версия компилятора CoffeeScript уже существовала, она использовалась для создания версии CoffeeScript компилятора CoffeeScript.
Это известно как self-hosting compiler.
это чрезвычайно распространено, и обычно приводит к желание автора использовать свой собственный язык для поддержания роста этого языка.
здесь дело не в компиляторах, а в выразительности языка, поскольку компилятор-это просто программа, написанная на каком-то языке.
когда мы говорим, что" язык написан/реализован", мы фактически имеем в виду, что компилятор или интерпретатор для этого языка реализован. Существуют языки программирования, на которых можно писать программы, реализующие этот язык (являются компиляторами/интерпретаторами для одного и того же языка). Эти языки называются универсальный языки.
чтобы понять это, подумайте о металлическом токарном станке. Это инструмент, используемый для формирования металла. Можно, используя только этот инструмент, создать другой, идентичный инструмент, создав его части. Таким образом, этот инструмент является универсальным станком. Конечно, первый был создан с использованием других средств (других инструментов) и, вероятно, был более низкого качества. Но первый был использован для создания новых с более высокой точностью.
3D-принтер является практически универсальным машина. Вы можете распечатать весь 3D-принтер с помощью 3D-принтера (вы не можете построить наконечник, который плавит пластик).
доказательство по индукции
индуктивный шаг
n+1-я версия компилятора написана в X.
таким образом, он может быть скомпилирован N-й версией компилятора (также написанной в X).
базовый
но первая версия компилятора, написанная в X, должна быть скомпилирована компилятором для X, написанным на языке, отличном от X. Этот шаг называется начальной загрузкой компилятора.
компиляторы берут высокоуровневую спецификацию и превращают ее в низкоуровневую реализацию, такую как может быть выполнена на аппаратном обеспечении. Поэтому нет никакой связи между форматом спецификации и фактическим выполнением, кроме семантики целевого языка.
Кросс-компиляторы переходят из одной системы в другую систему, кросс-языковые компиляторы компилируют одну спецификацию языка в другую спецификацию языка.
в основном компиляции это просто перевод, и уровень обычно выше уровня языка на более низкий уровень языка, но есть много вариантов.
компиляторы начальной загрузки являются наиболее запутанными, конечно, потому что они компилируют язык, на котором они написаны. Не забывайте о начальном шаге начальной загрузки, который требует по крайней мере минимальной существующей версии, которая является исполняемой. Многие загрузочные компиляторы сначала работают с минимальными возможностями языка программирования и добавляют дополнительный комплекс языковые функции продвигаются вперед до тех пор, пока новая функция может быть выражена с использованием предыдущих функций. Если бы это было не так, то потребовалось бы, чтобы эта часть "компилятора" была разработана на другом языке заранее.

Comments