Question

I have defined the following macros to get file, line and object/class from current location: http://pastebin.com/UsNLemnK

Using SBT, I have defined two projects, in order to compile the macros first, then the actual project using these macros.

The purpose of these macros are to be be used in a log method:

def log( msg: Any, srcFile: String = "", srcLine: String = "", srcClass:String = "")

I am then using this log method as follows:

log(msg, s"$F_",s"$L_",s"$C_")

where F_, L_ and C_ are defined in the macro.

Now, I would like to create a shortcut to avoid this boilerplate and just call:

log(msg)

which should automatically be replaced by

log(msg, s"$F_",s"$L_",s"$C_")

I could define a macro to do this:

def log_(msg: String) : Unit = macro logImpl
def logImpl( c: Context )(msg: c.Expr[String]): c.Expr[Unit] = {
  import c.universe._
  reify( log(msg.splice, srcFile=s"$F_", srcLine=s"$L_", srcClass=s"$C_") )
}

but again, this macro needs to be compiled before the project, where the log function itself is defined... So I don't see how to solve the compilation dependencies cycle...

Any suggestion about how to do this? Thanks

Was it helpful?

Solution

Barring the use of macro annotations (which would necessarily and significantly alter your API's syntax), the problem you have to face is that you need the type-checked identifier of your log function.

Since you can't import the entire log implementation, a solution would be to:

  • wrap the method into a trait,
  • define this trait in the "macro" project,
  • add an implicit parameter to the log_ method,
  • in your "main" project, create an implementation of this trait, and instantiate this implementation in an implicit val visible everywhere you'd like to use the log_ macro (in the package object for example).

Of course, you could also use a simple FunctionN here and avoid the trait definition and implementation, but this way you'll avoid potential conflicts with other same-typed implicits.

In general, your code would resemble the following:

//"macro" project
trait EncapsulatingTrait {
  def yourMethod(...)
}

object Macros {
  def myMacro(...)(implicit param: EncapsulatingTrait) = macro myMacroImpl
  def myMacroImpl( c: Context )(...)
                          (param: c.Expr[EncapsulatingTrait]): c.Expr[...] = {
    import c.universe._
    reify(param.splice.yourMethod(...))
  }
}

//--------------------------
//"main" project
class Impl extends EncapsulatingTrait {
  def yourMethod(...)
}

...

implicit val defaultParam = new Impl

import Macros.myMacro

myMacro(...)

In your specific case, here's how an implementation could look like:

//"macro" project
package yourpackage

import java.io.File
import language.experimental.macros
import scala.reflect.macros.Context

trait LogFunction {
  def log( msg: Any, srcFile: String = "", srcLine: Int = -1, srcClass:String = "")
}


object Macros {
      // get current line in source code
      def L_ : Int = macro lineImpl
      def lineImpl( c: Context ): c.Expr[Int] = {
        import c.universe._
        val line = Literal( Constant( c.enclosingPosition.line ) )
        c.Expr[Int]( line )
      }

      // get current file from source code (relative path)
      def F_ : String = macro fileImpl
      def fileImpl( c: Context ): c.Expr[String] = {
        import c.universe._
        val absolute = c.enclosingPosition.source.file.file.toURI
        val base = new File( "." ).toURI
        val path = Literal( Constant( c.enclosingPosition.source.file.file.getName() ) )
        c.Expr[String]( path )
      }

      // get current class/object (a bit sketchy)
      def C_ : String = macro classImpl
      def classImpl( c: Context ): c.Expr[String] = {
        import c.universe._

        val class_ = Literal( Constant( c.enclosingClass.toString.split(" ")( 1 ) ) )  
        c.Expr[String]( class_ )
      }


     def log_(msg: String)(implicit logFunc: LogFunction) : Unit = macro logImpl
     def logImpl( c: Context )(msg: c.Expr[String])(logFunc: c.Expr[LogFunction]): c.Expr[Unit] = {
      import c.universe._
      reify( logFunc.splice.log(msg.splice, srcFile=fileImpl(c).splice, srcLine=lineImpl(c).splice, srcClass=classImpl(c).splice) )
     }
}


//--------------------------
//"main" project
import yourpackage.LogFunction

class LogImpl extends LogFunction {
  def log( msg: Any, srcFile: String = "", srcLine: Int = -1, srcClass:String = "") {
    println(List(msg,srcFile,srcLine,srcClass).mkString("|"))
  }
}

object testLog {

  def main(args: Array[String]): Unit = {

    implicit val defaultLog = new LogImpl

    import yourpackage.Macros.log_

    log_("blah")

  }

}

(note that I had to correct the signature of log_ and tweak the macro call a bit)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top