Goでインタプリタ開発記(1)

今日から「Go言語でつくるインタプリタ」を読んでいる。

Go言語でつくるインタプリタ

Go言語でつくるインタプリタ

Go言語で、0から簡単なスクリプト言語を作っていこうという本です。簡単とは言っても、配列・ハッシュや高階関数も実装するらしいので結構夢がある感じがする。

読む目的は主に2つ。

インタプリタを作るということに関してだと、前にjavascriptで簡単な言語を実装するという記事も書いたし、「2週間でできる! スクリプト言語の作り方」という本も途中まで読んだ。しかし、いまいち理解が浅い気がするので、0から外部ツールを使わずにスクリプトを書く「Go言語でつくるインタプリタ」をやりきってスクリプト言語の実装の仕組みをちゃんと理解したい。

また、最近仕事でGoを書いたら結構面白くて、Goをまともに書けるようになりたいというのもある。仕事で書いたのはすごく小さいプログラムだが、インタプリタはシンプルなものでもそれなりに大きいコードベースになると思うので、Goでそれなりにでかいコードをどういう風に書けば良いのか学べるとありがたい。

今日は1章まで読んでLexerを実装した。インタプリタがプログラムを解釈するためには何段階かの工程を踏むのだが、Lexerはその中で一番最初のトークナイズを行うプログラムです。トークナイズは、プログラムという長い文字列を意味のある字句(トークン)に区切っていく作業。例えば、以下のようなプログラムがあるとすると、

if (a == 1) return 'hello'

Lexerはこの単なる文字列を、if, (, a, ==...のようなトークンに分ける作業を行う。文字列を意味の塊であるトークンに分けるというLexerの作業は当然だが大事で、例えば===, =という2つの連続した=なのか一つの演算子==なのかは、事前に決めたルールに基づいてLexerが決めるのである。

今回Goで書いたLexerはコード量で言うと300行くらいの小さなものだが、ちゃんとトークナイズできていてすごい。とりあえず今日で、以下のような入力プログラムをトークンに分けて表示するREPLができたので良かった。明日から構文木を作ったりしていくと思うので、引き続きがんばっていきたい。

>> let a = 1;
{Type:LET Literal:let}
{Type:IDENT Literal:a}
{Type:= Literal:=}
{Type:INT Literal:1}
{Type:; Literal:;}

>> let f = (a, b) { a + b; }
{Type:LET Literal:let}
{Type:IDENT Literal:f}
{Type:= Literal:=}
{Type:( Literal:(}
{Type:IDENT Literal:a}
{Type:, Literal:,}
{Type:IDENT Literal:b}
{Type:) Literal:)}
{Type:{ Literal:{}
{Type:IDENT Literal:a}
{Type:+ Literal:+}
{Type:IDENT Literal:b}
{Type:; Literal:;}
{Type:} Literal:}}

今日時点のコード:GitHub - ymr-39/monkey-lang at 433709c6872df6e4bf13bf58df2788fcdc747cad