LINUX.ORG.RU
Ответ на: комментарий от monk

Является ли транслятором команда touch ? Входные данные есть (имя файла), выполнение бинарного кода тоже...

при чём тут «команда touch»

Ещё раз, для тех, кто в танке: ты знаешь такую профессию «переводчик»? Ну вот компилятор/интерпретатор и есть «переводчик».

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

Ну а с какого на какой язык — значения не имеет. Вот gcc это вообще «полиглот». Кстати, с ключом -S gcc выплёвывает не бинарник, а текстовый файл.

Вот тебе hello world

	.file	"helloworld.c"
	.section	.rodata
.LC0:
	.string	"hello world"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$16, %esp
	movl	$.LC0, (%esp)
	call	puts
	movl	$0, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (GNU) 4.8.2"
	.section	.note.GNU-stack,"",@progbits

это C → IA-32(x86). Только коммпилятор схалявничал, и не доделал до конца. Обычно он этот код в двоичном виде делает:

вот первые байты:

00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 03 00 01 00 00 00  f0 82 04 08 34 00 00 00  |............4...|
00000020  34 0e 00 00 00 00 00 00  34 00 20 00 08 00 28 00  |4.......4. ...(.|
00000030  23 00 20 00 06 00 00 00  34 00 00 00 34 80 04 08  |#. .....4...4...|
00000040  34 80 04 08 00 01 00 00  00 01 00 00 05 00 00 00  |4...............|
00000050  04 00 00 00 03 00 00 00  34 01 00 00 34 81 04 08  |........4...4...|
00000060  34 81 04 08 13 00 00 00  13 00 00 00 04 00 00 00  |4...............|
00000070  01 00 00 00 01 00 00 00  00 00 00 00 00 80 04 08  |................|
00000080  00 80 04 08 08 06 00 00  08 06 00 00 05 00 00 00  |................|
00000090  00 10 00 00 01 00 00 00  08 06 00 00 08 96 04 08  |................|
000000a0  08 96 04 08 00 01 00 00  08 01 00 00 06 00 00 00  |................|
000000b0  00 10 00 00 02 00 00 00  1c 06 00 00 1c 96 04 08  |................|
000000c0  1c 96 04 08 c8 00 00 00  c8 00 00 00 06 00 00 00  |................|
000000d0  04 00 00 00 04 00 00 00  48 01 00 00 48 81 04 08  |........H...H...|
000000e0  48 81 04 08 20 00 00 00  20 00 00 00 04 00 00 00  |H... ... .......|
000000f0  04 00 00 00 50 e5 74 64  2c 05 00 00 2c 85 04 08  |....P.td,...,...|

emulek
()
Ответ на: комментарий от emulek

другие синхронно переводят живую речь (интерпретаторы).

Но sh (а также старый бейсик) никуда входной файл не переводят, а исполняют по мере чтения.

А если мы считаем, что выполнение — подвид «перевода», тогда вопрос про команду touch (а также ftp, mail, cp) остаётся открытым. Входные данные есть, действия есть.

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

Но sh (а также старый бейсик) никуда входной файл не переводят, а исполняют по мере чтения.

процессор не умеет исполнять бэйсик и sh, потому его надо переводить в ассемблер. А уже потом исполнять.

А если мы считаем, что выполнение — подвид «перевода», тогда вопрос про команду touch (а также ftp, mail, cp) остаётся открытым. Входные данные есть, действия есть.

есть действие. Это выполнение кода из файла /usr/bin/touch.

Оболочка парсит входной текст, видит там непонятное(ей) слово «touch», и потому ищет в путях $PATH такой файл. Если-бы это было «echo», а оболочкой /bin/bash, то она-бы сама «перевела» echo в код.

emulek
()
Ответ на: комментарий от d_Artagnan

я уже писал ниже: ассемблер взаимооднозначен машинному коду.

emulek
()
Ответ на: комментарий от emulek

Ещё раз, для тех, кто в танке: ты знаешь такую профессию «переводчик»? Ну вот компилятор/интерпретатор и есть «переводчик».

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

Так вот транслятор - это переводчик. Компилятор - это транслятор (то есть переводчик). Он переводи текст с ЯП на машкод.

А есть такая профессия - «исполнитель». Это товарищ, который берет текст на некотором языке (на английском) и исполняет этот текст. При этом он его не переводит. Он вообще никаких других языков не знает (кроме английского) и по-этому перевести не может - в принципе. Потому и не переводит. Так вот интерпретатор - это исполнитель. Он не переводчик. И не транслятор, соответственно.

anonymous
()
Ответ на: комментарий от anonymous

По-ходу, это ты еблан. Никакого исполнения нет, это удобная сказочка для тех, кто не в состоянии понять, что кроме трансляции ничего нет. Твое «исполнение» - это процесс трансляции. Надо же для недоумков что-то говорить, чтоб они хоть что-то могли написать.

anonymous
()
Ответ на: комментарий от anonymous

Да-да, даже небо, даже аллах - все суть «исполнение».

anonymous
()
Ответ на: комментарий от anonymous

Никакого исполнения нет, это удобная сказочка для тех, кто не в состоянии понять, что кроме трансляции ничего нет.

Хаскелл головного мозга? «Мы не изменяем Мир, а транслируем его старое состояние в новое»?

monk ★★★★★
()
Ответ на: комментарий от monk

Хаскелл головного мозга? «Мы не изменяем Мир, а транслируем его старое состояние в новое»?

Строго наоборот. Нет никаких состояний, есть только процесс перезаписи. Я бы сказал, абсолютный нондетерменизм. Хаскель, наоборот запарен на глобальном состоянии.

anonymous
()
Ответ на: комментарий от anonymous

Хаскель, наоборот запарен на глобальном состоянии.

Это где такое? Апологеты на ЛОРе наоборот говорят, что состояний у объектов нет, есть только функции. В качестве примера было «мы не красим холодильник в зелёный цвет, а определяем функцию, которая для холодильника возвращает такой же, но зелёный».

Нет никаких состояний, есть только процесс перезаписи. Я бы сказал, абсолютный нондетерменизм.

То есть процесс перезаписи никак не зависит от состояния на момент начала перезаписи (потому как состояния нет). Сильно!

monk ★★★★★
()
Ответ на: комментарий от monk

То есть процесс перезаписи никак не зависит от состояния на момент начала перезаписи

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

anonymous
()
Ответ на: комментарий от monk

есть только функции

В хашкеле ф-ции — это замыкания, они эквивалентны объектам в классовом ООП. Что они там себе мнят — это их проблемы.

anonymous
()
Ответ на: комментарий от monk

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

ИМХО, это эквивалентно присваиванию, при условии, что мы будем обращаться к «зеленому холодильнику» только через эту ф-цию. Семантически эквивалентно, я имею в виду. Это, видимо то, что называется монадами у них?

phill
()
Ответ на: комментарий от anonymous

Хм, но тут вопрос. Ведь перед тем как выполнить программу ее нужно привести к соответствующему машинному представлению. Это разве не трансляция? Где ошибка?

Twissel ★★★★★
()
Ответ на: комментарий от Twissel

Хм, но тут вопрос. Ведь перед тем как выполнить программу ее нужно привести к соответствующему машинному представлению.

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

anonymous
()
Ответ на: комментарий от anonymous

Хорошо, раз уж я ввязался в этот тред, тогда продолжим. В конечном счете, ведь тот же интерпретатор выполняется на конкретном ЦП, а не в абстрактном математическом вакууме, ведь так? Что тогда представляют из себя команды интерпретатора с точки зрения центрального процессора ПК ?!!

А именно есть ли такое преобразование, которое переводит систему команд интерпретатора в систему команд CPU?

Вот в чем мой вопрос.

Twissel ★★★★★
()
Последнее исправление: Twissel (всего исправлений: 1)
Ответ на: комментарий от anonymous

иногда такое приведение в принципе невозможно (нельзя сопоставить тексту интерпретируемой программы какой-либо машинный код)

ты бредишь: если нет, то как же этот код _исполняется_? Может ты хотел сказать «нельзя сопоставить заранее»? Ну да, такое есть. Ну и что?

PS: вот только что написал калькулятор, прям как в K&R. Там тоже интерпретация выражения вроде 2+2*2=?, выдаёт ессно 6. Да, какой именно код сопоставляется — от выражения зависит, и тем не менее, это всё на C++.

emulek
()
Ответ на: комментарий от emulek

как же этот код _исполняется_?

Исполняется код интерпретатора — он один на все интерпретируемые программы, которым ничего не соответствует кроме такого-то способа их исполнения этим интерпретатором.

Ну и что?

Ну и это интерпретатор, потому что если было бы сопоставление, был бы уже компилятор.

только что написал калькулятор

Теперь сделай функцию дампа своего машинного кода который «сопоставляется» в файл для любых выражений. Хотя погоди... тебе же придётся выкинуть свой интерпретатор и написать компилятор :)

quasimoto ★★★★
()
Ответ на: комментарий от Twissel

Что тогда представляют из себя команды интерпретатора с точки зрения центрального процессора ПК ?!!

Ничего не представляют.

А именно есть ли такое преобразование, которое переводит систему команд интерпретатора в систему команд CPU?

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

Самый очевидный пример - языки с фекспрами. Они не могут быть компилированы. То есть вообще никак, даже теоретически.

anonymous
()
Ответ на: комментарий от emulek

Может ты хотел сказать «нельзя сопоставить заранее»? Ну да, такое есть. Ну и что?

Ну и все. Нельзя данной интерпретируемой программе сопоставить конкретную последовательность команд. То есть перевода просто не существует.

если нет, то как же этот код _исполняется_?

Берет и исполняется. Для этого, к счастью, трансляция не требуется.

anonymous
()
Ответ на: комментарий от Twissel

А именно есть ли такое преобразование, которое переводит систему команд интерпретатора в систему команд CPU?

Интерпретатор принимает выражения своего языка, какой-то внешний вход и производит исполнение с эффектами, возвращает выход. То есть представляет из себя функцию/программу написанную на языке своей реализации — при компиляции она вполне может быть скомпилирована в такой-то код, как и всякая функция. Делать подобное выполнение это всё что он должен — остальное это детали реализации. А если под его системой команд понимаются не они, а выражение его языка, то им не соответствует кода — код один, это код интерпретатора, его входным выражениям соответствуют конкретные пути его исполнения.

Ещё см. Чем отличается компилятор от интерпретатора? (комментарий) — мы можем на любую функцию смотреть как на интерпретатор (то же исполнение). Например, strlen (максимально упрощённый случай) притворяется функцией возвращающей 5 если на вход подаётся строка длинной в пять символов, 7 — семь символов и т.д. Какой код CPU соответствует strlen? Какой код CPU соответствует строке в пять символов?

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Исполняется код интерпретатора — он один на все интерпретируемые программы, которым ничего не соответствует кроме такого-то способа их исполнения этим интерпретатором.

да говорю же  — ты бредишь. Выполни $ while true; do true; done. Выполнил? И чем у тебя CPU загружен?

Ну и это интерпретатор, потому что если было бы сопоставление, был бы уже компилятор.

дык есть сопоставление. В любом интерпретаторе.

Теперь сделай функцию дампа своего машинного кода который «сопоставляется» в файл для любых выражений. Хотя погоди... тебе же придётся выкинуть свой интерпретатор и написать компилятор :)

чего «годить»? Могу и сопоставить. Каждая строка C++ кода порождает вполне конкретный машинный код. Если его сохранить, получится компилятор. Для каждого выражения получается свой код.

emulek
()
Ответ на: комментарий от anonymous

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

в таком случае, выполнить её на IA-32/amd64 тоже невозможно.

Самый очевидный пример - языки с фекспрами. Они не могут быть компилированы. То есть вообще никак, даже теоретически.

да, конечно.

emulek
()
Ответ на: комментарий от anonymous

Может ты хотел сказать «нельзя сопоставить заранее»? Ну да, такое есть. Ну и что?

Ну и все. Нельзя данной интерпретируемой программе сопоставить конкретную последовательность команд. То есть перевода просто не существует.

с какого перепугу перевод обязан быть однозначным? Ещё раз, для тех кто в танке: обычный цикл выполняется неизвестно сколько раз. Рекурсия выполняется неизвестно сколько раз. Вот у меня например для выражения 1+2+3 две команды add, а для 1+2+3+4 четыре команды add. И что? Код один.

если нет, то как же этот код _исполняется_?

Берет и исполняется

ага. Благодатный огонь снизошёл.

emulek
()
Ответ на: комментарий от anonymous

то есть перевести в какой-то программный код.

машинный код selffix

Дзен-программа с фекспрами.

tailgunner ★★★★★
()
Ответ на: комментарий от emulek

Выполнил? И чем у тебя CPU загружен?

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

дык есть сопоставление.

Есть код интерпретатора, один. Интерпретируемые выражения соответствуют путям исполнения этого кода, одного и того же.

А в случае компилятора — свой код для каждой программы.

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

Могу и сопоставить.

Ну выкатывай код.

Каждая строка C++ кода порождает вполне конкретный машинный код.

Так ты калькулятор или интерпретатор C++ написал?

quasimoto ★★★★
()
Ответ на: комментарий от emulek

Это код интерпретатора. Теперь показывай код CPU в виде инструкций соответствующий его интерпретируемым программам, то есть строкам — «12345», например (5 при интерпретации).

quasimoto ★★★★
()
Ответ на: комментарий от anonymous

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

Twissel ★★★★★
()
Ответ на: комментарий от emulek

ага. Благодатный огонь снизошёл.

Аналогия близка к действительности. Без пруфов, дальнейшая дискуссия бессмысленна

Twissel ★★★★★
()
Ответ на: комментарий от quasimoto

которым ничего не соответствует кроме такого-то способа их исполнения этим интерпретатором.

Ну здесь мы все будем единодушны им соответствует их собственный исходный код.

Мне бы такое зрение увидеть никого, да еще на таком расстоянии (с)

Белый Король. Алиса в Зазеркалье

Twissel ★★★★★
()
Ответ на: комментарий от Twissel

То есть «SELECT foo FROM bar WHERE baz» соответствует не что иное, как «SELECT foo FROM bar WHERE baz»?

Это прямо архи полезно для знания семантики этого языка :)

Либо ей соответствует интерпретатор, точнее один из способов его выполнения, http://en.wikipedia.org/wiki/Operational_semantics, со своим смыслом, http://en.wikipedia.org/wiki/Denotational_semantics.

Или другая программа с эквивалентной семантикой, http://homepage.cs.uri.edu/faculty/hamel/courses/2011/fall2011/csc501/lecture..., http://homepage.cs.uiowa.edu/~slonnegr/plf/Book/Chapter7.pdf, и своим смыслом.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

В том то и подмена понятий, что SQL формальный язык запросов, а Си императивный и для него верно утверждение, что на данной аппаратной архитектуре, при заданной программной конфигурации в некий момент времени есть взамное и однозначное представление исходного текста на Си в Ассемблерный код данной архитектуры.

Так что приплетать сюда исчисление кортежей, как аргумент, несколько излишне.

Twissel ★★★★★
()
Ответ на: комментарий от Twissel

Нет подмены понятий, есть понятия языков, грамматик (это к синтаксису), трансляторов, интерпретаторов (это к соответствующим семантикам), это общие понятия. SQL — пример (никаких исчислений кортежей не подразумевалось), Си — пример, как и любые другие языки, будь то DSL или ЯП общего назначения.

И вообще я уже не знаю что мы обсуждаем.

Для си есть трансляция в машинный код? Ну да, поэтому существует и практикуется компиляция (или наоборот :)). Взаимно-однозначное? Нет, поэтому декомпиляция это гораздо более сложная задача и практикуется обратная разработка как поиск её решения. Каким боком это к теме?

Ещё раз ссылки, по мелочи:

http://en.wikipedia.org/wiki/Formal_language

http://en.wikipedia.org/wiki/Formal_grammar

http://en.wikipedia.org/wiki/Interpreter_(computing)

http://en.wikipedia.org/wiki/Translator_(computing) (предложение про интерпретатор проясняется дальше)

http://en.wikipedia.org/wiki/Operational_semantics

http://en.wikipedia.org/wiki/Denotational_semantics

Точнее (загугли pdf-ки):

http://www.amazon.com/Compilers-Principles-Techniques-Tools-Edition/dp/032148...

Остальные книжки про компиляторы больше по делу и без общих мест, но в том же духе — интерпретация и трансляция это две разные вещи.

Более новые, там так же, я выше по треду цитировал:

http://www.amazon.com/Concepts-Techniques-Models-Computer-Programming/dp/0262...

http://www.amazon.com/Programming-Language-Pragmatics-Third-Edition/dp/012374... (конкретно тут проясняется про интерпретатор в смысле википедии)

http://www.amazon.com/Design-Concepts-Programming-Languages-Franklyn/dp/02622...

Ещё:

http://homepage.cs.uiowa.edu/~slonnegr/plf/Book/Chapter7.pdf

http://homepage.cs.uri.edu/faculty/hamel/courses/2011/fall2011/csc501/lecture...

Такая терминология — способ прицепить слова к значениям. Можно не соглашаться и использовать альтернативную, конечно, но тогда тоже хочется ссылок.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

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

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

Есть код интерпретатора, один. Интерпретируемые выражения соответствуют путям исполнения этого кода, одного и того же. А в случае компилятора — свой код для каждой программы.

x+y всегда транслируется компилятором в команду add, и интерпретатором тоже(если без оптимизации конечно). Не вижу разницы в упор.

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

всё там прекрасно вытаскивается.

Ну выкатывай код.

не понимаю, код чего?

Ну возьми gdb и посмотри.

Так ты калькулятор или интерпретатор C++ написал?

калькулятор, который транслирует ЯП арифметических выражений(а это тоже алгоритмический ЯП) в машинный код, и его сразу выполняет. При чём тут C++? Кто вообще сказал, что программа на C++ должна обязательно компилироваться в C++, а уж потом в ассемблер? Мой калькулятор вообще никакого отношения к C++ не имеет, у него на входе арифметическое выражение, а на выходе машинный код, который прямо сразу и выполняется. И сам он на машинном коде, а на C++ только его исходник, который можно на что угодно переписать.

И да, скобки мой калькулятор умеет, а следовательно там есть «мощный eval». Как вы и заказывали.

emulek
()
Ответ на: комментарий от quasimoto

Это код интерпретатора. Теперь показывай код CPU в виде инструкций соответствующий его интерпретируемым программам, то есть строкам — «12345», например (5 при интерпретации).

это машинный код. Для любых ASCIIZ строк(ну в смысле <2³¹). Для «12345» оно тоже работает(правда там 6 итераций).

emulek
()
Ответ на: комментарий от quasimoto

Для си есть трансляция в машинный код? Ну да, поэтому существует и практикуется компиляция (или наоборот :)). Взаимно-однозначное? Нет, поэтому декомпиляция это гораздо более сложная задача

декомпиляция очень простая задача на самом деле. Вот только код после декомпиляции невозможно читать людям. Но это не относится к нашему спору.

И да, именно взаимно-однозначное существует. Просто людям неудобно им пользоваться. Мне проще писать ⅔x, а компилятор переделывает это в x*0xAAAAAAAA. Также мне нравится использовать все три типа циклов (for, while, do{}while), но компилятор юзает только последний тип. Ну а ещё компилятор через строку пихает goto, и прочую гадость. А мне так читать неудобно.

Теоретически возможно и действительно взаимо-однозначное, вот только _моё_ понятие «удобочитаемости» абсолютно не формализуется. Потому при двойном переводе получается нечитаемый говнокод. За то этот говнокод

1. эквивалентен моему коду

2. взаимо-однозначен

ссылки

ты путаешь подходы и определения. Формально компилятор и интерпретатор ничем не отличается, однако для построения удобного и годного DSL важно учитывать специфику. Т.е. в принципе можно запихать замкнутый eval в C→C, вот только оно нафиг никому не нужно. Сам ЯП заточен не компиляцию в машинный код. Также и введение оптимизируещего компилятора в какой-нить PHP тоже ничего не даст и не нужно. Компилятор всё равно не сможет оптимизировать утиную типизацию, которая определиться только в рантайме. Вот и будет на массив char'ов в 10 штук, хранить и пережёвывать 20 указателей в 8 char'ов каждый. Смысл?

emulek
()
Ответ на: комментарий от emulek

цикл транслируется в команды CPU, которые и выполняются.

Цикл у тебя сразу выполняется в случае pure interpretation. Команды CPU у тебя уже тут, в коде откомпилированного интерпретатора, его бинарника, он берёт твой текст/AST и делает своё дело как любая другая функция — нечего ему транслировать, так как выходного языка не предполагается.

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

Ну если взять интерпретатор, выкинуть интерпретатор и запилить компилятор, то получится компилятор вместо интерпретатора, да.

x+y всегда транслируется компилятором в команду add, и интерпретатором тоже(если без оптимизации конечно).

Да ладно, во что хочет, в то и транслируется, как хочет, так и интерпретирует, то что в C++ _+_ не имеет отношения к тому что в реализуемом языке _+_ (gmp_add или matrix_add, например) — поскипано во имя перегрузки.

Не вижу разницы в упор.

Ты просто привязался к своему «интерпретатор транслирует свои программы в инструкции CPU и выполняет их», хотя привязываться не нужно — нужно коммуницировать :)

То есть ты говоришь, что

void f(int n) {
    ...
    if (n == 2) {
        ... block ...
    }
}

транслирует (во время выполнения, чо) вход 2 в CPU инструкции block. Они полученные уже, правда, заранее компиляцией этого кода, да и не работает это — сделай block не локальным, но нетривиальным control flow и непонятно где твоё соответствие (в формальном смысле этого слова, то есть отображения из язык в язык, а то может ты «примерно» говоришь).

