LINUX.ORG.RU

Метапрограммирование в Rust

 ,


0

5

Насколько мощно? Можно ли изменить код программы во время ее выполнения?

Можно ли расширять компилятор, писать DSL'и, работать с AST?



Последнее исправление: elf80lvl (всего исправлений: 1)

изменить код программы во время ее выполнения?

Вообще, CL здесь — плохой пример. Его возможности, как раз в данном аспекте, сильно ограничены. Это характерная черта интерпретируемых языков, например newlisp:

(define (f)
  (begin
    (println (inc cnt))
    (push (last f) f -1)
    (if (> (length f) 3) (pop f 1))))

(f)

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

linux-101
()

Можно ли изменить код программы во время ее выполнения?

можешь открыть секцию .code для записи

MyTrooName ★★★★★
()

Можно ли расширять компилятор, писать DSL'и, работать с AST?

Да.

tailgunner ★★★★★
()

Можно ли изменить код программы во время ее выполнения?

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

Насколько мощно?

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

Вот немного ссылок, которые сразу вспомнились-нашлись:

  • http://doc.rust-lang.org/guide-macros.html - руководство по обычным макросам.
  • http://doc.rust-lang.org/guide-plugin.html - руководство по написанию плагинов к компилятору. Это уже посложнее, зато есть возможность как угодно модифицировать синтаксическое дерево. Тут документации не так много и эта часть языка, насколько я знаю, довольно активно меняется.
  • http://doc.rust-lang.org/syntax - немного автоматически генерируемой документации по парсеру, ast и макросам.
  • http://doc.rust-lang.org/rustc/plugin - тут еще немножко автоматически генерируемой документации по созданию плагинов.

    http://rustbyexample.com/staging/macros.html - недописанная глава про обычные макросы из Rust by Example.

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

Очевидно, нет, потому что D — тоже компилируемый язык.

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

В CL можно сделать так

