Какие свойства языка программирования делают компиляцию невозможной?

72

Вопрос:

«Некоторые свойства языка программирования могут требовать, чтобы единственный способ получить код, написанный на нем, выполнялся путем интерпретации. Другими словами, компиляция в собственный машинный код традиционного ЦП невозможна. Что это за свойства?»

Составители: принципы и практика Параг Х. Дейва и Химаншу Б. Дейва (2 мая 2012 г.)

Книга не дает подсказки об ответе. Я пытался найти ответ на Концепции языков программирования (SEBESTA), но безрезультатно. Поиски в Интернете тоже мало что дали. Есть ли у вас какие-либо подсказки?

Андерсон Насименто Нунес
источник
31
Знаменитый Perl даже не может быть разобран . Кроме этого, утверждение кажется тривиально неправильным без дополнительных предположений: если есть интерпретатор, я всегда могу объединить интерпретатор и код в один исполняемый файл, вуаля.
Рафаэль
4
@Raphael: Хорошая идея, но ... 1) Вы предполагаете, что код доступен до выполнения. Это не относится к интерактивному использованию. Конечно, вы можете использовать своевременную компиляцию с собственным кодом для операторов bash или содержимого стека PostScript, но это довольно сумасшедшая идея. 2) Ваша идея на самом деле не компилирует код: пакет не является скомпилированной версией кода, но все же является интерпретатором кода.
reinierpost
7
В старые добрые времена у меня было саморедактирование программ gwbasic (gwbasic хранит базовые программы в виде байт-кода). В настоящее время я не могу придумать разумного способа компилировать их в машинный код, сохраняя при этом их способность редактировать себя.
PlasmaHH
15
@PlasmaHH: Самомодифицирующийся код восходит к 1948 году. Первый компилятор был написан в 1952 году. Концепция самоизменяющегося кода была изобретена в собственном машинном коде.
Mooing Duck
10
@reinierpost Рафаэль занимает теоретическую позицию по этому вопросу. Это имеет смысл показать концептуальные ограничения вопроса. Компиляция - это перевод с языка S на язык T. Язык T может быть расширением S, к которому может быть добавлен интерпретирующий код в некотором другом языке. Таким образом, связывание S и его интерпретатора является программой на языке T. Для инженера это кажется абсурдом, но это показывает, что сформулировать вопрос нелегко. Как вы отличаете приемлемый процесс компиляции от недопустимого (например, Рафаэля) с инженерной точки зрения?
Бабу

Ответы:

61

Различие между интерпретируемым и скомпилированным кодом, вероятно, является фикцией, как подчеркивается в комментарии Рафаэля :

the claim seems to be trivially wrong without further assumptions: if there is
an interpreter, I can always bundle interpreter and code in one executable ...

Дело в том, что код всегда интерпретируется программным обеспечением, аппаратным обеспечением или их комбинацией, и процесс компиляции не может определить, каким он будет.

То, что вы воспринимаете как компиляцию, - это процесс перевода с одного языка (для источника) на другой язык T (для цели). И, интерпретатор для S , как правило , отличается от переводчика для T .STST

Скомпилированная программа переводится из одной синтаксической формы в другую синтаксическую форму P T , так что, учитывая предполагаемую семантику языков S и T , P S и P T имеют одинаковое вычислительное поведение, вплоть до нескольких вещей, которые вы обычно пытаются изменить, возможно, оптимизировать, например, сложность или простую эффективность (время, пространство, поверхность, потребление энергии). Я стараюсь не говорить о функциональной эквивалентности, поскольку это потребовало бы точных определений.пSпTSTпSпT

Некоторые компиляторы фактически использовались просто для уменьшения размера кода, а не для «улучшения» исполнения. Это имело место для языка, используемого в системе Платона (хотя они не называли это компиляцией).

Вы можете рассмотреть ваш код полностью составлен , если после процесса компиляции, вам больше не нужен переводчик для . По крайней мере, это единственный способ, которым я могу прочитать ваш вопрос, как технический, а не теоретический вопрос (поскольку, теоретически, я всегда могу перестроить переводчика).S

