概略
今回、go 言語でインタプリンタを作ろうと思った経緯は、筆者は半年ばかり独学で python の勉強をしてきたが、よりプログラミングの理解をより深めるために自作しようと思い立った。
go 言語を選んだ理由としては、静的型付け言語を触りたかったのと、なんとなく流行っているからである。
この記事はGo で作るインタプリンタを参考に進めていく。
本記事は、第一章を読み終えたのでこちらにまとめを書く。
第一章、 字句解析
字句解析とは
ソースコードからトークン列に変換することを「字句解析」という
let x = 5 + 5;
字句解析器
- token の定義
type Token struct {
Type string
Literal string
}
const (
ILLEGAL = "ILLEGAL" // 未知の文字列
EOF = "EOF" // 終わりを示す
IDENT = "IDENT" // 識別子
INT = "INT" // 数値型
ASSIGN = "="
PLUS = "+"
[...]
)
- Lexer(字句解析器)
type Lexer struct {
input string
position int // 読み込んでる文字
readPosition int // 次に読み込む文字
ch byte // 検査中の文字
}
func New(input string) *Lexer {
// Lexerに引数inputをセットしreturn
l := &Lexer{input: input}
l.readChar()
return l
}
func (l *Lexer) readChar() {
// 入力が終わったらchを0に
if l.readPosition >= len(l.input) {
l.ch = 0
// まだ終わっていない場合readPositionをchにセット
} else {
l.ch = l.input[l.readPosition]
}
// positionを次に進める
l.position = l.readPosition
// readpositonを次に進める
l.readPosition += 1
}
func (l *Lexer) NextToken() token.Token {
var tok token.Token
// トークンを生成する
switch l.ch {
case '=':
tok = newToken(token.ASSIGN, l.ch)
case '+':
tok = newToken(token.PLUS, l.ch)
case '-':
tok = newToken(token.MINUS, l.ch)
[...]
l.readChar()
return tok
}
func newToken(tokenType TokenType, ch byte) Token {
return Token{Type: tokenType, Literal: string(ch)}
}
これでトークンの生成ができるようになったが、一文字づつトークンを生成するので
let five = 5;
let ten = 10;
let add = fn(x, y) {
x + y;
};
let result = add(five, ten):
上記のような文を解析するにはまだ問題点がある
- 一文字づつしか解析できないので let(キーワード)でエラーを起こす
- 1 と同様に five(識別子)でエラー
- 数字の解析
まず初めに1、2の英字かどうかを判断する関数と非英字に到達するまで読み込みを進める関数の実装をする
// 非英字まで読み込みを進める
func (l *Lexer) readIdentifier() string {
position := l.position
// for文でisLetter関数からfalseが返ってくるまで読み込みを進める
for isLetter(l.ch) {
l.readChar()
}
// 最初の文字から非英字まで進めたpositionまでを返す
return l.input[position:l.position]
}
// 判断する関数
func isLetter(ch byte) bool {
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z'
}
この二つの関数を使い、NextToken()の switch 文に分岐を足してやると文字列のトークンを生成することに成功する
次に変数宣言にあたる let や関数の fn などを分別する必要がある
[...]
FUNCTION = "FUNCTION"
LET = "LET"
var keywords = map[string]TokenType{
"fn": FUNCTION,
"let": LET,
}
func LookupIdent(ident string) TokenType {
// readIdentifierで読み込んだ文字列を引数にとり、キーワードに存在する場合は適切なTypeを返す
if tok, ok := keywords[ident]; ok {
return tok
}
// 識別子の場合
return IDENT
}
数字のトークンだが isLetter と同じように分別する。コードは省略する
第一章の最後に REPL を実装する
REPL の実装
REPL とは
R Read(読み込み)
E Eval(評価)
P Print(表示)
L Loop(繰り返し)
REPL とは上記の略である。インタプリンタ言語では非常に馴染みのあるもので、入力を受け取り、評価し、結果を出力するのを繰り返しおこなう
const PROMPO = ">>"
func Start(in io.Reader, out io.Writer) {
// scannerの生成
scanner := bufio.NewScanner(in)
for {
fmt.Printf(PROMPO)
scanned := scanner.Scan()
if !scanned {
return
}
// 入力を受け取る
line := scanner.Text()
// 解析器に入力された文字列を入れる
l := lexer.New(line)
// 文字列を評価し出力、これを繰り返す。
for tok := l.NextToken(); tok.Type != token.EOF; tok = l.NextToken() {
fmt.Printf("%+v\n", tok)
}
}
}
以上で第一章を終わります。初めてこういう形で学習したものをまとめて発信することになりましたが、理解が深まりとても有意義な方法だと感じました。
第二章も続づけて更新していく予定です。