Open4

「Go言語で作るインタプリンタ」記録

にふにふ

内容

「Go言語で作るインタプリンタ」を読むので、その記録。
https://www.oreilly.co.jp/books/9784873118222/

目的

目的はGo言語の学習。
現在Go言語を扱うサーバーサイドの会社へ転職しようと画策中であり、Goを知るためにこの本を行う。
目的がGo言語の学習なので、あまり考えず写経して、ついでにインタプリンタ周りの知識を吸収していく。

方針

読んでためになったこと、疑問に思ったことをメモしてく。
調査にはchat-gptを使用しているところもある。
chat-gptで生成されたものなので間違えている可能性もあるが、間違えよりも解答の速さを優先して使用している。。
また、体感chat-gptが間違えてる確率は結構低い(と思っている)

にふにふ

第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を実装してることを示すにはこういうものが必要なんだな。