LINUX.ORG.RU

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

 ,


1

3

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

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

foo<a>(b);

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

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

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

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

★★

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

Кстати, Forth — ещё один язык с макросами, не лисп. Может этими макросами взаимодействовать друг с другом, как Common Lisp. И, также как лисп, может делать макросы, синтаксически неотличимые от встроенных конструкций.

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

Напиши мне в Clojure вот это: https://github.com/Lovesan/nnp/blob/master/src/sbcl/types.lisp

Чтобы результатами defoptype можно было бы пользоваться в макросах

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

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

(defmacro with-bounds-check ((array index type) &body body &environment env)
  (check-type array symbol)
  (check-type index symbol)
  `(progn
     ,@(when (sb-c:policy environment (plusp sb-c::insert-array-bounds-checks))
         `((sb-kernel:check-bound ,array 
                                  (array-total-size ,array)
                                  (+ ,index ,(1- (optype-simd-width type))))))
     (multiple-value-bind (,array ,index)
         (sb-kernel:%data-vector-and-index ,array ,index)
       ,@body)))
lovesan ★★
()
Ответ на: комментарий от monk

В Clojure так можно?

Насколько я понимаю, да. Но нет никаких гарантий, что я понимаю правильно %)

Можно какой-нибудь простой примерчик набросать для эксперимента.

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

Напиши мне в Clojure вот это

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

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

Что-то вроде

#[config]
struct VecConfig {
    #[source(env, config, default = 0)]
    use_check: u8,
}