Afaik, одна вещь, которая может создать проблему, это мета-цикличность . То есть, когда программа будет манипулировать синтаксическими структурами на своем собственном языке , создавая фрагмент программы, который затем интерпретируется так, как если бы он был частью исходной программы. Поскольку вы можете создавать произвольные фрагменты программы на языке S в результате произвольных вычислений, манипулирующих бессмысленными синтаксическими фрагментами, я думаю, вы можете сделать почти невозможным (с инженерной точки зрения) компиляцию программы в язык T , так что теперь генерировать фрагменты Т . Следовательно, потребуется интерпретатор для S или, по крайней мере, компилятор из S вSSTTSS дляоперативной компиляциисгенерированных фрагментов в S (см. Такжеэтот документ).TS

Но я не уверен, как это можно правильно оформить (и сейчас у меня нет на это времени). И невозможно это большое слово для вопроса, который не формализован.

Дальнейшие замечания

Добавлено через 36 часов. Вы можете пропустить это очень долгое продолжение.

Многочисленные комментарии к этому вопросу показывают два взгляда на проблему: теоретический взгляд, который рассматривает ее как бессмысленный, и инженерный взгляд, который, к сожалению, не так легко формализовать.

Есть много способов взглянуть на интерпретацию и компиляцию, и я постараюсь сделать несколько набросков. Я постараюсь быть настолько неформальным, насколько я могу управлять

Схема надгробия

Одной из ранних формализаций (с начала 1960-х до конца 1990-х годов) являются диаграммы T или Tombstone . Эти диаграммы представлены в виде компонуемых графических элементов, языка реализации интерпретатора или компилятора, исходного языка, который интерпретируется или компилируется, и целевого языка в случае компиляторов. Более сложные версии могут добавлять атрибуты. Эти графические представления можно рассматривать как аксиомы, правила вывода, которые можно использовать для механического получения генерации процессоров из доказательства их существования по аксиомам, как Карри-Ховард (хотя я не уверен, что это было сделано в шестидесятых годах :).

Частичная оценка

Другой интересный взгляд - это парадигма частичной оценки . Я просто рассматриваю программы как своего рода реализацию функций, которая вычисляет ответ с учетом некоторых входных данных. Затем интерпретатор для языка S представляет собой программу, взять программу р S , написанный на S и данных D для этой программы, и вычисляет результат в соответствии с семантикой S . Частичная оценка представляет собой метод , специализирующуюся программу двух аргументов 1 и 2 , когда только один аргумент, скажем , с 1яSSпSSdSa1a2a1, известен. Намерение состоит в том, чтобы иметь более быструю оценку, когда вы, наконец, получите второй аргумент . Это особенно полезно , если 2 изменяется чаще , чем в 1 , как стоимость частичной оценки с более 1 может быть амортизируется всех вычислений , где только 2 меняется.a2a2a1a1a2

Это частая ситуация при разработке алгоритмов (часто это тема первого комментария к SE-CS), когда некоторая более статическая часть данных предварительно обрабатывается, так что стоимость предварительной обработки может быть амортизирована во всех приложениях. алгоритма с более переменными частями входных данных.

Это также является самой ситуацией интерпретаторов, так как первый аргумент - это программа, которая должна быть выполнена, и обычно выполняется много раз с разными данными (или имеет части, выполненные много раз с разными данными). Следовательно, стало естественной идеей специализировать интерпретатора для более быстрой оценки данной программы, частично оценивая его в этой программе в качестве первого аргумента. Это можно рассматривать как способ компиляции программы, и была проведена значительная исследовательская работа по компиляции путем частичной оценки интерпретатора по его первому (программному) аргументу.

Теорема Smn

Приятный момент в подходе частичной оценки состоит в том, что он берет свои корни в теории (хотя теория может быть лжецом), особенно в теореме Клини о Смне . Я пытаюсь дать интуитивное представление об этом, надеясь, что это не расстроит чисто теоретиков.

С учетом нумерации Гёделя рекурсивных функций вы можете рассматривать φ как свое аппаратное обеспечение, так что с учетом числа Геделя p (считывание объектного кода ) программы φ p является функцией, определенной как p (то есть вычисляемой объектным кодом на вашем оборудовании ).φφпφпп

В простейшем виде теорема изложена в википедии следующим образом (с небольшим изменением в обозначениях):

