Upgrade to Pro — share decks privately, control downloads, hide ads and more …

サーバサイドエンジニアとして入社したら 全く気付かない間にUnityエンジニアになって コンパ...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for shumon84 shumon84
December 18, 2019

サーバサイドエンジニアとして入社したら 全く気付かない間にUnityエンジニアになって コンパイラを作っていた話

Avatar for shumon84

shumon84

December 18, 2019
Tweet

Other Decks in Technology

Transcript

  1. 自己紹介
 
 藤田 朱門 (@shumon_84)
 株式会社ミクシィ 2019 年度新卒 
 


    • デジタルエンターテインメント事業部所属 
 • 『共闘ことばRPG コトダマン』 のクライアント兼サーバ 
 • 業務では Unity と Java をやっています 
 • 好きな言語は Go
 • 『Dockerで始めるゲームボーイアドバンス開発入門』 著者
 ◦ 技術書典 8 では、新刊で vol.3 を頒布する予定です 
 3 しゅもん(@shumon_84) 

  2. コンパイラを作るまでの経緯
 4月 : サーバサイドエンジニア ( Go ) として新卒配属 
 ↓


    10月 : クライアントエンジニア ( Unity / C# ) に転向して、現在のコトダマンに異動 
 ↓
 11月 : API が 独自 IDL (インターフェース定義言語) で自動生成されてるけど、コンパイラ貧弱じゃね? 
 ↓
 現在 : コンパイラ自作するぞ 
 5 しゅもん(@shumon_84) 

  3. goyacc について
 文法を食わせると、その文法をパースできるコードを自動生成する「yacc」というツールのGo版。 
 出力として Go を吐いてくれるパーサジェネレータ 
 元々 Go

    のコンパイラを作るのに使用されていたため、Go1.7までは go tool に標準で入っていた 
 (今のコンパイラは goyacc を使っていない) 
 → 詳しくはドキュメント参照 golang.org/x/tools/cmd/goyacc 
 7 しゅもん(@shumon_84) 

  4. goyacc について
 goyacc に文法を教えるには、バッカス・ナウア記法という「文法を書くための言語」を使う。 
 - <文> ::= <宣言> |

    <式> ';' - <宣言> ::= <型> <文字列> ';' | <型> <文字列> '=' <式> ';' - <型> ::= <文字列> | <型> '[' ']' | '*' <型> - <式> ::= <数> | <式> <演算子> <式> こんな感じで <文法X> ::= Xの定義1 | Xの定義2 | ...... と書くだけ。多分フィーリングで書ける。 
 よく言語仕様書とかで、見かけるやつなので馴染み深い人もいるはず。 
 9 しゅもん(@shumon_84) 

  5. 独自 IDL について
 独自 IDL と言っても、それほど独特な文法ではない。 
 • *.str :

    型を記述するファイル 
 • *.act : RPCのインターフェースを記述するファイル 
 12 しゅもん(@shumon_84) 

  6. 構文解析器を作る
 1. BNF ( バッカス・ナウア記法 ) で *.str と *.act

    の文法を定義する 
 2. text/scanner を使って、 goyacc に対応した独自 IDL 用の字句解析器を作る 
 3. 抽象構文木のノードになる型を作る 
 4. parser.yにBNFを書く 
 5. parser.yに各定義と各ノード型の紐付けを書く 
 6. parser.y にマッチした定義と対応する型に定義を割り当て、ノードにする処理を書く 
 7. goyacc で parser.y から parser.go を生成 
 あとは parser.go に生成された yyParse() という関数に字句解析器を渡すと、抽象構文木を作ってくれる 
 14 しゅもん(@shumon_84) 

  7. 構文解析器を作る
 goyacc 生成のパーサの起点になる yyParse() のインターフェースはこんな感じ。 
 パースに成功したとき 0 を返し、失敗したときは 1

    を返す。( C 言語っぽい ) 
 yyParse() は自動生成なので内部エラーを error として返すのは難しい。 
 
 17 しゅもん(@shumon_84) 

  8. 構文解析器を作る
 yyParse() を Parse() でラップする 
 → lexer.Error() で panic()

    を投げるようにする 
 → yyParse() の外まで error が流れてくる 
 → yyParse() をラップした関数で recover() する 
 → recover() した error をラップ関数の外に渡す 
 19 しゅもん(@shumon_84) 

  9. 構文解析器を作る
 このアプローチを取ることで、 
 • Parse()だけを公開する形でparse package の切り出しが容易になった 
 • 字句解析器を完全に隠蔽できるようになった

    
 • 第1返り値に抽象構文木、第二返り値に error を返す関数になり、Goらしく扱えるようになった 
 一応、 panic-recover を積極的に使うのは Go のプログラムではアンチパターンとされているけど、 
 ジャンプが自動生成されたコードの中に閉じていて、人間がメンテする部分のコードからは、 
 隠蔽されているので許して欲しい 
 20 しゅもん(@shumon_84) 

  10. 構文解析器を作る
 これで error を外に運ぶ仕組みが出来たので、次はエラーメッセージに行数などのメタ情報を埋め込む。 
 lexer は text/scanner の scanner.Scanner

    をコンポジットし、 
 scanner.Scanner は scanner.Position をコンポジットしているので、 
 lexer からも Scanner や Position が見えることを利用して、 
 エラー発生時に読んでいたトークンや、そのトークンがあった行数と桁数をエラーメッセージに付け加える 
 21 しゅもん(@shumon_84) 

  11. 構文解析器を作る
 例えば「セミコロンが抜けている」という文脈依存エラーを出したいなら、BNFを少し工夫するとよい 
 フィールド定義の正しいBNFは次のように表せる 
 - <フィールド> ::= <型> <文字列>

    ';' ここで、わざと「間違った文法」をBNFに追加する 
 - <フィールド> ::= <型> <文字列> | <型> <文字列> ';' この 赤い方の定義 にマッチしたとき、セミコロンが抜けている旨を error に詰めると、 
 文脈依存なエラーメッセージが作れる。 
 23 しゅもん(@shumon_84) 

  12. まとめ
 • 【実績解除】業務で自作コンパイラ 
 • goyacc があればパーサを作るのは超簡単 
 • 分かりやすいエラーメッセージを出すのが異常に難しい

    
 • あらかじめ誤った文法を定義しておくのは制御しやすくて便利 
 • 1つのファイルから複数のコンパイルエラーを検出できない問題がまだ残ってる 
 • 独自IDL使うのやめたら?
 ◦ わかる〜、でもフレームワークにがっちり組み込まれてて、独自IDLを切り離すのは難しい 
 26 しゅもん(@shumon_84) 

  13. 話し切れなかったこと
 • マルチOS対応のために文字コード変換を挟む話 
 • 通常コメントとドキュメントコメントで別々に扱う仕組みの話 
 • リファレンスに存在しない文法が存在した話(partial class,

    継承) 
 • 意味解析の話
 • 解析結果を中間形式に保存して高速化する話 
 • テンプレートエンジンでコード生成する話 
 • 並行処理で出力が散らかるのを防ぐ話 
 このままだと2時間ぐらい話してしまうので、続きは懇親会で!
 28 しゅもん(@shumon_84)