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

src/compiler/scala/tools/nsc/Global.scala
をじっくり読もうかと思ったのですが、２４、２５行目の
import文がちょっと気になりました。

```scala:Global.scala
import ast._
import ast.parser._
```

ast といえば、abstract syntax treeの略でしょう。
しかも、parserとくれば、この辺が構文解析すくなくとも抽象構文木に関わっているのは間違いありません。

ということで、次の標的は
src/compiler/scala/tools/nsc/ast/parserディレクトリです。

Prasers.scala やら Scanners.scala, Tokens.scala, TreeBuilder.scalaなど
それっぽいファイルがありました。
この辺を読んでいきましょう。

まずは、Tokens.scalaから。

```scala:Tokens.scala
abstract class Tokens {
  /** special tokens */
  final val EMPTY = -3
  final val UNDEF = -2
  final val ERROR = -1
  final val EOF = 0

　（以下略）
```

定数の宣言だけなのかな。これはあれですね。字句解析結果のトークンを表す定数を定義しているだけのようです。
というか、lexみたいな字句解析器は使ってなさそうですね…。

次は、Scanners.scalaです。
ファイル名から考えて、ここでトークンを切り出してると思われます。
ですので、さきほどのTokens.scalaで定義されている定数をファイル中から
検索してみましょう。

例えば、"EQUALS"などで検索すると、５７５行目あたりの

```scala:Scanners.scala
    /** Can token start a statement? */
    def inFirstOfStat(token: Int) = token match {
      case EOF | CATCH | ELSE | EXTENDS | FINALLY | FORSOME | MATCH | WITH | YIELD |
           COMMA | SEMI | NEWLINE | NEWLINES | DOT | COLON | EQUALS | ARROW | LARROW |
           SUBTYPE | VIEWBOUND | SUPERTYPE | HASH | RPAREN | RBRACKET | RBRACE | LBRACKET =>
        false
      case _ =>
        true
    }
```

が見つかります。
inFirstOfStatというメソッドは、すぐ下にある
inLastOfStatというメソッドとペアだと思われます。

とりあえず、この２つのメソッドを頭にいれといて、さらに
"EQUALS"を探すと、１１６９行目あたりの

```scala:Scanner.scala
  // ------------- keyword configuration -----------------------------------

  private val allKeywords = List[(Name, Int)](
    nme.ABSTRACTkw  -> ABSTRACT,
    nme.CASEkw      -> CASE,
    ・・・（中略）・・・
    nme.EQUALSkw    -> EQUALS,

```

がみつかります。

このallKeywordsというリストは、名前通り「すべてのキーワードのリスト」と考えていいでしょう。

もし、Scalaの文法を変更するにあたり、キーワードを追加したいときには、ここに足してやればよさそうです。
nme.ほにゃらら、が何を表しているのかは調べておく必要がありそうですが…。

>nme.ほにゃらら　ですが、これは
>src/reflect/scala/reflect/internal/StdNames.scala
>の中のclass Keywordsで定義されているものみたいです。

# 字句解析を変更するには

ここまでをまとめておきます。

もし、字句解析器に変更を加えたいときには、

1. src/compiler/scala/tools/nsc/ast/parser/Tokens.scalaにキーワード用の定数を追加する。

2. src/compiler/scala/tools/nsc/ast/parser/Scanner.scalaのallKeywordsというリストに１で定義した定数を追加する。

3. メソッドinFirstOfStat, inLastOfStat に必要に応じて、１で定義した定数を追加してやる。

4. src/reflect/scala/reflect/internal/StdNames.scala のclass Keywordsに追加したいキーワードを定義しておく。具体的なコードは以下。

```scala:StdNames.scala
    final val HOGEHOGEkw: TermName       = kw("hogehoge")
```

３に関しては、似たような働きをするトークンと同じようにしてやればいいと思われます。まあ、inFirstOfStatは「文の最初にくることが可能なトークンかどうか」で判断して、inLastOfStatは「文の最後にくることが可能なトークンかどうか」で判断すればよさそうです。
ここで注意する点は、inFirstOfStatだと

```scala:Scanner.scala
    def inFirstOfStat(token: Int) = token match {
      case EOF | CATCH | ELSE | EXTENDS | FINALLY | FORSOME | MATCH | WITH | YIELD |
           COMMA | SEMI | NEWLINE | NEWLINES | DOT | COLON | EQUALS | ARROW | LARROW |
           SUBTYPE | VIEWBOUND | SUPERTYPE | HASH | RPAREN | RBRACKET | RBRACE | LBRACKET =>
        false
      case _ =>
        true
    }
```

のように、case EOF | … のほうは、falseを返すというところでしょうか。つまり、この場合だと、「EOFは文の最初にくることが出来ない」という意味になっています。

# 補足：nme.ほにゃらら

nme.ほにゃらら関係。

・nmeという変数が宣言されているファイルは
　src/reflect/scala/reflect/api/StandardNames.scala

・nmeの型：TermNamesApiに関係すると思われるファイルは
　src/reflect/scala/reflect/api/Names.scala

（つづく）