(defmacro defun* (name args &body body)
   `(progn
       (setf (gethash ',name *code*) ',body)
       (defun ,name ,args ,@ body)))

(defun* f ()
   (format nil "ok")
   (eval `(defun* f ()
             ,(list* '(format nil "added"))
                     (gethash 'f *code*)))))
monk ★★★★★
()
Ответ на: комментарий от monk

Ну, тебе тут нужен свой дефан, да еще и явный евал. Впрочем, я не ниспровергатель CL, поэтому спорить не буду :)

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

Ну, тебе тут нужен свой дефан, да еще и явный евал.

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

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

Так, вроде, и в Си можно) Не думаю, что вопоос был про подобное.

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

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

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

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

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

Открою тебе секрет, eval всегда вызывается астоматом во время интерпретации, разница в том, что тебе в cl b других языках придется явно преобразовывать структуру в функцию, компилировать ее, а затем выполнять, а в рефлексивных языках никакой разницы между списком (или строкой) и функцией вообще нет. Функция, в данном случае, является просто списком. Всегда.

linux-101
()
Ответ на: комментарий от linux-101

Открою тебе секрет, eval всегда вызывается астоматом во время интерпретации, разница в том, что тебе в cl b других языках придется явно преобразовывать структуру в функцию, компилировать ее, а затем выполнять, а в рефлексивных языках никакой разницы между списком (или строкой) и функцией вообще нет. Функция, в данном случае, является просто списком. Всегда.

Это можно нахуячить десятистрочным макросом my-yoba-define.

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

Да ты не врубаешься, что ты сделаешь для частного случая, своего рода DSL, костыль. А в интерпретаторах это работает в общем случае. Сделать то о чем ты говоришь, можно в любом языке с поддержкой eval, тот же похапэ вэтом смысле, ничем не отличается от cl. Иными словами, через костыль можно имитировать, но это не значит, что оно есть. Пиши на CL Интерпретатор CL, тогда будет, да.

linux-101
()
Ответ на: комментарий от linux-101

Да ты не врубаешься, что ты сделаешь для частного случая

При чем тут частный случай? Еще раз - прост определить дефайн, который будет сохранять свое тело и евалить его при вызове.

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

При чем тут частный случай?

Псевдокод:

(special-define test1 <definition here...>)
(caual-define test2 <definition here...>)
(reflective-call test1 <args...>); ok
(reflective-call test2 <args...>); huy
Костыль, язык в языке, что непонятно? Ты тогда все будешь вынужден определять через special-define, и забыть вообще о common lisp.

Уж если на то пошло, Javascript тут имеет преимущество над CL, потому что там любую (обычную) функцию можно привести явно к строковому представлению.

linux-101
()
Ответ на: комментарий от linux-101

Псевдокод:

Что-то у тебя неправильное. В реальности так:

...
...(rename-out [(my-yoba-define define) (ma-yoba-app #%app)])...
..,
#lang s-exp "my-yoba-module"
...
и дальше обычные дифайны и обычные коллы

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

Костыль, язык в языке, что непонятно? Ты тогда все будешь вынужден определять через special-define, и забыть вообще о common lisp.

Зачем же все? Только то, что нужно. Нахуя мне определять через такой дифайн библиотечные ф-и, например? Мне их код нахуй не нужен. А вот если надо будет написать ф-ю чей код требуется перепидорасить - пожалуйста, все на месте.

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

Я тебе еще раз говорю, в любом языке с эвалом можно это сделать.

РЕчь не о том что это можно сделать а о том, что это можно сделать _очень просто_. Если в языке Х нету некоторой фичи Y, но при этом ее можно сделать _очень просто_ - то можно считать, что такая фича есть. Потом что фичи нужны для того, чтобы ими можно было пользоваться. И если в общелиспе фичи нет, но на практике ей можно пользоваться так же просто как в ньюлиспе - то ну и что что ее нет?

anonymous
()

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

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

Простые макросы - да.

Про плагины компилятора на 100% точно не скажу - я сам ими пока не пользовался, а документации по ним не очень-то много. Но, думаю, что раз они императивные, то они могут быть и гигиеничными, и негигиеничными.

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

ozkriff пишет:

Вот немного ссылок...

О, спасибо мил человек! А интерпретатор вроде есть, rusti называется и ещё какой то Rust-APL

elf80lvl
() автор топика
Ответ на: комментарий от mix_mix

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

linux-101
()
Ответ на: комментарий от linux-101

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

call $+2
pop ebp
mov [ebp+6], сl
add eax, ebx
В eax и ebx идут параметры бинарной операции, в младших битах ecx её опкод. То есть мы можем в рантайме заменить add на sub, например. С длинами инструкций только мог немного напутать. Btw, всё то же самое можно сделать и на сишке, выглядит только чуть более вырвиглазно. В той же винде в начале каждой библиотечной функции до сих пор стоит «заглушка» в 5-9 байт, чтобы их можно было переписать на long-jmp, необходимый для хуков.

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

Что, так сразу и сольёшься, дебилушка? Или хоть что-нибудь скажешь в своё оправдание? Справедливости ради, не знаю как обстоит дело с хуками под 64-битной виндой, я что-то сейчас слабо представляю себе 9-байтное интро, для пяти-то байт лишний nop вставляли, а здесь уже без чуть более сложной техники с дизассемблером длин не обойтись.

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

на сишке

А ты, случаем, не попутал понятия модификация своего исходного кода в рантайме, с понятием модификация скомпилированного кода в рантайме?

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

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

00401000     E8 00 00 00 00     call $+5
00401005     5D                 pop ebp
00401006     88 4D 04           mov [ebp+4], cl
00401009     01 D8              add eax, ebx
Первый столбец есть адрес в оперативной памяти той или иной инструкции, второй — соответствующий ей машинный код, третий — собственно текстовое представление (в интеловском синтаксисе). Построчно:

  • Первая инструкция передаёт управление на адрес следующей инструкции, попутно занося этот же адрес в стек вызовов.
  • Вторая инструкция вынимает этот адрес в регистр ebp, теперь мы знаем где мы, как код, расположены в памяти (в данном случае там будет 00401005)
  • Третья инструкция перезаписывает первый байт по адресу что в ebp да плюс 4 (это будет 00401009, т.е. адрес последней инструкции) на содержимое регистра cl.

Если у нас в cl будет 01, то получаем инструкцию 01 D8 (add eax, ebx), то есть сложение, если там 29, то получаем инструкцию 29 D8 (sub eax, ebx), то есть вычитание, если 21, то 21 D8 (and eax, ebx), то есть битовое И, ну и так далее. И всё это происходит в рантайме, нам не нужно ничего перекомпилировать, нам не нужен никакой исходник, мы работаем напрямую с машинным кодом. Чаще всего такой фигнёй занимается вирусня и прочие не очень (или наоборот очень) полезные программы, на это дело часто остро реагируют антивирусы, а секция кода по умолчанию доступна только на чтение и исполнение.

Отладчики используют примерно такую же технику, но копаются уже в кишках других программ, они по мере продвижения по коду (step-by-step или по брекпоинтам) на месте тех самых точек останова тупо перезаписывают первый байт следующей инструкции на CC, превращая её в int 3, что при исполнении процессором генерирует исключение, которое уже и обрабатывается отладчиком, а там он показывает тебе состояние контекста и всего прочего. Вот как-то так в общих чертах.

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

А интерпретатор вроде есть, rusti называется и ещё какой то Rust-APL

rusti уже давно нет, да он и не был настоящим интерпретатором никогда.

По запросу «rust-apl» нашлось только https://github.com/AngryLawyer/rust-apl и последний коммит в нем 14 декабря 2013, так что оно вряд ли живое.

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

Ты показал модификацию машинного кода процессора на ходу выполнения. Но это не метапрограммирование.

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

Очень жаль, а вообще сложно делаются интерпретаторы компилируемых языков?

оффтопик: тут всегда так общаются? как будто на чат из доты2 смотришь...

elf80lvl
() автор топика

игнорирование утверждений по причине несовременной терминологии.

а конкретно:

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

сам знаешь кто.

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

Есть диаметрально противоположное мнение

синт сахар приводит к раку точки с запятой.

Сам знаешь кто

алсо, см normal considered harmful

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

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

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

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

Очень жаль, ...

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

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

Тут не подскажу, я залезал только во внутренности интерпретаторов вроде lua`шного. С Ржавчиной, с виду, сложнее)). Вот, вроде, актуальная задача про repl - https://github.com/rust-lang/rust/issues/9898.

оффтопик: тут всегда так общаются? как будто на чат из доты2 смотришь...

Печально, но да. Просто игнорируй)

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

alex_public пишет:

смотря на все эти обзоры Rust'a я совсем не вижу метапрограммирования. Т.е. даже хотя бы такого как в C++ (в D оно ещё намного сильнее чем в плюсах). А это очень существенная часть языка, причём отлично сочетающаяся и нужная как раз в системных языках, т.к. она позволяет вносить в язык очень высокий абстракции при этом не добавляя ни капли оверхеда в рантайм

так что не только для ИИ

elf80lvl
() автор топика

Давай задачу какую-нибудь рассмотрим. Именно задачу, а не «написать макрос/шаблон, который бы...». Т.е. что ты конкретно хочешь сделать.

anonymous
()
2 декабря 2015 г.
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.