всё там прекрасно вытаскивается.

Ну возьми gdb и посмотри.

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

При чём тут C++?

Ты говоришь

Каждая строка C++ кода порождает вполне конкретный машинный код.

А тебе надо, чтобы каждое арифметическое выражение транслировалось в машинный код (раз всё вытаскивается). Т.е. я тебе даю, например, (2+3)*5 — твой велосипед мне кидает кусок ассемблера/машинного кода, а если ты интерпретируешь, то нечего рассказывать, ты путаешь код самого интерпретатора с кодом который бы соответствовал (2+3)*5 при компиляции.

Мой калькулятор вообще никакого отношения к C++ не имеет, у него на входе арифметическое выражение, а на выходе машинный код, который прямо сразу и выполняется.

Да, я уже понял что ты пишешь, «у меня на входе strlen строки, а на выходе машинный код [который ты в виде *.s уже запостил], который прямо сразу и выполняется».

это машинный код. Для любых ASCIIZ строк

Ну то есть транслятора из строк в CPU код у тебя нет, я и говорю, что strlen это вырожденный пример интерпретатора (и транслятора, если говорить о трансляции в чистые значения, то есть о constant folding).

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от emulek

Но это не относится к нашему спору.

Это относится к тому, что compile/decompile не «взаимно-однозначны» — это сложно и невозможно, если только на классах эквивалентности modulo сохранению семантики — это возможно и легко, но не интересно и есть вопрос о том что есть сохранение семантики.

взаимно-однозначное существует

То есть ты можешь скомпилировать программу, потом декомпилировать её обратно и получить ту же программу?

Вот только код после декомпиляции невозможно читать людям

Нет, значит не взаимно-однозначное, если тебе не пофиг на читаемость и сохранение абстракций и не нужно только «работает так же» (да и то, не факт, что компиляция с оптимизациями, декомпиляция и повторная компиляция с оптимизациями будет «так же» работать).

Формально компилятор и интерпретатор ничем не отличается

А пруф? А то пишуть альтёрнатив апроач.

Но правда, что и то и другое можно объединить под общим «processing», «обработка».

З.Ы. в математике, тащемто, интерпретация и трансляция (гомоморфизм) это примерно то же, что эти понятия по ссылкам.

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от quasimoto

Цикл у тебя сразу выполняется в случае pure interpretation. Команды CPU⁽¹ у тебя уже тут, в коде откомпилированного интерпретатора, его бинарника, он берёт твой текст/AST и делает своё дело как любая другая функция — нечего ему транслировать⁽², так как выходного языка не предполагается.

т.е. ты утверждаешь, что (1 берётся из хрустального шара или другой магией(благодатным огнём), да?

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

Ну если взять интерпретатор, выкинуть интерпретатор и запилить компилятор, то получится компилятор вместо интерпретатора, да.

выкидывать ничего не нужно. Не передёргивай мои слова.

x+y всегда транслируется компилятором в команду add, и интерпретатором тоже(если без оптимизации конечно).

Да ладно, во что хочет, в то и транслируется, как хочет, так и интерпретирует, то что в C++ _+_ не имеет отношения к тому что в реализуемом языке _+_

ЯП и его стандарт не имеет. Однако компилятор в IA-32 других вариантов не знает. Как и интерпретатор. И не приплетай сюда перегрузку, она меняет сам смысл operator+(). Речь лишь про встроенный тип int/float. Я уже говорил выше, что если у тебя «утиная» типизация, то что компилятор, что интерпретатор, могут только в рантайме решить, каким кодом складывать числа. Посему тащат за собой всё множество кодов для любых сложений.

Ты просто привязался к своему «интерпретатор транслирует свои программы в инструкции CPU и выполняет их», хотя привязываться не нужно — нужно коммуницировать :)

просто это реальность такая, чудес не бывает.

транслирует (во время выполнения, чо) вход 2 в CPU инструкции block. Они полученные уже, правда, заранее компиляцией этого кода, да и не работает это — сделай block не локальным, но нетривиальным control flow и непонятно где твоё соответствие (в формальном смысле этого слова, то есть отображения из язык в язык, а то может ты «примерно» говоришь).

всё работает. Сделай block не локальным, и код будет находится в другом месте. Будет. И с интерпретатором также.

Ну возьми gdb и посмотри.

Это не вытаскивается, это каша

это не доказательство. Выхлоп интерпретатора никто не готовил специально для тебя. Твой CPU эту кашу жрёт, и просит ещё, а что тебе не пошло — твоя проблема. Выхлоп gcc -O2 тоже не слишком просто осилить, если программа нетривиальная.

Т.е. я тебе даю, например, (2+3)*5 — твой велосипед мне кидает кусок ассемблера/машинного кода, а если ты интерпретируешь, то нечего рассказывать, ты путаешь код самого интерпретатора с кодом который бы соответствовал (2+3)*5 при компиляции.

вот «код самого интерпретатора» и есть выход. Этот код может вычислить (2+3)*5, и дать ответ 25. Т.о. данный код является трансляцией любого выражения в машинный код, даже если само выражение заранее неизвестно. Как видишь, условие известности алгоритмов не обязательно. Алгоритм можно задать во время выполнения. Т.е. можно 2+(3*5), а можно (2+3)*5. Тут числа одинаковые, но алгоритм вычисления разный. Ответ тоже разный. Но интерпретатор один единственный, хотя машинный код порождается и выполняется разный.

Да, я уже понял что ты пишешь, «у меня на входе strlen строки, а на выходе машинный код [который ты в виде *.s уже запостил], который прямо сразу и выполняется».

у тебя тоже.

Ну то есть транслятора из строк в CPU код у тебя нет, я и говорю, что strlen это вырожденный пример интерпретатора (и транслятора, если говорить о трансляции в чистые значения, то есть о constant folding).

почему нет? Ок, напишу:

if(strcpy(input_string, "strlen")==0)
    fwrite(мой_код, размер_кода, 1, выходной_файл);
осталось сделать преобразование кода x в адрес определённой ячейки памяти. И компилятор готов. А если этого не делать, а просто занести x в edx, и выполнить мой код, то получится интерпретатор.

Ну то есть транслятора из строк в CPU код у тебя нет

зачем тебе такой транслятор? Что тебе мешает хранить строки как наборы байт? Зачем тебе их транслятор?

emulek
()
Ответ на: комментарий от quasimoto

То есть ты можешь скомпилировать программу, потом декомпилировать её обратно и получить ту же программу?

я на C/C++ — нет. Но теоретический такой код возможен и такой компилятор тоже. Просто оно никому не нужно. Если я пишу ⅔, то компилятор это превращает в 0x55555556, откуда ты ⅔ уже не вытащишь однозначно. Если я пишу x*=2, компилятор пишет x<<=1. Это тоже обратно переводится в x<<=1.

Нет, значит не взаимно-однозначное, если тебе не пофиг на читаемость и сохранение абстракций и не нужно только «работает так же»

при чём тут читаемость? Возможность есть. Просто она никому не нужна. Проще исходник приложить. Потому никто и не реализовал.

да и то, не факт, что компиляция с оптимизациями, декомпиляция и повторная компиляция с оптимизациями будет «так же» работать

ну дык с оптимизацией оно и так «не так» работает. А быстрее. Т.е. это не баг, а фича. Если я пишу 1+2, компилятору плевать на однозначность, и он пишет 3, откуда естественно не вытащить 1+2. Неужели ты думаешь, что невозможно написать такой компилятор, который будет в рантайме Over9000 раз складывать 1+2? Ради вселенской однозначности...

Формально компилятор и интерпретатор ничем не отличается

А пруф?

пруф — возможность исполнения и того и того в семантике одного и того же CPU. Потому что в CPU нет «двух режимов», а есть только один — исполнение кода. Потому, хоть в лоб, хоть полбу, если написано x+y, то это надо перевести в add.

emulek
()
Ответ на: комментарий от emulek

т.е. ты утверждаешь, что (1 берётся из хрустального шара или другой магией(благодатным огнём), да?

Чем отличается компилятор от интерпретатора? (комментарий)

A.

a program written in a source language S is directly executed by an S-interpreter, which is a program written in an implementation language

⁽¹ тебе по большому счёту не нужно, так как у тебя есть S-интерпретатор, который на своём уровне абстракций выполняет свои программы без всяких CPU. Сравни, допустим, с интерфейсом к файловым системам — ты можешь работать с ним как с API не зная ничего про архитектуру, код CPU, драйвера или ОС, тебе это не интересно на этом уровне. Есть представление о семантике implementation language и ладно — дальше нам не нужно.

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

(S-terms -> S-Machine) by S-interpreter

B.

Но если хочешь — пусть S-интерпретатор написан на своём языке Inter и может быть скомпилирован в язык CPU компилятором Inter -> CPU, это будет ⁽², язык CPU может быть выполнен CPU-интерпретатором — вот это уже ⁽¹. Интерпретация это семантическая обработка, с ней связан абстрактный вычислитель, то есть в случае S-интерпретатора совсем абстрактный, в случае CPU — и конкретный тоже.

(S-terms -> S-Machine) by S-interpreter

(Inter-terms -> CPU-terms) by Inter-to-CPU compiler for this particular S-interpreter

(CPU-terms -> CPU-machine) by CPU-interpreter

C.

In the translation approach, an S program is translated to a program in the target language T, which can be executed by a T-interpreter.

Чистый интерпретатор не делает S-terms -> T-terms, он берёт S, загружает в вычислитель и запускает его — S-terms -> S-machine. То есть с одной стороны у нас трансляция — синтаксическая обработка в виде чёрного ящика который работает как функция (http://en.wikipedia.org/wiki/Natural_transformation, http://en.wikipedia.org/wiki/Homomorphism, гомоморфизмы синтаксических структур из синтаксических категорий с естественностью по отношению к модельным функторам в семантические домены, то есть вообще говоря синтаксическая трансляция учитывает сохранение семантики), то есть принимает программы (синтаксис) и возвращает программы (он же), а с другой интерпретация — семантическая в виде ящика который работает как исполнитель/машина которая просто принимает программы и их выполняет (семантика, http://en.wikipedia.org/wiki/Functor, http://en.wikipedia.org/wiki/Interpretation_(logic), модели, семантические домены).

Речь лишь про встроенный тип int/float.

Сделай block не локальным, и код будет находится в другом месте.

Т.е. можно 2+(3*5), а можно (2+3)*5.

Ты как-то в сторону и на частности опять. Код не будет нигде находиться — см. A. выше. Но если берёшь B., то не путай и пойми простую вещь — терму S не соответствует CPU-терм (в ситуации к конкретно написанным (Inter-терм) и скомпилированным в конкретный CPU-терм интерпретатором), ему соответствует конкретный пробег CPU-машины/интерпретатора. Попросту говоря, block не будет в другом месте (это же тоже локально), он будет кусками размазан где попало, то есть это не конкретные инструкции, а нетривиальные отношения на них — вытащить их во что-то вменяемое не тривиально, поэтому компиляция отличается от интерпретации.

просто это реальность такая, чудес не бывает.

Да никакая «это» не «реальность» :)

вот «код самого интерпретатора» и есть выход.

То есть как у тебя получается — я спрошу что возвращает функция при вычислении с аргументом, ты скажешь, что свой код (машинный. А почему машинный? А для какой архитектуры? А почему этой, а не той?), но погоди — я ж спрашиваю про вычисление, а он (этот машинный код который у тебя возвращается) что возвращает при вычислении? Опять сам себя и так до бесконечности? Фигня это всё :)

Ок, напишу

Не так. Принимаешь source программу, то есть строку, во время компиляции вычисляешь её длину n и генерируешь target программу ret n. Это будет правильное продолжение аналогии.

зачем тебе такой транслятор?

Это пример.

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 2)
Ответ на: комментарий от emulek

с какого перепугу перевод обязан быть однозначным?

При чем тут однозначность? Его просто нет. Мы никак не можем перевести. Ни однозначно, ни многозначно. Вообше никак.

Ещё раз, для тех кто в танке: обычный цикл выполняется неизвестно сколько раз. Рекурсия выполняется неизвестно сколько раз.

И что?

anonymous
()
Ответ на: комментарий от emulek

дык есть сопоставление. В любом интерпретаторе.

В том и дело что нету. Не существует в общем случае последовательности команд, которую бы интерпретатор сопоставлял интерпретируемому коду.

anonymous
()
Ответ на: комментарий от emulek

в таком случае, выполнить её на IA-32/amd64 тоже невозможно.

Возможно. Выполняют ведь.

да, конечно.

Именно так, их нельзя скомпилировать. Это алгоритмически неразрешимая задача.

anonymous
()
Ответ на: комментарий от Twissel

да, конечно.

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

anonymous
()
Ответ на: комментарий от Twissel

Но конкретно в момент исполнения команд на ЦП эта последовательность задана и однозначна

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

anonymous
()
Ответ на: комментарий от anonymous

Кстати

constexpr int bottom() { return bottom(); }
template <int n> struct A { enum { value = n }; };
int main() { return A<bottom()>::value; }

хотя типичным лимитом на ресурсы всё становится тут же «разрешимым» :)

Но это уже в сторону мета-программирования и случаев когда интерпретаторы тривиально превращаются в компиляторы, а когда не очень (constant folding и обобщения, разновидности CTFE, макросы и стадии, аbstract interpretation, partial evaluation, Futamura projection).

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Посмотрел бегло пдф-ки по тем ссылкам, скажу честно: для моего мировосприятия и мозгов немного мудрённо. Вроде как - ехал Лисп через Лисп верхом на хромом Лиспе.

Потому и написал, что ухватка функциональщика.

Тут уже действительно разница мировосприятия, хотя готов признать, что доля истины в написанном Вами все же есть :)

Twissel ★★★★★
()
Ответ на: комментарий от emulek

Потому, хоть в лоб, хоть полбу, если написано x+y, то это надо перевести в add.

А можно и не переводя.

anonymous
()
Ответ на: комментарий от quasimoto

хотя типичным лимитом на ресурсы всё становится тут же «разрешимым» :)

И что же вернет «bottom()» при типичном ограничении на ресурсы? :)

anonymous
()
Ответ на: комментарий от Twissel

Вот pdf-ки:

http://www.google.com/search?q=dragon book pdf (как бы)

http://www.google.com/search?q=programming language pragmatics pdf

no MIT this time :)

Это обычные книжки по теме, там просто должно быть и Лиспа или функциональщины тоже быть не должно.

quasimoto ★★★★
()
Ответ на: комментарий от anonymous

clang в segmentation fault упадёт :)

Там -fconstexpr-depth, то есть полнота отсекается глубиной рекурсии (циклов всё равно нет).

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Ну то есть и результата не будет. По-этому странно говорить о разрешимости - как же задача решена, если никакого результата нет?:)

anonymous
()
Ответ на: комментарий от anonymous

А мы считаем отсеиваемые программы с синтаксическими ошибками, ошибками типов? Тогда сомнительные на завершимость программы так же отсеваем — «нет» с описанием что не так, иначе «да» и результат компиляции. Проблема компиляции разрешима. А если считаем, что вход уже корректен синтаксически и в типах, то и CTFE тоже уже должен быть выполнен, тогда это не проблема, а просто алгоритм (который всегда разрешим).

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от quasimoto

Тогда сомнительные на завершимость программы так же отсеваем — «нет» с описанием что не так, иначе «да» и результат компиляции.

Вопрос в конкретном определении понятия «сомнительная на завершаемость программа». По такой логике можно считать «сомнительными» все программы кроме одного единственного хелловролда и отсеивать их. Но это не решение задачи - это заметание проблем под ковер.

Но даже если и предположить что мы вот таким вот образом решили неразрешимую задачу, нам доподлинно известно, что сами интерпретаторы так не делают. Они берут и интерпретируют, не отсеивая :)

CTFE

Оно тут не при чем же.

anonymous
()
Ответ на: комментарий от quasimoto

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

То есть CISC инструкции интерпретируются RISC ядром устройства микропрограммного управления, тут победа за Вами.

На этом уровне действительно уже полностью справедливо утверждение, что интерпретация == исполнение.

На более высоком уровне абстрагирования это далеко не так очевидно.

Если ошибся, поправьте.

Twissel ★★★★★
()
Последнее исправление: Twissel (всего исправлений: 1)
Ответ на: комментарий от anonymous

Вопрос в конкретном определении понятия «сомнительная на завершаемость программа»

Помимо возможности взять интерпретатор рекурсивных функций и вставить туда recursion depth (как в gcc/clang) есть исследования на тему termination checker-ов (типа type только про проблему останова) — есть вполне нетривиальный подкласс RE и разные нетривиальные паттерны рекурсии и циклов для которых завершимость автоматическое свойство и которых хватает для многих случаев когда нужны рекурсия или циклы, а остальное — сомневаемся и отбрасываем или руками разрешаем / доказываем (но это уже делаем работу за компилятор).

Они берут и интерпретируют, не отсеивая

Ну да — полный интерпретатор в трансляторе -> неразрешимость, просто на практике в таких случаях вешают лимит на space/time (если нужна таки трансляция).

Оно тут не при чем же.

Ну что там вместо в C++ — обычные рекурсивные constexpr функции (F) вычисляющиеся (E) во время компиляции (CT).

quasimoto ★★★★
()
Ответ на: комментарий от anonymous

Хотелось бы примеров, т.к. абстрактное мышление тут уже не справляется.

Откуда берется этот «дефект массы» на входе некоторого исполнителя.

Twissel ★★★★★
()
Ответ на: комментарий от Twissel

Ну да, если компилятор сделает Lang -> Asm (в CISC, допустим), то на интерпретацию, которая в данном случае есть выполнение на камне, можно смотреть как на Asm -> CPU-machine, то есть на загрузку кода в память — у нас есть состояние CPU-machine, тогда запуск (как Endo(CPU-Machine), например) есть обычная работа CPU (с декодированием в RISC или что там у него) начиная с этого начального состояния.

Прикол в том, что CPU-machine может быть не «выполнением на камне», а чистой интерпретаций в каком-нибудь эмуляторе (или не чистой с JIT).

Ну и абстрактный интерпретатор — Очепятки в книге по функциональному программированию (комментарий), простой арифметики, но он шаблонный, совсем нельзя сказать что он делает в терминах CPU, но при этом понятно что он делает в терминах арифметических операций типа Value (которые играют роль «ассемблера» тут), потому что это абстрактный интерпретатор. И тот факт, что C++ предполагает компиляцию кода этого интерпретатора не играет роли — как бы и так всё понятно.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

a program written in a source language S is directly executed by an S-interpreter, which is a program written in an implementation language

это никак не противоречит тому, что S переводится(транслируется) в язык реализации. Мало того, это твоя цитата как раз подтверждает факт трансляции, ибо «directly executed» возможно только на языке реализации.

¹ тебе по большому счёту не нужно, так как у тебя есть S-интерпретатор, который на своём уровне абстракций выполняет свои программы без всяких CPU. Сравни, допустим, с интерфейсом к файловым системам — ты можешь работать с ним как с API не зная ничего про архитектуру, код CPU, драйвера или ОС, тебе это не интересно на этом уровне.

ну мой калькулятор тоже позволяет абстрагироваться от деталей реализации. И что? Всё равно при вычислении 17^17=650 выполняются вполне определённые команды, на вполне конкретном CPU. Мне этот калькулятор как раз для компиляции и нужен, ибо gcc не умеет возводить в 17ю степень над GF(1399). Потому в коде я могу либо 650 записать, либо умножать 17 само на себя 17 раз. Фактически, мой калькулятор я использую именно как компилятор, ибо число 650 я прямо так и пишу в свой код, и в комментарии указываю, что это 17^17.

Ты как-то в сторону и на частности опять. Код не будет нигде находиться — см. A. выше.

будет. См ответ на А выше.

Чистый интерпретатор не делает S-terms -> T-terms, он берёт S, загружает в вычислитель и запускает его — S-terms -> S-machine.

ну вот а S-machine можно сохранить, а можно выполнить. Как хочешь. В первом случае получится компиляттор, во втором интерпретатор.

Попросту говоря, block не будет в другом месте (это же тоже локально), он будет кусками размазан где попало, то есть это не конкретные инструкции, а нетривиальные отношения на них — вытащить их во что-то вменяемое не тривиально

«нетривиально» лишь только потому, что семантика языка S не рассчитана на это вытаскивание. Как и реализация интерпретатора. Можно-ли решетом носить воду? В принципе можно — доработать решето(например смазать маслом), либо наоборот использовать впитывающие свойства решета(в мокром решете немножко воды унести можно). Принципиально решето эквивалентно ведру, вот только много дырок, и они (для воды) слишком крупные. И нет удобной ручки. Но это всё непринципиальные отличия. Из ведра можно сделать хреновое решето, из решета — хреновое ведро.

просто это реальность такая, чудес не бывает.

Да никакая «это» не «реальность» :)

реальность. Просто ты видишь всё слишком поверхностно, и не видишь взаимосвязи вещей. На самом деле, все границы условны. В мире нет идеально чёрного и идеально белого. Есть только светло-серый, и тёмно-серый. Также нет и «чистого интерпретатора», они все немножко(или не немножко) компиляторы. «Чистотой» могут похвастаться лишь самые примитивные поделия из древности.

То есть как у тебя получается — я спрошу что возвращает функция при вычислении с аргументом, ты скажешь, что свой код (машинный. А почему машинный? А для какой архитектуры? А почему этой, а не той?), но погоди — я ж спрашиваю про вычисление, а он (этот машинный код который у тебя возвращается) что возвращает при вычислении? Опять сам себя и так до бесконечности? Фигня это всё :)

