StringContext 和宏:一个简单的例子 [英] StringContext and macros: a simple example

查看:37
本文介绍了StringContext 和宏:一个简单的例子的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实现一个 StringContext 扩展,它允许我写这个:

I'm trying to achieve a StringContext extension which will allow me to write this:

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

但是附加的警告是如果提供的时区无效,它应该无法编译(假设可以在编译时确定).

But with the added caveat that it should fail to compile if the supplied time-zone is invalid (assuming that can be determined at compile-time).

这是一个辅助函数:

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

我可以很容易地创建一个非宏实现:

I can create a non-macro implementation very easily:

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"),否则编译不会失败;代码在运行时失败.我想将其扩展为宏,但是,尽管阅读了 docs,我真的很纠结细节(准确地说是所有细节.)

So far, so good. Except that this doesn't fail compilation if the timezone is nonsensical (e.g. zone"foobar"); the code falls over at runtime. I'd like to extend it to a macro but, despite reading the docs, I'm really struggling with the details (All of the details, to be precise.)

有人可以帮助我开始这里吗?全能、全能解决方案应该查看 StringContext 是否定义了任何参数,并且(如果是)将计算推迟到运行时,否则尝试在编译时解析区域

Can anyone help to get me started here? The all-singing, all-dancing solution should look to see if the StringContext defines any arguments and (if so), defer calculation until runtime, otherwise attempting to parse the zone at compile-time

我尝试了什么?

好吧,宏定义似乎必须在可静态访问的对象中.所以:

Well, macro defs appear to have to be in statically accessible objects. So:

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 的建议,这是最新的尝试:


Based on som-snytt's suggestion below, here's the latest attempt:

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" 编译失败并出现错误:

At the use-site, a valid zone"UTC" fails to compile with the error:

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( .. )) 来包含它.我应该用什么?

Presumably I should not have used a Literal(Constant( .. )) to enclose it. What should I have used?

最后一个例子 - 基于下面 Travis Brown 的回答

Last example - based on Travis Brown's answer below

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))

}

推荐答案

这是处理时区插值的歌舞"解决方案:

This is the "song-and-dance" solution that handles interpolation of the timezone:

package object timezone {
  import scala.language.implicitConversions
  implicit def zoned(sc: StringContext) = new ZoneContext(sc)
}

package timezone {
  import scala.language.experimental.macros
  import scala.reflect.macros.Context
  import java.util.TimeZone

  class ZoneContext(sc: StringContext) {

    def tz(args: Any*): TimeZone = macro TimeZoned.tzImpl

    // invoked if runtime interpolation is required
    def tz0(args: Any*): TimeZone = {
      val s = sc.s(args: _*)
      val z = TimeZoned maybeTZ s getOrElse (throw new RuntimeException(s"Bad timezone $s"))
      TimeZone getTimeZone z
    }
  }
  object TimeZoned {
    def maybeTZ(s: String): Option[String] =
      if (TimeZone.getAvailableIDs contains s) Some(s) else None

    def tzImpl(c: Context)(args: c.Expr[Any]*): c.Expr[TimeZone] = {
      import c.universe._
      c.prefix.tree match {
        case Apply(_, List(Apply(_, List(tz @Literal(Constant(const: String)))))) =>
          maybeTZ(const) map (
            k => reify(TimeZone getTimeZone c.Expr[String](tz).splice)
          ) getOrElse c.abort(c.enclosingPosition, s"Bad timezone $const")
        case x =>
          val rts = x.tpe.declaration(newTermName("tz0"))
          val rt = treeBuild.mkAttributedSelect(x, rts)
          c.Expr[TimeZone](Apply(rt, args.map(_.tree).toList))
      }
    }
  }
}

用法:

package tztest 

import timezone._

object Test extends App {

  val delta = 8
  //Console println tz"etc/GMT+$delta"  //java.lang.RuntimeException: Bad timezone etc/GMT+8
  Console println tz"Etc/GMT+$delta"
  Console println tz"US/Hawaii"
  //Console println tz"US/Nowayi"     //error: Bad timezone US/Nowayi
}

这篇关于StringContext 和宏:一个简单的例子的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