2 votes

Macros Scala : Convertir/parser un arbre en un nom

Il s'agit d'un exemple simplifié, mais le problème reste le même.

Je souhaite y parvenir en utilisant des macros (pseudocode basé sur scala) :

(a: Int) => {
  val z = "toShort"
  a.z
}

Si je le réifie, j'obtiendrai quelque chose de semblable à ceci :

Function(
  List(
    ValDef(
      Modifiers(Flag.PARAM),
      newTermName("a"),
      Ident(scala.Int),
      EmptyTree
    )
  ),
  Block(
    List(
      ValDef(
        Modifiers(),
        newTermName("z"),
        TypeTree(),
        Literal(Constant("toShort"))
      )
    ),
    Apply(
      Select(
        Ident(newTermName("a")),
        newTermName("toShort")
      ),
      List()
    )
  )
)

Je ne sais pas comment accéder à une valeur et l'utiliser comme nom de terme.

J'ai essayé de remplacer newTermName("toShort") con newTermName(c.Expr[String](Select(Ident(newTermName("z")))).splice) mais le compilateur ne semble pas l'apprécier :

exception lors du macr java.lang.UnsupportedOperationException : la fonction que vous appelez n'a pas été épissée par > le compilateur. Cela signifie qu'une évaluation inter-étapes est impliquée et qu'elle doit être invoquée explicitement. si vous êtes sûr qu'il ne s'agit pas d'un oubli, ajoutez scala-compiler.jar au classpath, importer scala.tools.reflect.Eval et appeler <your expr>.eval au lieu de cela.

J'ai également essayé 'eval' comme suggéré par le compilateur : newTermName(c.eval(c.Expr[String](...)) mais aucune n'a fonctionné.

Comment puis-je convertir un arbre comme Select(Ident(newTermName("z"))) (qui est un accès à la valeur d'une valeur locale) à a Nom une chaîne de caractères qui peut être utilisée comme paramètre pour newTermName ? Est-ce possible ?

UPDATE :

Voici le vrai problème qui vous est posé en tant que liste !

Merci d'avance,

3voto

Leo Points 389

J'ai du mal à comprendre ce que vous essayez de faire et pourquoi vous utilisez des arbres partout. Les arbres sont vraiment de bas niveau, difficiles à utiliser, délicats, et il est très difficile de comprendre ce que fait le code. Quasiquotes ( http://docs.scala-lang.org/overviews/macros/quasiquotes.html ) sont en effet la voie à suivre et vous pouvez les utiliser sur la version de production de scala 2.10.x grâce au plugin macro paradise ( http://docs.scala-lang.org/overviews/macros/paradise.html ). Il suffit alors d'écrire q"(a: Int) => {val z = "toShort"; a.z}" et vous obtenez directement l'expression de l'arbre que vous venez de taper.

Pour répondre à votre question, il faut d'abord se rappeler que les macros sont évaluées au moment de la compilation. Elles ne peuvent donc pas générer de code dépendant d'une valeur d'exécution. C'est pourquoi le compilateur se plaint de votre splice . Mais si vous passez une valeur qui peut être calculée au moment de la compilation, typiquement un littéral, alors vous pouvez utiliser eval pour obtenir sa valeur dans votre code macro. Eval souffre cependant d'un bug, comme indiqué dans scaladoc. Il ne doit être appelé que sur des arbres non typés. Donc la façon d'appeler eval sur un s: c.Expr[String] l'expression serait val s2 = c.eval(c.Expr[String](c.resetAllAttrs(c.tree.duplicate))) qui vous donne un String vous pouvez alors l'utiliser normalement dans votre code, par exemple q"(a: Int) => a.${newTermName(s2)}" .

Pour résumer, imaginons que vous souhaitiez créer une macro qui produise une chaîne de caractères à partir d'un objet et de l'un de ses éléments suivants String champ. Il en résultera quelque chose comme

def attr[A](a: A, field: String): String = macro attrImpl[A]

def attrImpl[A: c.WeakTypeTag](c: Context)(a: c.Expr[A], field: c.Expr[String]) = {
  import c.universe._

  val s = c.eval(c.Expr[String](c.resetAllAttrs(field.tree.duplicate)))
  c.Expr[String](q"a.${newTermName(s)}")

}

Test de la session REPL :

scala> object a { val field1 = "field1"; val field2 = "field2" }
defined module a

scala> attr(a, "field1")
res0: String = field1

scala> attr(a, "field2")
res1: String = field2

Pour comprendre la différence entre le temps de compilation et le temps d'exécution, vous pouvez méditer sur le résultat suivant dans le REPL ;-)

scala> val s = "field1"; attr(a, s)
error: exception during macro expansion: 
scala.tools.reflect.ToolBoxError: reflective compilation has failed: 

$iw is not an enclosing class
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.throwIfErrors(ToolBoxFactory.scala:311)
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.compile(ToolBoxFactory.scala:244)
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.compile(ToolBoxFactory.scala:408)
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.eval(ToolBoxFactory.scala:411)
    at scala.reflect.macros.runtime.Evals$class.eval(Evals.scala:16)
    at scala.reflect.macros.runtime.Context.eval(Context.scala:6)
    at .attrImpl(<console>:14)

scala> val s = "field1"
s: String = field1

scala> attr(a, s)
res3: String = field1

J'espère que cela vous aidera ;))

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X