рекурсию ты придумал, у меня её нет. S-int → S-maсhine. Всё. Далее S-machine уходит в файл или в процессор, который и вычисляет результат.

Не так. Принимаешь source программу, то есть строку, во время компиляции вычисляешь её длину n и генерируешь target программу ret n. Это будет правильное продолжение аналогии.

угу. А у меня не так? И ты не путай строку-данные и строку-код. Строка-код принимает на вход строку-константные-данные. Если у тебя интерпретатор, то строка-код переводится в строка-машкод, строка-константные-данные тоже переводится в строка-машкод (например в C/C++ это адрес в памяти обычно), а уж потом это всё исполняется. В случае компилятора это(строка-машкод) записывается в файл. Если данные не константные, а например содержимое файла, то строка-данные-из-файла тоже переводится в код fopen(3)/fread(3)/fclose(3) к примеру.

Eval это перевод строка-кодстрока-код, в C/C++ не применяется, ибо семантика ЯП для этого очень плохо подходит. (заточена на преобразование в машкод). Ну и из-за необходимости для этого тащить весь gcc в любой хэлловорлд.

emulek
()
Ответ на: комментарий от anonymous

с какого перепугу перевод обязан быть однозначным?

При чем тут однозначность? Его просто нет. Мы никак не можем перевести. Ни однозначно, ни многозначно. Вообше никак.

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

Ещё раз, для тех кто в танке: обычный цикл выполняется неизвестно сколько раз. Рекурсия выполняется неизвестно сколько раз.

И что?

то, что необязательно делать свой цикл для каждого числа итераций, как делал интерпретатор в 70х годах прошлого века.

emulek
()
Ответ на: комментарий от anonymous

Не существует в общем случае последовательности команд, которую бы интерпретатор сопоставлял интерпретируемому коду.

а что тогда выполняет процессор?

emulek
()
Ответ на: комментарий от anonymous

Именно так, их нельзя скомпилировать. Это алгоритмически неразрешимая задача.

а как же полнота ЯП по Тьюрингу?

emulek
()
Ответ на: комментарий от anonymous

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

4.2

думаешь в машинном коде нельзя сделать бесконечный цикл?

emulek
()
Ответ на: комментарий от anonymous

Но конкретно в момент исполнения команд на ЦП эта последовательность задана и однозначна

В том и дело что нет. Во-первых эта последовательность зависит от входных данных

4.2

входные данные однозначно заданы.

во-вторых эта последовательность может быть актуально бесконечна - то есть перевода нету вовсе.

машинный код тоже может быть актуально бесконечный по числу команд и времени. Достаточно одной команды JMP, которая безусловно переходит сама на себя. Где ты увидел «конец»?

emulek
()
Ответ на: комментарий от anonymous

Потому, хоть в лоб, хоть полбу, если написано x+y, то это надо перевести в add.

А можно и не переводя.

сумму в хрустальном шаре смотреть будешь? Или таблицу сложения (2^32)^2 сохранишь?

emulek
()
Ответ на: комментарий от anonymous

И что же вернет «bottom()» при типичном ограничении на ресурсы? :)

UB

emulek
()
Ответ на: комментарий от quasimoto

Ну да — полный интерпретатор в трансляторе -> неразрешимость, просто на практике в таких случаях вешают лимит на space/time (если нужна таки трансляция).

То есть все сводится к тому, что мы можем интерпретировать то, что транслировать не получится. То, что есть некое подмножество программ, которые можно как транслировать, так и интерпретировать - никто не спорил. Но интерпретаторы ирл работают со всем множеством, а не с этим подмножеством.

anonymous
()
Ответ на: комментарий от Twissel

Хотелось бы примеров, т.к. абстрактное мышление тут уже не справляется.

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

anonymous
()
Ответ на: комментарий от emulek

это никак не противоречит тому, что S переводится(транслируется) в язык реализации. Мало того, это твоя цитата как раз подтверждает факт трансляции, ибо «directly executed» возможно только на языке реализации.

S-interpreter может быть реализован, например, аппаратно. То есть язык реализации - и есть S-язык. Как, допустим, в лисп-машинах язык реализации - лисп и лисп-машина интерпретировала лисп-программу.

ну вот а S-machine можно сохранить

Не может. Потому что нечего сохранять. Нету никакого «оттранслированного» кода, который был бы эквивалентен исходному . И получить его нельзя.

anonymous
()
Ответ на: комментарий от emulek

тогда и выполнение машкода невозможно, если этого машкода нет.

Коненчо возможно. Это доказывается хотя бы тем, что интерпретаторы ИРЛ существуют. Если бы это было невозможно - интерпретаторов бы не существовало.

то, что необязательно делать свой цикл для каждого числа итераций, как делал интерпретатор в 70х годах прошлого века.

Если внутри цикла есть eval (или фекспр или еще что подобное) - обязательно. Цикл целиком скомпилировать будет невозможно - только отдельную его итерацию. Которая в свою очередь тоже может содержать отдельный цикл (а может и не содержать). Итерации которого, в свою очередь, могут снова оказаться «плохими».

anonymous
()
Ответ на: комментарий от emulek

а как же полнота ЯП по Тьюрингу?

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

anonymous
()
Ответ на: комментарий от anonymous

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

здесь тоже нельзя сказать:

if(внешние_условие)
    действие;

emulek
()
Ответ на: комментарий от anonymous

S-interpreter может быть реализован, например, аппаратно. То есть язык реализации - и есть S-язык. Как, допустим, в лисп-машинах язык реализации - лисп и лисп-машина интерпретировала лисп-программу.

ну и покажи мне эту твою лисп-машину. Жду с нетерпением.

Не может. Потому что нечего сохранять. Нету никакого «оттранслированного» кода, который был бы эквивалентен исходному . И получить его нельзя.

откуда у тебя требование эквивалентности?

emulek
()
Ответ на: комментарий от anonymous

то, что необязательно делать свой цикл для каждого числа итераций, как делал интерпретатор в 70х годах прошлого века.

Если внутри цикла есть eval (или фекспр или еще что подобное) - обязательно. Цикл целиком скомпилировать будет невозможно

а мой калькулятор умеет скобки в которых может быть всё что угодно. А ты — скобочник-борщеед.

emulek
()
Ответ на: комментарий от anonymous

а что тогда выполняет процессор?

Команды процессора, очевидно. А что?

то, что ты говоришь «команд процессора нет и сделать их невозможно из интерпретируемого кода».

emulek
()
Ответ на: комментарий от emulek

S переводится(транслируется) в язык реализации. Мало того, это твоя цитата как раз подтверждает факт трансляции, ибо «directly executed» возможно только на языке реализации.

Ты продолжаешь

void wut(int);

«wut принимает числа и переводит(транслирует) их в свой собственный же код на языке своей реализации. Бо «wut takes an int and directly executes its code on it» возможно только на языке реализации».

Я не знаю что тут сказать.

мой калькулятор я использую именно как компилятор

Да, я выше писал — простой чистый интерпретатор в виде алгоритма f : Input -> Value тривиально превращается в компиляцию тривиальных программ как ret_instruction_is_target(f(compile_time_input_as_source)).

Но не f : Input -> Effects.

ну вот а S-machine можно сохранить, а можно выполнить

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

«нетривиально» лишь только потому, что семантика языка S не рассчитана на это вытаскивание.

Слово «семантика» решил вставить?)

Пока что ты не продемонстрировал ничего против того, что интерпретатор в компилятор превращается выбрасыванием интерпретатора и написанием компилятора (а то они же одно и то же и ты как-то хочешь вытаскивать транслированный код из интерпретатора). Нет трансляции при интерпретации — нет компилятора, говорить не о чем.

Можно-ли решетом носить воду?

Вот у тебя такое видение, а ты мне что-то рассказываешь про слишком поверхностные формулировки. Они ОК.

тыц

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

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

Угу, в википедии написано про пре-трансляцию, байткод и т.д. и т.п., я знаю. А кусок который конечный интерпретатор — интерпретатор, а не транслятор.

S-int → S-maсhine

Я про терм S спрашиваю, а не про S-int. И про хоть какой-то T термов в результате (машина — не язык).

Далее S-machine уходит в файл или в процессор, который и вычисляет результат.

Ещё раз — S-machine это модель машины.

А у меня не так?

Нет, ты не понял пример с strlen (у тебя в [inline] size_t не фигурирует, ну и ты опять написал нечто в духе того что выше про wut).

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 2)
Ответ на: комментарий от emulek

На самом деле, все границы условны. В мире нет идеально чёрного и идеально белого. Есть только светло-серый, и тёмно-серый.

А если под вещества, так вообще! Наверное :)

quasimoto ★★★★
()
Ответ на: комментарий от anonymous

Это единственный возможный пример? Хотеть видеть/знать больше примеров!

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

Хотя пример взят правильный это да - в случае с зависшей программой.

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

Twissel ★★★★★
()
Последнее исправление: Twissel (всего исправлений: 2)
Ответ на: комментарий от Twissel

А ты какой ответ спрашиваешь?

Даже не знаю, например:

#include <cstddef>

enum op { halt = 0, pls, dbl };

size_t ev(size_t in, op code[]) {

    for (size_t reg = in;; ++code)
        switch (*code) {
        case pls:
            ++reg;
            break;
        case dbl:
            reg *= 2;
            break;
        case halt:
            return reg;
        }

}

int main() {

    op code[] = { pls, dbl, pls, pls, dbl, dbl, halt };

    return ev(1, code);

}

теперь вопрос — как нам получить ассемблер соответствующий ev? Ну -S. Ассемблер для { pls, dbl, pls, pls, dbl, dbl, halt } — любо петросянство про то что ev для любого аргумента возвращает свой собственный машинный код (какого звучит, а :)), либо нам надо пройтись руками по цепочке, делать switch и собрать нужный код руками. Ну то есть выбросить интерпретатор и написать компилятор (не путаем с превращением ev в транслятор из (size_t, op[]) в size_t — речь про (size_t, op[]) -> asm).

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 2)
Ответ на: комментарий от quasimoto

Пока что ты не продемонстрировал ничего против того, что интерпретатор в компилятор превращается выбрасыванием интерпретатора и написанием компилятора (а то они же одно и то же и ты как-то хочешь вытаскивать транслированный код из интерпретатора).

ну термы входного языка вполне однозначно переводятся в термы машинного. С этим ты согласен? Что мешает тогда сохранять машинные термы?

emulek
()
Ответ на: комментарий от quasimoto

либо нам надо пройтись руками по цепочке, делать switch и собрать нужный код руками

this. Вот только «делать switch» не нужно, ты его и так делаешь. Просто сохраняй в файл команды.

Ну то есть выбросить интерпретатор и написать компилятор

то, что у тебя написано, уже и есть компилятор. Не хватает только записи в файл одной команды в каждом case'е. Причём места они не займут лишнего, ибо их можно поставить _вместо_ выполнения.

emulek
()
Ответ на: комментарий от Twissel

Добавим безусловных циклов, чтоб пример anonymous-а с зависанием был

#include <cstddef>
#include <cassert>

using Reg = size_t;

enum Op : Reg {
    HLT = 0,
    PLS = 1,
    DBL = 2
};

Op JMP(Reg label) {
    return Op(label + 3);
}

Reg label(Op op) {
    assert(op >= 3);
    return op - 3;
}

Reg run(const Op code[]) {

    for (Reg i = 0, reg = 0;;)
        switch (code[i]) {
        case HLT:
            return reg;
        case PLS:
            ++reg;
            ++i;
            break;
        case DBL:
            reg *= 2;
            ++i;
            break;
        default: // JMP
            i = label(code[i]);
            break;
        }

}

int main() {

    const Op code[] = { PLS, DBL, JMP(5), DBL, HLT, PLS, DBL, JMP(3) };

    return run(code);

}

Теперь трейсинг control flow с накоплением инструкций не прокатит, так как control flow может продолжаться сколько угодно долго.

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от emulek

Вообще? Ну да, я и говорю, пишем компилятор — переводим в машинный, пишем интерпретатор — нет (но можно написать компилятор, это как существование чётных чисел не делает нечётные чётными :)).

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

у тебя хреновая реализация jamp, она прыкает по входному коду, а должна прыгать по выходному коду. Т.е. тебе нужно:

1. добавить lable, такой NOP, который ничего не делает, а только сохраняет в памяти транслятора текущий адрес _выходного_ потока

2. добавить jmp lable, но вместо lable вставить адрес из п1.

Да, для переходов вперёд потребуется два прохода. Но ты не расстраивайся, тот же bash всё равно делает как минимум 2 прохода по файлу, пруф я уже здесь приводил.

emulek
()
Ответ на: комментарий от quasimoto

пишем интерпретатор — нет

почему «нет»? Между твоей командой PLS и командой INC из x86 существует взаимно-однозначное соответствие. Не вижу проблемы.

emulek
()
Ответ на: комментарий от emulek

Мне не нужно.

Оно не работает из коробки — нужно переписывать, в этом поинт. При том, что это максимально простой пример с четырьмя тупыми инструкциями, для «компиляции» вообще pretty printer-a хватит, AST-driven в обоих случаях, но разный код, разные типы аргументов и результатов, сигнатуры то есть, разное назначение.

Ещё можно взять такой же игрушечный

L : Set → Class
c : ℕ → L ℕ
v : ∀ {a} → a → L a
_·_ : ∀ {a b} → L (a → b) → L a → L b
_+_ : L ℕ → L ℕ → L ℕ
_*_ : L ℕ → L ℕ → L ℕ
ƛ : ∀ {a b} → (a → L b) → L (a → b)

run : ∀ {a} → L a → a
run (c n) = n
run (v v) = v
run (e₁ · e₂) = run e₁ (run e₂)
run (e₁ + e₂) = run e₁ + run e₂
run (e₁ * e₂) = run e₁ * run e₂
run (ƛ e) = run · e

что с ним делать?

Или любой реальный интерпретатор или компилятор — насколько это разные задачи (написать VM для эффективного прогона байткода, или заниматься многочисленными source-to-source/graph-to-graph на пассах).

quasimoto ★★★★
()
Ответ на: комментарий от emulek

здесь тоже нельзя сказать:

Ты не понял. В случае фекспров мы не знаем не только будут ли выполнены сайд-эффекты исполнения, но и будут ли выполнены сайд-эффекты «компиляции». Так что твой пример не подходит - мы можем заранее скомпилировать «действие;» и все будет ок. А в фекспрах - не можем, потому что не знаем надо ли будет это делать, а будет оно сделано или нет - может повлиять на контрол-флоу рантайма. В результате мы должны раскрывать макросы в рантайме.

anonymous
()
Ответ на: комментарий от emulek

ну и покажи мне эту твою лисп-машину.

http://upload.wikimedia.org/wikipedia/commons/1/16/LISP_machine.jpg

откуда у тебя требование эквивалентности?

Из определения трансляции. Если есть программа Х на языке ХХ, то результат трансляции - программа Y на языке YY, котора эквивалентна Х с точки зрения семантики. Иначе это не трансляция, а хуета.

anonymous
()
Ответ на: комментарий от Twissel

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

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

anonymous
()
Ответ на: комментарий от emulek

входные данные однозначно заданы.

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

машинный код тоже может быть актуально бесконечный по числу команд и времени.

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

Достаточно одной команды JMP, которая безусловно переходит сама на себя. Где ты увидел «конец»?

Тут и увидел. В это случае - программа состоит всего из одной единственной команды, собственно, джампа.

anonymous
()
Ответ на: комментарий от emulek

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

Причем твой калькулятор со скобками?

anonymous
()
Ответ на: комментарий от emulek

то, что ты говоришь «команд процессора нет и сделать их невозможно из интерпретируемого кода».

Команды процессора есть. Нету _конечной последовательности команд_, которую можно было бы считать переводом. Потому как бесконечная последовательность команд - текстом не является. И потому переводом быть не может.

anonymous
()
Ответ на: комментарий от quasimoto

Уже вспоминали на первой странице — http://en.wikipedia.org/wiki/Partial_evaluation

При чем тут частичные вычисления? У нас есть программа, ее нельзя оттранслировать, но можно интерпретировать. Все. Ч. Т. Д. Одной такой программы для одного какого-нибудь ЯП (пусть даже выдуманного специально для доказательства нашего тезиса) достаточно, чтобы утверждать, что интерпретатор, в общем случае, не является транслятором.

anonymous
()
Ответ на: комментарий от anonymous

Опять же тут больше вопрос логики, ибо

бесконечный код != циклический (зацикленный) код

Опять же либо не все столь однозначно. Либо пруфы кода, который есть бесконечный, но не есть цикличный :)

Twissel ★★★★★
()
Ответ на: комментарий от Twissel

Опять же либо не все столь однозначно. Либо пруфы кода, который есть бесконечный, но не есть цикличный :)

Не понял. Еще раз - контрол-флоу любой виснущей программы (в частности эвент-лупы, серверы, етц.) бесконечен. При чем тут зацикленность?

Либо пруфы кода, который есть бесконечный, но не есть цикличный :)

for (i = 0; i; i++){
    eval("print(" + intToStr(i) + ");")
}

бесконечный, нецикличный.

anonymous
()
Ответ на: комментарий от anonymous

Но for ведь тоже цикл с параметром ;)

А код без ветвлений, чисто линейный, по здравому размышлению, бесконечным быть не может.

Так что пока не убедили.

Или покажите ошибку в моих суждениях, такая критика всегда интересна :)

Twissel ★★★★★
()
Ответ на: комментарий от quasimoto

что с ним делать?

откуда я знаю? Конечно запостить на ЛОР. Что же ещё?

Или любой реальный интерпретатор или компилятор

facepalm. Давай с твоим выше разберёмся. Или слил?

emulek
()
Ответ на: комментарий от emulek

4.2

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

