Я работаю над встроенным DSL Scala, и макросы становятся основным инструментом для достижения моих целей. Я получаю сообщение об ошибке при попытке повторно использовать поддерево из входящего макроопределения в полученный. Ситуация довольно сложная, но (надеюсь) я упростил ее для ее понимания.
Предположим, что у нас есть этот код:
val y = transform {
val x = 3
x
}
println(y) // prints 3
где "transform" - вовлеченный макрос. Хотя может показаться, что он ничего не делает, он действительно преобразует показанный блок в это выражение:
3 match { case x => x }
Выполняется эта реализация макроса:
def transform(c: Context)(block: c.Expr[Int]): c.Expr[Int] = {
import c.universe._
import definitions._
block.tree match {
/* {
* val xNam = xVal
* xExp
* }
*/
case Block(List(ValDef(_, xNam, _, xVal)), xExp) =>
println("# " + showRaw(xExp)) // prints Ident(newTermName("x"))
c.Expr(
Match(
xVal,
List(CaseDef(
Bind(xNam, Ident(newTermName("_"))),
EmptyTree,
/* xExp */ Ident(newTermName("x")) ))))
case _ =>
c.error(c.enclosingPosition, "Can't transform block to function")
block // keep original expression
}
}
Обратите внимание, что xNam соответствует имени переменной, xVal соответствует его ассоциированному значению и, наконец, xExp соответствует выражению, содержащему переменную. Ну, если я печатаю исходное дерево xExp, я получаю Ident (newTermName ( "x" )), и это именно то, что установлено в случае RHS. Поскольку выражение может быть изменено (например, x + 2 вместо x), это не подходит для меня. То, что я хочу сделать, - это повторно использовать дерево xExp (см. Комментарий xExp), изменяя значение "x" (это определение во входном выражении, но будет случайной переменной LHS на выходе), но оно запускает суммарная ошибка:
symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details.
Мое текущее решение состоит в синтаксическом анализе xExp для поддержки всех Идентов с новыми, но это полностью зависит от внутренних компонентов компилятора и, следовательно, временного обходного пути. Очевидно, что xExp поставляется с дополнительной информацией, которую предлагает showRaw. Как я могу очистить этот xExp для того, чтобы позволить "x" использовать переменную case? Может ли кто-нибудь объяснить всю картину этой ошибки?
PS: Я безуспешно пытаюсь использовать семейство методов substitute * из TreeApi, но мне не хватает основ, чтобы понять его последствия.