При заданной нумерации Гёделя рекурсивных функций существует примитивная рекурсивная функция σ двух аргументов со следующим свойством: для каждого числа Гёделя q частично вычислимой функции f с двумя аргументами выражения φ σ ( q , x ) ( y ) и f ( x , y ) определены для одинаковых комбинаций натуральных чисел x и y , и их значения равны для любой такой комбинации. Другими словами, следующее экстенсиональное равенство функций выполняется для каждогоφσqfφσ(q,x)(y)f(x,y)xy : Иксφσ(q,x)λy.φq(x,y).

Теперь, взяв в качестве интерпретатора I S , x в качестве исходного кода программы p S и y в качестве данных d для этой программы, мы можем написать: qISxpSydφσ(IS,pS)λd.φIS(pS,d).

может рассматриваться как выполнение интерпретатора I S на аппаратных средств, то есть, как черный ящикготов интерпретировать программынаписанные на языке S .φISISS

Функция может рассматриваться как функция, которая специализирует интерпретатор I S для программы P S , как при частичной оценке. Таким образом, число Геделя σ ( я S , р S ) можно видеть , имеет объектный код , который скомпилированная версия программы р S .σISпSσ(яS,пS)пS

Так что функция можно рассматривать как функцию, которая принимает в качестве аргумента исходный код программы q S, написанный на языке S , и возвращает версию объектного кода для этой программы. Таким образом, C S - это то, что обычно называется компилятор.СSзнак равноλQS,σ((яS,QS)QSSСS

Некоторые выводы

σQSяS

Формализация более ограничительного представления о том, что такое компилятор, потребует более тонкого теоретического подхода. Я не знаю, что могло быть сделано в этом направлении. Самая реальная работа по частичной оценке более реалистична с инженерной точки зрения. И, конечно же, существуют другие методы написания компиляторов, включая извлечение программ из доказательства их спецификации, разработанные в контексте теории типов, основанные на изоморфизме Карри-Ховарда (но я выхожу за пределы своей компетенции) ,

Моя цель здесь состояла в том, чтобы показать, что замечание Рафаэля не "сумасшедшее", а разумное напоминание о том, что вещи не очевидны и даже не просты. Сказать, что что-то невозможно, является сильным утверждением, которое требует точных определений и доказательств, хотя бы для того, чтобы иметь точное понимание того, как и почему это невозможно . Но построение правильной формализации для выражения такого доказательства может быть довольно трудным.

При этом, даже если конкретная функция не компилируется, в смысле, понятном инженерам, стандартные методы компиляции всегда могут быть применены к частям программ, которые не используют такую ​​функцию, как отмечено в ответе Жиля.

Следуя ключевым замечаниям Жиля, что в зависимости от языка некоторые вещи могут быть выполнены во время компиляции, в то время как другие должны выполняться во время выполнения, что требует специального кода, мы можем видеть, что концепция компиляции на самом деле плохо определен и, вероятно, не может быть определен каким-либо удовлетворительным образом. Компиляция - это всего лишь процесс оптимизации, как я пытался показать в разделе частичной оценки , когда сравнивал его с предварительной обработкой статических данных в некоторых алгоритмах.

Как сложный процесс оптимизации, концепция компиляции фактически принадлежит континууму. В зависимости от характеристик языка или программы, некоторая информация может быть доступна статически и позволяет оптимизировать ее. Другие вещи должны быть отложены на время выполнения. Когда все становится действительно плохо, все должно быть сделано во время выполнения, по крайней мере, для некоторых частей программы, и объединение исходного кода с интерпретатором - это все, что вы можете сделать. Так что это связывание - только нижняя часть этого континуума компиляции. Большая часть исследований компиляторов посвящена поиску способов делать статически то, что раньше делалось динамически. Сборка мусора во время компиляции кажется хорошим примером.

Обратите внимание, что утверждение о том, что процесс компиляции должен генерировать машинный код, не поможет. Это именно то, что может сделать пакетирование, поскольку интерпретатор - это машинный код (ну, с кросс-компиляцией все может быть немного сложнее).

Babou
источник
3
« невозможно - это большое слово». Очень, очень большое слово. =)
Брайан С.
3
Если кто-то определяет «компиляцию», чтобы ссылаться на последовательность шагов, которые полностью выполняются до того, как исполняющая программа получает свой первый вход, и интерпретацию как процесс выполнения потока программы управления данными через средства, которые не являются частью модели абстрактной машины программы затем для того, чтобы язык был скомпилирован, компилятор должен иметь возможность определить до начала выполнения все возможные значения языковой конструкции. В языках, где языковая конструкция может иметь неограниченное количество значений, компиляция не будет работать.
суперкат
@BrianS Нет, это не так, и невозможно доказать обратное;)
Майкл Газонда
@supercat Это еще не определение. Что такое «значение» языковой конструкции?
Рифмоид
Мне нравится концепция просмотра компилятора / интерпретатора как своего рода частичное выполнение!
Берги
17