(let loop ([i 0])
    (eval `(begin (define (yoba) (print ,i))
                  (yoba)))
    (loop (add1 i)))
на каждом шаге генерится код, он компилируется в байткод и тот потом компилируется в машкод джитом. После джита на каждом шаге получается разный код, в силу того, что в нем разные константы (значения i). И этот машкод генерируется до бесконечности. В итоге выполняемый машкод будет следующим:
push 1
call print
push 2
call print
push 3
call print
...
и далее, бесконечная портянка (между этими командами понятно еще код виртуальной машины, я его опускаю). Видно, что этот код нецикличен - никакого подмножества команд, копиями которой этот код мог бы быть, тут не существует, так как в каждой паре своя собситвенная уникальная константа.

anonymous
()
Ответ на: комментарий от Twissel

Но for ведь тоже цикл с параметром ;)

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

for (i = 0; i; i++){
    print(i);
}
мы получим последовательность
push i
call print
push i
call print
...
Которая, очевидно, циклична (на деле вместо i будет адрес в памяти или регистр, это не суть, важно что это будет один и тот же адрес или один и тот же регистр). А в случае с евалом будет то, что я написал выше, отсутствие цикличности.

anonymous
()
Ответ на: комментарий от Twissel

А код без ветвлений, чисто линейный, по здравому размышлению, бесконечным быть не может.

может. На бесконечной ленте машины Тьюринга. На конечной ленте с n битами возможно ровно 2ⁿ состояний, после которых будет повтор прошлой комбинации(или раньше). Уже обсудили.

emulek
()
Ответ на: комментарий от anonymous

мы получим последовательность

нет. Мы получим цикл. Только на ассемблере. В ассемблере x86 есть команда jnz, которая переходит если не ноль. Тебе этого мало? Почему ты тело цикла переводишь в код, а сам цикл for(;;) не переводишь, и куда он у тебя исчез? Опять магия?

emulek
()
Ответ на: комментарий от anonymous

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

теперь докажи и покажи мне эту свою «портянку» из одних и тех же команд, и покажи, куда делся цикл?

Я тебе не верю, а дебажить твой говнокод на лиспе мне лень.

emulek
()
Ответ на: комментарий от emulek

Я имел ввиду на практике, а не в абстракциях.

Или просветите, как видите, я в этом деле дилетант, но с запалом :)

Twissel ★★★★★
()
Ответ на: комментарий от Twissel

на практике анонимус принимает желаемое за действительное, и показывает только те команды, которые понимает (почему-то push+printf без pop). С такой ерундой через микросекунду будет переполнение стека и сегфолт.

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

emulek
()
Ответ на: комментарий от emulek

Тем не менее, дискуссия получилась познавательной.

Лично для меня, мозг тоже иногда тренировать нужно :)

Twissel ★★★★★
()
Ответ на: комментарий от Twissel

да, у меня где-то валялся интерпретатор/компилятор для brainfuck'а. Это очень простой ЯП, и если ты попробуешь сделать его компилятор/интерпретатор, то ты поймёшь, что тут на самом деле всё просто. Там всего 8 команд: см. http://ru.wikipedia.org/wiki/Brainfuck

Т.ч. компилятор/интерпретатор будет очень простым. Что бы ещё упростить задачу, можно компилировать не в машкод, а в C код, который затем прогонять оптимизирующим компилятором типа gcc.

Данный ЯП обладает Тьюрринг-полнотой, а следовательно на нём можно переписать _любую_ задачу с _любого_ ЯП. Это доказано.

emulek
()
Ответ на: комментарий от emulek

откуда я знаю? Конечно запостить на ЛОР. Что же ещё?

Это интерпретатор типизированного лямбда-исчисления с числами и их арифметикой.

ТЫ рассказываешь как его превратить в компилятор из лямбда-термов в ассемблер x86_64 (если это «формально одно и то же» — этот интерпретатор на x86_64 запускается и работает, я проверил :)).

facepalm.

Ничего не фейсплалм. Бери любой интерпретатор байткода и любой компилятор и смотри какие там техники реализаций ([1], [2], [3], bytecode translation, bytecode execution, virtual machines & runtime, JIT, tracing JIT).

ТЫ объясняешь как они «формально одно и то же».

Давай с твоим выше разберёмся.

Так разобрались же уже — вот тебе и сказать нечего.

Компилятором будет

// gcc format

namespace x86_64 {

    // register allocation :)

    const char r1[] = "%eax";

    // instruction selection

    void gen_label(size_t i) {
        printf(".L%zu:\n", i);
    }

    void gen_ret() {
        printf("\tret\n");
    }

    void gen_pls() {
        printf("\tadd\t$1, %s\n", r1);
    }

    void gen_dbl() {
        printf("\tadd\t%s, %s\n", r1, r1);
    }

    void gen_jmp(size_t i) {
        printf("\tjmp\t.L%zu\n", i);
    }

    // wrapper

    void gen_main() {
        printf("\t.globl main\n");
        printf("main:\n");
        printf("\txor %s,%s\n", r1, r1);
    }

}

void compile(const Op code[], size_t len) {

    for (size_t i = 0; i < len; ++i) {
        x86_64::gen_label(i);
        switch (code[i]) {
        case HLT: x86_64::gen_ret(); break;
        case PLS: x86_64::gen_pls(); break;
        case DBL: x86_64::gen_dbl(); break;
        default : x86_64::gen_jmp(label(code[i])); break;
        }
    }

}

int main() {

    const Op code[] = { PLS, DBL, JMP(5), DBL, HLT, PLS, DBL, JMP(3) };

    x86_64::gen_main();
    compile(code, sizeof(code) / sizeof(Op));

}

compile вместо

Reg run(const Op code[]) {

    for (Reg i = 0, reg = 0;;)
        switch (code[i]) {
        case HLT: return reg;
        case PLS: ++reg; ++i; break;
        case DBL: reg *= 2; ++i; break;
        default : i = label(code[i]); break;
        }

}

Компилятор и интерпретатор тупейшего языка оба AST-driven — ну офигеть «формально одно и то же».

Рассказывай.

quasimoto ★★★★
()
Ответ на: комментарий от anonymous

У нас есть программа, ее нельзя оттранслировать, но можно интерпретировать

Вот там и превращают I_static * I_dynamic -> O в I_dynamic -> O предвычислением I_static, но это последнее I_dynamic -> O всегда остаётся и не устраняется, это то что ты говоришь — интерпретация всегда возможна, а трансляция только частично — всё равно останется кусок на интерпретацию.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

case PLS: ++reg; ++i; break;

тут ты тоже пиши

case PLS: exec_pls_in_c(&reg, &i); break;

…


void exec_pls_in_c(int *arg, int *i)
{
    int *arg;
    int *i;
}

а то ты для «компилятора» почему-то выписываешь функции, а для «интерпретатора» — нет.

Почему?

emulek
()
Ответ на: комментарий от quasimoto

Офигеть ты практик

за то это _полный_ компилятор/интерпретатор, а не просто составление AST. Потому ты и путаешься в примитивных вещах, т.к. у тебя нет опыта перевода AST на уровень железа. Даже для brainfuck'а.

emulek
()
Ответ на: комментарий от emulek

Почему ты тело цикла переводишь в код, а сам цикл for(;;) не переводишь, и куда он у тебя исчез? Опять магия?

Потому что цикла не получается. Код нециклический. Нельзя эту последовательность свернуть в циклический машкод.

anonymous
()
Ответ на: комментарий от emulek

теперь докажи и покажи мне эту свою «портянку» из одних и тех же команд, и покажи, куда делся цикл?

Он никуда не девался, его там не было. На каждой итерации объявляется новая функция, компилируется джит-компилятором и генерируется новый машкод, отличный от машкода других итераций. Потом этот новый машкод выполняется.

Я тебе не верю

Отличный аргумент!

anonymous
()
Ответ на: комментарий от emulek

на практике анонимус принимает желаемое за действительное, и показывает только те команды, которые понимает (почему-то push+printf без pop).

Я опустил несущественные команды. pop в самом printf. От того что ты добавишь туда еще каких-то команд код все равно не станет циклическим, он нециклический из-за констант. Пока ты не уберешь константы - он не может быть приведен к циклическому виду.

Как и их гипотетический код. Которого в этой реальности нет

Я тебе привел конкретный код, который есть в реальности, который можно выполнить и который работает вполне конкретным образом. Но ты же в домике.

anonymous
()
Ответ на: комментарий от emulek

Тебе разжёвывать нужно?

using Reg = ...; // (1)
using Address = Reg; // (1)

void exec_pls(Reg&); // (1)
void exec_dbl(Reg&); // (1)
void exec_inc(Reg&); // (1) (6)
void exec_jmp(Reg&, Address); // (1)

#define EXEC_HALT(R) return R // (2)

Reg run(const Op code[]) { // (3)
    for (Reg i = 0, r1 = 0;;) { // (4)
        switch (fetch(i, code)) { // (5)
        case HLT: EXEC_HALT(r1); // (2) (6)
        case PLS: exec_pls(r1); exec_inc(i); break; // (6)
        case DBL: exec_dbl(r1); exec_inc(i); break; // (6)
        default : exec_jmp(i, label(fetch(i, code))); break; // (6)
        }
    }
}
using RegName = ...; // (1)
using Label = ...; // (1)

void gen_ret(RegName); // (1) (2)
void gen_pls(RegName); // (1)
void gen_dbl(RegName); // (1)

void gen_label(Label); // (1)
void gen_jmp(Label); // (1)

void compile(const Op code[], size_t len) { // (3)
    RegName r1 = get_reg(); // (1)
    for (Label i = 0; i < len; ++i) { // (4)
        gen_label(i); // (6)
        switch (fetch(i, code)) { // (5)
        case HLT: gen_ret(r1); break; // (2) (6)
        case PLS: gen_pls(r1); break; // (6)
        case DBL: gen_dbl(r1); break; // (6)
        default : gen_jmp(label(fetch(i, code))); break; // (6)
        }
    }
}

Внимательно смотрим на различие в типах, сигнатурах, коде и назначении.

(1) — интерпретатор по мере исполнения своей программы работает с состоянием своей машины (регистры, адреса, эффекты исполнения), транслятор занимается синтаксическим переводом своей программы (имена, метки, генерация инструкций).

(2) — интерпретатору HLT это остановка, транслятору — нет, просто очередной кусок терма для перевода.

(3) — если выполнение интерпретатором закончится, то останется конечное состояние машины, трансляция всегда закончится, результатом будет выхлоп в виде перевода исходной программы.

(4) — обход программы интерпретатором начинается с исходного состояния машины и продолжается сколько угодно долго, это её выполнение, в т.ч. с зависаниями, у транслятора — один проход по программе за конечное время, это перевод, он начинается с одной программы и даёт в итоге другую.

(5) — даром что оба AST-driven, потому что примитивнейшие.

(6) — сами по себе действия разные.

И ты не отвечаешь про интерпретатор лямбда-исчисления с арифметикой — если что, то мне в x86_64, пжалуста, без стека, но с аллокацией регистров и оптимизацией выражений :)

И про реальные(tm) интерпретаторы с компиляторами тоже ничего не рассказываешь :(

quasimoto ★★★★
()
Ответ на: комментарий от Twissel

Неверное. jnz ничем не поможет, т.к. на каждой итерации надо перескакивать на _новый_ кусок машкода. И эти новые куски генерятся до бесконечности. Никакого конечного цикла, если эти куски склеить, не получится. На самом деле, там в коде есть три куска, первый и третий постоянны, а второй - генерится на каждой итерации, замещая предыдущий. То есть:

1. прыгнули на начало цикла, сгенерили машкод

2. прыгнули на сгенеренный машкод, вывели число, прыгнули на конец цикла

3. инкремент i, проверка на выход, прыжок на начало цикла

anonymous
()
Ответ на: комментарий от emulek

компилятор/интерпретатор

2 в 1?

Писать «компилятор/интерпретатор» с намёком на то что это одно и то же есть смысл только если ты хочешь чтобы тебя приняли за идиота или человека который не знает значения используемых слов.

Потому ты и путаешься в примитивных вещах, т.к. у тебя нет опыта перевода AST на уровень железа. Даже для brainfuck'а.

Ты путаешься в примитивных вещах, т.к. у тебя нет опыта разработки хоть сколько-нибудь сложного транслятора (brainfuck'и не считаются, а то может ты там напился, вот у тебя «компилятор/интерпретатор» и вышел).

quasimoto ★★★★
()
Ответ на: комментарий от Twissel

Почему сгенерили?

Потому что так racket работает.

Или мы уже перешли на рассмотрение JIT-компиляции, вместо классической?!

Можно и без jit. Можно либо не генерить машкод вовсе (и интерпретировать то что внутри евала), либо как будет в cl - генерить машкод ф-и сразу при ее объявлении (а не при вызове). Но один хрен на каждой итерации будет создан новый, отличный от других кусок машкода. И «классическая компиляция» тут просто невозможна. Нету кода, который можно получить до запуска программы. Он генерится во время работы программы (такова семантика программы) и потому бесконечен (т.к. программа его бесконечно генерит и выполняет и генерит...). Это как раз то, что получается, когда мы пытаемся компилировать интерпретируемый по дизайну язык.

anonymous
()
Ответ на: комментарий от anonymous

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

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

anonymous
()
Ответ на: комментарий от Twissel

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

Так он бесконечный. Он не влезет ни в какую библиотеку.

anonymous
()
Ответ на: комментарий от anonymous

(и никакого машкода нету вовсе)

Ну это уже перебор, скажем так статичного машкода. А в противном случае, как бы оно выполнялось на ЦП. Сферический конь?! :D

Twissel ★★★★★
()
Ответ на: комментарий от Twissel

Ну это уже перебор, скажем так статичного машкода.

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

Можно не интерпретировать а компилировать «кусками», тогда получается бесконечный машкод исполняемой программы. А конечного не выходит.

anonymous
()
Ответ на: комментарий от anonymous

Но действие то, одно и тоже - вывод символа.

Причем здесь бесконечность? Меняется ведь только аргумент функции.

В любом случае, благодарю за интересную беседу. Узнал кое-что концептуально интересное.

Twissel ★★★★★
()
Ответ на: комментарий от anonymous

Потому что цикла не получается. Код нециклический. Нельзя эту последовательность свернуть в циклический машкод.

но не в машинном ты же свернул? А почему в машинный нельзя без разворачивания?

emulek
()
Ответ на: комментарий от Twissel

Причем здесь бесконечность?

При том что бесконенчо генерируется разный код.

Меняется ведь только аргумент функции.

Нет, меняется вся итерация. Без евала просто выводится символ, с евалом - объявляется функция, компилируется в машкод, выполняется этот машкод. На каждой итерации.

anonymous
()
Ответ на: комментарий от anonymous

Он никуда не девался, его там не было. На каждой итерации объявляется новая функция, компилируется джит-компилятором и генерируется новый машкод, отличный от машкода других итераций. Потом этот новый машкод выполняется.

это называется «неявный цикл». Типа вот этого:

#include <stdio.h>

void f(int counter, int *y)
{
·   if(counter == 0return;
·   *y *= counter;
·   f(counter-1, y);
}

int main(int argc, char *argv[])
{
·   int n = 5;
·   int y = 1;
·   f(n, &y);
·   printf("%d!=%d\n", n, y);
·   return 0;
}
думаешь тут нет цикла? Это ты так думаешь, а gcc считает иначе:
08048450 <f>:
 8048450:	8b 44 24 04          	mov    eax,DWORD PTR [esp+0x4]
 8048454:	8b 4c 24 08          	mov    ecx,DWORD PTR [esp+0x8]
 8048458:	85 c0                	test   eax,eax
 804845a:	74 0c                	je     8048468 <f+0x18>
 804845c:	8b 11                	mov    edx,DWORD PTR [ecx]
 804845e:	66 90                	xchg   ax,ax
 8048460:	0f af d0             	imul   edx,eax
 8048463:	48                   	dec    eax
 8048464:	75 fa                	jne    8048460 <f+0x10>
 8048466:	89 11                	mov    DWORD PTR [ecx],edx
 8048468:	c3                   	ret    
как видишь, gcc свернул рекурсию в цикл. Твой хаскель/лисп тоже так делает. Причём это возможно всегда, хотя часто только ценой дополнительной памяти для стека.

emulek
()
Ответ на: комментарий от emulek

но не в машинном ты же свернул?

Свернул. Он же циклический.

А почему в машинный нельзя без разворачивания?

Без разворачивания нечего сворачивать. Чтобы получить свернутый машинный код, нам надо вообще для начала получить машинный код. А он бесконечный и нециклический. Мы можем сразу преобразовать в машинный код обвязку цикла, но то что внутри евала так и останется нескомпилированным кодом на лиспе до тех пор, пока выполнение туда не дойдет.

anonymous
()
Ответ на: комментарий от emulek

это называется «неявный цикл».

Нет, это не «неявный цикл». Тут вообще нету цикла.

как видишь, gcc свернул рекурсию в цикл.

То есть в твоем коде был цикл. Просто ты его записал в виде рекурсии. Если развернуть твой код в машкод мы получим последовательность повторяющихся команд. Она и сворачивается.

Твой хаскель/лисп тоже так делает.

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

anonymous
()
Ответ на: комментарий от anonymous

Я опустил несущественные команды. pop в самом printf

нет там pop. Учи матчасть, printf делает «push/pop» только для своих локальных переменных, для внешних локальных «push/pop» делает main.

От того что ты добавишь туда еще каких-то команд код все равно не станет циклическим, он нециклический из-за констант. Пока ты не уберешь константы - он не может быть приведен к циклическому виду.

это тебе так кажется. Ты путаешь «я не знаю как» с «это невозможно».

Я тебе привел конкретный код, который есть в реальности, который можно выполнить и который работает вполне конкретным образом. Но ты же в домике.

ещё раз повторяю: рекурсия всегда сворачивается в цикл. Только каждая итерация этого цикла может выполняться в своём собственном контексте. Т.ч. любой _код_ можно всегда свернуть в цикл, однако для этого может потребоваться неизвестно сколько памяти (точнее столько, сколько будет проходов рекурсии/цикла). Однако код может быть представлен в конечном виде всегда.

А этот твой eval == типичная рекурсия.

emulek
()
Ответ на: комментарий от emulek

Собственно вот:

Добро пожаловать в DrRacket, версия 6.0 [3m].
Язык: racket [выбранный].
> (require disassemble)
> (for ([i 10])
    (eval `(begin (define (x) ,i) (x)))
    (decompile x #:size 26))
00000000  488943F8          mov [rbx-0x8],rax
00000004  4883C3F8          add rbx,byte -0x8
00000008  B801000000        mov eax,0x1
0000000D  4C8B75C8          mov r14,[rbp-0x38]
00000011  4883C428          add rsp,byte +0x28
00000015  5F                pop rdi
00000016  5E                pop rsi
00000017  5B                pop rbx
00000018  5D                pop rbp
00000019  C3                ret

00000000  488943F8          mov [rbx-0x8],rax
00000004  4883C3F8          add rbx,byte -0x8
00000008  B803000000        mov eax,0x3
0000000D  4C8B75C8          mov r14,[rbp-0x38]
00000011  4883C428          add rsp,byte +0x28
00000015  5F                pop rdi
00000016  5E                pop rsi
00000017  5B                pop rbx
00000018  5D                pop rbp
00000019  C3                ret

00000000  488943F8          mov [rbx-0x8],rax
00000004  4883C3F8          add rbx,byte -0x8
00000008  B805000000        mov eax,0x5
0000000D  4C8B75C8          mov r14,[rbp-0x38]
00000011  4883C428          add rsp,byte +0x28
00000015  5F                pop rdi
00000016  5E                pop rsi
00000017  5B                pop rbx
00000018  5D                pop rbp
00000019  C3                ret

00000000  488943F8          mov [rbx-0x8],rax
00000004  4883C3F8          add rbx,byte -0x8
00000008  B807000000        mov eax,0x7
0000000D  4C8B75C8          mov r14,[rbp-0x38]
00000011  4883C428          add rsp,byte +0x28
00000015  5F                pop rdi
00000016  5E                pop rsi
00000017  5B                pop rbx
00000018  5D                pop rbp
00000019  C3                ret

00000000  488943F8          mov [rbx-0x8],rax
00000004  4883C3F8          add rbx,byte -0x8
00000008  B809000000        mov eax,0x9
0000000D  4C8B75C8          mov r14,[rbp-0x38]
00000011  4883C428          add rsp,byte +0x28
00000015  5F                pop rdi
00000016  5E                pop rsi
00000017  5B                pop rbx
00000018  5D                pop rbp
00000019  C3                ret

00000000  488943F8          mov [rbx-0x8],rax
00000004  4883C3F8          add rbx,byte -0x8
00000008  B80B000000        mov eax,0xb
0000000D  4C8B75C8          mov r14,[rbp-0x38]
00000011  4883C428          add rsp,byte +0x28
00000015  5F                pop rdi
00000016  5E                pop rsi
00000017  5B                pop rbx
00000018  5D                pop rbp
00000019  C3                ret

...
> 
как видишь на каждом шаге генерируется свой код. Обрати внимание на константу, которая кладется в eax.

anonymous
()
Ответ на: комментарий от emulek

нет там pop. Учи матчасть, printf делает «push/pop» только для своих локальных переменных, для внешних локальных «push/pop» делает main.

При чем тут main и printf? При чем тут вообще твоя сишка?

это тебе так кажется. Ты путаешь «я не знаю как» с «это невозможно».

Это невозможно, и это доказано.

Если это возможно - предъяви мне, пожалуйста, алгоритм, который это сделает.

ещё раз повторяю: рекурсия всегда сворачивается в цикл.

Во-первых, нет, не всегда. Во-вторых - нет, там нету рекурсии.

Однако код может быть представлен в конечном виде всегда.

Нет. И, что характерно - интерпретаторы этого не делают.

anonymous
()
Ответ на: комментарий от emulek

А этот твой eval == типичная рекурсия.

Где ты там увидел рекурсию вообще?

anonymous
()
Ответ на: комментарий от emulek

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

Вот беда - у нас цикл бесконечный!

anonymous
()
Ответ на: комментарий от quasimoto

(1) — интерпретатор по мере исполнения своей программы работает с состоянием своей машины (регистры, адреса, эффекты исполнения), транслятор занимается синтаксическим переводом своей программы (имена, метки, генерация инструкций).

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

Т.е. компилятор сначала просматривает цикл, а потом его переводит в машкод, и его записывает на диск.

Интерпретатор делает также, однако этот код никуда не пишет, а просто выполняет.

(2) — интерпретатору HLT это остановка, транслятору — нет, просто очередной кусок терма для перевода.

это особенности реализации. Команда HLT вообще не имеет отношения к алгоритмам.

(3) — если выполнение интерпретатором закончится, то останется конечное состояние машины, трансляция всегда закончится, результатом будет выхлоп в виде перевода исходной программы.

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

(4) — обход программы интерпретатором начинается с исходного состояния машины и продолжается сколько угодно долго, это её выполнение, в т.ч. с зависаниями, у транслятора — один проход по программе за конечное время, это перевод, он начинается с одной программы и даёт в итоге другую.

это тоже неверно. Например bash откажется выполнять цикл, в котором нет done. Даже не начнёт. Что доказывает тот факт, что bash сначала транслирует цикл, а уже потом его выполняет. Как и компилятор.

И ты не отвечаешь про интерпретатор лямбда-исчисления с арифметикой — если что, то мне в x86_64, пжалуста, без стека, но с аллокацией регистров и оптимизацией выражений :)

что, попроще никак не придумать? Думаешь я тебе лютую ☣ написать не смогу, которую тебе будет не распарсить?

И про реальные(tm) интерпретаторы с компиляторами тоже ничего не рассказываешь

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

emulek
()
Ответ на: комментарий от anonymous

на каждой итерации надо перескакивать на _новый_ кусок машкода.

зачем, если там тоже самое?

И там не может быть ничего другого, ибо этот кусок == результат трансляции одного и того же.

2. прыгнули на сгенеренный машкод, вывели число, прыгнули на конец цикла

ой, а можно подумать, что push умеет заталкивать в стек только предварительно вычисленные константы! ВНЕЗАПНО: она когда-то вообще константы не умела, а только регистры. А что-бы сделать из регистра с 123 регистр 124 не нужен специальный код. Достаточно одной команды INC на любое число.

emulek
()
Ответ на: комментарий от emulek

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

Нет, это не так для любого языка с евалом. Ладно там всякие лиспы, пехепе с джаваскриптом для тебя тоже не существует?

anonymous
()
Ответ на: комментарий от emulek

зачем, если там тоже самое?

Затем, что там НЕ то же самое.

ой, а можно подумать, что push умеет заталкивать в стек только предварительно вычисленные константы! ВНЕЗАПНО: она когда-то вообще константы не умела, а только регистры. А что-бы сделать из регистра с 123 регистр 124 не нужен специальный код. Достаточно одной команды INC на любое число.

А это тут при чем? Да, push может заталкивать регистр. Но у нас проблема в том, что она заталкивает число. Каждый раз разное. Если бы она заталкивала регистр - проблем бы и не было, ты прав. Но она заталкивает число.

anonymous
()
Ответ на: комментарий от quasimoto

Ты путаешься в примитивных вещах, т.к. у тебя нет опыта разработки хоть сколько-нибудь сложного транслятора (brainfuck'и не считаются, а то может ты там напился, вот у тебя «компилятор/интерпретатор» и вышел).

а у тебя _вообще_ нет опыта разработки транслятора с нуля, от и до. Ты умеешь только придумывать AST, и вставлять их в чужую реализацию. Потому ты и не понимаешь, как эти твои AST работают на реальном железе. Т.е. ты как блондинка за рулём: умеешь руль крутить, умеешь педали нажимать, но если хоть одна гайка открутиться, то тебе остаётся только плакать и звонить в СТО. За то ты отлично и лучше меня знаешь, как проехать в фитнес-центр своего Мухосранска. А я — да, ламо. И Мухосранска не знаю.

emulek
()
Ответ на: комментарий от Twissel

Но действие то, одно и тоже - вывод символа. Причем здесь бесконечность?

он думает, что рекурсия == бесконечный и не сворачиваемый код.

emulek
()
Ответ на: комментарий от emulek

он думает, что рекурсия

Рекурсия есть только в твоих фантазиях. И, еще раз, не любая рекурсия сворачивается в цикл. С рекурсией можно сделать irreducible флоу-граф. Он не сворачивается в цикл.

anonymous
()
Ответ на: комментарий от anonymous

Без разворачивания нечего сворачивать. Чтобы получить свернутый машинный код, нам надо вообще для начала получить машинный код

нет, не надо. Точнее он у нас уже есть. Смотри: у тебя есть функция foo(const char*s), которая умеет обрабатывать твой ЯП (не важно как, исполнять/компилировать). Там нет eval. Дык в чём проблема? Если у тебя в s встретился eval, просто выполни foo(то_что_в_eval). В машинном коде есть такая команда, а именно CALL для x86.

Я так и не увидел проблемы.

emulek
()
Ответ на: комментарий от emulek

нет, не надо. Точнее он у нас уже есть.

Нету. Откуда он возьмется?

Дык в чём проблема? Если у тебя в s встретился eval, просто выполни foo(то_что_в_eval). В машинном коде есть такая команда, а именно CALL для x86.

Ну да, именно так оно и работает. Но это ничего не меняет.

Я так и не увидел проблемы.

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

anonymous
()
Ответ на: комментарий от anonymous

То есть в твоем коде был цикл. Просто ты его записал в виде рекурсии.

в твоём коде есть eval, а это и есть рекурсия самого компилятора/интерпретатора. Т.е. цикл. Да, что-бы сделать хороший, годный eval, тебе придётся тащить весь компилятор/интерпретатор с собой и со своим кодом. Потому в C/C++ нет такого «хорошего и годного eval».

Итерации никогда не повторяются (т.к. в машкоде разные константы).

кто сказал, что это константы? Это для printf они константы, а для main это одна и та же переменная i, которая постоянно инкрементируется.

emulek
()
Ответ на: комментарий от anonymous

как видишь на каждом шаге генерируется свой код.

Язык: racket

и что? Тупой интерпретатор не осилил понять, что это цикл? Ну выкинь его на помойку. Такой код даже первоклассник свернёт в цикл.

emulek
()
Ответ на: комментарий от anonymous

При чем тут main и printf? При чем тут вообще твоя сишка?

дык ты начал с push i/printf. Я тебе и объясняю, что раз ты написал push, то будь любезен написать pop. Т.к. printf этого не сделает сама.

Если это возможно - предъяви мне, пожалуйста, алгоритм, который это сделает.

разворот рекурсии в цикл? Да легко: call на саму себя это и есть аналог jmp на саму себя, если допустить бесконечность стека. И любая рекурсия это тоже цикл, только в собственном локальном контексте.

ещё раз повторяю: рекурсия всегда сворачивается в цикл.

Во-первых, нет, не всегда.

контрпример?

Во-вторых - нет, там нету рекурсии.

а что по твоему eval, как не рекурсия?

Однако код может быть представлен в конечном виде всегда.

Нет.

контрпример?

И, что характерно - интерпретаторы этого не делают.

даже bash делает. Ну а твоя ракета — увы, из 70х годов прошлого века. Выполняет по одной команде, тупо, как QBASIC из Windoows95.

emulek
()
Ответ на: комментарий от emulek

и что? Тупой интерпретатор не осилил понять, что это цикл?

_ни один_ интерпретатор не осилит понять, что это цикл.

Такой код даже первоклассник свернёт в цикл.

Первоклассник - свернет. А вот компилятор/интерпретатор - не свернет, т.к. это алгоритмически неразрешимая задача. Такие дела.

Для того, чтобы было проще, видоизменим несколько задачу. Пусть у нас ест ф-я pi-n, которая принимает i и возвращает i-й знак десятичного представления числа пи. И пусть у нас есть 10 разных ф-й, которые принимают число и возвращают некоторый список, который представляет define-форму. Теперь делаем так:

(for ([i (in-naturals)])
    (eval ((eval (string->symbol (pi-n i))) i))
    (x))
теперь ф-и преобразования над i идут в порядке следования по номерам pi, соответственно, циклов здесь нету, в силу иррациональности pi.

Ах, да, самое прелестное - что в момент компиляции этого выражения сами номерные ф-и еще не определены, по-этому компилятор не может их проанализировать :(

anonymous
()
Ответ на: комментарий от anonymous

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

Вот беда - у нас цикл бесконечный!

если ты Бог, и будешь жить вечно, то разве тебе сложно найти/сделать бесконечную память для бесконечного цикла?

emulek
()
Ответ на: комментарий от emulek

даже bash делает.

Не делает. Знаешь, я могу понять, когда люди прост врут, но какой смысл врать глупо - когда тебя любой сразу уличит во лжи? Перестань так делать.

Т.к. printf этого не сделает сама.

Кто тебе сказал, что не сделает?

разворот рекурсии в цикл? Да легко: call на саму себя это и есть аналог jmp на саму себя, если допустить бесконечность стека. И любая рекурсия это тоже цикл, только в собственном локальном контексте.

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

контрпример?

Любой irreducible граф.

anonymous
()
Ответ на: комментарий от emulek

если ты Бог, и будешь жить вечно, то разве тебе сложно найти/сделать бесконечную память для бесконечного цикла?

Извини, но в машине тьюринга нету актуально бесконечной памяти :(

anonymous
()
Ответ на: комментарий от emulek

Когда транслятор зацикливается

Трансляторы не зацикливаются. И да, в этих вопросах ты точно теоретик-кун :) Ну то есть ты даже не представляешь как циклы компилируются на уровне манипуляций которые компилятор производит — апостериори только смотришь на вход и выхлоп. Посмотри хотя бы 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++ можно один в один переписать. И как его расширить до компилятора с аллокацией регистров и оптимизациями выражений?

(видимо древних книжек начитался)

Я дал три ссылки на книги про компиляторы — почему они древние и какие нужно читать вместо, в студию.

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от anonymous

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

Нет, это не так для любого языка с евалом. Ладно там всякие лиспы, пехепе с джаваскриптом для тебя тоже не существует?

они тоже сначала транслируют(циклы например), а потом уже исполняют.

emulek
()
Ответ на: комментарий от emulek

они тоже сначала транслируют(циклы например), а потом уже исполняют.

Нет :(

Проверь уже и перестань нести чепуху.

anonymous
()
Ответ на: комментарий от anonymous

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

в данном случае у тебя в рекурсивном коде меняется только одна, как ты говоришь «константа». Потому приведение тривиально: необходимо и достаточно использовать эту «константу» как аргумент функции. Т.е. код получается:

void foo(int i)
{
    printf("%d\n", i);
    foo(i+1);
}

А после TCO for(;; i++) printf("%d\n", i);

emulek
()
Ответ на: комментарий от emulek

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

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

anonymous
()
Ответ на: комментарий от emulek

а у тебя _вообще_ нет опыта разработки транслятора с нуля, от и до.

У меня-то есть.

Ты умеешь только придумывать AST, и вставлять их в чужую реализацию. Потому ты и не понимаешь, как эти твои AST работают на реальном железе.

Ты тоже не умеешь и не знаешь. Почему? А я так решил — ты решил что я знаю, а чего нет :)

quasimoto ★★★★
()
Ответ на: комментарий от anonymous

_ни один_ интерпретатор не осилит понять, что это цикл.

это самое обычное TCO. AFAIK скала его по стандарту обязана понимать. А gcc не обязан по стандарту, но всё равно делает (если оно даёт профиит конечно).

Первоклассник - свернет. А вот компилятор/интерпретатор - не свернет, т.к. это алгоритмически неразрешимая задача. Такие дела.

она «неразрешима» только в МТ на бесконечной ленте. В конечном случае — разрешима всегда.

Для того, чтобы было проще, видоизменим несколько задачу. Пусть у нас ест ф-я pi-n, которая принимает i и возвращает i-й знак десятичного представления числа пи.

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

Ах, да, самое прелестное - что в момент компиляции этого выражения сами номерные ф-и еще не определены, по-этому компилятор не может их проанализировать :(

потому что само число π точно не определено. Ни в какой момент. Ты можешь только конечный знак узнать за конечное время.

emulek
()
Ответ на: комментарий от anonymous

Не делает. Знаешь, я могу понять, когда люди прост врут, но какой смысл врать глупо - когда тебя любой сразу уличит во лжи? Перестань так делать.

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

Т.к. printf этого не сделает сама.

Кто тебе сказал, что не сделает?

это стандартные соглашения вызова функций. Делает pop тот, кто сделал push. Мне ещё раз продемонстрировать машинный код?

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

почему именно «портянку», а не исходник, из которого эта портянка получилась?

Любой irreducible граф.

бинарное дерево подойдёт?

emulek
()
Ответ на: комментарий от emulek

это самое обычное TCO.

При чем тут TCO? Тебя видимо, смутило, что там луп вызывается? Ну перепиши без лупа с for, это несущественно.

она «неразрешима» только в МТ на бесконечной ленте. В конечном случае — разрешима всегда.

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

НЕТ такой функции, и быть не может

Конечно, есть. Но, см. выше - даже если мы предположим, что нету, и ты будешь хуячить свой хитрый перебор - то ничего не меняется. Ведь мы точно знаем, что реальные интерпретаторы ТАК НЕ ДЕЛАЮТ.

Ты можешь только конечный знак узнать за конечное время.

А мне ничего другого и не надо. Только узнавать конечный знак за конечное время. На каждой итерации. И никаких проблем.

anonymous
()
Ответ на: комментарий от emulek

бесконечная лента уже не актуальна?

Она потенциально бесконечна. В каждый же момент времени у нас есть только конечный ее кусок и только с этим куском может работать машина тьюринга. То есть состояние МТ всегда конечно. По определению МТ.

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

anonymous
()
Ответ на: комментарий от emulek

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

И как это опровергает мои слова? Ну проверяет bash наличие done, и что?

Мне ещё раз продемонстрировать машинный код?

Машинный код чего? Твоих фантазий из сишки? Не надо, они не имеют отношения к разговору. Если мне надо будет для моей вм, чтобы был pop внутри функции - то он будет внутри функции.

почему именно «портянку», а не исходник, из которого эта портянка получилась?

Ну это и есть исходник. Портянка с кучей goto.

бинарное дерево подойдёт?

Нет, это reducible граф.

anonymous
()
Ответ на: комментарий от quasimoto

Трансляторы не зацикливаются. И да, в этих вопросах ты точно теоретик-кун :)

зацикливается. Например bash зацикливается на done, который передаёт управление на заголовок цикла. Если done не будет, то bash ни единой итерации не сделает.

Т.е. bash сначала транслирует цикл, а уже потом его выполняет. А не на оборот. Поэтому существует момент, когда цикл полностью и однозначно определён. Вот именно в этот момент его и нужно переводить в машинный код.

Интерпретатор — нет, какое нафиг «код никуда не пишет», если у него нет никакого кода, только выполнение (а это назначение интерпретатора).

«выполнение» это и есть машинный код. Ничего другого выполнять нельзя по определению.

ЩИТО? L ::= (PLS | DBL | JMP Number | HLT)* — грамматика языка в вопросе, изволь интерпретировать HLT как следует и компилировать так же.

HLT не относится к алгоритму, а относится к твоей реализации. Это «конечная точка из которой нет выхода». Дели на ноль сам.

У реализаций .NET, JVM и любых современных (продвинутых!) интерпретаторов обычно есть две трансляции и две интерпретации, то есть четыре _разных_ вещи, назови их.

я на собеседовании? Тогда встречный вопрос к тебе: какого цвета мой холодильник? Не знаешь? Следующий!

Компиляторы типа GCC транслируют код, ничего они потом его не выполняют.

интерпретаторы сами тоже ничего не выполняюют, а делегируют процессору выполнение(или LLVM).

emulek
()
Ответ на: комментарий от emulek

Поэтому существует момент, когда цикл полностью и однозначно определён. Вот именно в этот момент его и нужно переводить в машинный код.

Почему «по-этому»? Не существует такого момента, в том и дело.

anonymous
()
Ответ на: комментарий от emulek

вчера уже проверял.

Зачем ты снова врешь?

Мне для маздайщиков скриншот делать?

Так ведь не сделаешь.

anonymous
()
Ответ на: комментарий от anonymous

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

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

emulek
()
Ответ на: комментарий от anonymous

А после TCO for(;; i++) printf(«%d\n», i);

TCO не так работает. Ну да не суть, мы не о том.

вот как раз так оно и работает в итоге в данном конкретном случае. Выше я приводил пруф, с машинным кодом, в котором ты и можешь видеть этот цикл, из факториала. Тебе отмотать?

emulek
()
Ответ на: комментарий от emulek

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

Замечательно. Остается вопрос:

У нас есть интерпретируемая программа, какой ей соответствует машинный код? Откуда его взять?

Явно же откуда-то возможно, если интерпретатор является транслятор и программу транслирует?

anonymous
()
Ответ на: комментарий от emulek

вот как раз так оно и работает в итоге в данном конкретном случае.

Нет, не так.

Выше я приводил пруф, с машинным кодом, в котором ты и можешь видеть этот цикл, из факториала. Тебе отмотать?

А это и не ТСО. Это замена хвостовой рекурсии циклом.

anonymous
()
Ответ на: комментарий от anonymous

В бесконечной она неразрешима. Если ограничить размер ленты конечным числом - тоже неразрешима (т.к. при размере задачи n тебе потребуется памяти больше n, а ведь размер ленты ограничен n).

ты бредишь. В таком случае я «2+2=» не смогу вычислить, ибо «на этой ленте нет места для 4».

что НИ ОДИН из существующих интерпретаторов эту задачу не решает. Ведь такой интерпретатор будет выжирать аохуилибайты памяти и будет работать годами.

ну если пользоваться знаниями почерпнутыми из книжек 70х — да.

А мне ничего другого и не надо. Только узнавать конечный знак за конечное время. На каждой итерации. И никаких проблем.

и в чём твоя проблема? 10 знаков, 10 кодов. Запиши свои 10 кодов и получи желаемое.

emulek
()
Ответ на: комментарий от emulek

ты бредишь. В таком случае я «2+2=» не смогу вычислить, ибо «на этой ленте нет места для 4».

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

ну если пользоваться знаниями почерпнутыми из книжек 70х — да.

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

и в чём твоя проблема? 10 знаков, 10 кодов. Запиши свои 10 кодов и получи желаемое.

Так после десятого знака будет 11 знак. Потом 12. Потом 13. И так далее.

anonymous
()
Ответ на: комментарий от anonymous

Она потенциально бесконечна. В каждый же момент времени у нас есть только конечный ее кусок и только с этим куском может работать машина тьюринга. То есть состояние МТ всегда конечно. По определению МТ.

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

плохо у тебя. Если у нас есть лента длинной l, мы для решения задачи можем приклеить к ней конечный кусок n, и решить задачу. Ну и пофиг, что n=2^l. Да хоть l!.

emulek
()
Ответ на: комментарий от emulek

плохо у тебя.

Да нет, все хорошо.

Если у нас есть лента длинной l, мы для решения задачи можем приклеить к ней конечный кусок n, и решить задачу. Ну и пофиг, что n=2^l. Да хоть l!.

Именно так. Лента конечна в каждый момент времени, но бесконечна потенциально (мы можем подклеивать куски). И в таком предположении некоторые задачи - неразрешимы. Проблема останова, например.

anonymous
()
Ответ на: комментарий от emulek

зацикливается. Например bash

bash целиком не транслятор, а интерпретатор.

Если ты не заметил, то общепринятая терминология это языковой процессор — транслятор ИЛИ интерпретатор, интерпретатор — НЕ транслятор, трансляторы — компиляторы, декомпиляторы, ассемблеры, дисассемблеры и т.д.

Но ладно — считай, что во всех моих постах «транслятор» это «компилятор». Компиляторы не зацикливаются (если не брать разное суровое метапрограммирование). Интерпретаторы — зацикливаются, вполне.

«выполнение» это и есть машинный код. Ничего другого выполнять нельзя по определению.

У тебя машкод головного мозга, надо как-то лечить :)

Любая VM выполняет свои программы сама, без всяких машкодов. Как она это умудряется делать? Ну она написана и скомпилирована в твой машкод, работает и выполняет свои программы на своём уровне абстракции, сам машкод к этим программам не имеет отношения, но они вполне выполняются. Не имеет отношения — VM работает на любой архитектуре, железной реализации, ты можешь хоть на бумажке её выполнять, никаких интелов.

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

HLT не относится к алгоритму, а относится к твоей реализации. Это «конечная точка из которой нет выхода». Дели на ноль сам.

Мы написали интерпретатор и компилятор языка в котором есть HTL, вместо с DBL, PLS и JMP — четыре инструкции. Конечно, они имеют отношение самое прямое.

Но можно обобщить тезис — интерпретаторы зацикливаются (как в том — JMP зацикливает, HLT прерывает), трансляторы — нет (в компиляторе того примера JMP ничего не зацикливает, HLT ничего не прерывает).

я на собеседовании? Тогда встречный вопрос к тебе: какого цвета мой холодильник? Не знаешь? Следующий!

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

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

Вот опять. Ответь на вопрос про четыре вещи.

И ты сказал, что компиляторы что-то там выполняют, то есть ты имел в виду, что МЫ выполняем их выхлоп на CPU? Это ок. Но сами они ничего не выполняют.

(или LLVM).

Зачем LLVM? Причём тут LLVM вообще?

Доку по ссылке про циклы-то прочитал?

quasimoto ★★★★
()
Ответ на: комментарий от anonymous

Ну проверяет bash наличие done, и что?

а то, что когда bash увидит done, цикл будет полностью определён, а значит будет возможность его компиляции. Сделать «бесконечную портянку» у тебя не получится, т.к. цикл конечен и определён однозначно. Только на твоей ракете такое говно получается.

emulek
()
Ответ на: комментарий от quasimoto

Не имеет отношения — VM работает на любой архитектуре, железной реализации, ты можешь хоть на бумажке её выполнять, никаких интелов.

Я уже говорил ему про выполнение на бумажке. Ответ был ожидаем - «ололо борщевики, посчитай мне факториал гугола набумажке!».

anonymous
()
Ответ на: комментарий от anonymous

Ну это и есть исходник. Портянка с кучей goto.

только она не бесконечная, увы.

бинарное дерево подойдёт?

Нет, это reducible граф.

тогда что? Опять что-то бесконечное придумаешь?

emulek
()
Ответ на: комментарий от anonymous

Поэтому существует момент, когда цикл полностью и однозначно определён. Вот именно в этот момент его и нужно переводить в машинный код.

Почему «по-этому»? Не существует такого момента, в том и дело.

потому, что если bash просмотрит весь файл, и не найдёт done, то он рухнет с ошибкой. А если не рухнул, значит такой момент существует.

emulek
()
Ответ на: комментарий от emulek

а то, что когда bash увидит done, цикл будет полностью определён, а значит будет возможность его компиляции.

С чего ты взял? Из того, что баш проверяет наличие done совершенно никак возможность трансляции не следует. Racket, например, проверяет связанность переменных внутри модуля, и что?

Сделать «бесконечную портянку» у тебя не получится, т.к. цикл конечен и определён однозначно.

Конечно, получится, евал же в баше есть. Можно сделать такой же пример, как в Racket, и будет то же самое.

anonymous
()
Ответ на: комментарий от emulek

потому, что если bash просмотрит весь файл, и не найдёт done, то он рухнет с ошибкой.

И?

А если не рухнул, значит такой момент существует.

На основании чего сделан такой вывод? Если внутри цикла евал - то в момент просмотра цикла цикл не определен. Но баш прекрансо этот цикл выполняет. Так что практика опровергает твои глупые заявления.

Вообще чего ты уперся в этот done? Если я напишу программу которая проверяет в сфайле наличие вхождения слова «done» то эта программа с какого-то перепугу превратится в транслятор?

anonymous
()
Ответ на: комментарий от emulek

только она не бесконечная, увы.

Не бесконечная. Но в циклы не сворачивается.

тогда что? Опять что-то бесконечное придумаешь?

Нет, это графи в котором есть лупы с множественными тчоками входа.

for (...){
...
label 1;
...
if (...) goto 2;
...
}

for (...){
...
label 2;
...
if (...) goto 1;
...
}

вот тут будет irreducible flow graf

Теперь перепиши без гото.

anonymous
()
Ответ на: комментарий от anonymous

У нас есть интерпретируемая программа, какой ей соответствует машинный код?

если у тебя есть eval который eval'ит код, то машинное представление естественно зависит от данных, и в общем случае определено однозначно только лишь после однозначного определения входных данных. Либо тебе придётся тащить в своём коде свой компилятор/интерпретатор.

И что? В базу ЦАБ тоже нельзя сейчас записать дату твоей смерти, т.к. она не определена. Потому базы ЦАБ не существует, и быть не может, да?

Нет. База ЦАБ существует. И мало того, есть два решения этой проблемы:

1. не хранить данные о мёртвых (не юзать такой eval)

2. хранить NULL вместо даты смерти, и ставить её когда ты умрёшь. (делать eval когда нужно).

emulek
()
Ответ на: комментарий от emulek

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

Да определили мы входные данные, все в порядке (да, интерпретатор не является транслятором уже лишь только потому, что без входных данных работать не может, так что ты несешь заведомую чушь, мы просто пошли тебе навстречу и сейчас рассуждаем о сферическом в вакууме «а что если бы»). Машкод теперь как мне получить?

anonymous
()
Ответ на: комментарий от anonymous

А это и не ТСО. Это замена хвостовой рекурсии циклом.

это как раз TCO. А после неё замена на обычный цикл (когда ret меняется на jmp, этот jmp как раз и образует бесконечный цикл).

emulek
()
Ответ на: комментарий от emulek

это как раз TCO.

Нет, это не ТСО. Есть три разные оптимизации: ТСО, оптимизация хвостовой рекурсии и замена хвостовой рекурсии циклом.

anonymous
()
Ответ на: комментарий от anonymous

ты бредишь. В таком случае я «2+2=» не смогу вычислить, ибо «на этой ленте нет места для 4».

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

белены объелся? В моём компьютере память не ограничена?!

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

потому что прошло уже полвека, и за эти полвека придумали много нового. Потому вот и ЛОР работает на java. А если эту жабу писали-бы по книжкам 70х, то он бы годами посты отправлял и требовал эксабайты.

Так после десятого знака будет 11 знак. Потом 12. Потом 13. И так далее.

я всё равно не вижу проблемы. Ну запиши ты своё число в EAX, и будет у тебя работать до 2³²го знака, а дольше ты всё равно не проживёшь. Как и наша планета кстати.

emulek
()
Ответ на: комментарий от quasimoto

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

anonymous
()
Ответ на: комментарий от emulek

белены объелся? В моём компьютере память не ограничена?!

Конечно нет. Всегда можно подрубить еще один винт.

потому что прошло уже полвека, и за эти полвека придумали много нового

Это замечательно. Я готов услышать от тебя описание этих современных методов 21 века, которые позволяют решать алгоритмически неразрешимые задачи. Жду с нетерпением просто.

я всё равно не вижу проблемы. Ну запиши ты своё число в EAX, и будет у тебя работать до 2³²го знака, а дольше ты всё равно не проживёшь.

Ну замечательно, то есть ты согласен с тем, что мы получим бесконечную ациклическую последовательность?

anonymous
()
Ответ на: комментарий от anonymous

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

наша задача — разрешима. Всегда можно попросить систему выделить немножко байт для контекста. Потому можно преобразовывать любой код на любом ЯП в машинный, причём сам код преобразования будет иметь заранее известную длину. Просить байты у системы в рантайме мы будем только для данных, но не для машинного кода.

emulek
()
Ответ на: комментарий от emulek

А после неё замена на обычный цикл (когда ret меняется на jmp, этот jmp как раз и образует бесконечный цикл).

Не ret, а call. ret как был так и остается. И это (замена call на jmp) как раз и есть TCO.

anonymous
()
Ответ на: комментарий от emulek

наша задача — разрешима. Всегда можно попросить систему выделить немножко байт для контекста. Потому можно преобразовывать любой код на любом ЯП в машинный, причём сам код преобразования будет иметь заранее известную длину.

Замечательно. Раз она разрешима - я готов ознакомиться с алгоритмом. Ведь тебе, как человеку, обладающему знаниями 21 века не составит труда его привести?

anonymous
()
Ответ на: комментарий от quasimoto

Если ты не заметил, то общепринятая терминология

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

emulek
()
Ответ на: комментарий от emulek

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

Конечно, принципиальная. Компилятор - транслирует, интерпретатор - не транслирует.

Причём этот поток всегда(может быть) заранее ограничен, на любых данных. А значит, его всегда можно сохранить в файл, и выполнить потом, независимо от данных.

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

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

anonymous
()
Ответ на: комментарий от quasimoto

Компиляторы не зацикливаются (если не брать разное суровое метапрограммирование). Интерпретаторы — зацикливаются, вполне.

сами интерпретаторы тоже не зацикливаются. Зацикливается процессор, когда исполняет один и тот же код бесконечно. И не важно, чем этот код порождён, из файла, или из интерпретатора.

Любая VM выполняет свои программы сама, без всяких машкодов. Как она это умудряется делать?

при чём тут VM? Это тоже машина. И код у неё тоже машинный. Как и у процессора. Не вижу разницы.

Мы написали интерпретатор и компилятор языка в котором есть HTL

давай ещё обсудим, почему интерпретатор по другому делит на ноль, нежели компилятор? Ну нету результата у твоей HLT, стало быть и выполнения никакого нет(точнее «выполнение» тут не имеет смысла, как деление на ноль)

интерпретаторы зацикливаются

не. Интерпретатор штатно работает, выдавая одно и тоже.

Как и скомпилированный файл — выдаёт одни и те же байты JMP, которые процессор постоянно читает.

Причём современный интерпретатор в данном случае сначала посчитает байты JMP, а потом будет кормить процессор этими вычисленными байтами до бесконечности.

Ты можешь и компилятор также зациклить.

emulek
()
Ответ на: комментарий от emulek

Интерпретатоыр не зацикливаются. Компиляторы зацикливаются.

ОК, я все понял.

anonymous
()
Ответ на: комментарий от anonymous

Теперь перепиши без гото.

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

Хотя тут тоже непонятно: а почему твой говнокод нельзя скомпилировать С goto?

emulek
()
Ответ на: комментарий от emulek

давай ещё обсудим, почему интерпретатор по другому делит на ноль, нежели компилятор?

Не по-другому. Также.

anonymous
()
Ответ на: комментарий от emulek

Хотя тут тоже непонятно: а почему твой говнокод нельзя скомпилировать С goto?

Ты о чем? Речь шла о невозможности привести рекурсию к циклу. С гото, конечно, можно. С циклами (без гото) - нельзя.

anonymous
()
Ответ на: комментарий от emulek

ка что такое TCO? Я думал это tail call optimization, не?

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

anonymous
()
Ответ на: комментарий от anonymous

Конечно нет. Всегда можно подрубить еще один винт.

нельзя. Денег нет. Высылай, тогда утверждение будет доказано. ☺

Ну замечательно, то есть ты согласен с тем, что мы получим бесконечную ациклическую последовательность?

и что? Производящая рекуррентная функция будет конечной и очень простой. Причём её не нужно получать из портянки-кода, т.к. она записана в исходном тексте.

emulek
()
Ответ на: комментарий от emulek

и что?

У нас есть бесконечная ациклическая последовательность десятичных цифр. Теперь мы делаем 10 функций, каждая из которых принимает число и возвращает строку, которая потом пойдет на евал. Евал сгенерирует некоторый машкод и отдаст поток управления этому машкода. Т.к. последовательность применяемых ф-й ациклична - у тебя нету никакого способа «вынести» переменную i как ты сделал это с предыдущим примером. У тебя получится бесконечный ациклический машкод. Который никак не сделать конечным, не свернуть в цикл и т.п.

anonymous
()
Ответ на: комментарий от anonymous

Не ret, а call. ret как был так и остается. И это (замена call на jmp) как раз и есть TCO.

в данном случае ret тоже убирается. Т.к. он недостижим и вообще лишний.

emulek
()
Ответ на: комментарий от anonymous

Вот у тебя программа, входные данные, интерпретатор и логи его работы.

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

emulek
()
Ответ на: комментарий от emulek

в данном случае ret тоже убирается.

 8048468:	c3                   	ret  

не убирается

TCO (а так же оптимизация хвостовой рекурсии и сворачивание в цикл) вообще с ret никак не связаны. Такие дела.

anonymous
()
Ответ на: комментарий от emulek

Хорошо, и код интерпретатора.

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

anonymous
()
Ответ на: комментарий от anonymous

Ты о чем? Речь шла о невозможности привести рекурсию к циклу. С гото, конечно, можно. С циклами (без гото) - нельзя.

ну извини. Я не пользуюсь такими говноциклами, и не понимаю, на кой ляд они вообще нужны. ИМХО любой компилятор/интерпретатор имеет право отказаться жрать такое говно.

emulek
()
Ответ на: комментарий от anonymous

В качестве примера программы рассмотрим мой пример с евалом

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

Способ?

anonymous
()
Ответ на: комментарий от emulek

Я требую продолжения балета!

Ну ладно, JVM, .NET и реализации современных «интерпретируемых» языков.

Первая стадия трансляции — компиляция основного языка в байткод VM (сборка jar, например).

Это при сборке, AOT. Всё остальное — времени выполнения.

Первая стадия интерпретации — выполнение VM-ой этого байткода, VM написана на C или C++, скомпилирована и выполняется прямо на CPU, таким образом, «нативна», она использует функции рантайма, которые тоже нативны (аллокаторы, GC, операции примитивных типов и т.п.).

Вторая стадия трансляции — JIT, байткод, то есть IR VM, кроме непосредственно кода имеет связанную метаинформацию сохраняющую сведения о высоко-уровневых конструкциях, это позволяет VM при выполнении байткода детектировать впервые вызванные или просто горячие методы (типичный JIT) или горячие циклы (traced JIT), в этом случае нативный компилятор (который тоже часть рантайма) из IR в машинный код архитектуры на которой работает программа транслирует горячий байткод в машинный (со всякими оптимизациями опирающимися на информацию о текущем выполнении, которая есть у таких систем, в отличии от схемы AOT-сразу-в-машкод).

Вторая стадия интерпретации — нативное выполнение машкода от JIT, то есть VM/CPU в mixed-mode execution байткод/машкод.

А вот типичные компилируемые языки (C, C++) — только первая стадия сразу в машкод, и последняя на CPU. И LLVM тоже не VM (должно быть понятно).

А bash просто имеет перед своей стадией интерпретации стадию проверки (компиляции в IR?), если это понимать/различать, то в разговоре про непосредственно интерпретацию не должно быть спрыгиваний на проверки/пре-трансляцию (тут вторая цитата это же поясняет).

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от emulek

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

Да ради бога. Напоминаю - речь шла о приведении рекурсии к циклу. Ты сказал, что он всегда приводится. Я тебя поправил - нет, не всегда. На этом можно окончить разговор о рекурсии, циклах и гото.

anonymous
()
Ответ на: комментарий от anonymous

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

ок.

Но это не меняет тот факт, что любую рекурсию можно свернуть в цикл, только потребуется хранить n контекстов в памяти. Но это не важно, т.к. при рекурсии эти n контекстов тоже хранятся в стеке.

Этот ваш eval тоже можно свернуть, если он определён с точностью до кода (т.е. если там меняются только переменные).

Если ты используешь суперкомпиляцию в eval, то придётся тащить за собой твой суперкомпилятор, да.

emulek
()
Ответ на: комментарий от emulek

Но это не меняет тот факт, что любую рекурсию можно свернуть в цикл

Нет, нельзя если граф irreducible

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

И я все еще жду от тебя способ получения машкода.

anonymous
()
Ответ на: комментарий от anonymous

У нас есть бесконечная ациклическая последовательность десятичных цифр. Теперь мы делаем 10 функций, каждая из которых принимает число и возвращает строку, которая потом пойдет на евал. Евал сгенерирует некоторый машкод и отдаст поток управления этому машкода. Т.к. последовательность применяемых ф-й ациклична - у тебя нету никакого способа «вынести» переменную i как ты сделал это с предыдущим примером. У тебя получится бесконечный ациклический машкод.

не получится. Я использую массив указателей на 10 функций. Плюс ещё одну функцию, которая просматривает цифры в аргументе, и выполняет нужные функции в зависимости от цифр.

Это между прочим вполне рабочий код, именно так выполняются виртуальные функции в C++. Т.е. каждый объект класса хранит указатель на таблицу, а в таблице хранятся указатели на функции. В итоге, прямо в рантайме для каждого объекта подставляется своя виртуальная функция.

И ВНЕЗАПНО: процессоры IA-32/amd64 отлично с этим работают. Там и команды для этого есть специальные, для дважды косвенного перехода.

emulek
()
Ответ на: комментарий от anonymous

не убирается

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

emulek
()
Ответ на: комментарий от emulek

сами интерпретаторы тоже не зацикливаются

Ну CPU-то (интерпретатор же) зацикливается?

И не важно, чем этот код порождён, из файла, или из интерпретатора.

Мысленный эксперимент — бубунта без gcc и бинарным пакетом с каким-нибудь интерпретатором. Машинный код последнего есть. Машинного кода программ которые он интерпретирует нет (и не может быть, компилятора-то нет), если только ты не считаешь, что постоянный код самого интерпретатора это он и есть (но это не дело).

при чём тут VM? Это тоже машина. И код у неё тоже машинный. Как и у процессора. Не вижу разницы.

С любым интерпретатором связана VM. Её код это интерпретируемые программы. Машинный код он у скомпилированной реализации для запуска.

Ну нету результата у твоей HLT, стало быть и выполнения никакого нет

А у компилятора есть. В смысле, он на HLT смотрит совсем иначе.

Ты можешь и компилятор также зациклить.

gcc можешь? Иди на багтрекер тогда.

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от anonymous

Да ради бога. Напоминаю - речь шла о приведении рекурсии к циклу. Ты сказал, что он всегда приводится. Я тебя поправил - нет, не всегда.

причём показал почему-то цикл, а не рекурсию. Ты ничего не перепутал?

emulek
()
Ответ на: комментарий от anonymous

Ты же говоришь о том, что можно любую рекурсию привести к хвостовому виду

не правда. Я такого не говорил. Спорь дальше сам с собой.

emulek
()
Ответ на: комментарий от emulek

Плюс ещё одну функцию, которая просматривает цифры в аргументе, и выполняет нужные функции в зависимости от цифр.

Не получится. Каждая из 10 ф-й генерирует _код_, генерирующий функцию, а не саму ф-ю. То есть самих ф-й будет не 10 разных, а бесконечное число (своя ф-я на каждую итерацию).

anonymous
()
Ответ на: комментарий от quasimoto

А у компилятора есть. В смысле, он на HLT смотрит совсем иначе.

ну сделай в компиляторе HLT, что-бы смотрел также. Можешь там матерное слово по-русски написать прямо в коде. Чем тебе не HLT в компиляторе?

Ты можешь и компилятор также зациклить.

gcc можешь?

код чуток подправить, что-бы как в компиляторах, не останавливался, а дальше компиллил.

emulek
()
Ответ на: комментарий от emulek

Я такого не говорил.

Конечно не говорил, ты просто перепутал приведение рекурсии к хвостовому виду (которое возможно выталкиванием фреймов со стека на хип) и приведением рекурсии к циклу (которое невозможно).

anonymous
()
Ответ на: комментарий от anonymous

Врешь:

ещё раз повторяю: рекурсия всегда сворачивается в цикл.

это ты врёшь, когда приписываешь мне слова «не выделять стекфреймы вообще». И слова «к хвостовому виду (CPO transformation)». Это ты их придумал. Я говорил «в цикл», но про CPO — твоя фантазия.

emulek
()
Ответ на: комментарий от anonymous

и приведением рекурсии к циклу (которое невозможно).

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

emulek
()
Ответ на: комментарий от emulek

причём показал почему-то цикл, а не рекурсию. Ты ничего не перепутал?

Я показал гото, то есть хвостовую рекурсию. Циклы там постольку поскольку (можно было вместо них сделать другие гото, то есть рекурсию).

anonymous
()
Ответ на: комментарий от emulek

И слова «к хвостовому виду (CPO transformation)». Это ты их придумал. Я говорил «в цикл», но про CPO — твоя фантазия.

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

anonymous
()
Ответ на: комментарий от emulek

возможно, хотя и требует явного выделения памяти

Нет, ты сейчас говоришь про CPO (которое возможно, но при явном выделении памяти), а приведение к циклу - невозможно в случае irreducible графа. Сколько уже можно?

anonymous
()
Ответ на: комментарий от emulek

ну сделай в компиляторе HLT, что-бы смотрел также.

Тогда это сломает компилятор, так как семантика перестанет сохраняться. Он _должен_ смотреть иначе.

код чуток подправить, что-бы как в [dafaq], не останавливался

Код gcc поправить? Перестанет быть компилятором, говорю же.

Ну и вообще — майнтейнер собрать программу хочет, юзеры новую версию ждут, а ты предлагаешь чтобы gcc не останавливался.

дальше компиллил

Это win ящитаю.

чуток

Специалисты по коду gcc в треде.

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от quasimoto

а ты предлагаешь чтобы gcc не останавливался.

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

anonymous
()
Ответ на: комментарий от anonymous

-- Компилятор можно зациклить.

-- gcc можно?

-- Код чуток подправить, чтобы как в интерпретаторах, не останавливался, а дальше компилировал.

«как в интерпретаторах» «дальше компилировал» особо доставляет.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Только грим чуток подправить.

anonymous
()
Ответ на: комментарий от quasimoto

И последнее яблоко раздора, господа!

Как какому классу, в свете этого топика, отнести язык Forth?!

Думаю, что этот вопрос всех примирит :)

Twissel ★★★★★
()
Ответ на: комментарий от Twissel

http://en.wikipedia.org/wiki/Bytecode

Тут вики глаголит, что построением AST занимаются только Перл и Руби перед выполнением... А все остальные транслируют в оптимизированный байт-код и выполняют.

Вся соль в том, насколько я понимаю, что машкода не существуют с точки зрения программы внутри ВМ, есть только байт-код, который и выполняется на ЦП через ВМ, но уже как машкод, с точки зрения железа.

Различных людей со Scheme головного мозга,создавших всякие там Racket'ы, мы рассматривать не будем.

Уже и так, по-моему, заговорились.

Слава Богу, что без баттхерта :)

