「Go言語で作るインタプリンタ」記録
内容
「Go言語で作るインタプリンタ」を読むので、その記録。
目的
目的はGo言語の学習。
現在Go言語を扱うサーバーサイドの会社へ転職しようと画策中であり、Goを知るためにこの本を行う。
目的がGo言語の学習なので、あまり考えず写経して、ついでにインタプリンタ周りの知識を吸収していく。
方針
読んでためになったこと、疑問に思ったことをメモしてく。
調査にはchat-gptを使用しているところもある。
chat-gptで生成されたものなので間違えている可能性もあるが、間違えよりも解答の速さを優先して使用している。。
また、体感chat-gptが間違えてる確率は結構低い(と思っている)
環境構築
とりあえずGoを実行する環境を作成。
brew install goするだけでいいなんて素敵な言語。
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
go run main.go
これでもうhello worldできた、素敵。
第1章 字句解析
「*」にとは?
func New(input string) *Lexer {
l := &Lexer{input: input}
return l
}
ポインタ。そのインスタンスへのポインタを返す。参照渡しと呼ばれる。
ポインタを使わない場合、値渡しと呼ばれ、関数値を渡す際に、オブジェクトのコピーが生成され渡れる。コピーのため元のデータに影響しない。メモリ効率が悪いが、安全性が担保される。
"string"[0]などstringにindexでアクセスすると、stringではなくbyteが帰るのはなぜ?
goの文字列はUTF-8でエンコードされる。
UTF-8でエンコードされるということは、各文字は1バイトから複数バイトになる。
そのためstringとして一文字文を返す場合には、それを考慮したエンコーディングしないといけなく、処理が大変。
そのため、シンプルにindexでアクセスされた際には、 バイト単位で返す設計になっている。
では、stringにおいて文字毎にアクセスしたい場合はどうするのか?
goでは各文字にアクセスするためのものとして、rune型を用意。
rune型はUTF-8文字を表す。
rankgeで各文字にアクセスできる
str := "あなたの例文" // これはUTF-8でエンコードされた文字列です
for _, runeValue := range str {
fmt.Printf("%c ", runeValue) // 各rune(文字)を出力します
}
文字列を[]runeに変換してインデックスでアクセスすることもできる
str := "あなたの例文"
runes := []rune(str)
fmt.Println(string(runes[0])) // 最初のrune(文字)を出力します
for分
for tk := l.NextToken(); tk.Type != token.EOF {
fmt.Printf("%+v\n", tk)
}
最初上記のコードを読んでも、次のNextTokenが呼ばれず、ずっと最初に読み込んだLetが出力されるばかりだった。
for tk := l.NextToken(); tk.Type != token.EOF; tk = l.NextToken() {
fmt.Printf("%+v\n", tk)
}
原因としては、シンプルにtkを更新していないから。
このコードは、初期化; 条件式; 後処理式 という形式になっている。
この部分を勝手に条件文として捉えてしまっていたので、勘違いしてた
2章 構文解析
インタプリンタ
- インタプリンタなどで、ソースコードは構文木、もしくは抽象構文木として扱われる
- 抽象、と言ってるのは省かれるtokennも存在するため。
- どんな構文解析器でも利用できるような、普遍的なASTの形式は存在しない。似てるけど
マーカーインターフェース
// Node インターフェースの定義
type Node interface {
TokenLiteral() string
}
// Statement インターフェースの定義
type Statement interface {
Node
statementNode()
}
// MyStatement 構造体の定義
type MyStatement struct {
}
// MyStatement が Node インターフェースを実装するための TokenLiteral メソッド
func (ms *MyStatement) TokenLiteral() string {
return "MyStatement"
}
// MyStatement が Statement インターフェースを実装するための statementNode メソッド
// このメソッドは何もしない(マーカーとしての役割のみ)
func (ms *MyStatement) statementNode() {
}
func main() {
var stmt Statement = &MyStatement{}
// Statement インターフェースを実装していることを確認
fmt.Println(stmt.TokenLiteral()) // 出力: MyStatement
}
statementNodeはマーカーとして機能しており、MyStatementなど具象クラスがStatementを継承していることを示す。
goはgoは暗黙的に継承を解決するため、明治的にこのinterfaceを実装してることを示すにはこういうものが必要なんだな。