#[proc_macro]
pub fn with_bound_check(input: TokenStream) -> TokenStream {
    let input_parsed = parse_macro_input!(input as VecIdentBody);
    
    let name = input_parsed.vec;
    let index = input_parsed.index;
    let body = input_parsed.body;

    let expanded = 
      if VecConfig::parse().unwrap().use_check == 1 {
        quote! {
          {
            check_bound(stringify!(#name), stringify!(#index));
            #body
          }
        } 
      } else { 
        quote! {
          {
            #body
          }
        } 
      };
      
    TokenStream::from(expanded)
}

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

Мой макрос подхватывает политику оптимизации компилятора. В расте было бы что ему там вводят в командную строку, типа -O2, или -O0

Далее, тип который анализируется, берется из defoptype, определенного до этого

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

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

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

;; foo.clj
(ns foo)

(defmacro deffoo [val] `(def ~'foo ~val))
;; eval_when.clj
(ns eval-when
  (:require [foo :refer [deffoo]]))

(deffoo (atom 0))

(swap! foo inc)
(println @foo)
;; 1

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

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

В расте было бы что ему там вводят в командную строку, типа -O2, или -O0

Это и есть.

#[source(env, config, default = 0)]

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

типа -O2

Можно и так. Строка будет

#[source(clap(short = 'O'), env, config, default = 0)]

А

if VecConfig::parse().unwrap().use_check == 1 {

надо заменить на

if VecConfig::parse().unwrap().use_check == 2 {

Тогда сначала будет проверяться наличие -O2, потом use_check=2 в переменных окружения, потом в конфигурационном файле.

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

Тут немножко не про то.

Я про финты типа

;; foo.clj
(def FOUND_SO_FAR (atom #{}))

(defmacro deffoo [val] (swap! PRIMES_FOUND_SO_FAR conj prime-val) `(def ~'foo ~val))

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

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

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

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

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

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

могут ли они взаимодействовать с переменными в этот момент

Во время разворачивания макроса, в компайл-тайме — думаю, только с локальными. Обычных-то не будет до наступления рантайма. Интересный вопрос, конечно.

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

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

В Forth при выполнении Immediate слова, можно обращаться к данным которые уже были объявлены.

В нем можно написать такое:

Обычный код, и некий массив 
...
Immediate код модифицирующий массив выше, в скомпилированной версии это будет выглядеть как если бы в коде инициализировали массив некими значениями, пример int arr[] = {1,2,3,4};
...
Обычный код, все определения недоступны для изменения из Immediate выше

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

Ты не понял. Вот есть opt-level. Который указывается для rustc. Этот opt-level включает в себя информацию о проверках границ массивов, о количестве дебаг-информации и так далее. Вот исходя из текущего opt-level в момент раскрытия макроса в некоем данном конкретном файле, надо взять информацию о том, вставлять ли тут, в месте раскрытия макроса, чек на границы массивов. Без всяких конфигов и переменных среды сторонних.

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

Там всё банально как полено.

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

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defvar *optimization-levels* (make-hash-table))

  (defun set-optimization-level (name decl)
    (setf (gethash name *optimization-levels*)
          decl))

  (defun get-optimization-level (name)
    (or (gethash name *optimization-levels*)
        (error "Undefined optimization level: ~s" name))))

(defmacro define-optimization-level (name decl)
  `(eval-when (:compile-toplevel :load-toplevel :execute)
     (set-optimization-level ',name ',decl)))

(define-optimization-level :fast (optimize (speed 3) (safety 0) (debug 0)))
(define-optimization-level :safe (optimize (speed 0) (safety 3) (debug 3)))

(defmacro with-optimization-level (level &body body)
  (let ((decl (get-optimization-level level)))
    `(locally
         (declare ,decl)
       ,@body)))

Далее мы можем и там и сям определять define-optimization-level, и когда мы после этого пишем где-то например

(with-optimization-level :fast
  (+ 1 2))

оно нам раскроется, в

(locally (declare (optimize (speed 3) (safety 0) (debug 0)))
  (+ 1 2))

Всё это, естественно, на этапе компиляции. Т.е. либо в рамках одного файла, на который вызван compile-file, либо например в рамках модуля/системы ASDF, когда вызывается asdf:compile-system.

Макросы общаются друг с другом посредством сайд-эффектов, определенных в eval-when (:compile-toplevel)

Естественно, в Clojure такого нет. И много чего другого нет. Потому что Clojure - примитивная, непродуманная, кривая поделка, автор которой просто решил хайпануть, написал говно, просто не осилил реализовать действительно важные фичи того же CL и выкинул десятилетия развития лиспов в мусорку. Десятилетия продумывания как макросов, так и разнообразных оптимизаций, так и лисповых типов и структур данных, или даже ООП, и так далее. Зато налепил сбоку нахер никому не всравшееся pure FP, STM и прочий бесполезный дроч, ничем не отличающийся от дроча на паттерны у джавистов каких-нибудь. И всё бы ничего, если бы он свою игрушку для имбецилов лиспом не называл, и не пытался примазываться к лиспам. Или хотя бы не разводил вот этот весь дебильный хайп - мало ли сколько наколеночных недо-лиспов люди делают у себя на гитхабах.

Последователи Хики, при этом, еще хуже автора. Они мало что невежественны, они еще и уверены что «старые» лиспы «устарели» и вообще у них то есть всё, что нужно, а что Хики не осилил реализовать, так это никому и не надо. Это же натурально сраный blub paradox, что впрочем неудивительно, т.к. в кложуристы идут жопоскриптомакаки, питономакаки и жабомакаки, т.е. люди вообще без нормальных знаний и бэкграунда.

Как вот мне в комменте на реддите про кложуристов один парень написал.

My guess is that they mostly come from the java land and simply don’t have any experience in it. I’ve talked others in real life, and they were under the impression it was a ‘specification’ rather than a language.

The clojure programmers believe they get a CL experience, the jvm affords them ‘lisp like’ features (eg, exceptions are kinda like signaling, and exception handling are similar to "recovery from conditions’, and more), so they likely dont see what they are missing out on.

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

примитивная, непродуманная, кривая поделка

написал говно, просто не осилил

свою игрушку для имбецилов

дебильный хайп

жопоскриптомакаки, питономакаки и жабомакаки

Разрывом борщелиспера удовлетворён %)

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

можем и там и сям определять define-optimization-level

Ну хорошо. А зачем? Какую реальную задачу ты пытаешься таким образом решить?

на Clojure не смог написать, потому что у нее таких возможностей нету, она слишком тупая

У кложуристов таких проблем в принципе не возникает, потому что оптимизацией занимается JIT-компилятор, которому виднее, какие оптимизации где уместны. Примерно так же, как сборщик мусора освобождает борщелиспера от ручного управления памятью — но борщелиспер отчего-то не вопит, что его борщелисп слишком тупой по сравнению с б-жественным С++.

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

действительно важные фичи

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

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

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

Ну хорошо. А зачем? Какую реальную задачу ты пытаешься таким образом решить?

Посмотри библиотеку по ссылке

оптимизацией занимается JIT-компилятор

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

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

К сожалению (нет), в кложе нет глобальных имён.

Не в том смысле, что они без пространства имён.

А в том смысле, что после

(ns bar)

(mkdef)
(mkdef)
(mkdef)

(ns baz)

(mkdef)
(mkdef)

В bar должны определиться foo1, foo2, foo3, а в baz foo4, foo5. Даже если bar и baz описаны в разных файлах.

В Common Lisp так можно, в Racket и Rust только если из макроса писать/читать внешний файл.

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

Посмотри библиотеку по ссылке

по ссылке

Thanks, but no. Лучше словами объясни, зачем нужна эта библиотека и зачем в ней такие приседания с ручной настройкой оптимизации.

Ответ «потому что могу» засчитан, конечно, не будет %)

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

Насколько я понемаю, JIT работает на уровне байткода, и ему эквипенисуально, какой компилятор этот байткод выплюнул. Хоть ABCL преклонных годов.

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