LINUX.ORG.RU

Homoiconic C

 homoiconicity,


3

2

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

Это ж кайф. Выражения типа regexp(«^[a-z][a-z0-9]») или format(«%s - %s (%d)», bla1, bla2, i) можно будет автоматически обрабатывать макропроцессором и отправлять компилятору оптимизированный вариант. Это значит, регулярка, например, будет скопилирована в конечный автомат при компиляции программы, а не при выполнении.

Вот эта вот странная задачка, на которой dr_jumba проверял лаконичность языков, записывалась бы как-то вот так:

sample_function := fn(a(iterable(T))) to(T) {
    a select(match(regexp(/^J[a-z]+/))) each_chunk(3) map(format_with("~1 and ~2 follow ~3")) join("\n")
}

Дискас.

гомоиконным синтаксисом в стиле Io или Julia

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

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

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

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

а от попыток манипуляции AST на Си хотелось одновременно плакать и убивать.

Ну так моя идея в том и состоит, чтобы месить AST на чем-то типа CL-Io-Ruby.

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

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

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

Имело ввиду, что семантика того, что будет получаться после всех макроподстановок, не будет принципиально отличаться от Си

Тогда замени название топика на «Lisp-подобный Си». Но это тоже было %)

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

а как для этого си будет работать «код == данные == код» если его компилировать надо?

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

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

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

geekless ★★
() автор топика

Дяденька, а чем Вам так впилась гомоиконность синтаксиса? Просветите невежду? Я вот че нашел:

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

Ведь я могу на том же питоне превратить код в AST и дальше искалечить его как Б-г черепаху. Но синтаксис питона вроде не гомоиконен? Или гомоиконен?

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

Дяденька, а чем Вам так впилась гомоиконность синтаксиса?

Удобно же. Всё равно что спрашивать, чем удобен текстовый формат для работы с данными. Да тем, что не надо велосипед изобретать каждый раз.

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

Простой пример. Вот в обсуждении про Lua и NetBSD сегодня было сказано, что Си не может дать гарантию, что kmalloc не будет вызван из обработчика прерывания. Так вот на языке с развитым макропроцессром, возможно реализовать сосбтвенное расширение системы типов таким образом, чтобы такую гарантию можно было дать. И любой код, который вызывает или теоретически может вызвать kmalloc из прерывания, просто не скомпилируется.

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

Си пригоден только для того, чтобы месить память

Какая хорошая, правильная, точная и исчерпывающая характеристика!

Надо куда-нибудь записать на память.

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

Удобство понятие растяжимое. Лисповоду даже математику удобней писать в префиксной записи, а мне чем оно ближе к привычной со школы «обычной» записи, тем удобней.

Чем плохо взять тот же питон, превращать код в AST и дальше транслировать в C? Или, еще лучше - обрабатывать код на питоне так, что бы получалось AST. При этом часть инструкций воспринимается как куски AST, а часть работает как обычный питонячий код, дополняющий то же AST - получаются фактически макросы.

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

Вот в обсуждении про Lua и NetBSD сегодня было сказано, что Си не может дать гарантию, что kmalloc не будет вызван из обработчика прерывания. Так вот на языке с развитым макропроцессром, возможно реализовать сосбтвенное расширение системы типов таким образом, чтобы такую гарантию можно было дать.

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

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

Система типов будет реализована вот на том самом, что преобразует AST. На финальную компиляцию будет идти код, в котором из типов одни только «числа», «структуры» и «указатели».

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

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

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

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

Разумеется, синтаксис должен быть удобен и для человека. Вот Лиспы - это крайность. Удобно для машины, но взрывает мозг большинству программистов. А вот Io - ничо так.

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

ИМНО нафига проектировать 100501 ЯП - проще взять готовый. В питоне между прочим такие вещи делаются очень изящно... мы щас как раз чем то подобным на нем и занимаемся;-)

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

Система типов будет реализована вот на том самом, что преобразует AST.

По-моему, ты пытаешься изобрести Shen.

А когда ты при помощи макроподстановок реализуешь такой механизм гарантий

Как ты их реализуешь при помощи _макроподстановок_? Вот пример:

irqreturn_t handler(int irq, void *p)
{
   kmalloc(100, GFP_KERNEL);
   return IRQ_HANDLED;
}

Что здесь должно быть макросами и во что они должны разворачиваться, чтобы макропроцессор не дал скомпилировать код?

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

На уровне анализа AST можно поймать. Или на уровне конвертации сырца в ASТ;-)

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