Twissel ★★★★★
()
Последнее исправление: Twissel (всего исправлений: 1)
Ответ на: комментарий от Twissel

Форт это среда со всем вместе.

http://www.wulfden.org/downloads/Forth_Resources/LB__StartingForth.pdf

Compile

to generate a dictionary entry in computer memory from source text (the written­out form of a definition). Distinct from «execute.»

Execute

to perform. Specifically, to execute a word is to perform the operations specified in the compiled definition of the word.

Interpret

(when referring to Forth's text interpreter) to read the input stream, then to find each word in the dictionary or, failing that, to convert it to a number.

Dictionary

in Forth, a list of words and definitions including both «system» definitions (pre­defined) and «user» definitions (which you invent). A dictionary resides in computer memory in compiled form

When you define a new word, Forth translates your definition into dictionary form and writes the entry in the dictionary. This process is called «compiling.»

The text interpreter scans the input stream, looking for strings of characters separated by spaces. When a string is found, it is looked up in the dictionary. If the word is in the dictionary, it is pointed out to a word called EXECUTE. EXECUTE executes the definition (in this case an asterisk is printed). Finally, the interpreter says everything's «ok.»

The text interpreter finds the colon in the input stream, and points it out to EXECUTE. The compiler translates the definition into dictionary form and writes it in the dictionary. When the compiler gets to the semicolon, he stops, and execution returns to the text interpreter, who gives the message ok.

Например

http://github.com/AlexandreAbreu/jonesforth/blob/master/jonesforth.S

http://github.com/AlexandreAbreu/jonesforth/blob/master/jonesforth.f

(IR или машкод или «почти» машкод)

Какой-нибудь SBCL — тоже подобная среда, если в общем.

quasimoto ★★★★
()
Ответ на: комментарий от Twissel

Тут вики глаголит, что построением AST занимаются только Перл и Руби перед выполнением...

Не построением, а выполнением посредством обхода AST (вместо генерации и интерпретации байткода — в таком случае AST строить всё равно типично перед тем как начать трансляцию в байткод).

машкода не существуют с точки зрения программы внутри ВМ, есть только байт-код, который и выполняется на ЦП через ВМ, но уже как машкод, с точки зрения железа.

Ну примерно так, да. VM работает на CPU, выполняет свои байткод-программы (полученные заранее компиляцией), если JIT компилирует (во время выполнения) их в машкод, то получается смешанное выполнение CPU -> VM -> байткод, CPU -> машкод от JIT.

Различных людей со Scheme головного мозга,создавших всякие там Racket'ы, мы рассматривать не будем.

Так они чем-то принципиально отличаются хоть от всего остального?

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от quasimoto

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

Ладно там метапрограммирование с ним то понятно.

Но здесь это ведь создает дополнительные накладные расходы на выполнение.

С какого горя, спрашивается?

Для таких вот ветвистых тредов, как этот?

то получается смешанное выполнение CPU -> VM -> байткод, CPU -> машкод от JIT.

Тут действительно машкода в его чистом виде нет, ибо машкод это ВМ, которая крутится как белка в колесе, выполняясь на ЦП, очевидно.

С этим я полностью согласен.

Именно на этой оптимистической ноте и хотелось бы завершить топик.

Twissel ★★★★★
()
Последнее исправление: Twissel (всего исправлений: 2)
Ответ на: комментарий от anonymous

Я показал гото, то есть хвостовую рекурсию. Циклы там постольку поскольку (можно было вместо них сделать другие гото, то есть рекурсию).

тогда покажи нормальный пример, а не это говно.

emulek
()
Ответ на: комментарий от quasimoto

Ну и вообще — майнтейнер собрать программу хочет, юзеры новую версию ждут, а ты предлагаешь чтобы gcc не останавливался.

ну. и?

emulek
()
Ответ на: комментарий от Twissel

на одно и тоже действие динамически генериться разный код?

Так это в любом языке с eval и JIT так будет. На C/C++ можно написать такой же код (то есть тупо — в цикле забивать всё время разный машкод в буфер, кастовать его к типу функции и вызывать на каждой итерации). Почему новый код генерится каждый раз — потому что попросили, вот он и генерится :)

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

