かんたんな自作言語のコンパイラをいろんな言語で書いてみるシリーズ 28番目の言語は Scala です。
できたもの
不慣れな人が見様見真似で書いていますので、Scala のコードとして拙いところはご容赦ください。 Scala に詳しくなるのは後回しにしてとにかく動くものを作るぞ、という方向性です。
サイズ
( cd src/main/scala/mini_ruccola LANG=C wc -l *.scala lib/*.scala ) 358 CodeGenerator.scala 67 Lexer.scala 25 Main.scala 302 Parser.scala 92 lib/Json.scala 40 lib/Node.scala 62 lib/Token.scala 16 lib/Utils.scala 962 total
コンパイラのコア部分だけに絞ればこのくらい:
( cd src/main/scala/mini_ruccola LANG=C wc -l {Lexer,Parser,CodeGenerator}.scala lib/{Node,Token}.scala ) 67 Lexer.scala 302 Parser.scala 358 CodeGenerator.scala 40 lib/Node.scala 62 lib/Token.scala 829 total
動かし方の例
$ ./docker.sh build $ ./docker.sh run bash -l -c "sbt assembly" $ echo ' func add(a, b) { return a + b; } func main() { call add(1, 2); } ' | ./run.sh lex | ./run.sh parse | ./run.sh codegen # ↓アセンブリが出力される call main exit label add push bp mov bp sp mov reg_a [bp:2] push reg_a mov reg_a [bp:3] push reg_a pop reg_b pop reg_a add reg_a reg_b mov sp bp pop bp ret mov sp bp pop bp ret label main push bp mov bp sp mov reg_a 2 push reg_a mov reg_a 1 push reg_a _cmt call~~add call add add sp 2 mov sp bp pop bp ret (snip)
移植元
Ruby 版から移植しています。
<自作言語処理系(Ruby版)の説明用テンプレ>
自分がコンパイラ実装に入門するために作った素朴なトイ言語とその処理系です。
- コンパクト: コンパイラ部分は 1,000 行程度
- 無理して短く書くようなことはせず、読みやすさ優先で素直に書いてこのくらい
- pure Ruby / 標準ライブラリ以外のライブラリ不要
- 標準ライブラリ以外のライブラリ不要。ブラックボックスなしですべてを掌握できるように。
- x86風の自作VM向けにコンパイルする
- ライフゲームのために必要な機能だけ
- 変数宣言、代入、反復、条件分岐、関数定義
- 演算子:
+
,*
,==
,!=
のみ(優先順位は(
)
で明示) - 型なし(値は符号付き整数のみ)
- 作ったときに書いた備忘記事
- 製作メモ
- 作ったときの全過程を書いています
- この通りにやれば誰でも同じものを作れる……のは確かだけど、いろいろ改善点が見えてきたので全体的に改訂したい
- 凝ったことはしていないので Ruby を知らない人でも雰囲気くらいは分かるんじゃないかと
- 本体には含めていない後付けの機能など
- 真偽値リテラル / break / if/else / 単項マイナス / パーサの別実装 / 静的型検査 / etc.
- これらは後から追加できます
- 他言語への移植
- セルフホスト版
<説明用テンプレおわり>
メモ
- いつも通り Emacs で書いた
- 1,000行足らずの小さなプログラムなのでこれでも十分
- Dockerfile を書いて動かせるようになるまでに時間がかかった
そんな中、ずっと自分が勉強したいな〜と思いつつ放置している分野がコンパイラだ。 (略) 手間暇かかること請け合いの分野なので手が出せずにいる。
私も昔同じように考えていましたが、今は「入門用の簡単なものならそこまでハードル高くないですよ」「割とカジュアルに入門できますよ」と思っています。