(1) — интерпретатор по мере исполнения своей программы работает с состоянием своей машины (регистры, адреса, эффекты исполнения), транслятор занимается синтаксическим переводом своей программы (имена, метки, генерация инструкций).
не принципиально. Когда транслятор зацикливается, то если он компилятор, то он считает данный цикл обработанным, и переходит к следующему. Если это тупой интерпретатор, то он опять обрабатывает этот цикл. Сегодня таких интерпретаторов не бывает. Сейчас интерпретатор как и компилятор обрабатывает _весь_ цикл, а уж потом его исполняет, когда полностью до конца распарсит.
Т.е. компилятор сначала просматривает цикл, а потом его переводит в машкод, и его записывает на диск.
Интерпретатор делает также, однако этот код никуда не пишет, а просто выполняет.
(2) — интерпретатору HLT это остановка, транслятору — нет, просто очередной кусок терма для перевода.
это особенности реализации. Команда HLT вообще не имеет отношения к алгоритмам.
(3) — если выполнение интерпретатором закончится, то останется конечное состояние машины, трансляция всегда закончится, результатом будет выхлоп в виде перевода исходной программы.
это верно только для тупых интерпретаторов, которые выполняют по одной команде за один свой цикл.
(4) — обход программы интерпретатором начинается с исходного состояния машины и продолжается сколько угодно долго, это её выполнение, в т.ч. с зависаниями, у транслятора — один проход по программе за конечное время, это перевод, он начинается с одной программы и даёт в итоге другую.
это тоже неверно. Например bash откажется выполнять цикл, в котором нет done. Даже не начнёт. Что доказывает тот факт, что bash сначала транслирует цикл, а уже потом его выполняет. Как и компилятор.
И ты не отвечаешь про интерпретатор лямбда-исчисления с арифметикой — если что, то мне в x86_64, пжалуста, без стека, но с аллокацией регистров и оптимизацией выражений :)
что, попроще никак не придумать? Думаешь я тебе лютую ☣ написать не смогу, которую тебе будет не распарсить?
И про реальные(tm) интерпретаторы с компиляторами тоже ничего не рассказываешь
я только про них и говорю, а не про какие-то лютые абстракции, как ты. Сегодняшние реальные™ компиляторы сначала транслируют код, а потом его уже исполняют. А не «по одной команде», как ты почему-то считаешь (видимо древних книжек начитался). Потому при желании этот транслированный код можно и записать в файл и исполнять после.
на каждой итерации надо перескакивать на _новый_ кусок машкода.
зачем, если там тоже самое?
И там не может быть ничего другого, ибо этот кусок == результат трансляции одного и того же.
2. прыгнули на сгенеренный машкод, вывели число, прыгнули на конец цикла
ой, а можно подумать, что push умеет заталкивать в стек только предварительно вычисленные константы! ВНЕЗАПНО: она когда-то вообще константы не умела, а только регистры. А что-бы сделать из регистра с 123 регистр 124 не нужен специальный код. Достаточно одной команды INC на любое число.
я только про них и говорю, а не про какие-то лютые абстракции, как ты. Сегодняшние реальные™ компиляторы сначала транслируют код, а потом его уже исполняют.
Нет, это не так для любого языка с евалом. Ладно там всякие лиспы, пехепе с джаваскриптом для тебя тоже не существует?
ой, а можно подумать, что push умеет заталкивать в стек только предварительно вычисленные константы! ВНЕЗАПНО: она когда-то вообще константы не умела, а только регистры. А что-бы сделать из регистра с 123 регистр 124 не нужен специальный код. Достаточно одной команды INC на любое число.
А это тут при чем? Да, push может заталкивать регистр. Но у нас проблема в том, что она заталкивает число. Каждый раз разное. Если бы она заталкивала регистр - проблем бы и не было, ты прав. Но она заталкивает число.
Ты путаешься в примитивных вещах, т.к. у тебя нет опыта разработки хоть сколько-нибудь сложного транслятора (brainfuck'и не считаются, а то может ты там напился, вот у тебя «компилятор/интерпретатор» и вышел).
а у тебя _вообще_ нет опыта разработки транслятора с нуля, от и до. Ты умеешь только придумывать AST, и вставлять их в чужую реализацию. Потому ты и не понимаешь, как эти твои AST работают на реальном железе. Т.е. ты как блондинка за рулём: умеешь руль крутить, умеешь педали нажимать, но если хоть одна гайка открутиться, то тебе остаётся только плакать и звонить в СТО. За то ты отлично и лучше меня знаешь, как проехать в фитнес-центр своего Мухосранска. А я — да, ламо. И Мухосранска не знаю.
Рекурсия есть только в твоих фантазиях. И, еще раз, не любая рекурсия сворачивается в цикл. С рекурсией можно сделать irreducible флоу-граф. Он не сворачивается в цикл.
Без разворачивания нечего сворачивать. Чтобы получить свернутый машинный код, нам надо вообще для начала получить машинный код
нет, не надо. Точнее он у нас уже есть. Смотри: у тебя есть функция foo(const char*s), которая умеет обрабатывать твой ЯП (не важно как, исполнять/компилировать). Там нет eval. Дык в чём проблема? Если у тебя в s встретился eval, просто выполни foo(то_что_в_eval). В машинном коде есть такая команда, а именно CALL для x86.
Дык в чём проблема? Если у тебя в s встретился eval, просто выполни foo(то_что_в_eval). В машинном коде есть такая команда, а именно CALL для x86.
Ну да, именно так оно и работает. Но это ничего не меняет.
Я так и не увидел проблемы.
Проблема в том, что есть программа, которая интерпретируется, но не компилируется (нельзя привести ее к конечному машинному коду). Конечно же, если ты опишешь способ такого приведения (или хотя бы приведешь пример интерпретатора, который это делает), то проблема будет снята.
То есть в твоем коде был цикл. Просто ты его записал в виде рекурсии.
в твоём коде есть eval, а это и есть рекурсия самого компилятора/интерпретатора. Т.е. цикл. Да, что-бы сделать хороший, годный eval, тебе придётся тащить весь компилятор/интерпретатор с собой и со своим кодом. Потому в C/C++ нет такого «хорошего и годного eval».
Итерации никогда не повторяются (т.к. в машкоде разные константы).
кто сказал, что это константы? Это для printf они константы, а для main это одна и та же переменная i, которая постоянно инкрементируется.
При чем тут main и printf? При чем тут вообще твоя сишка?
дык ты начал с push i/printf. Я тебе и объясняю, что раз ты написал push, то будь любезен написать pop. Т.к. printf этого не сделает сама.
Если это возможно - предъяви мне, пожалуйста, алгоритм, который это сделает.
разворот рекурсии в цикл? Да легко: call на саму себя это и есть аналог jmp на саму себя, если допустить бесконечность стека. И любая рекурсия это тоже цикл, только в собственном локальном контексте.
ещё раз повторяю: рекурсия всегда сворачивается в цикл.
Во-первых, нет, не всегда.
контрпример?
Во-вторых - нет, там нету рекурсии.
а что по твоему eval, как не рекурсия?
Однако код может быть представлен в конечном виде всегда.
Нет.
контрпример?
И, что характерно - интерпретаторы этого не делают.
даже bash делает. Ну а твоя ракета — увы, из 70х годов прошлого века. Выполняет по одной команде, тупо, как QBASIC из Windoows95.
и что? Тупой интерпретатор не осилил понять, что это цикл?
_ни один_ интерпретатор не осилит понять, что это цикл.
Такой код даже первоклассник свернёт в цикл.
Первоклассник - свернет. А вот компилятор/интерпретатор - не свернет, т.к. это алгоритмически неразрешимая задача. Такие дела.
Для того, чтобы было проще, видоизменим несколько задачу. Пусть у нас ест ф-я pi-n, которая принимает i и возвращает i-й знак десятичного представления числа пи. И пусть у нас есть 10 разных ф-й, которые принимают число и возвращают некоторый список, который представляет define-форму. Теперь делаем так:
теперь ф-и преобразования над i идут в порядке следования по номерам pi, соответственно, циклов здесь нету, в силу иррациональности pi.
Ах, да, самое прелестное - что в момент компиляции этого выражения сами номерные ф-и еще не определены, по-этому компилятор не может их проанализировать :(
Не делает. Знаешь, я могу понять, когда люди прост врут, но какой смысл врать глупо - когда тебя любой сразу уличит во лжи? Перестань так делать.
Т.к. printf этого не сделает сама.
Кто тебе сказал, что не сделает?
разворот рекурсии в цикл? Да легко: call на саму себя это и есть аналог jmp на саму себя, если допустить бесконечность стека. И любая рекурсия это тоже цикл, только в собственном локальном контексте.
Нет, ты не понял. Я хочу, чтобы ты мне предъявил алгоритм, который берет произвольную портянку кода с гото и преобразует это дело в циклы.
Трансляторы не зацикливаются. И да, в этих вопросах ты точно теоретик-кун :) Ну то есть ты даже не представляешь как циклы компилируются на уровне манипуляций которые компилятор производит — апостериори только смотришь на вход и выхлоп. Посмотри хотя бы http://llvm.org/docs/tutorial/LangImpl5.html#for-loop-expression.
Сегодня таких интерпретаторов не бывает. Сейчас интерпретатор как и компилятор обрабатывает _весь_ цикл, а уж потом его исполняет, когда полностью до конца распарсит.
Ты путаешь, но подожди — я тебе ниже вопрос задам.
транслятор ... Если это тупой интерпретатор
Книжек не читай @ свои определения придумывай :/
Т.е. компилятор сначала просматривает цикл, а потом его переводит в машкод, и его записывает на диск. Интерпретатор делает также, однако этот код никуда не пишет, а просто выполняет.
Ну, допустим, компилятор переводит — можно и даже нужно результат сохранять, так как это и есть назначение компилятора. Интерпретатор — нет, какое нафиг «код никуда не пишет», если у него нет никакого кода, только выполнение (а это назначение интерпретатора).
это особенности реализации. Команда HLT вообще не имеет отношения к алгоритмам.
ЩИТО? L ::= (PLS | DBL | JMP Number | HLT)* — грамматика языка в вопросе, изволь интерпретировать HLT как следует и компилировать так же.
это верно только для тупых интерпретаторов, которые выполняют по одной команде за один свой цикл.
Это верно всегда.
это тоже неверно. Например bash откажется выполнять цикл, в котором нет done. Даже не начнёт. Что доказывает тот факт, что bash сначала транслирует цикл, а уже потом его выполняет. Как и компилятор.
Это тоже верно, кроме одного слова «один», тут имелся в виду мой конкретный пример (у него один проход, вообще — не обязательно).
И ты опять люто путаешь (про bash), смотри ниже вопрос.
я только про них и говорю, а не про какие-то лютые абстракции, как ты.
Тогда давай вопрос тебе:
У реализаций .NET, JVM и любых современных (продвинутых!) интерпретаторов обычно есть две трансляции и две интерпретации, то есть четыре _разных_ вещи, назови их.
Сегодняшние реальные™ компиляторы сначала транслируют код, а потом его уже исполняют.
Компиляторы типа GCC транслируют код, ничего они потом его не выполняют.
А не «по одной команде», как ты почему-то считаешь
С чего ты взял вообще что я как-то «считаю»? Я привожу простейший пример и про него говорю (потому что ты даже тут не понимаешь). Тут нечего считать. И это
что, попроще никак не придумать?
Чуть посложней пример, но ты не доволен. Что конкретно не понятно? Интерпретатор типизированного лямбда-исчисления с числами и арифметикой написанный на языке в котором есть функции, может даже на C++ можно один в один переписать. И как его расширить до компилятора с аллокацией регистров и оптимизациями выражений?
(видимо древних книжек начитался)
Я дал три ссылки на книги про компиляторы — почему они древние и какие нужно читать вместо, в студию.
я только про них и говорю, а не про какие-то лютые абстракции, как ты. Сегодняшние реальные™ компиляторы сначала транслируют код, а потом его уже исполняют.
Нет, это не так для любого языка с евалом. Ладно там всякие лиспы, пехепе с джаваскриптом для тебя тоже не существует?
они тоже сначала транслируют(циклы например), а потом уже исполняют.
Проблема в том, что есть программа, которая интерпретируется, но не компилируется (нельзя привести ее к конечному машинному коду). Конечно же, если ты опишешь способ такого приведения (или хотя бы приведешь пример интерпретатора, который это делает), то проблема будет снята.
в данном случае у тебя в рекурсивном коде меняется только одна, как ты говоришь «константа». Потому приведение тривиально: необходимо и достаточно использовать эту «константу» как аргумент функции. Т.е. код получается:
в данном случае у тебя в рекурсивном коде меняется только одна, как ты говоришь «константа». Потому приведение тривиально: необходимо и достаточно использовать эту «константу» как аргумент функции.
Это замечательно но мне более интерсен последний пример - где в ациклическом порядке к i применяются еще не определенные на момент компиляции цикла функции.
_ни один_ интерпретатор не осилит понять, что это цикл.
это самое обычное TCO. AFAIK скала его по стандарту обязана понимать. А gcc не обязан по стандарту, но всё равно делает (если оно даёт профиит конечно).
Первоклассник - свернет. А вот компилятор/интерпретатор - не свернет, т.к. это алгоритмически неразрешимая задача. Такие дела.
она «неразрешима» только в МТ на бесконечной ленте. В конечном случае — разрешима всегда.
Для того, чтобы было проще, видоизменим несколько задачу. Пусть у нас ест ф-я pi-n, которая принимает i и возвращает i-й знак десятичного представления числа пи.
вот это и есть то, что я называю «тупость скобочников-борщеедов». НЕТ такой функции, и быть не может, ибо число π имеет бесконечное число знаков, а у нас нет (и не может быть) бесконечных машин.
Ах, да, самое прелестное - что в момент компиляции этого выражения сами номерные ф-и еще не определены, по-этому компилятор не может их проанализировать :(
потому что само число π точно не определено. Ни в какой момент. Ты можешь только конечный знак узнать за конечное время.
Не делает. Знаешь, я могу понять, когда люди прост врут, но какой смысл врать глупо - когда тебя любой сразу уличит во лжи? Перестань так делать.
дык возьми и напиши цикл без done, и заставь его выполняться. И сразу увидишь, что это правда, и ни одной итерации выполнено не будет. Bash скажет «нету done, это не цикл, а говно какое-то»
Т.к. printf этого не сделает сама.
Кто тебе сказал, что не сделает?
это стандартные соглашения вызова функций. Делает pop тот, кто сделал push. Мне ещё раз продемонстрировать машинный код?
Нет, ты не понял. Я хочу, чтобы ты мне предъявил алгоритм, который берет произвольную портянку кода с гото и преобразует это дело в циклы.
почему именно «портянку», а не исходник, из которого эта портянка получилась?
При чем тут TCO? Тебя видимо, смутило, что там луп вызывается? Ну перепиши без лупа с for, это несущественно.
она «неразрешима» только в МТ на бесконечной ленте. В конечном случае — разрешима всегда.
В бесконечной она неразрешима. Если ограничить размер ленты конечным числом - тоже неразрешима (т.к. при размере задачи n тебе потребуется памяти больше n, а ведь размер ленты ограничен n). Но даже если предположить (чисто умозрительно), что она разрешима предложенным тобой способом, то мы с уверенностью можем утверждать, что НИ ОДИН из существующих интерпретаторов эту задачу не решает. Ведь такой интерпретатор будет выжирать аохуилибайты памяти и будет работать годами.
НЕТ такой функции, и быть не может
Конечно, есть. Но, см. выше - даже если мы предположим, что нету, и ты будешь хуячить свой хитрый перебор - то ничего не меняется. Ведь мы точно знаем, что реальные интерпретаторы ТАК НЕ ДЕЛАЮТ.
Ты можешь только конечный знак узнать за конечное время.
А мне ничего другого и не надо. Только узнавать конечный знак за конечное время. На каждой итерации. И никаких проблем.
Она потенциально бесконечна. В каждый же момент времени у нас есть только конечный ее кусок и только с этим куском может работать машина тьюринга. То есть состояние МТ всегда конечно. По определению МТ.
Поскольку формальыне определения для тебя сложны и ты предпочитаешь определения на пальцах и аналогии - можешь считать, что у нас есть конечная лента, к которой мы подклеиваем конечные же куски, когда лента вдруг заканчивается.
дык возьми и напиши цикл без done, и заставь его выполняться. И сразу увидишь, что это правда, и ни одной итерации выполнено не будет. Bash скажет «нету done, это не цикл, а говно какое-то»
И как это опровергает мои слова? Ну проверяет bash наличие done, и что?
Мне ещё раз продемонстрировать машинный код?
Машинный код чего? Твоих фантазий из сишки? Не надо, они не имеют отношения к разговору. Если мне надо будет для моей вм, чтобы был pop внутри функции - то он будет внутри функции.
почему именно «портянку», а не исходник, из которого эта портянка получилась?
Трансляторы не зацикливаются. И да, в этих вопросах ты точно теоретик-кун :)
зацикливается. Например bash зацикливается на done, который передаёт управление на заголовок цикла. Если done не будет, то bash ни единой итерации не сделает.
Т.е. bash сначала транслирует цикл, а уже потом его выполняет. А не на оборот. Поэтому существует момент, когда цикл полностью и однозначно определён. Вот именно в этот момент его и нужно переводить в машинный код.
Интерпретатор — нет, какое нафиг «код никуда не пишет», если у него нет никакого кода, только выполнение (а это назначение интерпретатора).
«выполнение» это и есть машинный код. Ничего другого выполнять нельзя по определению.
ЩИТО? L ::= (PLS | DBL | JMP Number | HLT)* — грамматика языка в вопросе, изволь интерпретировать HLT как следует и компилировать так же.
HLT не относится к алгоритму, а относится к твоей реализации. Это «конечная точка из которой нет выхода». Дели на ноль сам.
У реализаций .NET, JVM и любых современных (продвинутых!) интерпретаторов обычно есть две трансляции и две интерпретации, то есть четыре _разных_ вещи, назови их.
я на собеседовании? Тогда встречный вопрос к тебе: какого цвета мой холодильник? Не знаешь? Следующий!
Компиляторы типа GCC транслируют код, ничего они потом его не выполняют.
интерпретаторы сами тоже ничего не выполняюют, а делегируют процессору выполнение(или LLVM).
Это замечательно но мне более интерсен последний пример - где в ациклическом порядке к i применяются еще не определенные на момент компиляции цикла функции.
а вот для такого надо тащить за собой компилятор, который и будет переводить в код функции тогда, когда они определяться.
вот как раз так оно и работает в итоге в данном конкретном случае. Выше я приводил пруф, с машинным кодом, в котором ты и можешь видеть этот цикл, из факториала. Тебе отмотать?
В бесконечной она неразрешима. Если ограничить размер ленты конечным числом - тоже неразрешима (т.к. при размере задачи n тебе потребуется памяти больше n, а ведь размер ленты ограничен n).
ты бредишь. В таком случае я «2+2=» не смогу вычислить, ибо «на этой ленте нет места для 4».
что НИ ОДИН из существующих интерпретаторов эту задачу не решает. Ведь такой интерпретатор будет выжирать аохуилибайты памяти и будет работать годами.
ну если пользоваться знаниями почерпнутыми из книжек 70х — да.
А мне ничего другого и не надо. Только узнавать конечный знак за конечное время. На каждой итерации. И никаких проблем.
и в чём твоя проблема? 10 знаков, 10 кодов. Запиши свои 10 кодов и получи желаемое.
ты бредишь. В таком случае я «2+2=» не смогу вычислить, ибо «на этой ленте нет места для 4».
Именно так. Если память ограничена, то сложение не будет работать - ведь мы можем сложить такие числа, что выйдет за пределы памяти, которая у нас есть. Но это не я предложил считать память конечной, а ты.
ну если пользоваться знаниями почерпнутыми из книжек 70х — да.
Интересно. Получается, абсолютно все люди, которые пишут интерпретаторы, пользуются знаниями почерпнутыми из книжек 70-х? Ведь мы почему-то не наблюдаем интерпретаторов работающих годы и требующих эксабайты оперативной памяти.
и в чём твоя проблема? 10 знаков, 10 кодов. Запиши свои 10 кодов и получи желаемое.
Так после десятого знака будет 11 знак. Потом 12. Потом 13. И так далее.
Она потенциально бесконечна. В каждый же момент времени у нас есть только конечный ее кусок и только с этим куском может работать машина тьюринга. То есть состояние МТ всегда конечно. По определению МТ.
Поскольку формальыне определения для тебя сложны и ты предпочитаешь определения на пальцах и аналогии - можешь считать, что у нас есть конечная лента, к которой мы подклеиваем конечные же куски, когда лента вдруг заканчивается.
плохо у тебя. Если у нас есть лента длинной l, мы для решения задачи можем приклеить к ней конечный кусок n, и решить задачу. Ну и пофиг, что n=2^l. Да хоть l!.
Если у нас есть лента длинной l, мы для решения задачи можем приклеить к ней конечный кусок n, и решить задачу. Ну и пофиг, что n=2^l. Да хоть l!.
Именно так. Лента конечна в каждый момент времени, но бесконечна потенциально (мы можем подклеивать куски). И в таком предположении некоторые задачи - неразрешимы. Проблема останова, например.
Если ты не заметил, то общепринятая терминология это языковой процессор — транслятор ИЛИ интерпретатор, интерпретатор — НЕ транслятор, трансляторы — компиляторы, декомпиляторы, ассемблеры, дисассемблеры и т.д.
Но ладно — считай, что во всех моих постах «транслятор» это «компилятор». Компиляторы не зацикливаются (если не брать разное суровое метапрограммирование). Интерпретаторы — зацикливаются, вполне.
«выполнение» это и есть машинный код. Ничего другого выполнять нельзя по определению.
У тебя машкод головного мозга, надо как-то лечить :)
Любая VM выполняет свои программы сама, без всяких машкодов. Как она это умудряется делать? Ну она написана и скомпилирована в твой машкод, работает и выполняет свои программы на своём уровне абстракции, сам машкод к этим программам не имеет отношения, но они вполне выполняются. Не имеет отношения — VM работает на любой архитектуре, железной реализации, ты можешь хоть на бумажке её выполнять, никаких интелов.
Ну и жаль, что ты не знаешь математического смысла слов «интерпретация» и «трансляция» (машкод, ага).
HLT не относится к алгоритму, а относится к твоей реализации. Это «конечная точка из которой нет выхода». Дели на ноль сам.
Мы написали интерпретатор и компилятор языка в котором есть HTL, вместо с DBL, PLS и JMP — четыре инструкции. Конечно, они имеют отношение самое прямое.
Но можно обобщить тезис — интерпретаторы зацикливаются (как в том — JMP зацикливает, HLT прерывает), трансляторы — нет (в компиляторе того примера JMP ничего не зацикливает, HLT ничего не прерывает).
я на собеседовании? Тогда встречный вопрос к тебе: какого цвета мой холодильник? Не знаешь? Следующий!
Так не получится. Трудно сказать четыре коротких фразы? То есть у тебя из каждого предложения торчит нечто, поэтому мне лень комментировать, я хочу ответа на наводящий вопрос.
интерпретаторы сами тоже ничего не выполняюют, а делегируют процессору выполнение
Вот опять. Ответь на вопрос про четыре вещи.
И ты сказал, что компиляторы что-то там выполняют, то есть ты имел в виду, что МЫ выполняем их выхлоп на CPU? Это ок. Но сами они ничего не выполняют.
а то, что когда bash увидит done, цикл будет полностью определён, а значит будет возможность его компиляции. Сделать «бесконечную портянку» у тебя не получится, т.к. цикл конечен и определён однозначно. Только на твоей ракете такое говно получается.
а то, что когда bash увидит done, цикл будет полностью определён, а значит будет возможность его компиляции.
С чего ты взял? Из того, что баш проверяет наличие done совершенно никак возможность трансляции не следует. Racket, например, проверяет связанность переменных внутри модуля, и что?
Сделать «бесконечную портянку» у тебя не получится, т.к. цикл конечен и определён однозначно.
Конечно, получится, евал же в баше есть. Можно сделать такой же пример, как в Racket, и будет то же самое.