Что здесь должно быть макросами и во что они должны разворачиваться, чтобы макропроцессор не дал скомпилировать код?

irqreturn_t handler(int irq, void *p)

Вот эту штуку заменим ддля нагрядности на вот такую:

fn(handler, (int irq, void *p), irqreturn_t)

Так вот fn - макрос. И чтобы вмешаться в процесс макроподстановок, мы просто переопределим его.

А проверять AST будет очень просто. Введем новый аттрибут:

fn(handler, (int irq, void *p), irqreturn_t, nomalloc)

Все функции и указатели на функции у нас будут тэгированы - имеют они nomalloc или нет. Функция, которая имеет тэг nomalloc, может вызывать только функции, которые тоже имеют nomalloc. Проверять корректность этого мы будем в нашей fn().

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

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

Alv, это и тебе ответ.

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

Нет. Откомпилированая программа - это обычный код целевой платформы.

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

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

Ну а когда ты #define-ы расставляешь, ты не «модифицироваем компилятора» в примитивной форме занимаешься каждый раз?

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

Да просто geekless реально упоролся, это не так делается;-) Ничего тэгировать не надо, для фрагмента AST легко пишется чекер, проверяющий есть ли в нем искомый объект (в данном случае kmalloc). Когда ты вешаешь обработчик на прерывание (в этой гипотетической обсуждаемой вундервафле), его фрагмент проверяется на наличие kmalloc.

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

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

Да просто geekless реально упоролся, это не так делается;-) Ничего тэгировать не надо, для фрагмента AST легко пишется чекер, проверяющий есть ли в нем искомый объект (в данном случае kmalloc). Когда ты вешаешь обработчик на прерывание (в этой гипотетической обсуждаемой вундервафле), его фрагмент проверяется на наличие kmalloc.

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

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

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

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

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

Что, опять кто-то Лисп изобрёл?

И какой же из лиспов является статически типизированным языком, компилирующимся в маш.код?

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

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

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

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

Давай в личку aivanov(злая собака)keldysh(тчк)ru

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

И какой же из лиспов является статически типизированным языком, компилирующимся в маш.код?

typed racket?

lazyklimm ★★★★★
()

ЯННП

А как от этого выиграю я как быдлокодер? Смогу быстрее писать программы? Они будут работать быстрее?

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

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

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

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

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

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

Ну а когда ты #define-ы расставляешь, ты не «модифицироваем компилятора» в примитивной форме занимаешься каждый раз?

Даже и не близко. #define локален, а введение атрибута nomalloc потребует анализа кода за пределами expanded макросов.

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

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

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

Да просто geekless реально упоролся, это не так делается;-)

Он не упоролся, просто его способ реализации проверки атрибутов нереалистичен. А в остальном - это называется effect system, ЕМНИП.

Ничего тэгировать не надо

Надо.

для фрагмента AST легко пишется чекер, проверяющий есть ли в нем искомый объект (в данном случае kmalloc)

И каким образом он ловит следующее:

void *(*foo)(size_t, unsigned) = kmalloc;

irqreturn_t handler(int irq, void *p)
{
   foo(100, GFP_KERNEL);
   return IRQ_HANDLED;
}

?

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

Локален в каком пространстве?

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

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

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

Все таки речь идет о создании инструмента, помогающего писать программы, а не надсмотрщика которого надо обманывать;-)

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

Тип определяет возможный набор операций над объектом. Что у нас тут и происходит.

Аналогично, например, с const. В C++ фактически каждый тип данных двуедин: представлен как константный и неконстантный варианты. И ответы на вопрос «что можно делать с этим объектом этого типа?» зависят от того, костантный ли он.

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

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

Все таки речь идет о создании инструмента, помогающего писать программы, а не надсмотрщика которого надо обманывать;-)

У тебя может запросто быть какой-нибудь struct device_t {void (*somehandler)();}, ну ты понял. Так что указатели на функции првоерять надо. Не из перфекционизма, а просто потому, что на эти грабли обязательно наступят.

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

Если первая строка введена в обсуждаемой вундервафле, то ловит так же как и везде.

Обсуждаем вундервафлю geekless? Тогда не ловит (ну или я не понимаю, каким образом ловит).

А иначе... «можно придумать защиту от дурака, но только от неизобретательного»(с).

Это называется robustness.

Все таки речь идет о создании инструмента, помогающего писать программы, а не надсмотрщика которого надо обманывать;-)

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

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.