А как компилятор должен отличить виснущую программу от невиснущей?

ну если quasimoto захерачил HLT, то программа должна зависнуть, и пофиг, компилятор/интерпретатор. Во имя вселенской эквивалентности.

emulek
()
Ответ на: комментарий от emulek

H[A]LT

зависнуть

Англо-русский словарь тебе в руки.

пофиг, компилятор/интерпретатор

Компилятор не умеет виснуть, такие дела.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

КПД такого компилятора ровно ноль.

ну и что? Я разве где-то говорил про КПД?

emulek
()
Ответ на: комментарий от quasimoto

Англо-русский словарь тебе в руки.

дык я тебе ещё раз(очередной) говорю: напиши русское матерное слово в любом месте кода, и будет тебе «HLT» в твоём компиляторе.

Компилятор не умеет виснуть, такие дела.

словарь тебе в руки: у слова halt нет значения «зависнуть». Это скорее «hang».

emulek
()
Ответ на: комментарий от quasimoto

Хм, а я опять приметил маленькое жульничество :)

Тут ведь меняются только операнды, а не опкоды, т.е. нельзя сказать, что код меняется.

Требую сатисфакции в виде ассемблерных ( ;) ) примеров кода, где именно он изменяется, а не данные (аргументы), которые он обрабатывает!

