Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I generate Scala code from a template (of sorts)?

Can I generate Scala code from a template (of sorts)?

I know how to do this in Racket/Scheme/Lisp, but not in Scala. Is this something Scala macros can do?

I want to have a code template where X varies. If I had this code template:

def funcX(a: ArgsX): Try[Seq[RowX]] =
  w.getThing() match {
    case Some(t: Thing) => w.wrap(t){Detail.funcX(t, a)}
    case _ => Failure(new MissingThingException)
  }

and tokens Apple and Orange, a macro would take my template, replace the Xs, and produce:

def funcApple(a: ArgsApple): Try[Seq[RowApple]] =
  w.getThing() match {
    case Some(t: Thing) => w.wrap(t){Detail.funcApple(t, a)}
    case _ => Failure(new MissingThingException)
  }

def funcOrange(a: ArgsOrange): Try[Seq[RowOrange]] =
  w.getThing() match {
    case Some(t: Thing) => w.wrap(t){Detail.funcOrange(t, a)}
    case _ => Failure(new MissingThingException)
  }
like image 303
gknauth Avatar asked Oct 15 '19 18:10

gknauth


1 Answers

Try macro annotation with tree transformer

@compileTimeOnly("enable macro paradise")
class generate extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro GenerateMacro.impl
}

object GenerateMacro {
  def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._

    val trees = List("Apple", "Orange").map { s =>
      val transformer = new Transformer {
        override def transform(tree: Tree): Tree = tree match {
          case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" if tname.toString.contains("X") =>
            val tname1 = TermName(tname.toString.replace("X", s))
            val tparams1 = tparams.map(super.transform(_))
            val paramss1 = paramss.map(_.map(super.transform(_)))
            val tpt1 = super.transform(tpt)
            val expr1 = super.transform(expr)
            q"$mods def $tname1[..$tparams1](...$paramss1): $tpt1 = $expr1"
          case q"${tname: TermName} " if tname.toString.contains("X") =>
            val tname1 = TermName(tname.toString.replace("X", s))
            q"$tname1"
          case tq"${tpname: TypeName} " if tpname.toString.contains("X") =>
            val tpname1 = TypeName(tpname.toString.replace("X", s))
            tq"$tpname1"
          case q"$expr.$tname " if tname.toString.contains("X") =>
            val expr1 = super.transform(expr)
            val tname1 = TermName(tname.toString.replace("X", s))
            q"$expr1.$tname1"
          case tq"$ref.$tpname " if tpname.toString.contains("X") =>
            val ref1 = super.transform(ref)
            val tpname1 = TypeName(tpname.toString.replace("X", s))
            tq"$ref1.$tpname1"
          case t => super.transform(t)
        }
      }

      transformer.transform(annottees.head)
    }

    q"..$trees"
  }
}

@generate
def funcX(a: ArgsX): Try[Seq[RowX]] =
  w.getThing() match {
    case Some(t: Thing) => w.wrap(t){Detail.funcX(t, a)}
    case _ => Failure(new MissingThingException)
  }

//Warning:scalac: {
//  def funcApple(a: ArgsApple): Try[Seq[RowApple]] = w.getThing() match {
//    case Some((t @ (_: Thing))) => w.wrap(t)(Detail.funcApple(t, a))
//    case _ => Failure(new MissingThingException())
//  };
//  def funcOrange(a: ArgsOrange): Try[Seq[RowOrange]] = w.getThing() match {
//    case Some((t @ (_: Thing))) => w.wrap(t)(Detail.funcOrange(t, a))
//    case _ => Failure(new MissingThingException())
//  };
//  ()
//}

Also you can try approach with type class

def func[A <: Args](a: A)(implicit ar: ArgsRows[A]): Try[Seq[ar.R]] =
  w.getThing() match {
    case Some(t: Thing) => w.wrap(t){Detail.func(t, a)}
    case _ => Failure(new MissingThingException)
  }

trait ArgsRows[A <: Args] {
  type R <: Row
}
object ArgsRows {
  type Aux[A <: Args, R0 <: Row] = ArgsRows[A] { type R = R0 }

  implicit val apple: Aux[ArgsApple, RowApple] = null
  implicit val orange: Aux[ArgsOrange, RowOrange] = null
}

sealed trait Args
trait ArgsApple extends Args
trait ArgsOrange extends Args

trait Thing

sealed trait Row
trait RowApple extends Row
trait RowOrange extends Row

object Detail {
  def func[A <: Args](t: Thing, a: A)(implicit ar: ArgsRows[A]): ar.R = ???
}

class MissingThingException extends Throwable

trait W {
  def wrap[R <: Row](t: Thing)(r: R): Try[Seq[R]] = ???
  def getThing(): Option[Thing] = ???
}

val w: W = ???
like image 70
Dmytro Mitin Avatar answered Oct 18 '22 09:10

Dmytro Mitin