10

私はStringContextこれを書くことを可能にする拡張を達成しようとしています:

val tz = zone"Europe/London" //tz is of type java.util.TimeZone

ただし、指定されたタイムゾーンが無効な場合はコンパイルに失敗するという警告が追加されています(コンパイル時に決定できると仮定します)。

ヘルパー関数は次のとおりです。

def maybeTZ(s: String): Option[java.util.TimeZone] =
  java.util.TimeZone.getAvailableIDs collectFirst { case id if id == s =>
    java.util.TimeZone.getTimeZone(id)
  }

マクロ以外の実装を非常に簡単に作成できます。

scala> implicit class TZContext(val sc: StringContext) extends AnyVal {
 |   def zone(args: Any *) = {
 |     val s = sc.raw(args.toSeq : _ *)
 |     maybeTZ(s) getOrElse sys.error(s"Invalid zone: $s")
 |   }
 | }

それで:

scala> zone"UTC"
res1: java.util.TimeZone = sun.util.calendar.ZoneInfo[id="UTC",offset=0,...

ここまでは順調ですね。zone"foobar"ただし、タイムゾーンが無意味な場合 (例: )はコンパイルに失敗しません。コードは実行時に失敗します。それをマクロに拡張したいのですが、ドキュメントを読んでも、詳細に本当に苦労しています(正確にはすべての詳細です)。

ここで私を始めるのを手伝ってくれる人はいますか? 歌って踊るソリューションはStringContext、引数が定義されているかどうかを確認し、(定義されている場合) 実行時まで計算を延期し、そうでない場合はコンパイル時にゾーンを解析しようとする必要があります。


私は何を試しましたか?

マクロ定義は、静的にアクセス可能なオブジェクトにある必要があるようです。そう:

package object oxbow {
  implicit class TZContext(val sc: StringContext) extends AnyVal {
    def zone(args: Any *) = macro zoneImpl //zoneImpl cannot be in TZContext
  }

  def zoneImpl(c: reflect.macros.Context)
    (args: c.Expr[Any] *): c.Expr[java.util.TimeZone] = {
      import c.universe._
      //1. How can I access sc from here?

      /// ... if I could, would this be right?
      if (args.isEmpty) {
        val s = sc.raw()
        reify(maybeTZ(s) getOrElse sys.error(s"Not valid $s")) 
      }
      else {
        //Ok, now I'm stuck. What goes here?
      }
    }

}

以下のsom-snyttの提案に基づいて、これが最新の試みです:

def zoneImpl(c: reflect.macros.Context)
           (args: c.Expr[Any] *): c.Expr[java.util.TimeZone] = {
  import c.universe._
  val z =
    c.prefix.tree match {
      case Apply(_, List(Apply(_, List(Literal(Constant(const: String)))))) => gsa.shared.datetime.XTimeZone.getTimeZone(const)
      case x => ??? //not sure what to put here
    }

  c.Expr[java.util.TimeZone](Literal(Constant(z))) //this compiles but doesn't work at the use-site
                             ^^^^^^^^^^^^^^^^^^^
                             this is wrong. What should it be?
}

use-site で、validzone"UTC"が次のエラーでコンパイルに失敗します。

java.lang.Error: bad constant value: sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null] of class class sun.util.calendar.ZoneInfo

おそらく、Literal(Constant( .. ))それを囲むために a を使用すべきではありませんでした。私は何を使うべきでしたか?


最後の例 - 以下の Travis Brown の回答に基づく

def zoneImpl(c: reflect.macros.Context)
         (args: c.Expr[Any] *): c.Expr[java.util.TimeZone] = {
  import c.universe._
  import java.util.TimeZone

  val tzExpr: c.Expr[String] = c.prefix.tree match {
    case Apply(_, Apply(_, List(tz @ Literal(Constant(s: String)))) :: Nil)
      if TimeZone.getAvailableIDs contains s => c.Expr(tz)
    case Apply(_, Apply(_, List(tz @ Literal(Constant(s: String)))) :: Nil) =>
      c.abort(c.enclosingPosition, s"Invalid time zone! $s")
    case _ => ??? 
//            ^^^ What do I do here? I do not want to abort, I merely wish to 
//                "carry on as you were". I've tried ... 
//                    c.prefix.tree.asInstanceOf[c.Expr[String]]
//                ...but that does not work
  }
  c.universe.reify(TimeZone.getTimeZone(tzExpr.splice))

}
4

2 に答える 2

7

TimeZone問題は、マクロによって生成されたコードにのコンパイル時のインスタンスを密輸できないことです。ただし、文字列リテラルをすり抜けることができるため、コンパイル時に識別子が使用可能であることを確認しながら、実行時に必要なを構築するコードを生成できます。TimeZone

以下は、完全な動作例です。

object TimeZoneLiterals {
  import java.util.TimeZone
  import scala.language.experimental.macros
  import scala.reflect.macros.Context

  implicit class TZContext(val sc: StringContext) extends AnyVal {
    def zone() = macro zoneImpl
  }

  def zoneImpl(c: reflect.macros.Context)() = {
    import c.universe._

    val tzExpr = c.prefix.tree match {
      case Apply(_, Apply(_, List(tz @ Literal(Constant(s: String)))) :: Nil)
        if TimeZone.getAvailableIDs contains s => c.Expr(tz)
      case _ => c.abort(c.enclosingPosition, "Invalid time zone!")
    }

    reify(TimeZone.getTimeZone(tzExpr.splice))
  }
}

への引数reifyは、生成されたメソッドの本体になります—文字通り、あらゆる種類の評価の後ではありませんが、tzExpr.sliceビットがコンパイル時の文字列リテラルに置き換えられることを除いて (もちろん、リストで見つかった場合)そうしないと、コンパイル時にエラーが発生します)。

于 2013-06-10T19:03:46.297 に答える