Иначе признавайте, что выкладки анонима, по крайней мере неполны :)

А то и вовсе поражение последнего в данном вопросе.

Twissel ★★★★★
()
Последнее исправление: Twissel (всего исправлений: 1)
Ответ на: комментарий от emulek

HLT относительно того языка это такая же штука как return statement из C, который return [<expr>]; — нам в компиляторе не «останавливаться» или «возвращать» надо, нам транслировать нужно и продолжать. А при выполнении уже останавливаться/возвращать.

Я разве где-то говорил про КПД?

Он вообще не выполняет своей функции, если есть компилятор, то он не виснет, если он виснет то это уже не компилятор, или как минимум сломанный (то же при несохранении семантики).

quasimoto ★★★★
()
Ответ на: комментарий от Twissel

Опкодам тоже не проблема меняться.

Требую сатисфакции в виде ассемблерных ( ;) ) примеров кода

Ну

то есть тупо — в цикле забивать всё время разный машкод в буфер, кастовать его к типу функции и вызывать на каждой итерации

Вперёд :)

А так — трейсинг машинного кода REPL-а вообще одна большая каша из никак не упорядоченного кода — придумал функцию, написал, скомпилировал, выполнил и т.д., всё это в рамках одного выполнения одной и той же машины, при том что исходный код последней конечен и обозреваем («одно и тоже ``действие``» — «динамически генериться разный код»).

quasimoto ★★★★
()
Ответ на: комментарий от Twissel

Отличаются тем, что непонятно с какой целью создавался язык, где при определенных условиях, на одно и тоже действие динамически генериться разный код?

Не отличаются. В любой языке с евалом и джитом будет точно так же генериться разный код.

С какого горя, спрашивается?

Просто код так написан. Если написать без евала - ничего генериться не будет. А евал дефайн-формы это по сути команда вм «создать код для указанной ф-и». Что ей сказали, то она и делает :)

На практике можно, например, в рантайме создавать код заточенный под конкретные входные данные, компилировать его и за счет этого получать профит в производительности.

anonymous
()
Ответ на: комментарий от emulek

тогда покажи нормальный пример

for (...){
    ...
    label 1;
    ...
    if (...) goto 2;
    ...
}

for (...){
    ...
    label 2;
    ...
    if (...) goto 1;
    ...
}

->

label start1:
    ...
    label 1;
    ...
    if (...) goto 2;
    label end1:
    ...
    if (...) goto start1

label start2:
    ...
    label 2;
    ...
    if (...) goto 1;
    label end2:
    ...
    if (...) goto start2;

->

(define (label-start1)
  ...)

(define (label1)
  ...
  (if (...) (label2) (label-end1)))

(define (label-end1)
  ...
  (if (...) (label-start1) (label-start2)))

(define (label-start2)
  ...)

(define (label2)
  ...
  (if (...) (label1) (label-end2)))

(define (label-end2)
  ...
  (if (...) (start2) (void)))

вот тебе на рекурсии тот же код что и с гото.

anonymous
()
Ответ на: комментарий от Twissel

Тут ведь меняются только операнды, а не опкоды, т.е. нельзя сказать, что код меняется.

Так по факту меняется вся машинная комманда.

