我希望使用宏在Scala中实现外部DSL,例如SQL。我已经看过关于如何实现用Scala的论文。另外,我最近写了一篇关于如何在Java中实现这一点的文章。
现在,内部DSL总是感觉有点笨拙,因为它们必须在宿主语言(例如Scala)中实现和使用,并遵守宿主语言的语法约束。这就是为什么我希望Scala宏允许在不受任何约束的情况下将外部DSL内部化。然而,我并不完全理解Scala宏,也不完全理解它们能达到什么程度。我已经看到浮油和一个鲜为人知的名为平方型的库已经开始使用宏,但是SLICK使用了一种“SQL”语法来进行查询,而sqltyped使用宏来解析SQL字符串(这也可以不使用宏)。而且,对于我想做的事情来说,各种各样的Scala网站上的例子太琐碎了。
我的问题是:
给出了一个示例,外部DSL定义为一些BNF语法,如下所示:
MyGrammar ::= (
'SOME-KEYWORD' 'OPTION'?
(
( 'CHOICE-1' 'ARG-1'+ )
| ( 'CHOICE-2' 'ARG-2' )
)
)我是否可以使用Scala宏来实现上述语法以允许这样的客户端程序?或者Scala宏不够强大,无法实现这样的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)注意,我对解析字符串的解决方案不感兴趣,就像平方型那样
发布于 2015-03-26 00:31:12
这个问题已经有一段时间没有被范式所回答了,但我只是偶然发现了这个问题,并认为它值得扩展。
内部化的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吗?人们可能会想出一个更好的语法,例如使用Scala的后缀操作符表示法(language.postfixOps),但由于分号推断(您可以自己在REPL控制台或IntelliJ的Scala工作表中尝试),这变得很棘手。简单地将关键字与未定义的名称交织在一起是最简单的。
由于我们已经使语法合法化,我们可以使用宏处理该块:
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 }")
}
}宏通过对扩展的动态调用进行模式匹配来查找所有引入的名称,并生成所需的输出代码。注意,宏必须是白框,才能返回从签名中声明的类型派生的表达式。
发布于 2012-12-28 13:05:53
我不这样认为。传递给宏的表达式必须是有效的Scala表达式,并且应该定义标识符。
https://stackoverflow.com/questions/14069876
复制相似问题