つづいて、構文解析を読んでいきましょう。
src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
を見てみると、再帰降下法で地道に書かれたもののようです。
各メソッドが各(文法の)生成規則に対応しています。
ざっと見てみる
まずは、どんなクラス(やトレイト)があるか見てみます。
trait ParsersCommon extends ScannersCommon {
...
/** This is now an abstract class, only to work around the optimizer:
* methods in traits are never inlined.
*/
abstract class ParserCommon {
…
}
}
ParsersCommonというトレイトがあって、内部にParserCommonという抽象クラスがあります。
今回は、熟読するほどのものはなさそうです。
次に、
trait Parsers extends Scanners with MarkupParsers with ParsersCommon {
self =>
val global: Global
import global._
case class OpInfo(operand: Tree, operator: Name, offset: Offset)
class SourceFileParser(val source: SourceFile) extends Parser {
...
}
class OutlineParser(source: SourceFile) extends SourceFileParser(source) {
...
}
class UnitParser(val unit: global.CompilationUnit, patches: List[BracePatch]) extends SourceFileParser(unit.source) {
…
}
import nme.raw
abstract class Parser extends ParserCommon {
val in: Scanner
…
}
}
Parsersトレイトのなかに、
・case class OpInfo
・class SourceFileParser
・class OutlineParser
・class UnitParser
・abstract class Parser
が含まれています。
OutLineParserもUnitParserも、SourceFileParserを継承しています。
SourceFileParserは、名前から推測するにソースファイルを読み込んで構文解析するクラスっぽいですね。
この中で異質の抽象クラスParserがどうやら構文解析のメインになるようです。
抽象クラスParser
クラスParserの気になる部分を適当に眺めていきます。
/** The types of the context bounds of type parameters of the surrounding class
*/
private var classContextBounds: List[Tree] = Nil
@inline private def savingClassContextBounds[T](op: => T): T = {
val saved = classContextBounds
try op
finally classContextBounds = saved
}
classContextBoundsは、classContextBoundsを保存しておいて、op
を実行したら、保存しておいたclassContextBoundsに戻す。という処理ですね。
これは、クラス定義内で内部クラス等を定義した場合に備えてるのかな…(たぶん、おそらく、自信は無いが)。
def parseStartRule: () => Tree
/** This is the general parse entry point.
*/
def parse(): Tree = {
val t = parseStartRule()
accept(EOF)
t
}
メソッドparse()が、おそらく構文解析の始まりと思われます。
このなかで呼ばれているparseStartRule()を探してみると、
クラスSourceFileParserの中にありました。
その次の行の、
accept(EOF)は、「文末(End Of File)を受け取る」という意味でしょうね。
文末じゃなかったら、エラーでも起きるのかもしれませんね…。
/** The parse starting point depends on whether the source file is self-contained:
* if not, the AST will be supplemented.
*/
def parseStartRule =
if (source.isSelfContained) () => compilationUnit()
else () => scriptBody()
場合によって、compilationUnit()かscriptBody()が呼ばれる…と。
isSelfContainedの意味がよくわかりませんが(英語不得意)、
とりあえず、compilationUnit()でも見てみますか。
/** {{{
* CompilationUnit ::= {package QualId semi} TopStatSeq
* }}}
*/
def compilationUnit(): PackageDef = checkNoEscapingPlaceholders {
def topstats(): List[Tree] = {
...
ts.toList
}
resetPackage()
topstats() match {
case (stat @ PackageDef(_, _)) :: Nil => stat
case stats =>
val start =
if (stats forall (_ == EmptyTree)) 0
else {
val wpos = wrappingPos(stats)
if (wpos.isDefined) wpos.startOrPoint
else 0
}
makeEmptyPackage(start, stats)
}
}
topstats()の返り値によって、処理がわかれているようですが、重要なのは、topstats()っぽいですね。
省略しちゃたけど、真面目にみてみますか…。
def topstats(): List[Tree] = {
val ts = new ListBuffer[Tree]
while (in.token == SEMI) in.nextToken()
val start = in.offset
if (in.token == PACKAGE) {
in.nextToken()
if (in.token == OBJECT) {
// TODO - this next line is supposed to be
// ts += packageObjectDef(start)
// but this broke a scaladoc test (run/diagrams-filtering.scala) somehow.
ts ++= joinComment(List(makePackageObject(start, objectDef(in.offset, NoMods))))
if (in.token != EOF) {
acceptStatSep()
ts ++= topStatSeq()
}
} else {
in.flushDoc
val pkg = pkgQualId()
if (in.token == EOF) {
ts += makePackaging(start, pkg, List())
} else if (isStatSep) {
in.nextToken()
ts += makePackaging(start, pkg, topstats())
} else {
ts += inBraces(makePackaging(start, pkg, topStatSeq()))
acceptStatSepOpt()
ts ++= topStatSeq()
}
}
} else {
ts ++= topStatSeq()
}
ts.toList
}
えーと、
while (in.token == SEMI) in.nextToken()
で、空行を読み飛ばしてるのかな。
となると、in.tokenは現在のトークンを表していて、
in.nextToken()で次のトークンに移動ってとこですか。
もちろん、inは字句解析器からの入力…と。
if (in.token == PACKAGE) {
in.nextToken()
if (in.token == OBJECT) {
...
if (in.token != EOF) {
acceptStatSep()
ts ++= topStatSeq()
}
} else {
...
if (in.token == EOF) {
...
} else {
ts += inBraces(makePackaging(start, pkg, topStatSeq()))
acceptStatSepOpt()
ts ++= topStatSeq()
}
}
} else {
ts ++= topStatSeq()
}
の部分を見ると、
・トークンPACKAGEがあれば、パッケージの解析
・なければ、トップレベルの解析
ってことかな。
いずれにしても、topStatSeq()が呼ばれてますね。
(つづく)