Scalaのソースコードを読んでみる　その４


つづいて、構文解析を読んでいきましょう。

src/compiler/scala/tools/nsc/ast/parser/Parsers.scala

を見てみると、再帰降下法で地道に書かれたもののようです。
各メソッドが各（文法の）生成規則に対応しています。

# ざっと見てみる

まずは、どんなクラス（やトレイト）があるか見てみます。

```scala: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という抽象クラスがあります。
今回は、熟読するほどのものはなさそうです。

次に、

```scala:Parsers.scala
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の気になる部分を適当に眺めていきます。

```scala:Parsers.scala
    /** 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に戻す。という処理ですね。
これは、クラス定義内で内部クラス等を定義した場合に備えてるのかな…（たぶん、おそらく、自信は無いが）。

```scala:Parsers.scala
    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)を受け取る」という意味でしょうね。
>文末じゃなかったら、エラーでも起きるのかもしれませんね…。


```scala:Parsers.scala
    /** 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()でも見てみますか。

```scala:Parsers.scala
    /** {{{
     *  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()っぽいですね。
省略しちゃたけど、真面目にみてみますか…。

```scala:Parsers.scala
      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
      }
```

えーと、

```scala
        while (in.token == SEMI) in.nextToken()
```

で、空行を読み飛ばしてるのかな。
となると、in.tokenは現在のトークンを表していて、
in.nextToken()で次のトークンに移動ってとこですか。
もちろん、inは字句解析器からの入力…と。

```scala
        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()が呼ばれてますね。


（つづく）