Вопрос не в том, что компиляция невозможна . Если язык можно интерпретировать interpre, то его можно скомпилировать тривиальным способом, связав интерпретатор с исходным кодом. Вопрос в том, какие языковые особенности делают это, по сути, единственным способом.

Интерпретатор - это программа, которая принимает исходный код в качестве входных данных и ведет себя так, как указано в семантике этого исходного кода. Если необходим переводчик, это означает, что в языке есть способ интерпретации исходного кода. Эта функция называется eval. Если интерпретатор требуется как часть среды выполнения языка, это означает, что язык включает eval: либо evalсуществует как примитив, либо он может быть закодирован каким-либо образом. Языки, известные как языки сценариев, обычно включают evalфункцию, как и большинство диалектов Лисп .

То, что язык включает в себя eval, не означает, что большая его часть не может быть скомпилирована в нативный код. Например, есть оптимизирующие компиляторы Lisp, которые генерируют хороший нативный код и тем не менее поддерживают eval; eval'Код может быть интерпретирован или может быть скомпилирован на лету.

evalявляется конечной функцией, требующей интерпретатора, но есть и другие функции, которые требуют чего-то, кроме переводчика. Рассмотрим некоторые типичные фазы компилятора:

  1. анализ
  2. Проверка типа
  3. Генерация кода
  4. соединение

evalозначает, что все эти этапы должны выполняться во время выполнения. Есть и другие функции, которые затрудняют компиляцию. Взяв это снизу, некоторые языки поощряют поздние ссылки, предоставляя способы, которыми функции (методы, процедуры и т. Д.) И переменные (объекты, ссылки и т. Д.) Могут зависеть от нелокальных изменений кода. Это затрудняет (но не делает невозможным) создание эффективного собственного кода: проще сохранять ссылки на объекты в виде вызовов на виртуальной машине и позволить механизму виртуальной машины обрабатывать привязки на лету.

Вообще говоря, рефлексия затрудняет компиляцию языков в нативный код. Примитив eval - крайний случай отражения; многие языки не заходят так далеко, но, тем не менее, имеют семантику, определенную в терминах виртуальной машины, что позволяет, например, коду получать класс по имени, проверять его наследование, перечислять его методы, вызывать метод и т. д. Java с JVM и C # с .NET - два известных примера. Самый простой способ реализовать эти языки - это скомпилировать их в байт-код , но, тем не менее, существуют нативные компиляторы (многие из которых точно вовремя ), которые компилируют по крайней мере фрагменты программы, не использующие расширенные средства отражения.

Проверка типа определяет, является ли программа действительной. Разные языки имеют разные стандарты того, сколько анализа выполняется во время компиляции и во время выполнения: язык известен как «статически типизированный», если он выполняет много проверок перед началом выполнения кода, и «динамически типизированный», если он этого не делает. Некоторые языки включают в себя функцию динамического приведения или функцию «не проверять и проверять»; эти функции требуют встраивания проверки типов в среду выполнения. Это ортогонально требованиям включения генератора кода или интерпретатора в среду выполнения.

¹ Упражнение: определить язык, который нельзя интерпретировать.

