2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2013-07-10

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

src/compiler/scala/tools/nsc/ast/parser/Parsers.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という抽象クラスがあります。
今回は、熟読するほどのものはなさそうです。

次に、

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

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

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

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()でも見てみますか。

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

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
      }

えーと、

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

(つづく)

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?