自由帳

@_nibral の技術ブログ

Writing An Interpreter In Go その1

その0はこちら

1.3: Lexer(字句解析器)の実装

1.3: Implement lexer · nibral/monkey_interpreter@66eb6e3 · GitHub

lexer/lexer.go

ソースコードを文字列として受け取って、一文字ずつ読み進めながらトークンの配列に変換する処理を作成。

1文字の識別子だったらそのままトークンとして追加、複数文字の場合はそれが単語なのか数値なのかを判定して対応するトークンを追加していく。範囲を指定して配列の一部を取り出せるgolang最高じゃんという気持ちになった。

func (l *Lexer) readNumber() string {
    position := l.position
    for isDigit(l.ch) {
        l.readChar()
    }
    return l.input[position:l.position] <-便利
}

文字種の判定 (isLetter, isDigit) と空白文字読み捨ての処理 (skipWhitespace) もシンプルで、これで十分なんだと安心。

あと、本に書かれているLexerの処理結果がわかりやすかった。

let x = 5 + 5;
↓
[
  LET,
  IDENTIFIER("x"),
  EQUAL_SIGN,
  INTEGER(5),
  PLUS_SIGN,
  INTEGER(5),
  SEMICOLON
]

lexer/lexer_test.go

Lexerのテストコードを作成。

満たすべき条件を配列として用意しておいて、ループでチェックする。「テーブルドリブンテスト」といってgolangでは標準的な書き方らしい。

本の内容とは関係ないけど、golangは標準でフォーマットとテストの仕組みがあるのがいいなと思ったり。たとえばJavaScriptだとESLint入れてルール設定したりmocha入れたりで面倒なので…

token/token.go

トークンの構造と種類を定義した。

1文字の識別子 (=, +, etc.) をそのまま扱うのはいいとして、複数文字の場合は事前に定義したテーブルに存在するかチェックして、存在すれば予約語、しなければユーザが定義した識別子として扱うところがなるほどねという感じ。予約語を増やしたければここに追加していけばいいんだなというのがすぐわかる。

1.4: 1文字識別子とキーワードの追加

1.4: Add single character tokens and keywords · nibral/monkey_interpreter@cf28a6d · GitHub

lexer/lexer.go

1文字の識別子に -, !, *, /, <, >, カンマ を追加。caseを増やすだけなのですぐできる。

lexer/lexer_test.go

新たに処理できるようになった識別子のテストコードを追加。

機能が増えるにしたがってテスト内容のテーブルがどんどん長くなるけどこれでいいんだろうか。適当なところで分割とかするべきなのか。

token/token.go

トークンの種類と予約語を追加。* と / を MULTIPLE, DIVIDE ではなく ASTERISK, SLASH にするのはほかの用途でも使うから?

if/else や return を追加したが、現時点ではトークンとして認識できるというだけで制御構文としての意味は一切処理されていない。