> (for ([i (in-range 2 10])
    (eval `(begin (define (f x) (,(if (even? i) 'unsafe-fx+ 'unsafe-fx*) x ,i)) (f 0)))
    (decompile f #:size 46))

00000000  488943F8          mov [rbx-0x8],rax
00000004  4883C3F8          add rbx,byte -0x8
00000008  488B4308          mov rax,[rbx+0x8]
0000000C  4883C004          add rax,byte +0x4
00000010  4C8B75C8          mov r14,[rbp-0x38]
00000014  4883C428          add rsp,byte +0x28
00000018  5F                pop rdi
00000019  5E                pop rsi
0000001A  5B                pop rbx
0000001B  5D                pop rbp
0000001C  C3                ret

00000000  488943F8          mov [rbx-0x8],rax
00000004  4883C3F8          add rbx,byte -0x8
00000008  488B4308          mov rax,[rbx+0x8]
0000000C  BA06000000        mov edx,0x6
00000011  4889C6            mov rsi,rax
00000014  48D1FE            sar rsi,1
00000017  480FAFF2          imul rsi,rdx
0000001B  4889F0            mov rax,rsi
0000001E  80C801            or al,0x1
00000021  4C8B75C8          mov r14,[rbp-0x38]
00000025  4883C428          add rsp,byte +0x28
00000029  5F                pop rdi
0000002A  5E                pop rsi
0000002B  5B                pop rbx
0000002C  5D                pop rbp
0000002D  C3                ret

достаточно разный код? :)

anonymous
()
Ответ на: комментарий от anonymous

Да, в системе отсчета «ракеты» (или как ее там) действительно генерится новый двоичный код.

Это я проморгал...

Убедили :)

Twissel ★★★★★
()
Последнее исправление: Twissel (всего исправлений: 1)
Ответ на: комментарий от emulek

Это код. Многоточия может заполнить чем угодно. Важен тут CFG, а не то, что конкретное делается.

anonymous
()
Ответ на: комментарий от Twissel

Да, в системе отсчета «ракеты» (или как ее там) действительно генерится новый двоичный код.

Не в ракете, а в _любом_ языке с евалом, который джитится/компилируется. Лиспы, питоны, пехепе, джаваскрипт, руби и так далее.

anonymous
()
Ответ на: комментарий от anonymous

Это я так написал из-за интуитивного желания все-таки свернуть код в цикл ;)

Twissel ★★★★★
()
Ответ на: комментарий от anonymous

Понятно. Просто в данном случае мы рассматривали ракету и к слову пришлось ведь.

Twissel ★★★★★
()
Ответ на: комментарий от anonymous

Поясните тупому, что делает данный код.

Как я понимаю там в сначала идет сложение

четного числа, потом умножение четного же (в диапазоне от 2 до 10), только не понял самого на себя или 4*6 (не особо вникал)?!

Тут правда и действия разные. Так что с форматом машинной команды более показательный пример. Без различных фокусов действительно код там будет разный, т.к. там операнд часть машинной команды.

Согласен с Вами :)

Еще раз спасибо за беседу, было интересно.

Twissel ★★★★★
()
Последнее исправление: Twissel (всего исправлений: 1)
Ответ на: комментарий от Twissel

Поясните тупому, что делает данный код.

Если число четное, то мы получаем функцию: (define (f x) (+ x const)), если нечетное: (define (f x) (* x const)). То есть вычисляется выражение (if (even? i) '+ '*) для данного i и подставляется его результат (который будет '+ для четного числа и '* для нечетного) в код, который потом уходит на компиляцию. const на каждом шаге своя, понятно. итерации начиная с 2, потому что для случаев 0 и 1 эта ф-я вообще ничего складывать и умножать не будет - компилятор оптимизирует и возвращает сразу ответ (0 или х).

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

ЗЫ: unsafe-fx там добавляется, чтобы код итоговый был короче, в обычных +/* много лишнего (проверки аргументов, диспатчинг по типу и т.п.), в unsafe-версиях этих лишних вещей нет.

anonymous
()
Ответ на: комментарий от Twissel

То есть число складывается или умножается само на себя?

Складывается либо умножается на аргумент функции.

anonymous
()
Ответ на: комментарий от anonymous

Если число четное, то мы получаем функцию: (define (f x) (+ x const)), если нечетное: (define (f x) (* x const)). То есть вычисляется выражение (if (even? i) '+ '*) для данного i и подставляется его результат (который будет '+ для четного числа и '* для нечетного) в код, который потом уходит на компиляцию. const на каждом шаге своя, понятно. итерации начиная с 2, потому что для случаев 0 и 1 эта ф-я вообще ничего складывать и умножать не будет - компилятор оптимизирует и возвращает сразу ответ (0 или х).

ладно.

Принято.

На кой ляд это надо на практике?

emulek
()
Ответ на: комментарий от anonymous

В общем то я понял.

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

В некотором роде это схоже с концепцией шаблонного метапрограммирования в крестах (?).

Принято. Убедили.

Twissel ★★★★★
()
Последнее исправление: Twissel (всего исправлений: 2)
Ответ на: комментарий от anonymous

Нельзя данной интерпретируемой программе сопоставить конкретную последовательность команд. То есть перевода просто не существует.

Берет и исполняется.

существует неточный перевод <> «не существует точного перевода».

какая-то последовательность команд ведь всё-равно «берётся и исполняется»?

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

получаешь «неточный перевод». и трудности перевода, ога.

китайская комната, например. в какой момент там возникает «понимание смысла»?

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

в каком конкретно месте «невозможно сопоставить» ??? логи есть, все ходы записаны. специально обученный человек сидит в комнате (возможно, таки китаец), выдаёт на-гора надмозгов креатифф.

как минимум я могу протабулировать и сопоставить Dreck In / Dreck out. тоже последовательность инструкций «достать из CAM-памяти (хеш-таблицы)», результат тот же самый.

далее, допустим делаем для этих логов вх/выход что-то вроде delta debugging , минимизируем тестовый набор. получаем остов данных, которые всё ещё чувствительны на результат. может, не глобальный минимальный набор, а хватит локального минимума.

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

про неразрешимость говорят, что данных получится слишком много. Point в том, чтобы их по ходу дела сокращать и отсекать, используя метамодели.

--
кто сказал «нет перевода»?? креатифф выхлопа есть, минимальный тестовый набор на входе тоже есть. это и есть «перевод».

«нет перевода» вообще и нет «понимания перевода» это две большие разницы.

понимания перевода, оставаясь в языке может и не быть — скорее всего, это какой-то метаязык, в котором оно есть.

то есть, метамодель.

anonymous
()
Ответ на: комментарий от anonymous

ср. «неточная метамодель», не формализованая окончательно, N-модели и т.п.

«не существует точной модели» <> «не существует метамодели» <> «не существует какой-то эвристической метамодели» вообще <> «не существует формализованной метамодели» <> «исполняется, но не модель» (потому что кроме моделей ты ничего исполнить и не можешь, программа сама по себе модель)

или ты утверждаешь, что никакого подобного метаязыка в принципе не возможно построить??? и можешь это доказать?

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

(1) интерпретатор исполняет программу на каких-то данных := интерпретатор исполняет программу на метаязыке данных <=> интерпретатор исполняет программу на метаязыке <=> интерпретатор исполняет метапрограмму (I проекция Футамуры)

(2) программа при подаче на вход данных программы исполняет интерпретатор на каких-то данных интерпретатора := программа на метаязыке данных исполняет интерпретатор на метаязыке данных интерпретатора <=> метапрограмма исполняет метапрограмму интерпретатора (II проекция Футамуры)

(3) исполняется транслятор,который переводит программу в более низкоуровневую <=> исполняется метапрограмма перевода1

(4) исполняется транслятор, который переводит данные программы в данные интерпретатора <=> исполняется метапрограмма перевода2

(5) исполняется транслятор, который переводит данные интерпретатора в данные программы <=> исполняется метапрограмма перевода3 (инвертированные вычисления)

... (6) делаем особую магию с (3),(4),(5), получаем II проекцию Футурамыамуры

понятно, что в зависимости от метаязыка, эти (1),(2),(3) (и (4),возможно и (5)) могут быть эквиваленты.

понятно, что если делать шаги слишком малого радиуса, получаем зацикливание (как в ультраметрических пространствах или порядковых числах).

anonymous
()
Ответ на: комментарий от anonymous

какая-то последовательность команд ведь всё-равно «берётся и исполняется»?

Ну да. Бесконечная. А конечного перевода нет.

anonymous
()
Ответ на: комментарий от anonymous

ср. «неточная метамодель»

Что такое «метамодель»?

Пожалуйста, не упоминай больше суперкомпиляцию, проекции Футамуры и термины и формлаьной логики до тех пор, пока ты с ними не разберешься.

anonymous
()
Ответ на: комментарий от anonymous

и что, бесконечная последовательность на языке не позволяет создать конечный перевод на метаязык?

про Горбачёва говорили анекдот про перевод на китайский «сяо» (начал трындеть) «се сяо» (закончил трындеть). вполне адекватный перевод, ящитаю.

вот тебе один такой метаязык: запустить под интерпретатором, протабулировать ВСЕ значения в CAM-память (мемоизовать и закешировать). потом выкинуть код и доставать из кеша.

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

среди них и надо искать. другой вопрос, КПД этого поиска.

anonymous
()
Ответ на: комментарий от Twissel

Вообще, то о чём говорил anonymous это общее свойство машин Тьюринга, архитектуры фон Неймана и т.п. Т.е. скорее правило, чем исключение — скорее трудно найти цикличный поток выполнения, чем наоборот. Т.е. у нас есть программа которая загружена в память в виде кода машины, это одна вещь, другая — поток команд при её выполнении, IP скачет по этому коду в памяти и получается совсем другой код, довольно часто не цикличный. Например, безусловный loop { ... } может быть цикличным, он же с условиями, даже простой loop { ... if(...) { ...; } else { .... } ... }, будет нецикличным (и легко может быть бесконечным), точнее, цикличность будет зависеть от поведения условий, которое зависит от внешнего входа и свойств обрабатываемых данных — любые интерактивные программы, любые сервера, любые числовые свойства (типа примера с пи) могут быть примерами.

А REPL + JIT просто добавляют тот факт, что в процессе выполнения может появляться ещё и _новый_ код по которому может прыгать IP.

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от quasimoto

IP скачет по этому коду в памяти и получается совсем другой код

ты так и не сказал, кто скрыл от тебя исходник программы? Почему ты гадаешь на кофейной гущемашинном коде?

emulek
()
Ответ на: комментарий от emulek

ты так и не сказал, кто скрыл от тебя исходник программы?

Никто не скрывал. Так как мне по исходнику программы получить машинный код, если интерпретатор - есть, а компилятора - нет?

anonymous
()
Ответ на: комментарий от emulek

Я про то что нецикличный бесконечный код в потоке выполнения (он же control flow) это норма у всех машин для многих программ (твой исходный код) — цикличный-то проблема найти :)

quasimoto ★★★★
()
Ответ на: комментарий от anonymous
 
collection not found: "disassemble" in any of: (#<path:/home/tony/.racket/5.2.1/collects> #<path:/usr/share/racket/collects>) in: disassemble
> #<procedure:>>

Как подтянуть эту коллекцию (хидер-файл по-нашему)? Хочу глянуть на дамп в живую

Twissel ★★★★★
()
Ответ на: комментарий от anonymous

А если пишу так

(require disassemble)
(for ([i 10])
    (eval `(begin (define (x) ,i) (x))`
    (disassemble x #:size 26)))
Получаю:

ffi-obj: couldn't get "scheme_jit_find_code_end" from #f (/usr/bin/gracket: undefined symbol: scheme_jit_find_code_end)
Twissel ★★★★★
()
Ответ на: комментарий от Twissel

А откуда оно возникает? Телепаты в отпуске.

anonymous
()
Ответ на: комментарий от Twissel

Где ты это пишешь? В репле? В module-level оно и не будет работать. И что у тебя за кавычка в конце третьей строки?

anonymous
()
Ответ на: комментарий от Twissel

Все описанные проблемы возникают в гуевой среде DrRacket, я так понимаю это интерактивный интерпретатор?

Я просто думал, что кавычки должны быть парными, фейл.

Ваши строки ввожу там же, с директивой #lang racket

Twissel ★★★★★
()
Ответ на: комментарий от Twissel

Надо вводить в репле (часть окна снизу, с «>»), тогда должно все работать. #lang racket в репле вводить не надо.

anonymous
()
Ответ на: комментарий от Twissel

ffi-obj: couldn't get «scheme_jit_find_code_end»

Надо racket из git'а поставить. disassemble функцию уже из него берёт. Или ждать следующего релиза.

Или предыдущую версию disassemble из истории git'а вытащить

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

Спасибо. Но проще не морочить голову ради такой ерунды.

Twissel ★★★★★
()
Ответ на: комментарий от monk

Те же яйца, не дизассемблирует.

Пусть анон напишет свою версию среды и модуля.

А то меня начинают терзать смутные сомнения... :)

Twissel ★★★★★
()
Ответ на: комментарий от Twissel

Те же яйца, не дизассемблирует.

Возьми версию Racket отсюда: http://www.cs.utah.edu/plt/snapshots/ Тогда будет работать новая версия disassemble (параметр size тогда не нужен)

Или если хочешь точно как у анона, то бери disassemble с https://github.com/samth/disassemble/tree/ebf2e0c6c560a2af231e2dd7a770af95b67... (вручную замени файлы). Тогда ещё потребуется ndisasm: http://www.nasm.us/ (ну или смотри в дистрибутиве nasm)

monk ★★★★★
()
Ответ на: комментарий от monk

Да, теперь первый пример отработал, спасибо.

Twissel ★★★★★
()
Ответ на: комментарий от quasimoto

А можно на примере откомпилированной sbcl-ом программы и сохраненной в ELF. Это в какую степь?

anonymous
()
Ответ на: комментарий от anonymous

Выполнение в образе вообще и eval в частности будут делать всё что угодно (eval будет определённым образом интерпретировать код который делает всё что угодно в рамках образа, в т.ч. зовёт компилятор, опять eval и т.п.). Но это нормально. save-lisp-and-die будет делать вполне конкретную вещь. compile во время раскрытия макросов будет делать всё что угодно, но это типа местная особенность — писать базовый лисп в макросах лиспом как угодно, если это пропустить, то дальше компиляция базового лиспа вполне обычна.

P.S. sh (в т.ч. exec*), hibernate и make (пропустить — cc) :)

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Ну а как в целом описать процесс получения бинаря? Компиляция макросов и «базового» лиспа (и что-то мне подсказывает, что понятие неформализовано)? И дальнейшее обращение к интерпретатору (и, возможно компилятору) в образе.

Но, например, можно же (наверное долго и вряд ли кто-то делает) добавить к целям в исходниках pypy свой код и получить бинарь. По-моему довольно похоже («базовый лисп» <-> RPython). Но это же не делает из pypy _компилятор_ питона, почему же sbcl упорно называют компилятором. В любом случае, выхлоп «компилятора» не отпускается в свободное плавание, а обрабатывает вход под чутким контролем образа.

anonymous
()
Ответ на: комментарий от anonymous

Ну а как в целом описать процесс получения бинаря?

А зачем?

Всё это происходит как происходит — можно копаться в исходниках за подробностями, SBCL представляет из себя лисп-среду в которой полно всего, помимо непосредственно макро-экспандера, компилятора в IR1 и т.д. до машинного кода.

Компиляция макросов и «базового» лиспа (и что-то мне подсказывает, что понятие неформализовано)?

Понятие базового лиспа который остаётся после раскрытия макросов прекрасно описано в стандарте (и CLHS). И SBCL после раскрытия макросов и своих собственных макро-подобных определений занимается его компиляцией — не будь ясно что из себя представляет этот базовый лисп, непонятно было бы как составлять IR1.

В любом случае, выхлоп «компилятора» не отпускается в свободное плавание, а обрабатывает вход под чутким контролем образа.

Компилятор там обычный. Ну и везде так, то есть в любом рантайме оснащённым компилятором (JVM, .NET, Cling).

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

А зачем?

Очевидно зачем: чтоб прояснить суть определений компилятор/интерепретатор.

Компилятор там обычный. Ну и везде так, то есть в любом рантайме оснащённым компилятором (JVM, .NET, Cling).

Про компилятор никто не спорит. А вот значение рантайма, не уверен, что одно и то же (имхо наличие его принципиально, а jvm/.net + vm / vm + gc - несколько на другом уровне).

Так в контексте ваших определений pypy - _компилятор питона_? Если нет, то в чем принципиально отличие от sbcl.

anonymous
()
Ответ на: комментарий от anonymous

Очевидно зачем: чтоб прояснить суть определений компилятор/интерепретатор.

Так они и так ясны :)

Про компилятор никто не спорит. А вот значение рантайма, не уверен, что одно и то же

Ну если с компилятором всё понятно, то что ещё нужно? А VM/рантайм/железо это уже на тему выполнения (интерпретации в широком смысле).

Я как-то не вижу чем рантайм CL принципиально отличается.

Так в контексте ваших определений pypy - _компилятор питона_?

Я про него ничего не знаю.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Так они и так ясны :)

Так для себя же)

Я как-то не вижу чем рантайм CL принципиально отличается.

Тащит туда все, что было необходимо для компиляции. Аппелируя к: compilation : SourceLanguage -> TargetLanguage — чистая функция, есть соответствие. Если в рантайме в общем случае необходим(или ошибаюсь?) вызов компилятора или работа в режиме интерпретатора, то получается, что изначально была, по-крайней мере, _не полная_ компиляция.

anonymous
()
Ответ на: комментарий от anonymous

Тащит туда все, что было необходимо для компиляции.

Ну как и любой JIT? Компиляция в рантайме.

В CL раскрытие макросов, то есть произвольный пользовательский код входит в компиляцию, так что и его нужно тащить, но на это нужно смотреть как на написание этим произвольным кодом базового лиспа (по идее это должна быть честная пре-трансляция, то есть вполне предсказуемая, просто доопределяемая) который потом подвергается обычной компиляции. Где-то ещё тоже можно (хотя практически сложнее) написать произвольным кодом базовый код и подсунуть его компилятору в рантайме (через его API, как Cling, например, использует библиотеки Clang и LLVM).

compilation : SourceLanguage -> TargetLanguage — чистая функция, есть соответствие

Это упрощение, но у классической компиляции принцип примерно такой.

то получается, что изначально была, по-крайней мере, _не полная_ компиляция

Не очень понял. Вызов compile когда он происходит вполне полный, просто целиком CL это не только компилятор, но среда, ну как обычная ОС — можно в REPLе вызывать компиляцию сколько хочешь, писать новые программы, в т.ч. программы которые вызывают компилятор, вызывают новый сеанс REPL-а и т.д.

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от quasimoto

Вызов compile когда он происходит вполне полный, просто целиком CL это не только компилятор, но среда, ну как обычная ОС — можно в REPLе вызывать компиляцию сколько хочешь

Для нормального понятия компиляции существует понятие бинарного (выполняемого) кода, который может выполняться без наличия компилятора. UNIX тоже среда, но UNIX без компилятора Си может успешно работать. Может ли работать исполняемая среда Common Lisp без компилятора (либо интерпретатора) Common Lisp?

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

monk ★★★★★
()
Ответ на: комментарий от monk

UNIX без компилятора Си может успешно работать. Может ли работать исполняемая среда Common Lisp без компилятора (либо интерпретатора) Common Lisp?

Может (точка входа и шейкер, или по типу LLVM+Clang/GHC API с доступной разделяемой либой компилятора и т.п.), если нужен REPL/интерпретация — пусть будет интерпретатор (как bash и т.п.), если нужен REPL с JIT/компиляцией — пусть будет компилятор (аналогично с UNIX).

Если «обычная ОС» будет требовать наличия компилятора для функционирования, то я буду считать, что либо «ОС» не до конца скомпилирована, либо «компилятор» на самом деле интерпретатор

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

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от quasimoto

Ну ОС это типа рантайм, она уже скомпилирована нормально и работает, требует наличия компилятора в машкод не она, а возможности

Ну вот. А в случае интерпретатора (bash, например) или Common Lisp компилятор требуется всегда.

Может (точка входа и шейкер, или по типу LLVM+Clang/GHC API с доступной разделяемой либой компилятора и т.п.)

В смысле, компилятор в полном объёме? Тогда получается что программа требует компилятора во время выполнения, то есть с точки зрения использования — интерпретируется.

Или только используемые функции? Тогда что будет при выпадении в отладчик. Там же должен быть REPL? Или мы уже не про Common Lisp? В Scheme/Racket или Haskell — да, в рантайме есть только GC, то есть там однозначно компилятор.

monk ★★★★★
()
Ответ на: комментарий от monk

А в случае интерпретатора (bash, например) или Common Lisp компилятор требуется всегда.

Интерпретатору не требуется компилятор (в машкод). Он требуется для JIT, это дополнительная оптимизация, как и байткод, но без них всё равно возможна интерпретация.

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

Wut? Любое выполнение это интерпретация чего-то чем-то. А компилятор во время выполнения нужен... если он нужен, то есть если нашей системе нужен JIT.

Тогда что будет при выпадении в отладчик. Там же должен быть REPL? Или мы уже не про Common Lisp?

Там может ничего не быть (как в SBCL с ключами которые всё это отключают). Там может быть обычный интерпретатор, он же какой-то REPL. Если этот интерпретатор хочет JIT — тогда ему нужен компилятор.

В Scheme/Racket или Haskell — да, в рантайме есть только GC, то есть там однозначно компилятор.

У GHC есть API доступный в рантайме, то есть компилятор. Ну и я всё-таки не понял о чём мы тут — что значит «однозначно компилятор», в SBCL тоже однозначно компилятор, выполнение рантаймом и все-все-все.

quasimoto ★★★★
()
Ответ на: комментарий от monk

В Scheme/Racket или Haskell — да, в рантайме есть только GC, то есть там однозначно компилятор.

Чего? Евал ты куда потерял?

anonymous
()
Ответ на: комментарий от anonymous

Евал ты куда потерял?

В случае Scheme если явно в коде нет комбинации (eval ...) то вызвать его никак не сможешь. Поэтому его там нет. Также как из Си я могу вызвать system(«gcc ...»), но это надо явно написать в коде, а не необходимая возможность для любой программы на Си.

monk ★★★★★
()
Ответ на: комментарий от quasimoto

Интерпретатору не требуется компилятор (в машкод).

Я имел в виду, что может быть компилятор внутри интерпретатора (perl, python, bash). Пусть утверждение звучит так «в случае интерпетатора во время выполнения программы необходима доступность исполнителя, понимающего исходный язык программы».

Там может ничего не быть (как в SBCL с ключами которые всё это отключают).

Это уже нарушение стандарта. http://clhs.lisp.se/Body/09_a.htm

If there is no such handler and the signaling function is error or cerror, the debugger is entered in the dynamic environment of the signaler

если он нужен, то есть если нашей системе нужен JIT

JIT по-факту интерпретатор байткода VM (JVM или LLVM). Но компилятор исходного языка для JIT не нужен. Скажем в Racket в байткоде не будет половины переменных и почти всех констант, потому как оптимизация. А вот в CL необходим именно компилятор или интерпретатор CL при выполнении программы.

monk ★★★★★
()
Ответ на: комментарий от quasimoto

У GHC есть API доступный в рантайме, то есть компилятор.

На GHC программа может его не использовать и тогда его в рантайме (в бинарнике) не будет. Это аналог system(«gcc ...») для Си или линковки с libgcc.

monk ★★★★★
()
Ответ на: комментарий от monk

Пусть утверждение звучит так «в случае интерпетатора во время выполнения программы необходима доступность исполнителя, понимающего исходный язык программы».

Интерпретатор это и есть исполнитель понимающий исходный язык программы. Возможность заменить его предварительной компиляцией в другой язык который будет выполнять другой исполнитель это уже плюшки.

JIT по-факту интерпретатор байткода VM (JVM или LLVM).

Нет, сам по себе JIT это компилятор, а не интерпретатор. И когда я говорил JIT, я подразумевал рантайм компилятор в машинный код — в JVM и LLVM именно такой. JVM называют VM потому что там может отсутствовать JIT и VM будет интерпретировать/исполнять байткод, а в LLVM есть JIT и в этой части он вовсе не VM (хотя там EE вроде предоставляет возможность обычной интерпретации). Какого рода компилятором является JIT — из исходного кода или из байткода это уже зависит, в JVM это из байткода, в байткод компиляция ahead of time, в LLVM — из IR/биткода или средствами Clang+LLVM можно сделать прямо из исходного кода.

Это уже нарушение стандарта.

А вот в CL необходим именно компилятор или интерпретатор CL при выполнении программы.

Если хочется полноценной CL среды — конечно. Но не обязательно компилятор, может быть просто интерпретатор/VM, то есть как везде.

На GHC программа может его не использовать и тогда его в рантайме (в бинарнике) не будет. Это аналог system(«gcc ...») для Си или линковки с libgcc.

Да, хотя бы dlopen(3) & co.

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от quasimoto

Возможность заменить его предварительной компиляцией в другой язык который будет выполнять другой исполнитель

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

Нет, сам по себе JIT это компилятор, а не интерпретатор

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

Если хочется полноценной CL среды — конечно.

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

Какого рода компилятором является JIT — из исходного кода или из байткода это уже зависит

Не видел ни одного JIT из исходника.

или средствами Clang+LLVM можно сделать прямо из исходного кода.

В смысле в исполняемом файле код на Си и компилируется во время исполнения? А как такое там сделать?

monk ★★★★★
()
Ответ на: комментарий от monk

В случае Scheme если явно в коде нет комбинации (eval ...) то вызвать его никак не сможешь. Поэтому его там нет.

Есть. Исполнение программы это (семантически) евал форм. Когда рантайм видит (define ...), то выполняет определенные сайд-эффекты.

anonymous
()
Ответ на: комментарий от anonymous

Есть. Исполнение программы это (семантически) евал форм

Семантически — да. Но любую форму eval от константного выражения можно заменить на результат компиляции. Таким образом в скомпилированной программе ссылки на eval (и всё, от чего он зависит) уже нет.

Когда рантайм видит (define ...),

Например, в Racket в рантайме уже нет никаких define. Там есть только байт-код LLVM.

monk ★★★★★
()
Ответ на: комментарий от monk

Но он всё равно интерпретатор (так как исполняет код, а не формирует бинарный файл).

Ок, код от JIT выполняют процессор с рантаймом, вот

http://root.cern.ch/drupal/content/cling

Cling is an interactive C++ interpreter, built on the top of LLVM and Clang libraries. Its advantages over the standard interpreters are that it has command line prompt and uses just-in-time (JIT) compiler for compilation. Many of the developers (e.g. Mono in their project called CSharpRepl) of such kind of software applications name them interactive compilers.

ещё интерактивным компилятором обзывают.

По отношению к куску кода — да. По отношению к программе в целом — нет

Вот так это может выглядеть

http://llvm.org/docs/doxygen/html/classllvm_1_1ExecutionEngine.html

http://llvm.org/docs/doxygen/html/classllvm_1_1JIT.html

http://llvm.org/docs/doxygen/html/classllvm_1_1Interpreter.html

ExecutionEngine::getPointerToFunction реализует JIT::getPointerToFunction — он будет компилировать, чего не будет делать Interpreter, выполнять можно с помощью ExecutionEngine::runFunction, который реализуют и JIT::runFunction, который просто просто звонит на скомпилированный код, и Interpreter::runFunction, который интерпретирует биткод.

Не видел ни одного JIT из исходника.

А в SBCL не JIT из исходника? :)

В смысле в исполняемом файле код на Си и компилируется во время исполнения? А как такое там сделать?

У clang тоже есть API, так что прямо начиная с парсера и т.д.

quasimoto ★★★★
()
Ответ на: комментарий от monk

Но любую форму eval от константного выражения можно заменить на результат компиляции. Таким образом в скомпилированной программе ссылки на eval (и всё, от чего он зависит) уже нет.

Нельзя, я же могу взять и: (define eval 'huy)

Например, в Racket в рантайме уже нет никаких define.

Есть.

anonymous
()
Ответ на: комментарий от monk

А где в рантайме есть дефайн? Если бы в рантайме был дефайн, можно было бы сделать (define (codefine x y) (define x y)). Нигде его нет, не только в Racket. Вообще, в рантайме нет никаких спецформ и макросов.

anonymous
()
Ответ на: комментарий от anonymous

Исполнение программы это (семантически) евал форм

Исполнение программы это семантически тот eval, который сам есть исполнитель этой программы, который находится на верхнем уровне. А тебе, на уровне ЯП, могут предоставить и урезанную версию, и вовсе ничего.

anonymous
()
Ответ на: комментарий от anonymous

Нигде его нет, не только в Racket. Вообще, в рантайме нет никаких спецформ и макросов.

В Common Lisp есть. По любой неотловленной ошибке обязан вызываться отладчик с REPL. Где, в частности можешь и играться с командами типа (defmacro (mydefun &body body) `(cl:defun @,body)).

А в любом _компилируемом_ языке нет. Потому что в целевом языке (машинных кодов, LLVM, JVM) такой команды нет и реализовать её нельзя (требуется семантика не целевого, а исходного языка).

monk ★★★★★
()
Ответ на: комментарий от anonymous

А тебе, на уровне ЯП, могут предоставить и урезанную версию, и вовсе ничего.

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

anonymous
()
Ответ на: комментарий от anonymous

говорили о конкретном ЯП, где предоставляется нормальный евал

Вот дам я тебе бинарник, написанный на Racket — как ты сможешь там вызвать define?

monk ★★★★★
()
Ответ на: комментарий от monk

Вот дам я тебе бинарник, написанный на Racket — как ты сможешь там вызвать define?

Бинарников в Racket нет, компиляция идет в байткод. В байткоде есть дефайн-форма.

anonymous
()
Ответ на: комментарий от anonymous

Сколько 4.2 сразу.

Бинарников в Racket нет

Есть http://docs.racket-lang.org/raco/exe.html

компиляция идет в байткод

> (require disassemble)
> (define (f x) 1)
> (disassemble f)
       0: 488943f8                       (mov (mem64+ rbx #x-8) rax)
       4: 4883c3f8                       (add rbx #xfffffffffffffff8)
       8: b803000000                     (mov eax #x3)
       d: 4c8b75c8                       (mov r14 (mem64+ rbp #x-38))
      11: 4883c428                       (add rsp #x28)
      15: 415d                           (pop r13)
      17: 415c                           (pop r12)
      19: 5b                             (pop rbx)
      1a: 5d                             (pop rbp)
      1b: c3                             (ret)

В байткоде есть дефайн-форма.

И что? В бинарнике Си есть почти все ассемблерные команды. Значит ли это, что если я тебе дам прграмму /bin/true, то ты сможешь исполнить произвольный ассемблерный код? И кроме того байт, который можно декомпилировать в define != форма define. А то можно договориться до того, что любая программа на Си содержит в себе язык Си (ведь декомпилировать в какой-то степени можно).

monk ★★★★★
()
Ответ на: комментарий от monk

Есть http://docs.racket-lang.org/raco/exe.html

Этот бинарник содержит байткод с интерпретатором и джитом. Машкода в нем нету.

(require disassemble)

Да только этот машкод появляется исключительно при первом запуске ф-и. В ехешнике никакого машкода нет.

И что?

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

Раз ты не можешь отличить Racket по семантике от «чистого» интерпретатора то можно считать, чот дефайн-формы там етсь. Просто потмоу что язык и рантайм устроент так, чтобы ты _не мог_ построить контрпример.

anonymous
()
Ответ на: комментарий от anonymous

Конечно же есть. В рантайме есть символ #%define-values, с ним связана определенная семантика.

anonymous
()
Ответ на: комментарий от monk

И что? В бинарнике Си есть почти все ассемблерные команды. Значит ли это, что если я тебе дам прграмму /bin/true, то ты сможешь исполнить произвольный ассемблерный код?

скорее всего да, если это gnu /bin/true. В силу того, что её исходник содержит Over9000 строк на C (в развёрнутом виде).

emulek
()
Ответ на: комментарий от emulek

скорее всего да, если это gnu /bin/true.

А каким образом ты выполнишь этот произвольный код? Мне честно интересно. Есть бинарник gnu /bin/true. 16Kb ассемблерного кода. Твоя задача: с его помощью исполнить ассемблерный код, выводящий на stdout строку «Hello» (нужные ассемблерные команды там есть, так как справку он штатно выводить умеет). Твои действия?

monk ★★★★★
()
Ответ на: комментарий от monk

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

просто вместо этого:

Использование: /bin/true [ игнорируемые аргументы командной строки ]
       или:    /bin/true КЛЮЧ
Выходит с успешным статусом.
...
напишу в бинарнике Hello. Ну и сломаю вызов getopt(3), что-бы оно всегда --help отрабатывала.

emulek
()
Ответ на: комментарий от emulek

просто вместо этого ... напишу в бинарнике Hello

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

Хотя мне это доказательство напоминает доводы в пользу утверждения «неважно, на каком языке писать». Ведь все тьюринг-полные языки эквивалентны, а значит на брейнфаке или в машинных кодах можно написать всё, что можно написать на C++. И в этом смысле компилятор от интерпретатора не отличается вообще ничем. Только удобством изменения программы после запуска этой программы.

monk ★★★★★
()
Ответ на: комментарий от monk

Хотя мне это доказательство напоминает доводы в пользу утверждения «неважно, на каком языке писать». Ведь все тьюринг-полные языки эквивалентны, а значит на брейнфаке или в машинных кодах можно написать всё, что можно написать на C++. И в этом смысле компилятор от интерпретатора не отличается вообще ничем. Только удобством изменения программы после запуска этой программы.

ну вот. На восемнадцатой странице обсуждения, кто-то понял, о чём я говорил. Это радует.

emulek
()
Ответ на: комментарий от emulek

На восемнадцатой странице обсуждения, кто-то понял, о чём я говорил.

Наверное потому что люди определяют объекты по их свойствам, а не по сути. А твои утверждения выглядят как «Чем алмаз от угля отличается? --- Ничем: они состоят из одинаковых атомов. --- Но ведь алмаз твёрдый, а уголь мягкий. --- И что? Из угля можно получить алмаз при некоторых условиях, значит это одно и то же.».

А свойства интерпретатора и компилятора существенно отличаются.

monk ★★★★★
()
Ответ на: комментарий от monk

Наверное потому что люди определяют объекты по их свойствам, а не по сути. А твои утверждения выглядят как «Чем алмаз от угля отличается? --- Ничем: они состоят из одинаковых атомов. --- Но ведь алмаз твёрдый, а уголь мягкий. --- И что? Из угля можно получить алмаз при некоторых условиях, значит это одно и то же.».

ну дык алмаз и графит — действительно одно и то же. А «судят по свойствам» только неучи, которые не окончили даже 7 классов средней школы.

ИМХО свойства вторичны, их можно поменять, они и меняются. Современные интерпретаторы уже сейчас слишком близки к компиляторам (см JIT например. Или это ещё «интерпретация»? ИМХО — нет).

emulek
()
Ответ на: комментарий от emulek

ну дык алмаз и графит — действительно одно и то же.

Чтоб ты всю жизнь резал стёкла графитом. Что до топика: пока не запустишь линукс на си-интерпретаторе - в топик не возвращайся.

yyk ★★★★★
()
Ответ на: комментарий от yyk

Чтоб ты всю жизнь резал стёкла графитом.

я режу стёкла стеклорезом.

Что до топика: пока не запустишь линукс на си-интерпретаторе - в топик не возвращайся.

Может ты изучишь матчасть? Linux отлично работает без всяких компиляторов. Вот как раз для работы необходимы интерпретаторы, не сишные конечно, а gawk/sed/sh, и т.п.

emulek
()
Ответ на: комментарий от anonymous

Ещё один дислектик?

Ядро _сначала_ компилируют и собирают. А потом оно уже работает (на голом процессоре или эмуляторе любой тормознутости - не важно).

Я предлагаю запустить ядро «из сырцов» на интерпретаторе си - может тогда кто-то увидит разницу между этими процессами?

yyk ★★★★★
()
Ответ на: комментарий от yyk

И чем это эмулятор процессора, да еще и на интерпретируемом языке, менее интерпретатор чем эмулятор виртуальной машины Python, в которую компилируются питоньи скрипты?

anonymous
()
Ответ на: комментарий от anonymous

Ты и логика из разных вселенных...

yyk ★★★★★
()
Ответ на: комментарий от emulek

Я-я-натюрлих, всем кто вникнет, что ты за незадачливое существо, тоже становится тебя жалко. Ты уж там держись, как-нибудь где-нибудь, побеждай собственное убожество. Удачи!

anonymous
()
Ответ на: комментарий от emulek

Ай-яй-яй. Ядро ты тоже на интерпритаторе BASH/SH запустишь, который написан сам на себе и сам себя запускать будет? На нижнем уровне всегда будет работать код, который не имеет отношения к интерпретаторам, а скомпилированный, написанный на низкоуровневом языке, типа C, максимум - уровня C++, при том, со вставками ASM-а, если не чистого машинного кода.

peregrine ★★★★★
()
Ответ на: комментарий от anonymous

А что, Форт интерпретатор, написанный на Форте будет интерпретировать сам себя на голом железе? Давай мне такой интерпретатор или не неси чушь, работающий без ОС, не забудь, что сам интерпретатор должен быть по твоему условию не откомпилирован.

peregrine ★★★★★
()
Ответ на: комментарий от peregrine

Ядро ты тоже на интерпритаторе BASH/SH запустишь

прости, но ты идиот. Ядро запускается НЕ на компиляторе. Как и bash/sh. Учи матчасть.

emulek
()
Ответ на: комментарий от anonymous

зависит от того, насколько много в рантайм тащится информации. в лиспе вроде все есть, и в новой scala-meta хотят все АСТ в рантайме иметь.

anonymous
()
Ответ на: комментарий от peregrine

Это не фундаменальная разница. Да и не всегда это правда. Эмулятор openrisc на javascript на жирном xeon-е работает не медленнее настоящей железки на 50MHz.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.