Могу ли я использовать макросы Scala для интернализации внешнего DSL?

Я хотел бы реализовать внешнюю DSL, такую ​​как SQL в Scala, используя макросы. Я уже видел документы о том, как внедрить внутренние DSL с помощью Scala . Кроме того, я недавно написал статью о , как это можно сделать на Java .

Теперь внутренние DSL всегда чувствуют себя немного неуклюжими, поскольку они должны быть реализованы и использованы на языке хоста (например, Scala) и придерживаться синтаксических ограничений на языке хоста. Вот почему я надеюсь, что Scala Macros позволит интернализировать внешний DSL без каких-либо ограничений. Тем не менее, я не совсем понимаю Scala Macros и насколько я могу пойти с ними. Я видел, что SLICK , а также гораздо менее известная библиотека под названием sqltyped начали использовать макросы, но SLICK использует синтаксис Scalaesque для запроса, который на самом деле не является SQL, тогда как sqltyped использует макросы для синтаксического анализа SQL (это можно сделать и без макросов). Кроме того, различные примеры, приведенные на веб-сайте Scala , слишком тривиальны для что я пытаюсь сделать

Мой вопрос:

Учитывая пример внешней DSL, определенный как некоторая грамматика BNF, такая как:

MyGrammar ::= ( 
  'SOME-KEYWORD' 'OPTION'?
    (
      ( 'CHOICE-1' 'ARG-1'+ )
    | ( 'CHOICE-2' 'ARG-2'  )
    )
)

Могу ли я реализовать вышеупомянутую грамматику с помощью Scala Macros, чтобы разрешить такие клиентские программы? Или Scala Macros недостаточно мощны для реализации такой DSL?

// This function would take a Scala compile-checked argument and produce an AST
// of some sort, that I can further process
def evaluate(args: MyGrammar): MyGrammarEvaluated = ...

// These expressions produce a valid result, as the argument is valid according
// to my grammar
val result1 = evaluate(SOME-KEYWORD CHOICE-1 ARG-1 ARG-1)
val result2 = evaluate(SOME-KEYWORD CHOICE-2 ARG-2)
val result3 = evaluate(SOME-KEYWORD OPTION CHOICE-1 ARG-1 ARG-1)
val result4 = evaluate(SOME-KEYWORD OPTION CHOICE-2 ARG-2)

// These expressions produce a compilation error, as the argument is invalid
// according to my grammar
val result5 = evaluate(SOME-KEYWORD CHOICE-1)
val result6 = evaluate(SOME-KEYWORD CHOICE-2 ARG-2 ARG-2)

Заметьте, меня не интересуют решения, которые анализируют строки , способ sqltyped делает

3
nl ja de
@EugeneBurmako: Спасибо за авторитетную обратную связь! Это может квалифицироваться как ответ, так как вы парень Scala Macros :-)
добавлено автор Lukas Eder, источник
@TravisBrown: Да, я понимаю, что есть преимущества в использовании макросов для синтаксического анализа строк. Но тогда эти строки должны быть (встроенными) строковыми литералами, что заставляет меня чувствовать, что подход с синтаксическим анализом - это «kludge» между истинными внутренними и истинными внешними DSL ... За исключением случаев, когда я чего-то не хватает?
добавлено автор Lukas Eder, источник
@TravisBrown: Да, это правда. Хотя он работает только для статического regex/SQL, а не динамического regex/SQL, я думаю ...
добавлено автор Lukas Eder, источник
@LukasEder: Вы говорите, что метод строкового разбора «может быть выполнен без макросов», но это только половина истины - не стоит недооценивать ценность безопасности во время компиляции.
добавлено автор Travis Brown, источник
Нет, вы правы, поскольку общее решение DSL это kludgey, но во многих случаях (особенно, когда использование строковых литералов уже является обычной практикой, например, регулярные выражения, SQL-запросы), способные проверять внедренный язык во время компиляции, это огромный плюс.
добавлено автор Travis Brown, источник
В настоящее время макросы ограничены вызовами функции ванили. То есть единственным способом запуска макрообмена является вызов макроса def. Более того, аргументы для макросов def должны быть хорошо типизированными выражениями Scala, что означает, что вы должны соответствовать синтаксису и правилам печати Scala. Будущие исследования могут поднять последнее, а также даже прежнее ограничение, но как и когда это происходит, неясно.
добавлено автор Eugene Burmako, источник

