4

我正在编写一个 Scala 编译器插件,并且已经到了编写“apply(unit:CompilationUnit”方法)的地步。但是,该方法中的语法超出了我的范围。以下代码来自http://www.scala-lang .org/node/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")
      }

此表达式查找所有除以零。我不知道如何做类似的事情来查找所有可执行语句(TermTrees??)。

4

1 回答 1

7

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

class TwoStatements {
  def f {
    println("first statement")
    println("second statement")
  }
}

试试这些命令:

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 

请注意,这它将产生输出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
 )

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

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

如果您正在寻找类似的东西tree @ Statement(...),它不存在。您可以使用TermTree(或者更好的是 method 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 }
于 2012-05-02T19:28:09.610 回答