LINUX.ORG.RU

Контекстно-зависимость в синтаксисе ЯП

 ,


1

3

Вот такой чисто теоретический вопрос, навеянный топиком Странная ошибка c шаблоном (или я идиот, или одно из двух)

В C++ одна из особенностей, делающих грамматику контекстно-зависимой — это неоднозначность парсинга выражения

foo<a>(b);

То ли это шаблонная функция foo<a>, вызываемая с аргументом b.

То ли это выражение (foo < a) > b.

Чтобы это понять, компилятор должен иметь доступ к декларации foo.

Как бы вы сделали эту часть грамматики контекстно-свободной, если бы дизайнили ЯП с нуля? Ваши идеи?

★★★

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

Кстати, когда мне понадобились разные виды математических скобок при вводе алгебраических выражений, я взял синтаксис питона и добавил его вот такими штуками (%...%), [%...%], {%...%}, <%...%>, |%...%| — оказалось афигенно удобно. Можно и для шаблонов и пр. че то такое ввести.

AntonI ★★★★★
()

Как бы вы сделали эту часть грамматики контекстно-свободной, если бы дизайнили ЯП с нуля? Ваши идеи?

Выбирал бы между двумя вариантами:
* не делать операторы «больше» и «меньше» (нафиг они не сдались, чтобы под них кракозябру ещё резервировать)
* не делать скобок на угловых скобках (вообще лучше бы в коде поменьше кракозябр, в том числе и скобок)

Или, ещё лучше, оба варианта осуществить одновременно.

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

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

(apply_template template_name param1 param2 ...)

что обозначает - специализируем template_name c параметрами …

любые формы тождественные этой будут правильными.

например заменим apply_template на @ и получим

@template_name(param1, param2, ...)

например вызов темплейтной функции будет таким

@template_fun(int, int)(1,2,3)

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

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

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

Поэтому если синтаксис не предусматривает запись template_fun(1,2,3) вместо @template_fun(int, int)(1,2,3), то задача не решена.

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

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

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

alysnix ★★★
()

Можно запретить некоторые одноранговые операторы без скобок.

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

Если сложение, вычитание, умножение и некоторые другие операции используются в C++ так же как и в математике, то то же сравнение нет. В математике есть a > b > c, в C++ это будет сравнение с булева результатом сравнения, что чаще является ошибкой, чем желаемым поведением.

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

Как бы вы сделали эту часть грамматики контекстно-свободной, если бы дизайнили ЯП с нуля? Ваши идеи?

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

foo«a»(b); // шаблон  
foo<a>(b); // сравнение  

UPD: прочитал тему, это уже трёхкратный баян

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

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

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

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

Процедурные макросы в расте равномощны лисповым макросам (даже чуть мощнее за счёт гигиены).

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

А в чём разница?

В том что @lovesan яростно дрочит на лисп и не может допустить мысли, что это не единственное семейство языков, где можно разобрать AST средствами самого языка.

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

Я бы сделал нормальный макропроцессор, тогда шаблоны были бы в принципе не нужны

Если это препроцессор уровня OCaml PPX работающий на уровне AST то да это даже мощнее шаблонов C++ и приближается к макросам лиспа или немерле. Но такой препроцессор для С++ будет очень сложным, так как или должен фактически включать в себя очень большую часть компилятора или же должен напрямую поддерживаться компилятором (в OCaml это вывели на уровень библиотеки, но сам язык для парсинга намного проще С++).

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

В чём измеряется «мощность»? Я думаю что «негигиеничные» макросы мощннее, потому что на них можно организовать «гигиену». А вот «разгигиеничить» «гигеничные» можно ли?

// Ну я думаю ты не будешь спорить что писать макросы в таких языках как элексир или руст — это больно и сложно по сравнению с лисп-подобными.
Вот насчёт dylan не знаю, но вряд ли удобнее лисподобных.

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

Но такой препроцессор для С++ будет очень сложным

там какой-то чел в одного затащил — язык circle https://www.circle-lang.org/quickref.html

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

там какой-то чел в одного затащил — язык circle

Интересная штука, если с наскоку правильно понял, то перетащили главную фишку zig полноценный интерпретатор языка работающий в compile time, но это не препроцессор, это скорее ближе к аналогу TypeScript для C++, но не с добавлением типизации, а с добавлением мета программирования. Из новых убийц C++ ближе всего по моему к препроцессору cppfront

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

В чём измеряется «мощность»?

В математическом смысле. Любой макрос лиспа можно написать на расте и наоборот.

писать макросы в таких языках как элексир или руст — это больно и сложно по сравнению с лисп-подобными.

Не знаю насчет элексира, а с растом можно сравнить