Жиль "ТАК - перестань быть злым"
источник
(1) Я не согласен по поводу объединения интерпретатора с подсчетом исходного кода как компиляцией, но остальная часть вашего поста превосходна. (2) Полностью согласен с Eval. (3) Я не понимаю, почему рефлексия затруднила бы компиляцию языков в нативный код. Objective-C имеет отражение, и (я предполагаю), как правило, компилируется. (4) Смутно связанное примечание: метамагия шаблона C ++ обычно интерпретируется, а не компилируется, а затем выполняется.
Mooing Duck
Только что со мной произошло, Lua составлен. Он evalпросто компилирует байт-код, а затем в качестве отдельного шага бинарный код выполняет байт-код. И это определенно отражается в скомпилированном бинарном файле.
Mooing Duck
На машине Гарвардской архитектуры компиляция должна давать код, к которому никогда не нужно обращаться как к «данным». Я хотел бы предположить, что информация из исходного файла, которая в конечном итоге должна быть сохранена как данные, а не код, на самом деле не «компилируется». Нет ничего плохого в том, что компилятор принимает такое объявление int arr[] = {1,2,5};и генерирует раздел с инициализированными данными, содержащий [1,2,5], но я бы не описал его поведение как перевод [1,2,5] в машинный код. Если почти вся программа должна храниться в виде данных, какая ее часть действительно будет «скомпилирована»?
Суперкат
2
@supercat Вот что математики и компьютерные ученые называют тривиальным. Это соответствует математическому определению, но ничего интересного не происходит.
Жиль "ТАК - перестань быть злым"
@Gilles: если термин «компилировать» зарезервирован для перевода в машинные инструкции (без сохранения связанных «данных») и допускает, что в языке компиляции поведение объявления массива не «компилирует» массив, то Есть некоторые языки, на которых невозможно скомпилировать какую-либо значимую часть кода.
суперкат
13

Я думаю, что авторы предполагают, что компиляция означает

  • исходная программа не должна присутствовать во время выполнения, и
  • никакой компилятор или интерпретатор не должен присутствовать во время выполнения.

Вот некоторые примеры функций, которые сделают его проблематичным, если не «невозможным» для такой схемы:

  1. Если вы можете запросить значение переменной во время выполнения, ссылаясь на переменную по ее имени (которая является строкой), то вам понадобятся имена переменных, чтобы быть рядом во время выполнения.

  2. Если вы можете вызывать функцию / процедуру во время выполнения, ссылаясь на нее по ее имени (которая является строкой), тогда вам понадобятся имена функций / процедур во время выполнения.

  3. Если вы можете создать часть программы во время выполнения (в виде строки), скажем, запустив другую программу или считав ее из сетевого подключения и т. Д., То вам потребуется либо интерпретатор, либо компилятор во время выполнения, чтобы запустить эту часть программы.

Лисп имеет все три функции. Таким образом, системы Lisp всегда имеют интерпретатор, загруженный во время выполнения. Языки Java и C # имеют имена функций, доступные во время выполнения, и таблицы, чтобы посмотреть, что они означают. Вероятно, такие языки, как Basic и Python, также имеют имена переменных во время выполнения. (Я не уверен на 100% в этом).

Удай Редди
источник
Что если «интерпретатор» скомпилирован в код? Например, используя таблицы диспетчеризации для вызова виртуальных методов, это пример интерпретации или компиляции?
Эрвин Болвидт
2
"во время выполнения не должно присутствовать никакого компилятора или интерпретатора", а? Что ж, если это правда, то в глубоком смысле C не может быть «скомпилирован» на большинстве платформ. Среда выполнения C не имеет ничего общего: запуск, настройка стеков и т. Д., А также завершение работы для atexitобработки. Но это все еще должно быть там.
псевдоним
1
«В системах Lisp всегда есть интерпретатор, загружаемый во время выполнения». - Не обязательно. Многие системы Lisp имеют компилятор во время выполнения. У некоторых вообще нет переводчика.
Йорг Миттаг
2
Хорошая попытка, но en.wikipedia.org/wiki/Lisp_machine#Technical_overview . Они компилируют Лисп и предназначены для эффективного выполнения результата.
Питер - Восстановить Монику
@Pseudonym: среда выполнения C - это библиотека, а не компилятор и интерпретатор.
Mooing Duck
8

Возможно, что текущие ответы «переосмысливают» утверждение / ответы. возможно, авторы имеют в виду следующее явление. у многих языков есть команда, подобная eval; например, см. javascript eval, и его поведение обычно изучают как особую часть теории CS (например, Lisp). функция этой команды заключается в оценке строки в контексте определения языка. следовательно, в действительности он имеет сходство с «встроенным компилятором». компилятор не может знать содержимое строки до времени выполнения. поэтому компиляция результата eval в машинный код невозможна во время компиляции.

