如何在编译器插件中找到Scala程序中的语句?

Bri*_*box 4 compiler-construction plugins scala

我正在编写一个Scala编译器插件,并且已经开始编写"apply(unit:CompilationUnit"方法).该方法中的语法超出了我的范围.以下代码来自http://www.scala-lang .ORG /节点/ 140

    for ( tree @ Apply(Select(rcvr, nme.DIV), List(Literal(Constant(0)))) <- unit.body;
         if rcvr.tpe <:< definitions.IntClass.tpe) {
        unit.error(tree.pos, "definitely division by zero")
      }
Run Code Online (Sandbox Code Playgroud)

该表达式将所有除以零.我无法弄清楚如何做类似的事情来找到所有可执行语句(TermTrees ??).

Dan*_*ral 7

好的,假设TwoStatements.scala下面的文件:

class TwoStatements {
  def f {
    println("first statement")
    println("second statement")
  }
}
Run Code Online (Sandbox Code Playgroud)

试试这些命令:

scalac -Yshow-trees -Xprint:typer TwoStatements.scala 
scalac -Yshow-trees-compact -Xprint:typer TwoStatements.scala 
scalac -Yshow-trees-stringified -Xprint:typer TwoStatements.scala 
scalac -Ybrowse:typer TwoStatements.scala 
Run Code Online (Sandbox Code Playgroud)

请注意,typer是相会产生输出.因此,请选择适合您自己的插件运行阶段的阶段.

-Yshow-trees-compact是一个产生与你自己的代码中使用的输出完全(或非常接近)的输出,但它很难阅读.

其他的更容易阅读,但可能会混淆翻译成您自己的代码.这-Yshow-trees-stringified可能会比-Yshow-trees显示最多信息更有用.另一方面,它-Ybrowse:typer是交互式的,并显示所选树节点的代码,这可能会有所帮助,尤其是当您查看较大的程序时.

如果您尝试-Yshow-trees-compact使用链接博客中的示例,您会看到以下代码段:

 Apply(
   Select(
     Select(This(newTypeName("Test")), newTermName("five")), // assigned to rcvr
     newTermName("$div")                                     // compared to nme.DIV
   ), 
   List(Literal(Constant(0))))                               // as is
 )
Run Code Online (Sandbox Code Playgroud)

所以我建议你看看你想要处理的代码是如何在你的插件工作的阶段被翻译成的,然后只需抓住代码片段,并用变量替换任何不感兴趣的部分.

你会注意到每个DefDef都有身体作为它的第六个元素.它可能是一个Block带有List多个语句的,它可能是方法call(Apply),getter(Select),assigment(Assign),if语句(If)等等.其他类型的声明,例如ValDef,也有与之关联的代码.

如果您正在寻找类似的东西tree @ Statement(...),它就不存在了.您可以使用TermTree(或者更好的方法isTerm)来识别代表代码的内容,但这不会让您将表达式或完整块的部分语句分开.

查看实际代码的AST,并了解它是如何工作的.

编辑

观看演示文稿只是在scala/reflecti/api/Trees.scala文件末尾的这个评论中引用了我:

