我正在努力实现一个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")
| }
| }
Then:
scala> zone"UTC"
res1: java.util.TimeZone = sun.util.calendar.ZoneInfo[id="UTC",offset=0,...
到目前为止,一切都很好。但如果时区无意义(例如,zone"foobar"
);代码在运行时失败。我想将其扩展到宏,但是,尽管阅读了docs http://docs.scala-lang.org/overviews/macros/overview.html,我真的很难处理细节(准确地说,是所有细节。)
有人可以帮助我从这里开始吗?全面唱歌、全面跳舞的解决方案应该看看是否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?
}
在使用现场,有效zone"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( .. ))
将其括起来。我应该用什么?
最后一个例子 - 基于下面特拉维斯·布朗的回答
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))
}