другие ответы указывают на то, что различие интерпретируемых и скомпилированных языков может значительно размываться во многих случаях, особенно в более современных языках, таких как, скажем, Java с «как раз вовремя» компилятором, также называемым «горячей точкой» (движки javascript, например V8, все чаще используют эту же технику). "eval-like" функциональность, безусловно, одна из них.

ВЗН
источник
2
V8 хороший пример. Это чистый компилятор, никакой интерпретации никогда не происходит. Тем не менее, он по-прежнему поддерживает полную семантику ECMAScript, включая неограниченную eval.
Йорг Миттаг,
1
Луа делает то же самое.
Mooing Duck
3

LISP - ужасный пример, так как он был задуман как своего рода «машинный» язык более высокого уровня как основа для «настоящего» языка. Сказанный «настоящий» язык никогда не материализовался. Машины LISP были построены на идее выполнения (большей части) LISP в аппаратном обеспечении. Поскольку интерпретатор LISP - это просто программа, в принципе возможно реализовать его в схемотехнике. Возможно, не практично; но далеко не невозможно.

Кроме того, в кремнии запрограммировано много интерпретаторов, обычно называемых «CPU». И часто бывает полезно интерпретировать (пока не существует, нет под рукой ...) машинные коды. Например, Linux 'x86_64 был впервые написан и протестирован на эмуляторах. Когда на рынке появились микросхемы, имелись в наличии полные дистрибутивы, даже для тех, кто только начал тестирование. Java часто компилируется в код JVM, который является интерпретатором, который не составит труда написать в кремнии.

Большинство «интерпретируемых» языков компилируются во внутреннюю форму, которая оптимизируется, а затем интерпретируется. Это, например, то, что делают Perl и Python. Существуют также компиляторы для предполагаемых интерпретируемых языков, например оболочка Unix. С другой стороны, можно интерпретировать традиционно скомпилированные языки. Одним из самых экстремальных примеров, которые я видел, был редактор, который использовал интерпретированный C как язык расширения. Это C может запускать обычные, но простые программы без проблем.

С другой стороны, современные процессоры даже принимают ввод «машинного языка» и переводят его в инструкции более низкого уровня, которые затем переупорядочиваются и оптимизируются (то есть «компилируются») перед передачей для выполнения.

Весь этот «компилятор» против «переводчика» различия действительно спорный вопрос, где - то в стеке есть окончательный интерпретатор , который принимает «код» и выполняет его «напрямую». Ввод от программиста претерпевает преобразования вдоль линии, которая называется «компиляция», просто рисует произвольную линию на песке.

vonbrand
источник
1

Реальность такова, что существует большая разница между интерпретацией некоторой базовой программы и выполнением ассемблера. И есть промежуточные области с P-кодом / байт-кодом с компиляторами или без них. Поэтому я попытаюсь обобщить некоторые моменты в контексте этой реальности.

  • Если способ анализа исходного кода зависит от условий выполнения, написание компилятора может оказаться невозможным или настолько сложным, что никто не будет беспокоиться.

  • Код, который изменяет сам себя, в общем случае невозможно скомпилировать.

  • Программа, которая использует функцию, подобную eval, обычно не может быть полностью скомпилирована заранее (если вы рассматриваете строку, переданную ей как часть программы), хотя, если вы собираетесь запускать eval'ed код повторно, она все равно может быть полезно, чтобы ваша eval-подобная функция вызывала компилятор. Некоторые языки предоставляют API для компилятора, чтобы упростить это.

  • Возможность ссылаться на вещи по имени не исключает компиляции, но вам нужны таблицы (как уже упоминалось). Вызов функций по имени (например, IDispatch) требует много усилий, и я думаю, что большинство людей согласятся с тем, что мы фактически говорим о интерпретаторе вызова функций.

  • Слабая типизация (независимо от вашего определения) делает компиляцию труднее и, возможно, результат менее эффективным, но часто не невозможным, если разные значения не вызывают разный анализ. Здесь есть скользящая шкала: если компилятор не может определить фактический тип, ему нужно будет генерировать ветви, вызовы функций и тому подобное, которые иначе не были бы там, эффективно встраивая биты интерпретатора в исполняемый файл.

Анонимный трус
источник
1

