diff options
author | Paul Phillips <paulp@improving.org> | 2013-04-20 12:25:03 -0700 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2013-04-20 14:58:09 -0700 |
commit | c4d0fd9998d96843cd9704d5610a29ab752ecb14 (patch) | |
tree | c8bf37f4c1e474b85c859593463a319ff1407104 | |
parent | 68c6ba7e59befa88b2f383dd7eca71a9329a8b6d (diff) | |
download | scala-c4d0fd9998d96843cd9704d5610a29ab752ecb14.tar.gz scala-c4d0fd9998d96843cd9704d5610a29ab752ecb14.tar.bz2 scala-c4d0fd9998d96843cd9704d5610a29ab752ecb14.zip |
-Yshow-member-pos, print the positions of members.
Here for instance is a command line which leverages the
output of this option to print the method bodies of all
methods called 'transformInfo' found under src. Given
1500 source files it accomplishes this in four seconds,
thanks to -Ystop-after:parser.
% scalac -Yshow-member-pos sed -Ystop-after:parser \
$(find src/compiler -name '*.scala') | \
grep transformInfo | sed 's/ # .*//;' | \
while read line; do echo "// $line" && gsed -n $line && echo; done
Or more simply, the start/end lines of each member of Random:
% scalac -Yshow-member-pos "" ./src/library/scala/util/Random.scala
./src/library/scala/util/Random.scala
20,134 class Random
33 def nextBoolean
38 def nextBytes
43 def nextDouble
48 def nextFloat
54 def nextGaussian
59 def nextInt
65 def nextInt
70 def nextLong
81,89 def nextString
82,86 def safeChar
83 val surrogateStart
84 val res
94,98 def nextPrintableChar
[snip]
It makes me sad I'm always in the position of having to hack
the compiler to do this sort of thing. All we need is something
like -Yinsert-phase:Foo where Foo is a class implementing a
(Phase, Tree) => Tree method, and the compiler runs all the
unit.bodies through it after each phase, and then one could
easily accomplish this in the privacy of one's own compiler.
-rw-r--r-- | src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala | 92 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/settings/ScalaSettings.scala | 1 | ||||
-rw-r--r-- | test/files/run/memberpos.check | 11 | ||||
-rw-r--r-- | test/files/run/memberpos.scala | 39 |
4 files changed, 131 insertions, 12 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala index 80d70e6428..3a695c6f59 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala @@ -11,30 +11,98 @@ import javac._ /** An nsc sub-component. */ abstract class SyntaxAnalyzer extends SubComponent with Parsers with MarkupParsers with Scanners with JavaParsers with JavaScanners { + import global._ val phaseName = "parser" - def newPhase(prev: Phase): StdPhase = new ParserPhase(prev) - class ParserPhase(prev: scala.tools.nsc.Phase) extends StdPhase(prev) { + abstract class MemberDefTraverser extends Traverser { + def onMember(defn: MemberDef): Unit + + private var depth: Int = 0 + private def lower[T](body: => T): T = { + depth += 1 + try body finally depth -= 1 + } + def currentDepth = depth + + /** Prune this tree and all trees beneath it. Can be overridden. */ + def prune(md: MemberDef): Boolean = ( + md.mods.isSynthetic + || md.mods.isParamAccessor + || nme.isConstructorName(md.name) + || (md.name containsName nme.ANON_CLASS_NAME) + ) + + override def traverse(t: Tree): Unit = t match { + case md: MemberDef if prune(md) => + case md @ PackageDef(_, stats) => traverseTrees(stats) + case md: ImplDef => onMember(md) ; lower(traverseTrees(md.impl.body)) + case md: ValOrDefDef => onMember(md) ; lower(traverse(md.rhs)) + case _ => super.traverse(t) + } + } + + class MemberPosReporter(unit: CompilationUnit) extends MemberDefTraverser { + private var outputFn: MemberDef => String = outputForScreen + val path = unit.source.file.path + + // If a single line, outputs the line; if it spans multiple lines + // outputs NN,NN with start and end lines, e.g. 15,25. + def outputPos(md: MemberDef): String = { + val pos = md.pos + val start = pos.focusStart.line + val end = pos.focusEnd.line + + if (start == end) "" + start else s"$start,$end" + } + def outputForSed(md: MemberDef): String = { + val pos_s = "%-12s" format outputPos(md) + "p" + s"$pos_s $path # ${md.keyword} ${md.name}" + } + def outputForScreen(md: MemberDef): String = { + val pos_s = "%-20s" format " " * currentDepth + outputPos(md) + s"$pos_s ${md.keyword} ${md.name}" + } + + def onMember(md: MemberDef) = println(outputFn(md)) + // It recognizes "sed" and "anything else". + def show(style: String) { + if (style == "sed") { + outputFn = outputForSed + traverse(unit.body) + } + else { + outputFn = outputForScreen + println(path) + traverse(unit.body) + } + println("") + } + } + + private def initialUnitBody(unit: CompilationUnit): Tree = { + if (unit.isJava) new JavaUnitParser(unit).parse() + else if (global.reporter.incompleteHandled) newUnitParser(unit).parse() + else newUnitParser(unit).smartParse() + } + + class ParserPhase(prev: Phase) extends StdPhase(prev) { override val checkable = false override val keepsTypeParams = false - def apply(unit: global.CompilationUnit) { - import global._ + def apply(unit: CompilationUnit) { informProgress("parsing " + unit) - // if the body is already filled in, do nothing + // if the body is already filled in, don't overwrite it // otherwise compileLate is going to overwrite bodies of synthetic source files - if (unit.body == EmptyTree) { - unit.body = - if (unit.isJava) new JavaUnitParser(unit).parse() - else if (reporter.incompleteHandled) newUnitParser(unit).parse() - else newUnitParser(unit).smartParse() - } + if (unit.body == EmptyTree) + unit.body = initialUnitBody(unit) if (settings.Yrangepos && !reporter.hasErrors) validatePositions(unit.body) + + if (settings.Ymemberpos.isSetByUser) + new MemberPosReporter(unit) show (style = settings.Ymemberpos.value) } } } - diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index ee9a3aed2a..a30f144802 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -164,6 +164,7 @@ trait ScalaSettings extends AbsScalaSettings val refinementMethodDispatch = ChoiceSetting ("-Ystruct-dispatch", "policy", "structural method dispatch policy", List("no-cache", "mono-cache", "poly-cache", "invoke-dynamic"), "poly-cache") val Yrangepos = BooleanSetting ("-Yrangepos", "Use range positions for syntax trees.") + val Ymemberpos = StringSetting ("-Yshow-member-pos", "output style", "Show start and end positions of members", "") withPostSetHook (_ => Yrangepos.value = true) val Yreifycopypaste = BooleanSetting ("-Yreify-copypaste", "Dump the reified trees in copypasteable representation.") val Yreplsync = BooleanSetting ("-Yrepl-sync", "Do not use asynchronous code for repl startup") val Yreploutdir = StringSetting ("-Yrepl-outdir", "path", "Write repl-generated classfiles to given output directory (use \"\" to generate a temporary dir)" , "") diff --git a/test/files/run/memberpos.check b/test/files/run/memberpos.check new file mode 100644 index 0000000000..e7d3534000 --- /dev/null +++ b/test/files/run/memberpos.check @@ -0,0 +1,11 @@ +newSource1 +2,4 class A +6,28 object A + 7,10 def bippy + 8 def hello + 11,27 class Dingo + 12,26 def foooooz + 22 val a +30 class B + 30 def f + diff --git a/test/files/run/memberpos.scala b/test/files/run/memberpos.scala new file mode 100644 index 0000000000..f2b79c0ec1 --- /dev/null +++ b/test/files/run/memberpos.scala @@ -0,0 +1,39 @@ +import scala.tools.partest._ + +// Simple sanity test for -Yshow-member-pos. +object Test extends DirectTest { + override def extraSettings: String = "-usejavacp -Ystop-after:parser -Yshow-member-pos \"\" -d " + testOutput.path + override def show() = compile() + override def code = """ +class A(val a: Int = 1) { + +} + +object A { + def bippy = { + def hello = 55 + "" + hello + } + class Dingo { + def foooooz = /**** + + + + + + ****/ { + + + + val a = 1 + + + a + } + } +} + +class B { def f = 1 } + +""" +} |