// A standard pattern match
  case EmptyTree =>
  case PackageDef(pid, stats) =>
     // package pid { stats }
  case ClassDef(mods, name, tparams, impl) =>
     // mods class name [tparams] impl   where impl = extends parents { defs }
  case ModuleDef(mods, name, impl) =>                             (eliminated by refcheck)
     // mods object name impl  where impl = extends parents { defs }
  case ValDef(mods, name, tpt, rhs) =>
     // mods val name: tpt = rhs
     // note missing type information is expressed by tpt = TypeTree()
  case DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
     // mods def name[tparams](vparams_1)...(vparams_n): tpt = rhs
     // note missing type information is expressed by tpt = TypeTree()
  case TypeDef(mods, name, tparams, rhs) =>                       (eliminated by erasure)
     // mods type name[tparams] = rhs
     // mods type name[tparams] >: lo <: hi,  where lo, hi are in a TypeBoundsTree,
                                              and DEFERRED is set in mods
  case LabelDef(name, params, rhs) =>
     // used for tailcalls and like
     // while/do are desugared to label defs as follows:
     // while (cond) body ==> LabelDef($L, List(), if (cond) { body; L$() } else ())
     // do body while (cond) ==> LabelDef($L, List(), body; if (cond) L$() else ())
  case Import(expr, selectors) =>                                 (eliminated by typecheck)
     // import expr.{selectors}
     // Selectors are a list of pairs of names (from, to).
     // The last (and maybe only name) may be a nme.WILDCARD
     // for instance
     //   import qual.{x, y => z, _}  would be represented as
     //   Import(qual, List(("x", "x"), ("y", "z"), (WILDCARD, null)))
  case Template(parents, self, body) =>
     // extends parents { self => body }
     // if self is missing it is represented as emptyValDef
  case Block(stats, expr) =>
     // { stats; expr }
  case CaseDef(pat, guard, body) =>                               (eliminated by transmatch/explicitouter)
    // case pat if guard => body
  case Alternative(trees) =>                                      (eliminated by transmatch/explicitouter)
    // pat1 | ... | patn
  case Star(elem) =>                                              (eliminated by transmatch/explicitouter)
    // pat*
  case Bind(name, body) =>                                        (eliminated by transmatch/explicitouter)
    // name @ pat
  case UnApply(fun: Tree, args)                                   (introduced by typer, eliminated by transmatch/explicitouter)
    // used for unapply's
  case ArrayValue(elemtpt, trees) =>                              (introduced by uncurry)
    // used to pass arguments to vararg arguments
    // for instance, printf("%s%d", foo, 42) is translated to after uncurry to:
    // Apply(
    //   Ident("printf"),
    //   Literal("%s%d"),
    //   ArrayValue(<Any>, List(Ident("foo"), Literal(42))))
  case Function(vparams, body) =>                                 (eliminated by lambdaLift)
    // vparams => body  where vparams:List[ValDef]
  case Assign(lhs, rhs) =>
    // lhs = rhs
  case AssignOrNamedArg(lhs, rhs) =>                              (eliminated by typer, resurrected by reifier)
    // @annotation(lhs = rhs)
  case If(cond, thenp, elsep) =>
    // if (cond) thenp else elsep
  case Match(selector, cases) =>
    // selector match { cases }
  case Return(expr) =>
    // return expr
  case Try(block, catches, finalizer) =>
    // try block catch { catches } finally finalizer where catches: List[CaseDef]
  case Throw(expr) =>
    // throw expr
  case New(tpt) =>
    // new tpt   always in the context: (new tpt).<init>[targs](args)
  case Typed(expr, tpt) =>                                        (eliminated by erasure)
    // expr: tpt
  case TypeApply(fun, args) =>
    // fun[args]
  case Apply(fun, args) =>
    // fun(args)
    // for instance fun[targs](args)  is expressed as  Apply(TypeApply(fun, targs), args)
  case ApplyDynamic(qual, args)                                   (introduced by erasure, eliminated by cleanup)
    // fun(args)
  case Super(qual, mix) =>
    // qual.super[mix]     qual is always This(something), if mix is empty, it is tpnme.EMPTY
  case This(qual) =>
    // qual.this
  case Select(qualifier, selector) =>
    // qualifier.selector
  case Ident(name) =>
    // name
    // note: type checker converts idents that refer to enclosing fields or methods
    // to selects; name ==> this.name
  case ReferenceToBoxed(ident) =>                                 (created by typer, eliminated by lambdalift)
    // synthetic node emitted by macros to reference capture vars directly without going through ``elem''
    // var x = ...; fun { x } will emit Ident(x), which gets transformed to Select(Ident(x), "elem")
    // if ReferenceToBoxed were used instead of Ident, no transformation would be performed
  case Literal(value) =>
    // value
  case TypeTree() =>                                              (introduced by refcheck)
    // a type that's not written out, but given in the tpe attribute
  case Annotated(annot, arg) =>                                   (eliminated by typer)
    // arg @annot  for types,  arg: @annot for exprs
  case SingletonTypeTree(ref) =>                                  (eliminated by uncurry)
    // ref.type
  case SelectFromTypeTree(qualifier, selector) =>                 (eliminated by uncurry)
    // qualifier # selector, a path-dependent type p.T is expressed as p.type # T
  case CompoundTypeTree(templ: Template) =>                       (eliminated by uncurry)
    // parent1 with ... with parentN { refinement }
  case AppliedTypeTree(tpt, args) =>                              (eliminated by uncurry)
    // tpt[args]
  case TypeBoundsTree(lo, hi) =>                                  (eliminated by uncurry)
    // >: lo <: hi
  case ExistentialTypeTree(tpt, whereClauses) =>                  (eliminated by uncurry)
    // tpt forSome { whereClauses }
Run Code Online (Sandbox Code Playgroud)