я бы предположил, что главной особенностью языка программирования, делающей невозможным компилятор для этого языка (в строгом смысле, см. также самоприемник ), является функция самомодификации . Значение языка позволяет изменять исходный код во время выполнения (компилятор, генерирующий, фиксированный и статический, объектный код не может этого сделать). Классический пример - Лисп (см. Также Гомойконичность ). Аналогичные функциональные возможности предоставляются с использованием языковой конструкции, такой как eval , включенной во многие языки (например, javaScript). Eval фактически вызывает интерпретатор (как функцию) во время выполнения .

Другими словами, язык может представлять свою собственную мета-систему (см. Также Метапрограммирование )

Обратите внимание, что отражение языка в смысле запроса метаданных определенного исходного кода и, возможно, изменения только метаданных (например, механизма отражения Java или PHP) не является проблематичным для компилятора, поскольку он уже имеет те метаданные во время компиляции и могут сделать их доступными для скомпилированной программы при необходимости, если это необходимо.

Еще одна особенность, которая делает компиляцию трудной или не самой лучшей опцией (но не невозможной), - это схема типизации, используемая в языке (т.е. динамическая типизация против статической типизации и строгая типизация против свободной типизации). Это мешает компилятору иметь всю семантику во время компиляции, поэтому эффективно часть компилятора (другими словами, интерпретатор) становится частью сгенерированного кода, который обрабатывает семантику во время выполнения . Другими словами, это не компиляция, а интерпретация .

Никос М.
источник
LISP - ужасный пример, так как он был задуман как sprt
vonbrand
@vonbrand, может быть, но отображает как концепцию гомойконичности, так и единообразную двойственность кода данных
Никос М.
-1

Я чувствую, что первоначальный вопрос не очень хорошо сформирован. Авторы вопроса, возможно, намеревались задать несколько иной вопрос: какие свойства языка программирования способствуют написанию для него компилятора?

Например, проще написать компилятор для контекстно-свободного языка, чем контекстно-зависимый язык. Грамматика, которая определяет язык, также может иметь проблемы, которые затрудняют его компиляцию, такие как неясности. Такие проблемы могут быть решены, но требуют дополнительных усилий. Точно так же, языки, определяемые неограниченной грамматикой, труднее анализировать, чем контекстно-зависимые языки (см. Иерархию Хомского ). Насколько мне известно, наиболее широко используемые процедурные языки программирования близки к контекстно-свободному, но имеют несколько контекстно-зависимых элементов, что делает их относительно легко компилируемыми.

Джорджи
источник
2
Вопрос явно намеревается противопоставить / сравнить компиляторы и интерпретаторы. Хотя они могут работать по-разному и, как правило, работают за исключением предельного случая @Raphael, описанного выше, у них есть точно такие же проблемы, касающиеся синтаксического анализа и неоднозначности. Таким образом, синтаксис не может быть проблемой. Я также считаю, что синтаксическая проблема обычно не является основной проблемой при написании компилятора в наше время, хотя это было в прошлом. Я не downvoter: я предпочитаю комментировать.
Бабу
-1

На вопрос есть правильный ответ, настолько очевидный, что его обычно упускают из виду как тривиальный. Но это имеет значение во многих контекстах и ​​является основной причиной существования интерпретируемых языков:

Компиляция исходного кода в машинный код невозможна, если у вас еще нет исходного кода.

Интерпретаторы добавляют гибкость, и, в частности, они добавляют гибкость запуска кода, который не был доступен при компиляции базового проекта.

tylerl
источник
2
«Я потерял исходный код» - это свойство не языка программирования, а конкретной программы, так что это не отвечает на вопрос. И вы , безусловно , нужна цитата для утверждения , что избежать потерь исходного кода является «основной причиной , почему существуют интерпретируемые языки», или даже причина , почему они существуют.
Дэвид Ричерби
1
@DavidRicherby Я думаю, что сценарий использования Tyleri - это интерактивная интерпретация, то есть код, введенный во время выполнения. Я согласен, однако, что это выходит за рамки вопроса, так как это не особенность языка.
Рафаэль
@DavidRicherby и Raphael, я говорю, что автор этого поста подразумевает (то, что я описываю в своем ответе) как функцию само-модификации, которая, конечно, является языковой конструкцией, а не артефактом какой-то конкретной программы
Никос М.