Я ищу дизайн DSL в Scala с минимальным количеством синтаксиса. Он предназначен для использования пользователями, которые не знают Scala, но могут использовать систему типов Scala для проверки и проверки ошибок. В моей голове DSL выглядит следующим образом:Scala - проектирование DSL с миниатюрным синтаксисом
outer {
inner(id = "asdf") {
value("v1")
value("v2")
}
}
Этот отрезала должен произвести значение, как это:
Outer(Inner("asdf", Value("v1") :: Value("v2") :: Nil))
Указанные структуры данных
case class Outer(inner: Inner)
case class Inner(values: List[Value])
case class Value(value: String)
Идея заключается в том, что inner
функция только доступный в закрытии после outer
, value
функция доступна только после закрытия после inner
, и т. д. То есть следующее не будет компилироваться: outer { value("1") }
.
Как я могу реализовать что-то вроде этого? В конце концов, структуры данных не обязательно должны быть неизменными, это может быть что угодно, пока оно строго типизировано.
У меня нет знакомства с макросами Scala, но могу ли я решить эту проблему с помощью макросов?
Ближайший я до сих пор является следующая реализация:
object DSL extends App {
def outer = new Outer()
class Outer(val values: mutable.MutableList[Inner] = mutable.MutableList.empty) {
def inner(id: String): Inner = {
val inner = new Inner(id)
values += inner
inner
}
def apply(func: Outer => Unit): Outer = {
func(this)
this
}
override def toString: String = s"Outer [${values.mkString(", ")}]"
}
class Inner(val id: String, val values: mutable.MutableList[Value] = mutable.MutableList.empty) {
def value(v: String): Value = {
val value = new Value(v)
values += value
value
}
def apply(func: Inner => Unit): Unit = func(this)
override def toString: String = s"Inner (${values.mkString(", ")})"
}
class Value(val str: String) {
override def toString: String = s"Value<$str>"
}
val value = outer { o =>
o.inner(id = "some_id") { i =>
i.value("value1")
i.value("value2")
}
}
println(value)
Как я могу избавиться от анонимных функций аннотаций (т.е. o =>
и o.
и т.д.)?
Альтернативно существует способ лечения outer
как new Outer
(в этом случае следующий блок кода будет рассматриваться как конструктор, и я смогу вызвать функции-члены)?
Этот блестящий. Я не задавал этот вопрос, но я рад, что наткнулся на этот ответ. Я пытался решить проблему, но мне никогда не приходило в голову, как легко это может быть с «Builder» и «dsl-paradise»! Благодаря! –