2 ответы

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

Интернализованный DSL действительно должен быть действительным кодом Scala со всеми именами, определенными перед расширением макроса, однако один может преодолеть это ограничение с тщательно разработанным синтаксисом и Динамика .

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

people {
  introduce John please
  introduce Frank and Lilly please
}

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

new People {
    val john: Person = new Person("John")
    val frank: Person = new Person("Frank")
    val lilly: Person = new Person("Lilly")
}

Чтобы сделать это возможным, нам нужно определить некоторые искусственные объекты и классы, имеющие две цели: определение грамматики (несколько ...) и обман компилятора при принятии неопределенных имен (например, John или Lilly ).

import scala.language.dynamics

trait AllowedAfterName

object and extends Dynamic with AllowedAfterName {
  def applyDynamic(personName: String)(arg: AllowedAfterName): AllowedAfterName = this
}

object please extends AllowedAfterName

object introduce extends Dynamic {
  def applyDynamic(personName: String)(arg: AllowedAfterName): and.type = and
}

Эти фиктивные определения делают наш DSL-код законным - компилятор переводит его на код ниже, прежде чем перейти к расширению макроса:

people {
    introduce.applyDynamic("John")(please)
    introduce.applyDynamic("Frank")(and).applyDynamic("Lilly")(please)
}

Нужен ли нам этот уродливый и, казалось бы, избыточный please ? Вероятно, можно было бы получить более сильный синтаксис, например, используя нотацию Postfix оператора Scala ( language.postfixOps ), но это становится сложно из-за вывода точки с запятой (вы можете попробовать это самостоятельно в консоли REPL или IntelliJ's Scala Worksheet). Проще всего просто чередовать ключевые слова с неопределенными именами.

Поскольку мы сделали синтаксис законным, мы можем обработать блок с помощью макроса:

def people[A](block: A): People = macro Macros.impl[A]

class Macros(val c: whitebox.Context) {
  import c.universe._

  def impl[A](block: c.Tree) = {
    val introductions = block.children

    def getNames(t: c.Tree): List[String] = t match {
      case q"applyDynamic($name)(and).$rest" =>
        name :: getNames(q"$rest")
      case q"applyDynamic($name)(please)" =>
        List(name)
    }

    val names = introductions flatMap getNames

    val defs = names map { n =>
      val varName = TermName(n.toLowerCase())
      q"val $varName: Person = new Person($n)"
    }

    c.Expr[People](q"new People { ..$defs }")
  }
}

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

3
добавлено
Это очень интересно! Большое спасибо за продолжение этого старого вопроса ... Я расскажу вам подробнее, но это похоже на то, что я изначально просил
добавлено автор Lukas Eder, источник

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

2
добавлено
Спасибо за ответ. Это также то, что сказал Евгений Бурмако в своем комментарии
добавлено автор Lukas Eder, источник
Может ли макрос Lisp анализировать синтаксис не-lispy?
добавлено автор CMCDragonkai, источник
pro.jvm
pro.jvm
3 503 участник(ов)

Сообщество разработчиков Java Scala Kotlin Groovy Clojure Чат для нач-их: @javastart Наш сайт: projvm.com projvm.ru Наш канал: @proJVM Вакансии: @jvmjobs Конфы: @jvmconf

Scala User Group
Scala User Group
1 486 участник(ов)

[RU] Scala Chat. Rules, additional links, FAQ: https://telegra.ph/Russian-Speaking-Scala-User-Group-08-27

Scala Jobs
Scala Jobs
852 участник(ов)

Rules: http://telegra.ph/My-lyudi-i-ehto-znachit-chto-nam-nuzhna-organizaciya-02-07 Main Scala Channel: https://t.me/scala_jobs_feed Flood Room: https://t.me/scala_ponv