src/compiler/scala/tools/nsc/Global.scala
をじっくり読もうかと思ったのですが、24、25行目の
import文がちょっと気になりました。
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から。
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"などで検索すると、575行目あたりの
/** 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というメソッドとペアだと思われます。
とりあえず、この2つのメソッドを頭にいれといて、さらに
"EQUALS"を探すと、1169行目あたりの
// ------------- 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で定義されているものみたいです。
字句解析を変更するには
ここまでをまとめておきます。
もし、字句解析器に変更を加えたいときには、
src/compiler/scala/tools/nsc/ast/parser/Tokens.scalaにキーワード用の定数を追加する。
src/compiler/scala/tools/nsc/ast/parser/Scanner.scalaのallKeywordsというリストに1で定義した定数を追加する。
メソッドinFirstOfStat, inLastOfStat に必要に応じて、1で定義した定数を追加してやる。
src/reflect/scala/reflect/internal/StdNames.scala のclass Keywordsに追加したいキーワードを定義しておく。具体的なコードは以下。
final val HOGEHOGEkw: TermName = kw("hogehoge")
3に関しては、似たような働きをするトークンと同じようにしてやればいいと思われます。まあ、inFirstOfStatは「文の最初にくることが可能なトークンかどうか」で判断して、inLastOfStatは「文の最後にくることが可能なトークンかどうか」で判断すればよさそうです。
ここで注意する点は、inFirstOfStatだと
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
(つづく)