#[proc_macro_attribute]
pub fn log(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let function = parse_macro_input!(item as ItemFn);

    let name = &function.sig.ident;
    let inputs = &function.sig.inputs;
    let output = &function.sig.output;
    let block = &function.block;

    let expanded = quote! {
        fn #name(#inputs) #output {
            println!("Calling function: {}", stringify!(#name));
            #block
        }
    };

    TokenStream::from(expanded)
}

#[log]
fn hello_world() {
    println!("Hello, world!");
}

против

(defmacro log1 (funcdef)
  (let ((name (cadr funcdef))
        (inputs (caddr funcdef))
        (body (cdddr funcdef)))
    `(defun ,name ,inputs
       (format t "Calling function: ~a~%" ',name) 
       ,@body)))

(log1
  (defun hello-world()
    (format t "Hello, world!~%")))
monk ★★★★★
()
Ответ на: комментарий от monk

let block = &function.block;

А если нам нужно будет блок распарсить и заменить имя какого-то символа на другое или вставить после каждого чайлда узла block наш какой-то узел? Или сплайснуть один блок в другой?

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

против

В CL ещё нужно учесть опциональные декларации и документацию между lambda-list (inputs) и body. Для полноты картины.

(log1
  (defun hello-world ()
    (declare (optimize speed))
    "Beautifully logged highly optimized Hello World routine"
    (format t "Hello, world!~%")))

=>

; in: DEFUN HELLO-WORLD
;     (DECLARE (OPTIMIZE SPEED))
; 
; caught ERROR:
;   There is no function named DECLARE.  References to DECLARE in some contexts
;   (like starts of blocks) are unevaluated expressions, but here the expression is
;   being evaluated, which invokes undefined behaviour.

;     (FORMAT T "Hello, world!~%")
; ==>
;   "Hello, world!~%"
; 
; note: deleting unreachable code

; compiling (HELLO-WORLD); 
; compilation unit finished
;   caught 1 ERROR condition
;   printed 1 note


; wrote /home/apeIIU/prog.fasl
; compilation finished in 0:00:00.147
Unhandled SIMPLE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING
                                    {10005D05B3}>:
  compilation failed
korvin_ ★★★★★
()
Ответ на: комментарий от Bad_ptr

А если нам нужно будет блок распарсить и заменить имя какого-то символа на другое или вставить после каждого чайлда узла block наш какой-то узел? Или сплайснуть один блок в другой?

Блок здесь уже распарсеный. В поле stmts вектор из выражений. Можно редактировать как хочется перед тем, как запаковать в quote!.

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

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

Его даже локально в одном файле делать нельзя. Только отдельный пакет. Но это некритично.

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

В обоих случаях макрос = функция, получающая на вход список токенов и возвращающая список токенов.

Нет, конечно.

В лиспе нет никаких токенов, нет никакого AST, есть только объекты лиспа, чаще всего списки, символы, и вот это всё. И макросы не только чисто-функциональный код генерируют. И общаются друг с другом посредством побочных эффектов в eval-when, и так далее, не буду даже углубляться в детали всяких &environment в разных реализациях.

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

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

Хотя закапывать надо когда сравнивают с лисповыми макросами вообще, понятия не имея как работает лисп-система.

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

Реализации типа SBCL действительно позволяют ковыряться и в самом уже AST(ну не AST, а flow graph, там всё сложно) и других внутренних структурах компилятора, но это крайние меры, IRL до них доходят ну совсем уж редко, т.к. это все громоздко, и самое главное, невероятно сложно.

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

А в чём разница? В обоих случаях макрос = функция, получающая на вход список токенов и возвращающая список токенов.

Лисповый макрос получает на вход структуру данных и возвращает тоже структуру данных. Code is data is code же, ну. Вся мощь стандартной библиотеки доступна при манипуляции стандартными структурами данных, бла-бла-бла. И нестандартной тоже.

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

eval-when, главное

Фетишисты — они такие %)

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

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

Так у раста тоже стандартные для раста объекты и векторы.

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

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

у раста тоже стандартные для раста объекты и векторы

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

возможность из макроса писать в переменную, а из другого макроса читать записанное вроде только в Common Lisp

Не вижу, что мне может помешать сделать символу def в одном макросе и обратиться к нему в другом.

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

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

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

Так у раста тоже стандартные для раста объекты и векторы.

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

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

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

Макросы (в кложе) всегда работают на этапе компиляции. Происходит это до запаковки в jar (AOT-компиляция, jar с байткодом) или после (JIT-компиляция, jar с исходниками) — мне кажется, без разницы.

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

Каюсь, с растовыми макросами не знаком.

Я же пример привёл: Контекстно-зависимость в синтаксисе ЯП (комментарий)

Не вижу, что мне может помешать сделать символу def в одном макросе и обратиться к нему в другом.

В Clojure так можно? В Racket, например, раздельная компиляция для каждого модуля и если другой макрос в другой единице компиляции, то ничего он не получит.

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

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

Нет, конечно. Язык макросов = язык рантайма в Rust, Template Haskell, Num, Scala, …

У лиспов только одно кардинальное преимущество: вызов макроса визуально никак не отличается от других синтаксических структур. В более синтаксически богатых языках приходится либо ограничивать макрос, чтобы он был похож на функцию, либо придумывать дополнительные конструкции, чтобы анализатор мог понять, где заканчиваются параметры макроса. То есть, если в лиспе можно написать свои if и defun, то в других языках их придётся делать непохожими на встроенные.

Но это всё украшательства.

monk ★★★★★
()