【问题标题】:How to print source code of "IF" condition in "THEN"如何在“THEN”中打印“IF”条件的源代码
【发布时间】:2014-08-21 05:15:32
【问题描述】:

我想在 THEN 部分打印 IF 条件的 Scala 源代码。

例如:IF{ 2 + 2 < 5 } THEN { println("I am in THEN because: " + sourceCodeOfCondition) }

我们现在跳过THEN部分,问题是:如何获取IF之后的block源代码?

我假设 IF 应该是一个宏...

注意:这个问题是Macro to access source code of function at runtime 的重新定义版本,我在其中描述了{ val i = 5; List(1, 2, 3); true }.logValueImpl 对我有用(根据其他问题Macro to access source code text at runtime)。

【问题讨论】:

    标签: scala reflection macros scala-macros


    【解决方案1】:

    即兴实施,因为我只有一分钟时间:

    import scala.reflect.macros.Context
    import scala.language.experimental.macros
    
    case class Conditional(conditionCode: String, value: Boolean) {
      def THEN(doIt: Unit) = macro Conditional.THEN_impl
    }
    
    object Conditional {
      def sourceCodeOfCondition: String = ???
    
      def IF(condition: Boolean) = macro IF_impl
    
      def IF_impl(c: Context)(condition: c.Expr[Boolean]): c.Expr[Conditional] = {
        import c.universe._
    
        c.Expr(q"Conditional(${ show(condition.tree) }, $condition)")
      }
    
      def THEN_impl(c: Context)(doIt: c.Expr[Unit]): c.Expr[Unit] = {
        import c.universe._
    
        val rewriter = new Transformer {
          override def transform(tree: Tree) = tree match {
            case Select(_, TermName("sourceCodeOfCondition")) =>
              c.typeCheck(q"${ c.prefix.tree }.conditionCode")
            case other => super.transform(other)
          }
        }
    
        c.Expr(q"if (${ c.prefix.tree }.value) ${ rewriter.transform(doIt.tree) }")
      }
    }
    

    然后:

    object Demo {
      import Conditional._
    
      val x = 1
    
      def demo = IF { x + 5 < 10 } THEN { println(sourceCodeOfCondition) }
    }
    

    最后:

    scala> Demo.demo
    Demo.this.x.+(5).<(10)
    

    这是对来源的脱糖表示,但在我脑海中,我认为这是你能得到的最好的。

    有关该技术的一些讨论,请参阅我的博客文章 here

    【讨论】:

    • 非常感谢您的解决方案,不幸的是我在case Select(_, TermName("sourceCodeOfCondition")) =&gt; 中收到了not found: value TermName,您的设置是什么?要添加依赖项,我使用了 SBT 交叉编译 docs.scala-lang.org/overviews/quasiquotes/setup.html.
    • 我不认为在 2.11 之前有 TermName 提取器,但是用警卫完成同样的事情很容易。你在 2.10 上吗?
    • 是的,我在 2.10。我将引用@Eugene Burmako 写给我的内容:“我已经看到您很可能在 2.10 上遇到错误,但这些应该或多或少容易修复。而不是 case Select(_, TermName("sourceCodeOfCondition")) 只需写case Select(_, name) if name == newTermName("sourceCodeOfCondition"),这应该足以让事情顺利进行。” 是的,尤金,这行得通!非常感谢你们。
    【解决方案2】:

    从 2.13 开始,您还可以通过包装表达式来做到这一点,这意味着您不必定义自定义 if 函数:

    implicit def debugIf[A]: DebugIf => Unit = { cond: DebugIf =>
      logger.info(s"condition = {}, result = ${cond.result}", cond.code)
    }
    
    decorateIfs {
      if (System.currentTimeMillis() % 2 == 0) {
        println("decorateIfs: if block")
      } else {
        println("decorateIfs: else block")
      }
    }
    

    使用宏实现:

      def decorateIfs[A: c.WeakTypeTag](a: c.Expr[A])(output: c.Expr[DebugIf => Unit]): c.Expr[A] = {
        def isEmpty(tree: Trees#Tree): Boolean = {
          tree match {
            case Literal(Constant(())) =>
              true
            case other =>
              false
          }
        }
    
        c.Expr[A] {
          a.tree match {
            // https://docs.scala-lang.org/overviews/quasiquotes/expression-details.html#if
            case q"if ($cond) $thenp else $elsep" =>
              val condSource = extractRange(cond) getOrElse ""
              val printThen = q"$output(DebugIf($condSource, true))"
              val elseThen = q"$output(DebugIf($condSource, false))"
    
              val thenTree = q"""{ $printThen; $thenp }"""
              val elseTree = if (isEmpty(elsep)) elsep else q"""{ $elseThen; $elsep }"""
              q"if ($cond) $thenTree else $elseTree"
            case other =>
              other
          }
        }
      }
    
      private def extractRange(t: Trees#Tree): Option[String] = {
        val pos = t.pos
        val source = pos.source.content
        if (pos.isRange) Option(new String(source.drop(pos.start).take(pos.end - pos.start))) else None
      }
    
      case class DebugIf(code: String, result: Boolean)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-26
      • 2015-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-03
      • 1970-01-01
      相关资源
      最近更新 更多