tag:crieit.net,2005:https://crieit.net/tags/%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9/feed 「コンパイラ」の記事 - Crieit Crieitでタグ「コンパイラ」に投稿された最近の記事 2021-12-21T08:00:24+09:00 https://crieit.net/tags/%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9/feed tag:crieit.net,2005:PublicArticle/17878 2021-12-21T07:40:58+09:00 2021-12-21T08:00:24+09:00 https://crieit.net/posts/Bash シェルスクリプト(Bashスクリプト)でかんたんな自作言語のコンパイラを書いた <p>シェルスクリプト Advent Calendar 2021<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2021/shellscript">https://qiita.com/advent-calendar/2021/shellscript</a><br /> の20日目の記事です。</p> <p><a href="https://crieit.now.sh/upload_images/d022495e2d320868f0b13d07c98d3f9861b66f943faf6.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d022495e2d320868f0b13d07c98d3f9861b66f943faf6.png?mw=700" alt="image" /></a></p> <p>Bashがある環境ならどこでも自作言語で書いたプログラムをコンパイルできるようになりました。</p> <h1 id="できたもの"><a href="#%E3%81%A7%E3%81%8D%E3%81%9F%E3%82%82%E3%81%AE">できたもの</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-bash">https://github.com/sonota88/vm2gol-v2-bash</a></p> <p>いろいろと雑なのですが、アドベントカレンダーの期日があるのでとりあえず公開します。</p> <p>サイズはこんな感じ。</p> <pre><code>$ wc -l {lexer,parser,codegen}.sh lib/*.sh 220 lexer.sh 655 parser.sh 525 codegen.sh 42 lib/common.sh 193 lib/json.sh 284 lib/utils.sh 1919 合計 </code></pre> <h1 id="動かし方の例"><a href="#%E5%8B%95%E3%81%8B%E3%81%97%E6%96%B9%E3%81%AE%E4%BE%8B">動かし方の例</a></h1> <pre><code class="bash">echo ' func add(a, b) { return a + b; } func main() { var one = 1; var result; call_set result = add(one, 2); } ' | ./lexer.sh | ./parser.sh | ./codegen.sh # ↓アセンブリが出力される call main exit label add push bp cp sp bp cp [bp:2] reg_a push reg_a cp [bp:3] reg_a push reg_a pop reg_b pop reg_a add_ab cp bp sp pop bp ret label main push bp cp sp bp sub_sp 1 cp 1 reg_a cp reg_a [bp:-1] sub_sp 1 cp 2 reg_a push reg_a cp [bp:-1] reg_a push reg_a _cmt call~~add call add add_sp 2 cp reg_a [bp:-2] cp bp sp pop bp ret # (snip) </code></pre> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">https://github.com/sonota88/vm2gol-v2</a></p> <p>Bashスクリプト版のベースになっているバージョンは <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/62">tag:62</a> のあたり</p> <p><自作言語処理系の説明用テンプレ></p> <p>自分がコンパイラ実装に入門するために作った素朴なトイ言語とその処理系です。簡単に概要を書くと下記のような感じ。</p> <ul> <li>小規模: コンパイラ部分は 1,000 行程度</li> <li>pure Ruby / 標準ライブラリ以外のライブラリ不要</li> <li>x86風の自作VM向けにコンパイルする</li> <li>ライフゲームのために必要な機能だけ <ul> <li>変数宣言、代入、反復、条件分岐、関数定義</li> <li>演算子: <code>+</code>, <code>*</code>, <code>==</code>, <code>!=</code> のみ(優先順位は <code>(</code> <code>)</code> で明示)</li> <li>型なし(値は符号付き整数のみ)</li> </ul></li> <li>作ったときに書いた備忘記事 <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/f9cb3fc4a496b354b729">RubyでオレオレVMとアセンブラとコード生成器を2週間で作ってライフゲームを動かした話</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/2b95378b43a22109513c">Rubyでかんたんな自作言語のコンパイラを作った</a></li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/vm2gol_v2_additional_features">本体には含めていない後付けの機能など</a> <ul> <li>真偽値リテラル / break / if/else / 単項マイナス / Racc などを使って書いたパーサの別実装</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">Ruby 以外の言語への移植</a> <ul> <li>コンパイラ部分のみ</li> <li>Python, Java, TypeScript など、2021-12-19 の時点では 20言語</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/1e683276541cf1b87b76">セルフホスト版</a></li> <li>製作過程を知りたい場合は<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/05/04/234516">製作メモ</a>を見てください</li> </ul> <p><説明用テンプレおわり></p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <h2 id="文字列"><a href="#%E6%96%87%E5%AD%97%E5%88%97">文字列</a></h2> <p>下記の記事のおかげでなんとかなりました。ありがとうございます 🙏</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/richmikan@github/items/6b763519a28b4ce40031">シェルスクリプトはバイナリを扱えない。さてどうしよう…… - Qiita</a></p> <p>バイト単位で16進数表現との相互変換さえできてしまえば、あとは煮るなり焼くなり思いのまま、です。</p> <h2 id="データ構造"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0">データ構造</a></h2> <pre><code class="json">[ 123, "fdsa", [ 11, "FDSA", [] ] ] </code></pre> <p>のような、リストの要素として整数、文字列、リストを持たせることができる再帰的なデータ構造が必要です。Bashスクリプトの配列は単純な値を入れる分にはいいのですが、ネストしたリストを扱うことができなさそうでした。</p> <p>(※ いつもならこういうことをやりたくなった段階で別の言語で書くことを検討しますが、今回は Bashスクリプトで書くこと自体が目的です)</p> <p>そこで、ひとまず下記のようにしました。もっと良いやり方がある気がします。</p> <p>リストの要素にあたるデータの表現はこんな感じ。</p> <pre><code class="bash"># 整数 "int:-123" # 文字列 "str:fdsa" # リスト "list:1" </code></pre> <ul> <li>先頭に型を表すタグを付けた1行の文字列</li> <li>文字列の値(<code>str:...</code> の <code>...</code> の部分)は改行を含まない(レキサで捨てる)ため、改行についての考慮は不要</li> <li><code>list:</code> の後に続く数字はリストID</li> </ul> <p>たとえば <code>["+", 1, -2]</code> というリストは次のように3行の文字列で表せます。1行がリストの要素1つに対応します。</p> <pre><code class="bash">"str:+ int:1 int:-2 " </code></pre> <p>この文字列をグローバルな配列変数に入れて管理する形にしました。要素のインデックスがリストIDに対応します。</p> <p>たとえば、入れ子のあるリスト <code>["+", 1, ["*", 2, 3]]</code> は次のようになります。</p> <pre><code class="bash"># GLOBAL[0] "str:* int:2 int:3 " # GLOBAL[1] "str:+ int:1 list:0 " </code></pre> <p>パフォーマンスの問題は別途ありますが、要するに下記のような操作ができるインターフェイスが用意できればOK。</p> <ul> <li>リストの要素数を求める</li> <li>リストからn番目の要素を読み書きする</li> <li>リストの要素の型を判別する</li> <li>リストの要素の値を取りだす</li> </ul> <h2 id="関数からの値の返却"><a href="#%E9%96%A2%E6%95%B0%E3%81%8B%E3%82%89%E3%81%AE%E5%80%A4%E3%81%AE%E8%BF%94%E5%8D%B4">関数からの値の返却</a></h2> <p>関数 <code>myfunc</code> から標準出力に出力して <code>result="$(myfunc)"</code> のようにコマンド置換で受け取る方法だと不都合な点が2つあります。</p> <ul> <li>(1) 末尾の改行の有無を制御しにくい <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ko1nksm/items/7c4040c6b6f3fac14db7">シェルスクリプトにはコマンド出力を変数に入れると末尾の改行が全部消えてしまう罠がある! - Qiita</a> に書かれている通り</li> </ul></li> <li>(2) 呼び出した関数内でグローバル変数を更新できない <ul> <li>コマンド置換だとサブシェルになるので</li> </ul></li> </ul> <p>ちょっと悩みましたが、結局下記の方式に落ち着きました。</p> <ul> <li>関数からの戻り値の受け渡し用のグローバル変数 <code>RV1</code> を用意する</li> <li><code>$(...)</code> で囲まずに普通に関数を呼び出す</li> <li>関数側では、返したい値を <code>RV1</code> に代入する</li> <li>呼び出し元では、関数呼び出しの直後に <code>RV1</code> から移し替える</li> </ul> <p>単純な例として、文字列を2つ受け取って連結した文字列を返す関数。</p> <pre><code class="bash"># return value RV1= myfunc() { RV1="${1}${2}" } myfunc "foo" "bar" retval="$RV1" </code></pre> <p>こうですね。単にこのパターンで書けばよいだけなので頭は使わなくてよいです。無心に書いていくとコンパイラができあがります。並行処理とかやってないですし、関数から戻った直後で受け取る約束さえ守っていれば問題は起こりません。</p> <p>全然スマートじゃなくてなんじゃこりゃって感じのソリューションですけど、この方法にも良いところがあって、<code>RV2</code>, <code>RV3</code>, ... と増やすことで複数の値をいくらでも返すことができます。あと、コマンド置換よりはオーバーヘッドが小さいかもしれません(未確認)。</p> <p>関数の中でグローバル変数を書き換える実際の例としてはリストの生成処理があります。ヒープにリスト用の領域を確保して、リストを指し示すポインタを返すようなイメージです。</p> <pre><code class="bash">new_list() { new_gid local self_=$RV1 GLOBAL[$self_]="" RV1=$self_ } new_list list1_=$RV1 </code></pre> <p>Ruby で書くとしたらこんな感じ。</p> <pre><code class="ruby">def new_list self_ = new_gid() # 新しいインデックスを採番 $GLOBAL[self_] = "" return self_ end list_ = new_list() </code></pre> <hr /> <p>というわけで、かんたんなコンパイラを書くことにより、Bashスクリプトにおける</p> <ul> <li>文字列処理</li> <li>データ構造(入れ子のあるリスト)</li> <li>関数からの値の返却</li> </ul> <p>についてのノウハウが新たに得られました。めでたしめでたし。</p> <h1 id="この記事を読んだ人は(たぶん)こちらも読んでいます"><a href="#%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A0%E4%BA%BA%E3%81%AF%EF%BC%88%E3%81%9F%E3%81%B6%E3%82%93%EF%BC%89%E3%81%93%E3%81%A1%E3%82%89%E3%82%82%E8%AA%AD%E3%82%93%E3%81%A7%E3%81%84%E3%81%BE%E3%81%99">この記事を読んだ人は(たぶん)こちらも読んでいます</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/1482e236054ad3edfdf5">Perlでかんたんな自作言語のコンパイラを書いた</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/54c65e97aa563009137e">なでしこ3でかんたんな自作言語のコンパイラを書いた</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/31c127c42a9722892aa8">素朴な自作言語のコンパイラをLibreOffice Basicに移植した</a></p> sonota486 tag:crieit.net,2005:PublicArticle/17680 2021-09-18T09:58:36+09:00 2022-07-10T10:55:15+09:00 https://crieit.net/posts/simple-compiler-php PHPでかんたんな自作言語のコンパイラを書いた <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/18/193844">ブログに書いていたもの</a>を引っ越してきました。元の記事公開日は 2020-09-18 です。</p> <hr /> <p>やっつけなので汚いです。ライフゲームのコンパイルが通ったのでヨシ、という程度の雑なものです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-php">https://github.com/sonota88/vm2gol-v2-php</a></p> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <p>自作言語の概要などについてもこちらを参照してください。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">Rubyで素朴な自作言語のコンパイラを作った</a></li> </ul> <p>大元は Ruby 版なのですが、実質的には <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/08/053329">Perl版</a>からの移植です。</p> <p>ベースになっているバージョン: <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/50">tag:50</a> のあたり<br /> (追記 2021-09-18: <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20210911_vm2gol_v2_60_pass_stmt">ステップ60</a> の修正まで適用しました)</p> <p>作り方はここに全部書いています(Ruby 版のものですが): <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/05/04/234516">vm2gol v2 製作メモ</a></p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <p>主な部分のサイズ(2021-09-18 現在):</p> <pre><code>$ wc -l {lexer,parser,codegen}.php lib/utils.php 66 lexer.php 536 parser.php 423 codegen.php 42 lib/utils.php 1067 合計 </code></pre> <hr /> <ul> <li>PHP を書くのは今回が初めて</li> <li>さらっと文法を見た感じ Perl に近そうだったので、<br /> Perl版をコピーして書き換えていった <ul> <li>割と機械的に置き換え</li> <li>my を消して</li> <li>sub => function</li> <li>elsif => elseif</li> <li>正規表現はダブルクォートで囲んで preg_match にして</li> <li>……などなど</li> <li>Perl → PHP という流れで進んだのは良かった</li> </ul></li> <li>Emacs に php-mode を追加するのがめんどくさかったので perl-mode で書いてた</li> <li>クラスも普通に使えるし、 int と string の型の判別もできるし、 Perl より楽ちんという印象。<br /> 正直なところあまり書くことがないです……。</li> </ul> <hr /> <p>今回の実験コーナー。</p> <p>これまで <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/22/162201">Dart への移植</a>のときに set を不要にし、<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/121437">Java への移植</a>のときに call を不要にし、<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/08/053329">Perl への移植</a>のときに call_set を不要にしてきました。</p> <p>これらを全部適用するとどうなるか <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-php/tree/trial">trial ブランチ</a><br /> でやってみました。</p> <p>とりあえず <code>parse_stmt()</code> の分岐のとこだけ貼ってみます。</p> <pre><code class="php"><?php # ... # if ($t->str_eq("set" )) { return parse_set(); } # if ($t->str_eq("call" )) { return parse_call(); } # elseif ($t->str_eq("call_set")) { return parse_call_set(); } elseif ($t->str_eq("return" )) { return parse_return(); } elseif ($t->str_eq("while" )) { return parse_while(); } elseif ($t->str_eq("case" )) { return parse_case(); } elseif ($t->str_eq("_cmt" )) { return parse_vm_comment(); } else { if ( $t->kind_eq("ident") && peek(1)->is("sym", "=") ) { if ( peek(2)->kind_eq("ident") && peek(3)->is("sym", "(") ) { return parse_call_set(); } else { return parse_set(); } } elseif ( $t->kind_eq("ident") && peek(1)->is("sym", "(") ) { return parse_call(); } else { throw not_yet_impl($t); } } </code></pre> <p>個別に試していたときはそれぞれの個別のことだけ考えてやればよかったのが、3つを同時に満たそうとして else に押し込めてとりあえずこうなった、というものです。ちょっと野暮ったいですが <code>peek(n)</code> で先読みしてやればなんとかなるようです。</p> <p>funcall を expr に含めるとかするともうちょっとすっきりする気がします。</p> <p>vgコードはこんな感じ。ここまでやるとかなり普通の言語っぽい(というか JavaScript っぽい)見た目になりますね。</p> <pre><code class="javascript">func main() { var w = 5; // 盤面の幅 var h = 5; // 盤面の高さ // 初期状態の設定 vram_set(w, 1, 0, 1); vram_set(w, 2, 1, 1); vram_set(w, 0, 2, 1); vram_set(w, 1, 2, 1); vram_set(w, 2, 2, 1); var gen_limit = 0; var gen = 1; while (gen != gen_limit) { make_next_gen(w, h); replace_with_buf(); gen = gen + 1; } } </code></pre> <h1 id="他の言語への移植"><a href="#%E4%BB%96%E3%81%AE%E8%A8%80%E8%AA%9E%E3%81%B8%E3%81%AE%E7%A7%BB%E6%A4%8D">他の言語への移植</a></h1> <p>| <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/18/193844">PHP</a> | <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-php">github</a> | 2020-09-18 |<br /> --></p> <div class="table-responsive"><table> <thead> <tr> <th>記事</th> <th>リポジトリ</th> <th>日付</th> </tr> </thead> <tbody> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20210628_vm2gol_v2_haskell">Haskell</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-haskell">github</a></td> <td>2021-06-28</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/06/26/054540">OCaml</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-ocaml">github</a></td> <td>2021-06-26</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/4d446b778ffbbaab267c">Pascal</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-pascal">github</a></td> <td>2021-05-22</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/b80696f23a08c3c2fc5d">Julia</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-julia">github</a></td> <td>2021-05-03</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20210407_vm2gol_v2_rust">Rust</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-rust">github</a></td> <td>2021-04-07</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/275c2b5407986b100cc8">Crystal</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-crystal">github</a></td> <td>2021-03-27</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/1e683276541cf1b87b76">Pric(セルフホスト)</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/pric">github</a></td> <td>2021-02-21</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/14/032813">Kotlin</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-kotlin">github</a></td> <td>2021-01-14</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/07/235019">Zig</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-zig">github</a></td> <td>2021-01-07</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/31c127c42a9722892aa8">LibreOffice Basic</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-libreoffice-basic">github</a></td> <td>2020-12-14</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/25/073200">Go</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-go">github</a></td> <td>2020-09-25</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/13/133735">C♭</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-cflat">github</a></td> <td>2020-09-13</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/08/053329">Perl</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-perl">github</a></td> <td>2020-09-08</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/06/043607">C</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-c">github</a></td> <td>2020-09-06</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/121437">Java</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-java">github</a></td> <td>2020-08-30</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/22/162201">Dart</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-dart">github</a></td> <td>2020-08-22</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/5267dcd4750235680c8f">Python</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-python">github</a></td> <td>2020-08-19</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/15/114754">TypeScript (Deno)</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-typescript/tree/20200815">github</a></td> <td>2020-08-15</td> </tr> </tbody> </table></div> sonota486 tag:crieit.net,2005:PublicArticle/17672 2021-09-14T07:28:41+09:00 2021-09-14T20:06:20+09:00 https://crieit.net/posts/simple-compiler-perl Perlでかんたんな自作言語のコンパイラを書いた <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/08/053329">ブログに書いていたもの</a>を引っ越してきました。元の記事公開日は 2020-09-08 です。</p> <hr /> <p>20年ぶりくらいに Perl のコードを書きました。やっつけなので汚いです。ライフゲームのコンパイルが通ったのでヨシ、という程度の雑なものです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-perl">https://github.com/sonota88/vm2gol-v2-perl</a></p> <hr /> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">Rubyで素朴な自作言語のコンパイラを作った</a><br /> (自作言語の概要などについてはこちらを参照してください)</p> <p>ベースになっているバージョン: <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/49">tag:49</a> のあたり<br /> (追記 2021-09-11: ステップ60の修正まで適用しました)</p> <p>作り方はここに全部書いています(Ruby 版のものですが): <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/05/04/234516">vm2gol v2 製作メモ</a></p> <p>ただ、Ruby 版よりは主に <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/06/043607">C言語版</a> を見ながら書き写していました。クラスを使わなかったのもあり、C言語版に近いところも多いです。</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li>20年ぶりくらいといっても、当時はちょっと入門した程度だったしもうほとんど忘れてるので<br /> ほぼゼロから再入門といった感じ</li> <li>でも 2日(日曜+月曜)でなんとか完成した。殴り書きです。</li> <li>配列・ハッシュとリファレンスが絡むあたりが最初よく分からなかった <ul> <li>まず配列の要素数の取り方が分からないとか、<code>for my $x (@$xs) { ... }</code> の <code>@</code> がなくて動かないとか、たぶん初心者あるある</li> </ul></li> <li>数と文字列の区別がない(シェルスクリプトっぽい)のをどうするかちょっと悩んだ。データの型を見て処理を分岐しているところがあるので。 <ul> <li>ハッシュでラッパーオブジェクトみたいなものを作ってどうにかした( <code>lib/Val.pm</code> )。</li> <li>最初は C言語版のときの NodeList, NodeItem 構造体を使う書き方にすれば何も考えずに機械的に移植できそうと思ったけど、あれはあれでリストの処理が煩雑・大げさで、配列で済むならその方がシンプルなので、最低限文字列と数だけ判別できるようにした。</li> </ul></li> <li>クラスの使い方もちょっと調べてみたけど、 もうちょっと Perl 力が上がった状態で使わないと逆に回り道っぽくなりそうな気がして、 C言語版の「構造体+関数群のセット」方式でやってしまった</li> </ul> <hr /> <p><code>my $foo = ...</code> をいっぱい書かないといけなくてさすがに面倒だったので途中で次のような Emacs Lisp を書いて <code>Ctrl + Alt + M</code> で入力できるようにしたり。yasnippet でも良かったかも。</p> <pre><code class="lisp">(add-hook 'perl-mode-hook '(lambda () (local-set-key (kbd "C-M-m") (lambda () (interactive) (insert "my $ = ;") (backward-char 4))))) </code></pre> <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/22/162201">Dart版</a>で <code>call</code>、<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/121437">Java版</a>で <code>set</code> を不要にした流れで、今回は <code>call_set</code> キーワードを不要にしてみました。同じ要領でできますね、ということが分かりました。</p> <p>(追記 2021-04-09: この修正はいったん revert しましたが、一応 <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-perl/tree/trial">trial ブランチ</a>に残してあります。)</p> <pre><code class="perl">sub parse_stmt { my $t = peek(0); if (Token::is($t, "sym", "}")) { return -1; } if (Token::str_eq($t, "func" )) { return parse_func(); } elsif (Token::str_eq($t, "var" )) { return parse_var(); } elsif (Token::str_eq($t, "set" )) { return parse_set(); } elsif (Token::str_eq($t, "call" )) { return parse_call(); } # elsif (Token::str_eq($t, "call_set")) { return parse_call_set(); } elsif (Token::str_eq($t, "return" )) { return parse_return(); } elsif (Token::str_eq($t, "while" )) { return parse_while(); } elsif (Token::str_eq($t, "case" )) { return parse_case(); } elsif (Token::str_eq($t, "_cmt" )) { return parse_vm_comment(); } else { if (Token::kind_eq($t, "ident")) { return parse_call_set(); } else { p_e("parse_stmt", $t); die "not_yet_impl"; } } } </code></pre> <hr /> <p>(追記 2021-08-30: その後の修正により、下記の部分はなくなりました)</p> <p>それと、今回はコード生成処理のアレ、何度も似たようなのが出てきて鬱陶しかった部分をためしにサブルーチンに抽出して共通化してみました。うーん。どうでしょう。どうしようかな。とりあえず名前が微妙。</p> <p>名前が微妙な上に上手い抽象でもない気がする……となるとこれはメソッド抽出すべきではないパターンのように思えます。いい解決法が見つかるまで本家の Ruby版には取り込めなさそう。とはいえコーディングの手間・退屈さは減らせるので、移植版では気軽に使っていこうかなと。</p> <pre><code class="perl">sub to_asm_str { my $fn_arg_names = shift; my $lvar_names = shift; my $val = shift; if (Utils::is_arr($val)) { return undef; } elsif (Val::kind_eq($val, "int")) { return $val->{"val"}; } elsif (Val::kind_eq($val, "str")) { my $str = $val->{"val"}; if (0 <= str_arr_index($fn_arg_names, $str)) { return to_fn_arg_ref($fn_arg_names, $str); } elsif (0 <= str_arr_index($lvar_names, $str)) { return to_lvar_ref($lvar_names, $str); } else { return undef; } } else { return undef; } } </code></pre> <p>呼び出し箇所は6箇所。それなりに使いまわせているようではあります。</p> <pre><code>vgcg.pl:132: $push_arg = to_asm_str($fn_arg_names, $lvar_names, $val); vgcg.pl:240: $push_arg = to_asm_str($fn_arg_names, $lvar_names, $fn_arg); vgcg.pl:304: $src_val = to_asm_str($fn_arg_names, $lvar_names, $expr); vgcg.pl:316: my $vram_ref = to_asm_str($fn_arg_names, $lvar_names, sval($vram_arg)); vgcg.pl:342: my $vram_ref = to_asm_str($fn_arg_names, $lvar_names, sval($vram_arg)); vgcg.pl:375: my $vram_ref = to_asm_str([], $lvar_names, sval($vram_arg)); </code></pre> <h1 id="他の言語への移植"><a href="#%E4%BB%96%E3%81%AE%E8%A8%80%E8%AA%9E%E3%81%B8%E3%81%AE%E7%A7%BB%E6%A4%8D">他の言語への移植</a></h1> <div class="table-responsive"><table> <thead> <tr> <th>記事</th> <th>リポジトリ</th> <th>日付</th> </tr> </thead> <tbody> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20210628_vm2gol_v2_haskell">Haskell</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-haskell">github</a></td> <td>2021-06-28</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/06/26/054540">OCaml</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-ocaml">github</a></td> <td>2021-06-26</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/4d446b778ffbbaab267c">Pascal</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-pascal">github</a></td> <td>2021-05-22</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/b80696f23a08c3c2fc5d">Julia</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-julia">github</a></td> <td>2021-05-03</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20210407_vm2gol_v2_rust">Rust</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-rust">github</a></td> <td>2021-04-07</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/275c2b5407986b100cc8">Crystal</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-crystal">github</a></td> <td>2021-03-27</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/1e683276541cf1b87b76">Pric(セルフホスト)</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/pric">github</a></td> <td>2021-02-21</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/14/032813">Kotlin</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-kotlin">github</a></td> <td>2021-01-14</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/07/235019">Zig</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-zig">github</a></td> <td>2021-01-07</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/31c127c42a9722892aa8">LibreOffice Basic</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-libreoffice-basic">github</a></td> <td>2020-12-14</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/25/073200">Go</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-go">github</a></td> <td>2020-09-25</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/18/193844">PHP</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-php">github</a></td> <td>2020-09-18</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/13/133735">C♭</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-cflat">github</a></td> <td>2020-09-13</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/06/043607">C</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-c">github</a></td> <td>2020-09-06</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/121437">Java</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-java">github</a></td> <td>2020-08-30</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/22/162201">Dart</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-dart">github</a></td> <td>2020-08-22</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/5267dcd4750235680c8f">Python</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-python">github</a></td> <td>2020-08-19</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/15/114754">TypeScript (Deno)</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-typescript/tree/20200815">github</a></td> <td>2020-08-15</td> </tr> </tbody> </table></div> sonota486 tag:crieit.net,2005:PublicArticle/17423 2021-06-21T19:23:12+09:00 2022-09-03T10:49:49+09:00 https://crieit.net/posts/pric-self-hosting-compiler 素朴な自作言語Ruccolaのコンパイラをセルフホストした <p>自作言語 Ruccola のコンパイラがセルフホストできました!</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/ruccola/tree/main/selfhost">https://github.com/sonota88/ruccola/tree/main/selfhost</a></p> <p>※ <a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/1e683276541cf1b87b76">Qiita に書いた記事</a>のクロス投稿です</p> <p>※ 最初は Pric という名前だったのですが、 <strong>Ruccola</strong> に変更しました。以下、修正できていない箇所は読み替えてください。</p> <h1 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h1> <h2 id="これまでのあらすじ"><a href="#%E3%81%93%E3%82%8C%E3%81%BE%E3%81%A7%E3%81%AE%E3%81%82%E3%82%89%E3%81%99%E3%81%98">これまでのあらすじ</a></h2> <p>これまで vm2gol(virtual machine to game of life)というプロジェクト名で以下のようなことをやってきました。</p> <ul> <li>(2018) <a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/f9cb3fc4a496b354b729">RubyでオレオレVMとアセンブラとコード生成器を2週間で作ってライフゲームを動かした話</a> <ul> <li>vm2gol-v1</li> <li>高水準言語の代わりに構文木を直接書き、それをコンパイルして VM で動かすところまで</li> <li>ライフゲームだけコンパイル + 実行できればOK</li> <li>雑、汚い</li> </ul></li> <li>(2019〜) <a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/2b95378b43a22109513c">Rubyで素朴な自作言語のコンパイラを作った</a> <ul> <li>vm2gol-v2</li> <li>v1 のリライト版(機能的にはほぼ同じ)にフロントエンド部分を追加したもの</li> </ul></li> <li>(2020〜) <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">vm2gol-v2 をいろんな言語に移植</a> <ul> <li>アセンブラ・VM は除いてコンパイラ部分のみ</li> <li>TypeScript / Python / Dart / Java / C / Perl / C♭ / PHP / Go / LibreOffice Basic / Zig / Kotlin / Crystal / Rust / Julia / Pascal / OCaml ... 気が向けば続く予定</li> </ul></li> </ul> <p>今回のセルフホスト編は何もないところから全部書いたわけではなく、すでに v2 がある状態から出発しています。</p> <h2 id="概観"><a href="#%E6%A6%82%E8%A6%B3">概観</a></h2> <p><a href="https://crieit.now.sh/upload_images/911326a328169de98219750f55d6303a60d066da1922d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/911326a328169de98219750f55d6303a60d066da1922d.png?mw=700" alt="image" /></a></p> <p>※ 独自VM向けなので「仮想機械語」と呼ぶべきかもしれませんが、この記事では単に「機械語」とします。<br /> ※ 図ではコンパイラが直接機械語を出力しているように見えますが、実際はアセンブルの過程があります。説明の簡略化のために省略しています。</p> <hr /> <p>T図式でも描いてみました。図の「Pric M」は Pric VM 向けの機械語。</p> <p><a href="https://crieit.now.sh/upload_images/27b190e50a15a1327a9c03e29272adab60d06733795cc.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/27b190e50a15a1327a9c03e29272adab60d06733795cc.png?mw=700" alt="image" /></a></p> <h2 id="期間"><a href="#%E6%9C%9F%E9%96%93">期間</a></h2> <p>2021-01-12 〜 2021-02-21</p> <h2 id="規模"><a href="#%E8%A6%8F%E6%A8%A1">規模</a></h2> <p>第2世代コンパイラのサイズ(行数)はこのくらい(2021-05-25 現在):</p> <pre><code class="sh">$ wc -l lexer.pric parser.pric codegen.pric \ > lib/json.pric lib/std.pric lib/types.pric lib/words.pric 250 lexer.pric 973 parser.pric 1325 codegen.pric 170 lib/json.pric 536 lib/std.pric 466 lib/types.pric 305 lib/words.pric 4025 合計 </code></pre> <pre><code class="sh"># 補助ツール的なもの $ wc -l check.rb preproc.rb 110 check.rb 19 preproc.rb 129 合計 </code></pre> <h1 id="バージョンと世代"><a href="#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%81%A8%E4%B8%96%E4%BB%A3">バージョンと世代</a></h1> <p>以下、v2/v3 と 第1世代/第2世代 という表現が出てくるので先に説明しておきます。</p> <ul> <li>バージョン: 言語仕様=機能セットの違い <ul> <li>Pric v2: ライフゲームはコンパイルできる。セルフホストはできない。</li> <li>Pric v3: セルフホストに必要な機能を v2 に足したもの</li> </ul></li> <li>世代: 記述言語の違い <ul> <li>第1世代コンパイラ: Ruby で書いたもの</li> <li>第2世代コンパイラ: Pric で書いたもの</li> </ul></li> </ul> <p>※ 今回作った v3 ではライフゲーム(game of life)専用ではなくなるのと、やっぱり呼びやすい名前がほしくなって <strong>Pric</strong> という名前を付けました。 vm2gol = Pric (プロジェクト名・言語名)という雑な捉え方でだいたい大丈夫です。 vm2gol がコードネームで Pric が正式名みたいな雰囲気。</p> <h1 id="工程"><a href="#%E5%B7%A5%E7%A8%8B">工程</a></h1> <h2 id="ステップ (1) 第1世代 v2 コンパイラにセルフホストに必要な機能を追加"><a href="#%E3%82%B9%E3%83%86%E3%83%83%E3%83%97+%281%29+%E7%AC%AC1%E4%B8%96%E4%BB%A3+v2+%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E3%81%AB%E3%82%BB%E3%83%AB%E3%83%95%E3%83%9B%E3%82%B9%E3%83%88%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E6%A9%9F%E8%83%BD%E3%82%92%E8%BF%BD%E5%8A%A0">ステップ (1) 第1世代 v2 コンパイラにセルフホストに必要な機能を追加</a></h2> <p>第1世代なのでここは Ruby のコードの修正です。どのような機能を追加したかについては後述。</p> <p><a href="https://crieit.now.sh/upload_images/03b51b5b2f890722399eb89433f2d9c160d068e93d939.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/03b51b5b2f890722399eb89433f2d9c160d068e93d939.png?mw=700" alt="image" /></a></p> <p>できたもの: 第1世代-v3 なコンパイラ</p> <h2 id="ステップ (2) Pric 言語 v3 でコンパイラ v2 を書く"><a href="#%E3%82%B9%E3%83%86%E3%83%83%E3%83%97+%282%29+Pric+%E8%A8%80%E8%AA%9E+v3+%E3%81%A7%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9+v2+%E3%82%92%E6%9B%B8%E3%81%8F">ステップ (2) Pric 言語 v3 でコンパイラ v2 を書く</a></h2> <p>別の言い方でいえば:</p> <ul> <li>コンパイラ v2 を Pric 言語 v3 に移植</li> <li>ステップ (1) で作った第1世代-v3 なコンパイラで第2世代-v2 なコンパイラをコンパイルできるようにする</li> </ul> <p><a href="https://crieit.now.sh/upload_images/f3ebfb601e870cf878e749b1f6f93f8f60d068f7772ca.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f3ebfb601e870cf878e749b1f6f93f8f60d068f7772ca.png?mw=700" alt="image" /></a></p> <p>ここからは Pric 言語でもりもり書いていきます。<br /> 第2世代コンパイラはこの時点では v2 の機能しかサポートしておらず、ライフゲームはコンパイルできますがセルフホストはまだできません。</p> <p>v2 コンパイラの移植作業はこれまで何度もやってきていてだいぶ慣れています。Pric 版に一番近い <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/06/043607">C 言語版</a>もすでにありますし、実装で悩んだら C言語版を見ながら移植していけばOK。また、これまでの移植の過程で整備されてきたテスト用データもそのまま使えます。</p> <p>できたもの: 第2世代-v2 なコンパイラ</p> <h2 id="ステップ (3) 第2世代コンパイラ v2 を v3 にしていく"><a href="#%E3%82%B9%E3%83%86%E3%83%83%E3%83%97+%283%29+%E7%AC%AC2%E4%B8%96%E4%BB%A3%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9+v2+%E3%82%92+v3+%E3%81%AB%E3%81%97%E3%81%A6%E3%81%84%E3%81%8F">ステップ (3) 第2世代コンパイラ v2 を v3 にしていく</a></h2> <p>やることはだいたいステップ (1) と同じ。</p> <p><a href="https://crieit.now.sh/upload_images/698c6d4ba14bb131dd435760e3b15f4860d06904de855.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/698c6d4ba14bb131dd435760e3b15f4860d06904de855.png?mw=700" alt="image" /></a></p> <p>できたもの: 第2世代-v3 なコンパイラ … これができれば完成</p> <hr /> <p>ステップ (2) の移植作業はこれまでやってきた移植作業と同じようにやればいい。</p> <p>ステップ (3) は Ruby で (1) をやったことを繰り返して Pric で同じように書けばいい。</p> <p>というわけで、全体の中ではステップ (1) が今回の目玉でした。今回自分にとって一番収穫の大きかったのがステップ (1)。</p> <p>(1) ができた時点で (3) で何をやればいいか大体の目星が付き、後はやっていけば終わるなという感じで割と気楽に進めていきました。とはいえ、退屈な作業ということはなく、自作の言語でこれくらいの分量のプログラムを書くのは初めての経験でしたから、 (2) も (3) も十分新鮮でおもしろかったです。</p> <h1 id="方針"><a href="#%E6%96%B9%E9%87%9D">方針</a></h1> <ul> <li>セルフホストに必要かどうかで機能を選ぶ <ul> <li>v1 では「ライフゲームのコンパイルに必要か?」で決めていたが、<br /> 今回も同様に「セルフホストに必要か?」で判断する。</li> <li>なるべく寄り道しない / まずは曳光弾を通す / 後回しにできるものは後回し <ul> <li>ここらへんも v1 のときと同じ。目標を絞って集中し、長期化させないように。</li> <li>「とにかくセルフホストできれば細かいところはどうでもいい」くらいの気持ちで</li> <li>セルフホストすることが目的で機能追加はそのための手段</li> </ul></li> <li>後回しにした機能については後述の「TODO」の節を参照</li> </ul></li> <li>なるべく少ない機能で <ul> <li>ミニマル・シンプルに作りたい気持ち</li> <li>禁欲的に / ケチケチと</li> <li>(1) で機能を盛りすぎると (3) が大変になりそう <ul> <li>ステップ (1) で機能を追加するとき、つねに「これを追加するとステップ(3) で大変にならないか?」と考える。ステップ (3) の負担がなるべく小さくなるように意識する。</li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/DQNEO/items/2efaec18772a1ae3c198">DQNEO さんの minigo のセルフホストの話</a>などで大変そうな話を見聞きしていたのが影響していると思います。先達の経験談、ありがたや……。</li> </ul></li> </ul></li> <li>適宜 C言語を参考にするが、あくまで参考程度。C への準拠にはこだわらない。</li> <li>自作言語でセルフホストするのは今回初めてなので、無理しない。適宜ハードルを下げる。 <ul> <li>空間効率、速度効率は気にせず富豪的に。開発効率的に辛くなってきたらそのときなんとかする。先回りしすぎないように。</li> <li>なるべく凝ったことをせず、多少冗長な書き方だったり遅い書き方でもよいので確実に動きそうな方に倒す(そこらへんの改善はセルフホストできた後でやればよい)</li> </ul></li> </ul> <h1 id="セルフホストに必要な機能"><a href="#%E3%82%BB%E3%83%AB%E3%83%95%E3%83%9B%E3%82%B9%E3%83%88%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E6%A9%9F%E8%83%BD">セルフホストに必要な機能</a></h1> <p>はっきり決めていたわけではありませんが、基本的には C言語版に近い感じになるのかなとイメージしていました。<br /> そうすると、 C言語版では使っていて Pric v2 にない機能をリストアップしていけば、それが v3 で追加すべき機能の候補ということになります。</p> <ul> <li>入出力</li> <li>ポインタ</li> <li>ヒープ / malloc</li> <li>配列</li> <li>文字列</li> <li>構造体</li> <li>グローバル変数</li> <li>プリプロセッサ <ul> <li>ファイル分割 / <code>#include</code></li> <li>マクロ / <code>#define</code></li> </ul></li> </ul> <p>ざっとこんなところかなあとぼんやり考えて、後は作りながら決めていきました。<br /> 以下、思い出しながらそれぞれについて書いてみました。</p> <p>※ 以下は説明のために整理していて、実際に考えたり作った順番とは多少違いがあります。</p> <p>※ ちなみに、私はC言語はずっと前にちょっとやった程度でだいぶ忘れています。素人が書いていると思って読んでいただきたく……勘違い・浅い理解・不正確な記述などあると思います。</p> <h2 id="入出力"><a href="#%E5%85%A5%E5%87%BA%E5%8A%9B">入出力</a></h2> <p>1文字ずつ読み書き。これは必要。<br /> VM に命令を追加: <code>read</code>, <code>write</code></p> <p>入り口と出口で文字と数(アスキーコード)との変換を行い、コンパイル処理本体で扱っているのはすべて単なる数(整数)となるイメージです。</p> <pre><code>"def main()\n ..." ↓ read 100 101 102 32 109 97 105 110 40 41 10 ... ↓コンパイル 32 32 99 97 108 108 32 109 97 105 110 ... ↓ write " call main\n exit\n ..." (アセンブリコード) </code></pre> <p>こうやって見てみると、コンパイラがやっていることは「ある数の並びを別の数の並びに変換する」処理だと見ることができます。おもしろいですね。結局それしかやっていないというか。</p> <p>それはさておき、出力ができるようになると print デバッグができるようになります。また、出力を検証する形でテストできるようになり、これだけでもだいぶ進歩を感じるものです。「おっ、この機能ができたらアレとアレができるようになるじゃん!」という、ブートストラップの楽しさ。</p> <p>hello world はこんな感じ。こんなのでも動くと嬉しい。</p> <pre><code class="ruby">def putchar(c) write(c, 1); end def main() putchar( 72); # H putchar(101); # e putchar(108); # l putchar(108); # l putchar(111); # o putchar( 44); # , putchar( 32); # (SPACE) putchar( 87); # W putchar(111); # o putchar(114); # r putchar(108); # l putchar(100); # d putchar( 33); # ! putchar( 10); # (LF) end </code></pre> <h2 id="比較演算子"><a href="#%E6%AF%94%E8%BC%83%E6%BC%94%E7%AE%97%E5%AD%90">比較演算子</a></h2> <p>演算子としては小なり <code><</code> だけ追加しました。これだけ追加すれば、 v2 からある <code>==</code>、<code>!=</code> と組み合わせてあれこれできます。</p> <p>VM にレジスタを追加: <code>sf</code> (sign flag)<br /> VM に命令を追加: <code>jump_g</code> ... x86 の <code>jg</code> 命令を参考に</p> <h2 id="ファイル分割"><a href="#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%88%86%E5%89%B2">ファイル分割</a></h2> <pre><code>#include lib/std.pric </code></pre> <p>のような行を探し、指定されているファイルを読み込んでその場に埋め込むスクリプトを Ruby で書きました。<br /> が、これは単に cat コマンドで繋げるだけもよかったですね。そうしておけばさらに簡略化できた。</p> <p>※ 機能として挙げましたが、この部分はコンパイラの本体に含めず、補助ツールの類である(セルフホストの対象外)ということにしました。</p> <h2 id="アドレス演算子・デリファレンス演算子"><a href="#%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E6%BC%94%E7%AE%97%E5%AD%90%E3%83%BB%E3%83%87%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9%E6%BC%94%E7%AE%97%E5%AD%90">アドレス演算子・デリファレンス演算子</a></h2> <p>C の <code>&</code> と <code>*</code></p> <ul> <li>ステップ (1) の中ではここがキモ <ul> <li>キモといいつつちょっと怪しいので改めて整理したい……</li> </ul></li> <li>「<a target="_blank" rel="nofollow noopener" href="https://www.sbcr.jp/product/4797337958/">ふつうのコンパイラをつくろう</a>」を読み返す <ul> <li>デリファレンス演算が左辺にあるときと右辺にあるときの違い</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/rui314/chibicc">chibicc</a> や <a target="_blank" rel="nofollow noopener" href="https://github.com/aamine/cbc">cbc</a> の出力するアセンブリを観察 <ul> <li>なるべく最適化されていないアセンブリ出力を見たい</li> <li><a target="_blank" rel="nofollow noopener" href="https://gcc.godbolt.org/">Compiler Explorer</a> もお手軽・便利</li> </ul></li> <li>アドレス演算のために VM に <code>lea</code> 命令を追加 <ul> <li>x86 の <code>lea</code> 命令を参考に</li> </ul></li> <li>「ポインタがある」というか「アドレス演算とデリファレンス演算と、アドレスの入った変数がある」といった雰囲気(一緒?)</li> </ul> <hr /> <p>デリファレンスの括弧の中の部分は単なる式ということにしています。</p> <pre><code>*( some_addr_ + 2 ) ^^^^^^^^^^^^^^ この部分は単なる式 </code></pre> <p>途中紆余曲折があって <code>*(some_addr_, 2)</code> のような書き方を試したりしましたが、括弧の中にはただの式を書くことにして単純化し、C っぽい見た目に落ち着きました。</p> <p>デリファレンス演算の視点(?)では「とにかく何か値(式の評価結果=アドレス)が渡されるので、そのアドレスが指している場所を操作対象とする」のように考えればよい、ということに。</p> <hr /> <p>(余談)乗算の <code>*</code> と被るのでデリファレンス演算子には別の記号を使うのはどうかと考えて <code>^</code> にしてみたり、前置じゃなくて後置にするのはどうだろう? と考えて <code>( ... )^</code> にしてみたり……という試みが途中ありました。</p> <p>下手な考え休むに似たりかと思いきや、Pascal ではそのように書くのだと後から知りました。図らずも似たところに辿りついていたようでちょっとおもしろかったです。</p> <p>他には <code>addr(some_var)</code>, <code>deref(addr_expr)</code> のように普通の関数っぽい構文にしてしまう、という案もありました。パースは少し楽になりそうですが、視認性が悪そう(普通の関数と見分けにくそう)な気がしてボツに。</p> <h2 id="ヒープ / malloc"><a href="#%E3%83%92%E3%83%BC%E3%83%97+%2F+malloc">ヒープ / malloc</a></h2> <p>メモリのスタック領域として使っていた部分のうち、アドレスの小さい方の端をヒープとしました。</p> <p>malloc もまじめに作らないといけないのかなと思ってあれこれ調べていたんですが、単に確保した分だけカーソルを動かすだけなら簡単で私でもすぐ作れそうね、じゃあそれで、ということになりました。</p> <p>※ bump allocator とか stack allocator と呼ばれている方式だそうです。 参考: <a target="_blank" rel="nofollow noopener" href="https://os.phil-opp.com/allocator-designs/">Allocator Designs | Writing an OS in Rust</a></p> <p>というわけで allocate 関数の中身はこれだけ。</p> <pre><code class="ruby">def allocate(g_, size) # 変更前のカーソル位置 var head_addr = *(g_ + GO_ALLOC_CURSOR()); # 変更後のカーソル位置 var next_addr = *(g_ + GO_ALLOC_CURSOR()) + size; # カーソル位置を更新 *(g_ + GO_ALLOC_CURSOR()) = next_addr; # スタックと衝突していないかチェック check_heap_stack_overlap(g_); # 確保した領域の始点アドレスを返す return head_addr; end </code></pre> <p>(<code>g_</code> はグローバル変数構造体のアドレス。後述。)</p> <p>free はありません(C言語版でも free してなかった)。ここは富豪的に。<br /> 富豪的にやってどのくらいメモリが必要だったかというと、最終的には番地の数で 2,600,000 となりました。1 byte/番地で換算すると約 2.6 MB。</p> <p>あと、ヒープとスタックの衝突を Pric コードのレイヤーで検出する作りにしていて、そのためにレジスタ <code>sp</code> の値を取得する組み込み関数を追加しました。ここはちょっとやっつけ。</p> <h2 id="配列"><a href="#%E9%85%8D%E5%88%97">配列</a></h2> <p>配列は作る前からイメージがあって特に詰まらずに作れました。<br /> 「<a target="_blank" rel="nofollow noopener" href="https://www.sigbus.info/compilerbook">低レイヤを知りたい人のためのCコンパイラ作成入門</a>」にならって、普通のローカル変数と同じようにスタックに置きます。</p> <p>構造体の配列を使いたい場合は構造体のデータそのものは持たせず、ポインタというかアドレスを持たせる形で使います。もしデータそのものを持たせるとしたら各要素のオフセットの計算は自前でやらないとダメ(言語によるサポートはない)。</p> <hr /> <p><code>配列変数名[添字]</code> で配列の要素にアクセスする構文を一度用意して、ステップ (1) では実装までしましたが、ステップ (3) でやることが増えるので取りやめにして実装も削除しました。</p> <p>デリファレンス演算子があれば</p> <pre><code class="ruby">a = *(xs_ + 1); *(xs_ + 1) = 2; </code></pre> <p>のように配列の要素を読み書きできます。つまりポインタ演算のシンタックスシュガーなわけですね。<br /> 機能はなるべく少なくしたいという方針に沿って、今回は <code>[]</code> 演算子は取りやめにして実装も削除しました。</p> <p>ただし、「これは配列を扱っているんだぞ」という意図を表すために実際は下記のような関数を介して操作するようにしています。</p> <pre><code class="ruby">def aset(array_, i, val) *(array_ + i) = val; end def aget(array_, i) return *(array_, i); end </code></pre> <p>Pric 言語で書けるものに関してはこのようにユーティリティ関数を作っていきました。</p> <hr /> <p>それで、結局配列のために手を加える必要があったのは</p> <ul> <li>変数宣言時に幅を考慮(スタックポインタを幅1つ分ではなく配列の長さの分ずらす)</li> <li>変数名からスタック上の位置を割り出す際に配列変数の幅を考慮</li> </ul> <p>これだけといえばこれだけですね。<br /> 「配列という機能を追加した」というよりは「変数の幅の考慮とポインタ演算を組み合わせることによって配列の操作に相当する操作ができるようになった」という感覚です。</p> <h2 id="文字列"><a href="#%E6%96%87%E5%AD%97%E5%88%97">文字列</a></h2> <p>配列があればOK。プログラマ(私)が「これは文字列なのだ」と思い込むことにより文字列として扱うことができます。</p> <p>たとえば C だったらこのように書くところを、</p> <pre><code class="c">char str[] = "hello"; </code></pre> <p>Pric では次のように書きます。</p> <pre><code class="ruby">var [6]str; aset(&str, 0, 104); # h aset(&str, 1, 101); # e aset(&str, 2, 108); # l aset(&str, 3, 108); # l aset(&str, 4, 111); # o aset(&str, 5, 0); # (NUL) </code></pre> <p>さすがに毎回手で書くのは面倒なので適当なスクリプトで生成してターミナルからエディタに貼り付けていました。</p> <p>また、コード上での行数が多くて邪魔なのと、複数箇所で同じ文字列を使うケースがあることから、実際は次のように文字列に値を詰める処理を関数にして使っています。</p> <pre><code class="ruby">var [6]s_hello; set_s_hello(&s_hello); </code></pre> <p>ちなみに、文字列関連の何かを調べていたときに Pascal の文字列(先頭に文字列長を持つ)について知りました。Pascal 方式の方がバグが発生しにくそうな感じがしてちょっとなびきかけましたが、ひとまず C に寄せました(Pascal よりは C の方がなじみがあるため)。</p> <h2 id="構造体"><a href="#%E6%A7%8B%E9%80%A0%E4%BD%93">構造体</a></h2> <ul> <li>構造体のサイズ分の領域の確保ができて、</li> <li>起点と、各メンバのオフセットと幅が扱えて、</li> <li>あとはポインタ演算ができれば読み書きできる</li> </ul> <p>心の目で見ると構造体のようなものに見える何か。これもすでにある機能でまかなえてしまいます。</p> <p>構造体のデータはヒープに置いていていますが、これはそういう制約がある訳ではなくとりあえずのルールです。たぶんスタックにも置けるはず。</p> <p><code>some_struct.some_member</code> や <code>some_struct->some_member</code> のような構文は用意せず、操作用のインターフェイスとなる関数を作ってその中でごにょごにょしています。<br /> 例を挙げると以下は List 構造体の size メンバの値を返す getter 関数です。アドレスがあればそれで操作できるぞい、というノリ。</p> <pre><code class="ruby">def List_size(self_) var size = *(self_ + LIST__SIZE()); return size; end # 呼び出し側 # C でいえば list_-&gt;size に相当 var list_size = List_size(list_); </code></pre> <p>結局、メモリ上の位置とデータがあって操作がある、ということでしょうか(それはそう)。</p> <h2 id="グローバルな定数"><a href="#%E3%82%B0%E3%83%AD%E3%83%BC%E3%83%90%E3%83%AB%E3%81%AA%E5%AE%9A%E6%95%B0">グローバルな定数</a></h2> <p>関数はどこ(他のどの関数)からでも呼び出せますから、固定値を返す関数があればグローバルな定数と等価なものとして扱えます。マクロのような仕組みを追加しなくても、すでにある関数を使えばOK。じゃあそれで。</p> <p>※ ということにしましたが、コンパイラの機能に含めないことにしてプリプロセッサでやってしまってもよかったかも。</p> <p>たとえば 1つ前の「構造体」の節のコード例に出てくる <code>LIST__SIZE()</code> がこれです。</p> <h2 id="グローバル変数"><a href="#%E3%82%B0%E3%83%AD%E3%83%BC%E3%83%90%E3%83%AB%E5%A4%89%E6%95%B0">グローバル変数</a></h2> <p>たとえば ELF だと .data セクションや .bss セクションに置く、という話があります。<br /> しかし、その方向で進めるとなるとアセンブリ・機械語のファイルのフォーマットやメモリレイアウトも考えないといけなくなります。話が大きくなってハードルが上がってしまう(完成までの道のりが長くなってしまう)ように思われました。</p> <p>そこで、グローバル変数に関してもすでにある機能でなんとかできないかと。</p> <p>要するに</p> <ul> <li>どこでも使えて</li> <li>読み書き両方できる</li> </ul> <p>という要件を満たせれば、その何かをグローバル変数相当のものとして使えます。</p> <p>そこで思いついたのが、エントリポイントである main 関数で構造体を1つ用意して、そのアドレスを関数の引数でバケツリレーしていく方法です。よし、じゃあそれで。</p> <h2 id="機能追加まとめ"><a href="#%E6%A9%9F%E8%83%BD%E8%BF%BD%E5%8A%A0%E3%81%BE%E3%81%A8%E3%82%81">機能追加まとめ</a></h2> <ul> <li>入出力: 追加した</li> <li>比較演算子: <code><</code> だけ追加した</li> <li>ファイル分割: セルフホストの対象外とした</li> <li>アドレス演算子・デリファレンス演算子: 追加した</li> <li>ヒープ / malloc: ほぼ追加なし</li> <li>配列: 幅を考慮して変数を扱えるようにした</li> <li>文字列: 追加なし</li> <li>構造体: 追加なし</li> <li>グローバルな定数: 追加なし</li> <li>グローバル変数: 追加なし</li> </ul> <p>こうして見てみると、今回は</p> <ul> <li>アドレス演算子・デリファレンス演算子を使って間接参照ができるようになった</li> <li>メモリ上のある範囲をまとめて扱えるようになり、間接参照と組み合わせて配列・文字列・構造体相当のデータが扱えるようになった</li> </ul> <p>ことの比重が大きく、これらを利用することでできることの幅がかなり広がった、と振り返ることができそうです。</p> <p>また、グローバルな定数や配列・構造体操作用の関数に加えて <code>-</code>, <code><=</code>, <code>&&</code>, <code>||</code> などの演算子も関数でなんとかしていて、関数があることでなんとかなっている部分も多いですね。関数えらい。抽象化の力。</p> <h1 id="感想など"><a href="#%E6%84%9F%E6%83%B3%E3%81%AA%E3%81%A9">感想など</a></h1> <ul> <li>v1 のときほどのがんばりは必要なく(そういう意味では盛り上がりに若干欠け)比較的落ち着いて進めた感じでしたが、<br /> それはそれでOK。十分楽しく、達成感もありました!</li> <li>先にC言語版を作っていたのが結果として良かった。ここで悩まずに済んだのも大きかった。</li> <li>今回セルフホストまで達成できましたが、思い返してみれば<br /> 一度 v1 を作ったことが足がかりとなり、視界が開けて「自走できるようになった感」が得られたように思います。<br /> がんばって v1 作って良かったなあとしみじみ。</li> <li>ステップ (1) の部分は単純に知識・経験が得られてよかった(低レイヤーの知識がまたちょっと増えた) <ul> <li>メモリ操作用言語でメモリ操作している、みたいな感覚が体験できた</li> </ul></li> <li>(今回は)構造体や文字列など、いろんなものを「見立て」でなんとかしている。つまり、概念は開発者(=自分)の頭の中にある。「これ不便だからこうしたら便利じゃね?」みたいなアイデア(シンタックスシュガーなど)も同様。 <ul> <li>現在使われている言語の機能も、かつて開発者の頭の中にあったものが歴史を経て少しずつコードに落とされてきたものの蓄積……と考えると感慨深いものがありますね。今もいろんな人の頭の中に未来の便利機能のアイデアが眠っているのでしょう。</li> </ul></li> <li>節目で何度か<a target="_blank" rel="nofollow noopener" href="https://prog-lang-sys-ja-slack.github.io/wiki/">言語処理系Slack</a>で進捗報告してリアクションをいただけたのがたいへん励みになりました。ありがとうございました。</li> </ul> <h1 id="TODO"><a href="#TODO">TODO</a></h1> <p>まずは文字列リテラル(に相当する部分)を静的なデータとして使いまわせるようにするところから手を付けたいですね。<br /> おそらくここが速度的に一番のボトルネックになっているので、ここをなんとかするだけでかなり速くなって、後が楽になるんじゃないかと。</p> <hr /> <p>パッと思いつくもの、やりたいなーと思いつつ自重していたのはこのあたり:</p> <ul> <li>文字列リテラル</li> <li>配列アクセス演算子 <code>[]</code></li> <li>演算子の優先順位 <ul> <li>明示的に <code>(</code> <code>)</code> で囲めば済むので結局今回もお流れとなった</li> </ul></li> <li>演算子の追加 <code>-</code>, <code>/</code>, <code>%</code>, <code>></code>, <code>!</code>, <code>&&</code>, etc.</li> <li><code>&&</code>, <code>||</code> の短絡評価</li> <li>まともな構造体</li> <li>まともなグローバル変数</li> <li>型</li> <li>関数の途中での return</li> <li>break/continue</li> <li>その他、雑多な改善: リファクタリング・最適化</li> </ul> <p>あんまり詳しく考えてないですが、C に近づいていくのかなあ。</p> <p>v1, v2 では初心者向け教材っぽくしたくて(想定読者=どうやってコンパイラを作ればいいのか分からなかった頃の自分)「なるべくベタな仕様・見た目にしたい」「高度な機能や独自色をなるべく入れない」という気持ちがあったのですが、v3 ではその縛りは外してしまってもいいかなと思っています。</p> <hr /> <p>というわけで本体は以上で終わりです。以下、苦労話や枝葉な部分のメモです。</p> <h1 id="苦労話"><a href="#%E8%8B%A6%E5%8A%B4%E8%A9%B1">苦労話</a></h1> <h2 id="遅い"><a href="#%E9%81%85%E3%81%84">遅い</a></h2> <p>lexer, parser, codegen を全部コンパイルすると現状では 15分くらいかかります(第2世代コンパイラが育つに連れて伸びていき、最終的にこのくらい)。<br /> ふつうに辛いのですが、終盤ではゴールが近いと分かっていたので下手にジタバタせず逃げ切りました。特に対策せず、実行して待ってる間に部屋の掃除、実行して部屋の掃除、を繰り返していました。部屋がちょっときれいになりました。</p> <p>※ 「TODO」の節で書いたようにこれから速くしていく予定です</p> <h2 id="配列の範囲外アクセス"><a href="#%E9%85%8D%E5%88%97%E3%81%AE%E7%AF%84%E5%9B%B2%E5%A4%96%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9">配列の範囲外アクセス</a></h2> <p>プログラムが大きくなってくるとアセンブリや機械語やメモリのダンプを見てもさっぱり分からん……ので <code>git reset --hard</code> して歩幅を小さくしてやり直して、それで8割くらいはなんとかなった気がします。残りの2割はがんばってなんとか。gdb とか便利なものがないのでここはちょっと辛いですね。</p> <p>ただ、長くても数時間くらいでなんとかなるレベルで、バグの原因が判明するまで何日もかかるということはありませんでした。<br /> なるべく複雑なことをしないように作っていたのが良かったかもしれません。</p> <h2 id="エラー発生時にスタックトレースが見れない"><a href="#%E3%82%A8%E3%83%A9%E3%83%BC%E7%99%BA%E7%94%9F%E6%99%82%E3%81%AB%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B9%E3%81%8C%E8%A6%8B%E3%82%8C%E3%81%AA%E3%81%84">エラー発生時にスタックトレースが見れない</a></h2> <p>print デバッグなどでがんばる……。</p> <p>ある関数で配列の範囲外アクセスが原因と思われるエラーが発生し、おそらく呼び出し元で範囲指定をミスしており、そして呼び出し元がたくさんあり、まず呼び出し元の特定が必要……のようなパターン。</p> <p>セルフホスト達成後に試したところ、VM などにちょいと細工をするだけでスタックトレースを見られるようにできました。若干チート感がありますが、これはさっさとやっておけばよかったかも。</p> <h2 id="関数呼び出し時の引数の個数のまちがい"><a href="#%E9%96%A2%E6%95%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E6%99%82%E3%81%AE%E5%BC%95%E6%95%B0%E3%81%AE%E5%80%8B%E6%95%B0%E3%81%AE%E3%81%BE%E3%81%A1%E3%81%8C%E3%81%84">関数呼び出し時の引数の個数のまちがい</a></h2> <p>これは結構鬱陶しくて、よく間違えるのと、個数が違うのが原因だと分かりにくく不毛だったため、さすがに対策しました。<br /> 構文木の段階で関数の定義と呼び出しを突き合わせてチェックする数十行程度のスクリプトを、これは補助ツールだから……と言い訳しつつ Ruby で書き、パース処理とコード生成処理の間に挟みました。<br /> すごく原始的な静的解析でしょうか。</p> <h1 id="おまけ・細かい話"><a href="#%E3%81%8A%E3%81%BE%E3%81%91%E3%83%BB%E7%B4%B0%E3%81%8B%E3%81%84%E8%A9%B1">おまけ・細かい話</a></h1> <p>v2 から v3 への変更のうち、セルフホストにはそんなに関係ない部分、その他。</p> <h2 id="文頭の set, call, call_set を廃止"><a href="#%E6%96%87%E9%A0%AD%E3%81%AE+set%2C+call%2C+call_set+%E3%82%92%E5%BB%83%E6%AD%A2">文頭の set, call, call_set を廃止</a></h2> <p>v2 では文頭に <code>set</code>, <code>call</code>, <code>call_set</code> を明示的に書く方式にしていましたが、これを不要にしました。ある程度量を書くとなると、やはり鬱陶しいので。これは割と小さめの修正でOK。</p> <pre><code class="diff">-set a = 123; +a = 123; -call foo_func(); +foo_func(); -call_set a = foo_func(); +a = foo_func(); </code></pre> <hr /> <p>関数の引数として任意の式を書けるようにし、また、関数呼び出しを式として扱うようにしました。これにより、たとえば次のように関数呼び出しを関数の引数として書けるようになります。</p> <pre><code class="ruby">foo_func(bar_func()); </code></pre> <p>だいぶ普通の言語っぽくなりました。</p> <h2 id="case 文の else"><a href="#case+%E6%96%87%E3%81%AE+else">case 文の else</a></h2> <p>v2 には else がなく</p> <pre><code class="ruby">case when ... # ... when (0 == 0) # else の代わり # ... end </code></pre> <p>のように書いていたところを、次のように else で書けるようにしました。</p> <pre><code class="ruby">case when ... # ... else # ... end </code></pre> <h2 id="if 文"><a href="#if+%E6%96%87">if 文</a></h2> <p>case文があれば if文はなくていいかなと思っていたのですが</p> <ul> <li>あればあったでちょっと書きやすい</li> <li>少しの手間(数行の変更)で実現できそう</li> </ul> <p>ということで書けるようにしました。</p> <p>レキサで処理しています。<code>if</code> が来たら <code>case</code> <code>when</code> の2つのトークンに変換するだけ。</p> <pre><code class="ruby">if ( ... ) ... end ↓ 変換 case when ( ... ) ... end </code></pre> <p>これだけで <code>else</code> 節のある if文も書けるようになります。</p> <pre><code class="ruby">if ( ... ) # ... else # ... end ↓ 変換 case when ( ... ) # ... else # ... end </code></pre> <h2 id="Ruby っぽい見た目に変更"><a href="#Ruby+%E3%81%A3%E3%81%BD%E3%81%84%E8%A6%8B%E3%81%9F%E7%9B%AE%E3%81%AB%E5%A4%89%E6%9B%B4">Ruby っぽい見た目に変更</a></h2> <p>すでにここまで貼ったコード片で見てきた通りで、 Ruby っぽい見た目に変えてみました。見た目は Ruby っぽいけど中身は C っぽい、ちょっと不思議な書き味です。</p> <p>これはなんとなくできそうだったので、試してみて、なんとなくこうなったというものです。<br /> v2 とのギャップを小さくするために JavaScript っぽい構文に戻そうかなとも考えていましたが、元に戻すのが億劫になるところまで進めてしまって、めんどくさくなって結局そのままという。</p> <p>アドレス演算子、デリファレンス演算子があるため Ruby と文法コンパチにはなっていないと思います。</p> <h1 id="ポインタ変数"><a href="#%E3%83%9D%E3%82%A4%E3%83%B3%E3%82%BF%E5%A4%89%E6%95%B0">ポインタ変数</a></h1> <p>コードの見た目だけでは通常の変数なのかアドレスが入ってる変数なのか、使用箇所を見ても宣言を見ても分かりません。</p> <pre><code class="ruby">var a; # 普通の整数が入る var b; # アドレスが入る </code></pre> <p>そこで、見た目で瞬時に判別できるように、アドレスが入っている変数には末尾に <code>_</code> を付けています。</p> <pre><code class="ruby">def foo( arg1, # 普通の変数 arg2_ # アドレスが入っている ) # ... end foo( a, # 普通の整数を渡している b_ # アドレスを渡している ); </code></pre> <hr /> <p>というわけでおまけコーナーは以上です。また何か思い出したら追記するかもしれません。</p> sonota486 tag:crieit.net,2005:PublicArticle/17398 2021-06-14T06:43:44+09:00 2021-06-15T07:18:25+09:00 https://crieit.net/posts/ruby-simple-compiler-vm2gol-v2 Rubyでかんたんな自作言語のコンパイラを作った <p>※ <a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/2b95378b43a22109513c">Qiitaに書いた記事</a>のクロス投稿です。</p> <p><a href="https://crieit.now.sh/upload_images/696f2eb5274806d16114ce8b256cda6260c67b4de7922.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/696f2eb5274806d16114ce8b256cda6260c67b4de7922.png?mw=700" alt="image" /></a></p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/f9cb3fc4a496b354b729">RubyでオレオレVMとアセンブラとコード生成器を2週間で作ってライフゲームを動かした話</a>の続きです。このときスコープ外にしていたフロントエンド部分(高水準言語から構文木への変換部分)をやっと書きました。3日くらいでできたのでさっさとやっておけばよかった。</p> <p>パーサまで揃っていなかったため仕方なく「コード生成器を作った」という表現にしていましたが、ここまでやったら「コンパイラを作った」と言っていいはず!</p> <p>※ <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">ブログに書いていたもの</a>を引っ越してきました。元の記事公開日は 2020-05-04 です。</p> <hr /> <ul> <li>パーサ <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/blob/41/vgparser.rb">https://github.com/sonota88/vm2gol-v2/blob/41/vgparser.rb</a></li> </ul></li> <li>ライフゲームのコード <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/blob/41/gol.vg.txt">https://github.com/sonota88/vm2gol-v2/blob/41/gol.vg.txt</a></li> </ul></li> </ul> <p>※ 2020-05-04 時点のものです。その後の改良も加わったものが見たい場合は <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">mainブランチ</a> を見てください。<br /> ※ パーサ以外の部分をどうやって作ったか知りたい方は<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/05/04/234516">こちら</a>をどうぞ<br /> ※ 他の言語にも移植していますので Ruby わからんという方は<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">こちら</a>もどうぞ(2021-06-15 現在での移植先: TypeScript, Python, Dart, Java, C, Perl, C♭, PHP, Go, LibreOffice Basic, Zig, Kotlin, Pric, Crystal, Rust, Julia, Pascal)</p> <hr /> <ul> <li>ふつうの手書き再帰下降パーサ <ul> <li>400行ちょっと</li> </ul></li> <li>難しいことはしない <ul> <li>再帰下降パーサを実際に書くのは今回が初めてなので</li> <li>まずはハードルを下げて「とにかく動く」ところまで持って行く <ul> <li>下げ過ぎた気がしなくもない</li> </ul></li> <li>構文木を割とそのままマッピングしていて、気の利いた変換とかはやってない</li> <li>式の優先順位は考慮しない <ul> <li>明示的に括弧を書く</li> <li>気が向いたら後でやる</li> </ul></li> </ul></li> <li>ベタな文法でよい / 高度な機能や独自色をなるべく入れないようにしたい <ul> <li>初心者向け教材っぽくしておきたい気持ち</li> </ul></li> <li>汎用ではない <ul> <li>ライフゲームだけコンパイルできればよい</li> </ul></li> <li>エラーハンドリングは適当 <ul> <li>ライフゲームだけコンパイルできればよい</li> </ul></li> <li>ふつうの文法でふつうの再帰下降パーサなので、あまり書くことがないです……</li> </ul> <h1 id="文法サンプル"><a href="#%E6%96%87%E6%B3%95%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB">文法サンプル</a></h1> <p>見ての通り、特にひねりのない感じです。パッと見では JavaScript に近いでしょうか。</p> <p><code>set</code>, <code>call</code>, <code>call_set</code> は明示的に書きます。</p> <pre><code class="javascript">// コメント // 関数定義 func add(a, b) { // return(返り値は省略可) return a + b; } // main 関数は必須 func main() { // ローカル変数宣言 var a; // ローカル変数宣言+初期化 var b = 1; // ローカル変数への代入 set a = 2; // 関数呼び出し call add(1, 2); // 関数呼び出して返り値をローカル変数に代入 call_set c = add(1, 2); // while while (a != 10) { // ... } // case case { (a == 1) { // ... } (a == 2) { // ... } (0 == 0) { // else の代わり // ... } } // VMコメント _cmt("..."); } </code></pre> <h1 id="コンパイルからVMでの実行までの流れ"><a href="#%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB%E3%81%8B%E3%82%89VM%E3%81%A7%E3%81%AE%E5%AE%9F%E8%A1%8C%E3%81%BE%E3%81%A7%E3%81%AE%E6%B5%81%E3%82%8C">コンパイルからVMでの実行までの流れ</a></h1> <p>足し算をする関数を呼び出して結果を受け取るだけのサンプルで高水準言語から機械語までの変換の流れを具体的に見てみます。</p> <hr /> <p>高水準言語:</p> <pre><code class="javascript">func add(a, b) { var result = a + b; return result; } func main() { var result; call_set result = add(1, 2); } </code></pre> <p>↓ <code>ruby vgparser.rb add.vg.txt > add.vgt.json</code> で AST に変換(実際の出力は改行が多くて冗長なので下記は手動で整形しています)</p> <pre><code class="json">[ "stmts", [ "func", "add", ["a", "b"], [ ["var", "result", ["+", "a", "b"]], ["return", "result"] ] ], [ "func", "main", [], [ ["var", "result"], ["call_set", "result", ["add", 1, 2]] ] ] ] </code></pre> <p>↓ <code>ruby vgcg.rb add.vgt.json > add.vga.txt</code> でアセンブリコードに変換</p> <pre><code class="sh"> call main exit label add push bp cp sp bp # 関数の処理本体 sub_sp 1 push [bp+2] push [bp+3] pop reg_b pop reg_a add_ab cp reg_a [bp-1] cp [bp-1] reg_a cp bp sp pop bp ret label main push bp cp sp bp # 関数の処理本体 sub_sp 1 push 2 push 1 _cmt call_set<del>add call add add_sp 2 cp reg_a [bp-1] cp bp sp pop bp ret </code></pre> <p>↓ <code>ruby vgasm.rb add.vga.txt > add.vge.yaml</code> で機械語コードに変換</p> <pre><code class="yaml">--- - call - 35 - exit - label - add - push - bp - cp - sp - bp - sub_sp - 1 - push - "[bp+2]" - push - "[bp+3]" - pop - reg_b - pop - reg_a - add_ab - cp - reg_a - "[bp-1]" - cp - "[bp-1]" - reg_a - cp - bp - sp - pop - bp - ret - label - main - push - bp - cp - sp - bp - sub_sp - 1 - push - 2 - push - 1 - _cmt - call_set</del>add - call - 5 - add_sp - 2 - cp - reg_a - "[bp-1]" - cp - bp - sp - pop - bp - ret </code></pre> <p>(2021-01-11 追記)<br /> この機械語コードのフォーマットは1命令1行となるようにその後変更しました: <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/10/124904">vm2gol v2 (51) 機械語コードのフォーマットを固定長風に変更</a><br /> (追記ここまで)</p> <p>あとは <code>STEP= ruby vgvm.rb add.vge.yaml</code> とすると VM で1命令ずつステップ実行できます。</p> <p>また、これまでと同様に <code>run.sh</code> を使って <code>./run.sh gol.vg.txt</code> のように実行すると<br /> パース → コード生成 → アセンブル → VMで実行<br /> がまとめて実行できます。</p> <h1 id="2020-06-25 追記"><a href="#2020-06-25+%E8%BF%BD%E8%A8%98">2020-06-25 追記</a></h1> <p>節目っぽいのでこの時点での行数を数えてみました。空行やコメントだけの行も含めた単純な行数です。</p> <pre><code> 14 common.rb 63 vgasm.rb 474 vgcg.rb 433 vgparser.rb 491 vgvm.rb 1475 合計 </code></pre> <p>思ったより少ない。2,000行超えてるかなと思ってましたが。</p> <p><code>gol.vg.txt</code> と、コンパイル・アセンブルで生成される AST・アセンブリコード・機械語コードも行数を見てみます。</p> <pre><code> 208 gol.vg.txt 874 tmp/gol.vgt.json 693 tmp/gol.vga.txt 1299 tmp/gol.vge.yaml </code></pre> <p>フーン、という感じですね(気の利いた感想が出てこなかった)。</p> <h1 id="その後の変更"><a href="#%E3%81%9D%E3%81%AE%E5%BE%8C%E3%81%AE%E5%A4%89%E6%9B%B4">その後の変更</a></h1> <p>けっこういいかげんなところがあるので後から修正しています。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/23/154759">vm2gol v2 (47) 引数のパースの厳密化など</a></li> </ul> <hr /> <p>(2021-02-21 追記)いくつか機能を足してセルフホストできるようになりました。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/pric">https://github.com/sonota88/pric</a></li> </ul> <h1 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://b.hatena.ne.jp/sonota88/Parser/">Parserに関するsonota88のブックマーク - はてなブックマーク</a></li> </ul> <h1 id="関連"><a href="#%E9%96%A2%E9%80%A3">関連</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">vm2gol-v2 移植まとめ</a> <ul> <li>いろんな言語に移植してみています</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/07/05/111103">四則演算と剰余のみのexprコマンドをRubyで作ってみた</a> <ul> <li>演算子の優先順位の考慮も必要な場合はこの方法で。<br /> ただ、ライフゲームを動かす分にはあんまり必要ないんですよね……。</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/a5d6d3539e0fb8040f74">リレー式論理回路シミュレータを自作して1bit CPUまで動かした</a> <ul> <li>もっと低いレイヤーにも手を出してみました</li> </ul></li> </ul> <hr /> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/05/04/234516">目次ページに戻る</a> / <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/12/21/144455">前</a> / <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/06/20/083831">次</a></li> </ul> sonota486 tag:crieit.net,2005:PublicArticle/17234 2021-05-23T06:37:49+09:00 2021-05-23T06:37:49+09:00 https://crieit.net/posts/simple-compiler-pascal Pascalでかんたんな自作言語のコンパイラを書いた <p>Pascal 初めて書きました。処理系は Free Pascal です。<br /> いつもの通りでライフゲームのコンパイルが通ったのでヨシ、という程度の雑なものです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-pascal">https://github.com/sonota88/vm2gol-v2-pascal</a></p> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">Rubyで素朴な自作言語のコンパイラを作った</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">https://github.com/sonota88/vm2gol-v2</a></li> </ul> <p>ライフゲームのプログラムだけコンパイルできればOKという簡単なコンパイラです。Ruby 版だとコンパイラ部分だけで 1000行くらい。<br /> ベースになっているバージョンは <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/58">ステップ 58</a> のあたり</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <p>主な部分のサイズ(行数)はこんな感じ。</p> <pre><code>$ wc -l *.pas lib/*.pas 491 codegen.pas 153 lexer.pas 533 parser.pas 184 lib/json.pas 222 lib/types.pas 195 lib/utils.pas 1778 合計 </code></pre> <hr /> <p>※ VB はほとんど書いたことがなく、一方 LibreOffice Basic の方は多少書いたことあるので、以下では LibreOffice Basic を挙げていますが、たぶん VB でも大体似た感じだと思います。</p> <p>ちなみに少し前に LibreOffice Basic で書いたもの:</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/31c127c42a9722892aa8">素朴な自作言語のコンパイラをLibreOffice Basicに移植した</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/501f0efa437bc4d53cc7">LibreOffice BasicでLispインタプリタ(mal)を書いた</a></li> </ul> <hr /> <ul> <li>自分が知っているものの中では C と LibreOffice Basic の中間みたいな印象 <ul> <li>Pascal + 動的型 → (VB) → LibreOffice Basic という流れ?</li> </ul></li> <li>string 型があって文字列の扱いは C より楽</li> <li>Emacs に pascal-mode をインストール……しなくてもすでに入っていた(インストールした記憶がないので最初から入ってた?) <ul> <li>デフォルト設定だとインデントがスペース3つ</li> </ul></li> <li>今回は fpc をインストールした Docker イメージを使うようにしてみた</li> <li>処理系に配慮された整理された文法になっていて、たとえば C コンパイラを作るより Pascal コンパイラを作る方がハードルが低そう(コンパイラというか主に構文解析部分?) <ul> <li>教育的、というか元々教育目的として生まれた言語だそう</li> <li>言語処理系自作に興味がある人は触れておいて損はなさそう</li> <li>書籍『<a target="_blank" rel="nofollow noopener" href="https://www.ohmsha.co.jp/book/9784274221163/">コンパイラ 作りながら学ぶ</a>』の PL/0' も Pascal 系の言語</li> </ul></li> </ul> <hr /> <p>Pascal は今まで触れる機会がなくて全然知らない文化圏という意識でいたのですが、実際触ってみると完全に異文化ということもなくて、自分が書いたことのある言語でいえば LibreOffice Basic が近いです(とりあえず見た目は)。</p> <p>たとえば整数を2つ受け取って足した結果を返す関数は次のようになります。</p> <pre><code class="pascal">// Pascal function add(a : integer; b : integer) : integer; begin add := a + b; end; </code></pre> <pre><code class="vb">' LibreOffice Basic function add(a, b) add = a + b end function </code></pre> <ul> <li><code>return 返り値</code> ではなく <code>関数名 = 返り値</code> と書く</li> <li>返り値の有無で <code>sub</code>(Pascal では <code>procedure</code>) と <code>function</code> を使い分ける</li> <li>関数・プロシージャ呼び出し時に引数がない場合は括弧を省略可</li> </ul> <p>といったあたりが Pascal 由来と思われます。構造体の扱いも似ているような?</p> <p>書いているときの感触としては「LibreOffice Basic の変数に型が付いて、文法はなんとなく似ていて、低水準の操作ができる」みたいな感じでした。</p> <hr /> <p>LibreOffice Basic 以外でも、JavaScript の <code>with</code> も Pascal 由来かな? とか、Go の <code>:=</code> も Pascal 由来かな? とか、 Scala や Rust、Zig など、コロンで区切って型を後置するのも Pascal 由来かな? といった感じで、他の言語への影響と思われる(※確認はしていなくて、あくまで「思われる」という程度)要素がいろいろあって面白かったです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://kuina.ch/kuin">Kuin</a> の言語仕様の解説にも Pascal への言及がちらほら出てきますね。たとえば「<a target="_blank" rel="nofollow noopener" href="https://kuina.ch/kuin/spec3">Kuin言語仕様3 演算子</a>」など。</p> <h1 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://dotinstall.com/lessons/basic_pascal">Pascal入門 (全18回) - プログラミングならドットインストール</a> <ul> <li>とりあえず最初にこれを見るのがおすすめ</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/yaju/items/16d91d58ae65af1ecab9">変数の代入演算子と等価演算子について - Qiita</a></li> <li>(2019) <a target="_blank" rel="nofollow noopener" href="https://news.mynavi.jp/article/programinglanguageoftheworld-22/">世界のプログラミング言語(22) 今も改良が続くPascal/Delphi言語の栄枯盛衰 | TECH+</a></li> <li>(2015) <a target="_blank" rel="nofollow noopener" href="https://developers.srad.jp/story/15/01/27/0543208/">Pascalは過小評価されている | スラド デベロッパー</a></li> </ul> <h1 id="他の言語への移植"><a href="#%E4%BB%96%E3%81%AE%E8%A8%80%E8%AA%9E%E3%81%B8%E3%81%AE%E7%A7%BB%E6%A4%8D">他の言語への移植</a></h1> <div class="table-responsive"><table> <thead> <tr> <th>記事</th> <th>リポジトリ</th> <th>日付</th> </tr> </thead> <tbody> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/b80696f23a08c3c2fc5d">Julia</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-julia">github</a></td> <td>2021-05-03</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20210407_vm2gol_v2_rust">Rust</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-rust">github</a></td> <td>2021-04-07</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/275c2b5407986b100cc8">Crystal</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-crystal">github</a></td> <td>2021-03-27</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/14/032813">Kotlin</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-kotlin">github</a></td> <td>2021-01-14</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/07/235019">Zig</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-zig">github</a></td> <td>2021-01-07</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/31c127c42a9722892aa8">LibreOffice Basic</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-libreoffice-basic">github</a></td> <td>2020-12-14</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/25/073200">Go</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-go">github</a></td> <td>2020-09-25</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/18/193844">PHP</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-php">github</a></td> <td>2020-09-18</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/13/133735">C♭</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-cflat">github</a></td> <td>2020-09-13</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/08/053329">Perl</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-perl">github</a></td> <td>2020-09-08</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/06/043607">C</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-c">github</a></td> <td>2020-09-06</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/121437">Java</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-java">github</a></td> <td>2020-08-30</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/22/162201">Dart</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-dart">github</a></td> <td>2020-08-22</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/19/065056">Python</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-python">github</a></td> <td>2020-08-19</td> </tr> <tr> <td><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/15/114754">TypeScript (Deno)</a></td> <td><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-typescript/tree/20200815">github</a></td> <td>2020-08-15</td> </tr> </tbody> </table></div> sonota486 tag:crieit.net,2005:PublicArticle/17077 2021-05-10T04:15:28+09:00 2021-05-10T04:15:28+09:00 https://crieit.net/posts/simple-compiler-julia Juliaでかんたんな自作言語のコンパイラを書いた <p>とりあえず触って慣れようということで、何も分からないところからいきなり作り初めてその都度調べるという感じで雑に書いてみました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-julia">https://github.com/sonota88/vm2gol-v2-julia</a></p> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">Rubyで素朴な自作言語のコンパイラを作った - memo88</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">https://github.com/sonota88/vm2gol-v2</a></li> </ul> <p>ライフゲームのプログラムだけコンパイルできればOKという簡単なコンパイラです。<br /> ベースになっているバージョン: <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/57">tag:57</a> のあたり</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <p>できあがりサイズ(行数)は Ruby 版と同じくらい。</p> <pre><code>$ wc -l *.jl lib/*.jl 359 codegen.jl 113 lexer.jl 360 parser.jl 107 lib/json.jl 56 lib/utils.jl 995 合計 </code></pre> <hr /> <p>Ruby のような動的言語で書くのと似たフィーリングで書けました。</p> <p>ただし、こういうのはダメで、</p> <pre><code class="julia">xs = ["a"] push!(xs, 1) # xs に整数を追加 #=> ERROR: LoadError: MethodError: Cannot `convert` an object of type Int64 to an object of type String </code></pre> <p>次のように型について教えてあげないといけない、なんて場面はあったり。</p> <pre><code class="julia">xs = Any["a"] push!(xs, 1) </code></pre> <hr /> <p>今回のおためし。<br /> ブランチ: <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-julia/tree/trial">trial</a></p> <p>今回は case 文のネストを減らしてみました。<br /> おためしというか <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/pric">v3</a> ですでに適用済みになっていて、 v2 にも取り込もうかなと考えているもの。</p> <pre><code class="javascript">// before case { (current_val == 0) { case { (count == 3) { set next_val = 1; } } (0 == 0) { case { (count == 2) { set next_val = 1; } (count == 3) { set next_val = 1; } } } } </code></pre> <pre><code class="javascript">// after case when (current_val == 0) { case when (count == 3) { set next_val = 1; } when (0 == 0) { case when (count == 2) { set next_val = 1; } when (count == 3) { set next_val = 1; } } </code></pre> <p><code>{</code> 〜 <code>}</code> が1段なくなるため、ネストが減り、行数も減ってちょっとコンパクトになります。</p> <p>ついでに、when 句の先頭にキーワード <code>when</code> を置くようにしてみました。パーサの都合としてはあってもなくてもほとんど違いはなく、 <code>when</code> を置いた方がなんとなく見た目的に落ち着きがよいかな、という程度の理由です。</p> <h1 id="他の言語への移植"><a href="#%E4%BB%96%E3%81%AE%E8%A8%80%E8%AA%9E%E3%81%B8%E3%81%AE%E7%A7%BB%E6%A4%8D">他の言語への移植</a></h1> <p>上から新しい順。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20210327_vm2gol_v2_crystal">Crystal</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20210407_vm2gol_v2_rust">Rust</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/14/032813">Kotlin</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/07/235019">Zig</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/12/14/042153">LibreOffice Basic</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/25/073200">Go</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/18/193844">PHP</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/13/133735">C♭</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/08/053329">Perl</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/06/043607">C</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/121437">Java</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/22/162201">Dart</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/19/065056">Python</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/15/114754">TypeScript (Deno)</a></li> </ul> sonota486 tag:crieit.net,2005:PublicArticle/16829 2021-04-10T06:46:21+09:00 2021-04-10T06:47:07+09:00 https://crieit.net/posts/Rust-6070cb2d112f0 素朴な自作言語のコンパイラをRustに移植した <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">移植一覧に戻る</a></p> <hr /> <p>やっつけなので汚いです。ライフゲームのコンパイルが通ったのでヨシ、というレベルの雑なものです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-rust">https://github.com/sonota88/vm2gol-v2-rust</a></p> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">https://github.com/sonota88/vm2gol-v2</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">Rubyで素朴な自作言語のコンパイラを作った</a></li> </ul> <p>ベースになっているバージョン: <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/56">tag:56</a> のあたり</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li>理解は後回しにして、かっこ悪い書き方でもいいのでとにかく完成まで持って行く……といういつも通りの方針で、時間をかけすぎないように。理解は後でゆっくり。まずは手を動かして慣れる。</li> <li><a target="_blank" rel="nofollow noopener" href="https://tourofrust.com/">Tour of Rust</a> <ul> <li>情報量が多すぎずよく整理されていて、一番最初にこれを読めばよかった</li> </ul></li> <li>借用とかムーブとか <ul> <li>このくらいのプログラムを書いて動かせる程度には分かってきた、はず。<br /> でもけっこう怪しい。</li> </ul></li> <li>ライフタイム <ul> <li>まだよく分かっていなくて、コンパイルが通らなかったらライフタイムが絡まない書き方にして回避したりしている</li> </ul></li> <li>連結リストが難しそうだったので<br /> <code>Vec<NodeId></code> で管理する方式にしてとりあえず回避</li> <li>レキサの入力文字列は最初に <code>Vec<char></code> にして使い回し <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/06/043607">C版</a> や <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/07/235019">Zig版</a> のように単純なバイト列として扱ってもよかったが、<br /> せっかくなので UTF-8 文字列として扱ってみた</li> </ul></li> </ul> <h1 id="TODO"><a href="#TODO">TODO</a></h1> <p>気が向いたらあとで</p> <ul> <li>List にノードID ではなくノードを直接持たせる</li> <li>List のイテレータ対応</li> </ul> sonota486 tag:crieit.net,2005:PublicArticle/16778 2021-03-28T06:15:18+09:00 2021-03-28T06:52:10+09:00 https://crieit.net/posts/Crystal 素朴な自作言語のコンパイラをCrystalに移植した <p>Crystal が 1.0 になりましたね 🎉</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://crystal-lang.org/2021/03/22/crystal-1.0-what-to-expect.html">Crystal 1.0 - What to expect - The Crystal Programming Language</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://codezine.jp/article/detail/13820">プログラミング言語Crystal、初のメジャーリリースとなるバージョン1.0を公開:CodeZine(コードジン)</a></li> </ul> <p>めでたい。</p> <p>というわけで(といいつつ少し前から作っていたのですが)移植しました。例によってライフゲームのコンパイルが通ったのでヨシ、という程度の雑なものです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-crystal">https://github.com/sonota88/vm2gol-v2-crystal</a></p> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">Rubyで素朴な自作言語のコンパイラを作った</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">https://github.com/sonota88/vm2gol-v2</a></li> </ul> <p>ベースになっているバージョン: <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/56">tag:56</a> のあたり</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li><code>is_a?</code> で型を調べて分岐させると型が制限される。ここらへんは自分が知ってる中では TypeScript が近い。 <ul> <li><a target="_blank" rel="nofollow noopener" href="https://ja.crystal-lang.org/reference/syntax_and_semantics/if_varis_a.html">https://ja.crystal-lang.org/reference/syntax_and_semantics/if_varis_a.html</a></li> </ul></li> <li>ユニオン型からのキャスト: <code>as</code> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://ja.crystal-lang.org/reference/syntax_and_semantics/as.html">https://ja.crystal-lang.org/reference/syntax_and_semantics/as.html</a></li> </ul></li> <li><code>of</code> による型の指定 <ul> <li>> これは推論された型を置き換えるので、生成時には同じ型しか入っていないけれど、あとで別の型が入ってくるような場合に対応できます。<br /> <a target="_blank" rel="nofollow noopener" href="https://ja.crystal-lang.org/reference/syntax_and_semantics/literals/array.html">https://ja.crystal-lang.org/reference/syntax_and_semantics/literals/array.html</a></li> </ul></li> <li>String からシンボルへの変換 <ul> <li>できなさそうだった</li> <li><a target="_blank" rel="nofollow noopener" href="https://ja.crystal-lang.org/reference/syntax_and_semantics/literals/symbol.html">https://ja.crystal-lang.org/reference/syntax_and_semantics/literals/symbol.html</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://crystal-lang.org/api/1.0.0/String.html">https://crystal-lang.org/api/1.0.0/String.html</a></li> </ul></li> <li>グローバル変数は使えない <ul> <li>> しかし Crystal にグローバル変数はありません。代わりにクラス変数や定数を使ってください。<br /> <a target="_blank" rel="nofollow noopener" href="https://crystal-jp.github.io/introducing-crystal/chapters/03-syntax.html">https://crystal-jp.github.io/introducing-crystal/chapters/03-syntax.html</a></li> </ul></li> <li>レキサの出力も <code>["kw", "func", 1]</code> のような JSON にしてみた。 少し冗長になるけど、扱いが楽になるので良さそう。 これは Ruby 版にフィードバックしようかな。</li> </ul> sonota486 tag:crieit.net,2005:PublicArticle/16762 2021-03-22T05:03:43+09:00 2021-03-22T08:06:00+09:00 https://crieit.net/posts/Dart 素朴な自作言語のコンパイラをDartに移植した <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">移植一覧に戻る</a></p> <hr /> <p>Dart で書いてみました。やっつけなので汚いです。Dart よく知らないけどライフゲームが動いたのでヨシ、というレベルのものです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-dart">https://github.com/sonota88/vm2gol-v2-dart</a></p> <hr /> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">Rubyで素朴な自作言語のコンパイラを作った</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">https://github.com/sonota88/vm2gol-v2</a></li> </ul> <p>ベースになっているバージョン: <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/46">tag:46</a> のあたり</p> <ul> <li>追記 2021-03-21: Dart のバージョンアップ(2.9.1 => 2.12.0)に合わせて修正 <ul> <li>null safety まわりを適当に修正</li> </ul></li> <li>追記 2021-03-21: <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20210317_vm2gol_v2_step56_builtin_vram_function">ステップ56</a> の修正まで適用しました</li> </ul> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li>アセンブラ・VM は移植対象から外しました。Ruby 版のものを使います。</li> <li>トークナイザとパーサを分けてみた</li> <li>テストのステップを細かくした</li> <li>ファイルを直接まじめに読まなくても、 標準入力が読めればあとはシェルスクリプトでラップしてあげたりすればいいので、 もうそれでいいかなという気持ちになった。</li> </ul> <hr /> <p>Dart でプログラムを書くのは今回初めてでしたが、JavaScript と Java あたりを知っていればかなりスムーズに書き始められるなーという感想でした。引っかかるところがほとんどなかったです(今回書いた範囲では)。適当にググりながら 1日で書けてしまいました。</p> <hr /> <p>今回の実験的な要素としては、パーサをいじって <code>set</code> を不要にしてみました。</p> <p>(追記 2021-03-21: この修正はいったん revert しましたが、一応 <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-dart/tree/trial">trial</a> ブランチに残してあります。)</p> <pre><code class="diff"> func main() { var a; - set a = 42; + a = 42; } </code></pre> <p>文の先頭の <code>set</code> で判別するのをやめて、識別子だったら <code>parseSet_v2()</code> を呼ぶ。もうちょっとめんどくさいかなと思ってましたが、これだけだったら意外と簡単ですね。</p> <pre><code class="dart">List parseSet_v2() { // consume("set"); ... これが不要になる final t = peek(); pos++; final varName = t.value; consume("="); final expr = parseExpr(); consume(";"); return ["set", varName, expr]; } List parseStmt() { final t = peek(); if (t.value == "}") { return null; } if (t.value == "func" ) { return parseFunc(); } // ... // else if (t.value == "set" ) { return parseSet(); } // ... else if (t.value == "_cmt" ) { return parseVmComment(); } else { if (t.type == "ident") { return parseSet_v2(); } else { throw notYetImpl([ t ]); } } } </code></pre> sonota486 tag:crieit.net,2005:PublicArticle/16727 2021-03-10T05:00:58+09:00 2021-03-10T07:30:31+09:00 https://crieit.net/posts/TypeScript-Deno 素朴な自作言語のコンパイラをTypeScript(Deno)に移植した <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">移植一覧に戻る</a></p> <hr /> <p>TypeScript 入門というか、とりあえず何か書いて慣れようと思って書いてみました。やっつけなので汚いです。TypeScript まだよく分からないけどなんか動いたのでヨシ、というレベルのものです。<br /> (※ <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/15/114754">2020-08-15 に書いた記事</a>のクロス投稿です)</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-typescript">https://github.com/sonota88/vm2gol-v2-typescript</a></p> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">https://github.com/sonota88/vm2gol-v2</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">Rubyで素朴な自作言語のコンパイラを作った</a></li> </ul> <p>ベースになっているバージョン: <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/45">tag:45</a> のあたり</p> <p>(201-03-06 追記: <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20210206_vm2gol_v2_step55_delete_set_reg_a_b">step 55 の修正</a>まで適用しました。それに伴い、アセンブラ・VM 関連のコードを <code>old_version</code> ディレクトリに移動しました。全部修正すると大変なので)</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li>YAML/JSON ライブラリへの依存をなくしてみた <ul> <li>vge コードは YAML ではなく簡単な独自フォーマットにして自力でパースするようにした</li> <li>単に行ごとに読んで文字列か数かを判別できればよい</li> <li>vgt コード(JSON)も自力でパースしてみた</li> </ul></li> <li>Ruby 版で作った出力データがすでにあるので、<br /> それに一致するように未実装部分を潰していくだけ。<br /> ゼロから作っていた Ruby 版のときに比べたら遥かに楽。</li> <li>今回もなんとなく VM → アセンブラ → コード生成器 → パーサ<br /> の順で作ってしまって、インクリメンタルな作り方を試せばかったな……と作り終わってから思った</li> </ul> sonota486 tag:crieit.net,2005:PublicArticle/16602 2021-01-15T05:11:53+09:00 2021-01-15T08:49:14+09:00 https://crieit.net/posts/Kotlin-6000a589e43c6 素朴な自作言語のコンパイラをKotlinに移植した <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">移植一覧に戻る</a></p> <hr /> <p><a href="https://crieit.now.sh/upload_images/8c8511d3a32db4f8b9038c1702b366546000a3e7ba0b2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8c8511d3a32db4f8b9038c1702b366546000a3e7ba0b2.png?mw=700" alt="image" /></a></p> <p>Kotlin に移植してみました。やっつけなので汚いです。ライフゲームのコンパイルが通ったのでヨシ、というレベルのものです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-kotlin">https://github.com/sonota88/vm2gol-v2-kotlin</a></p> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">Rubyで素朴な自作言語のコンパイラを作った</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/05/04/232236">RubyでオレオレVMとアセンブラとコード生成器を2週間で作ってライフゲームを動かした話</a></li> </ul> <p>ベースになっているバージョン: <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/51">tag:51</a> のあたり</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/121437">Java版</a> からコピーしてきて修正してできあがり。3日くらい。</li> <li>Kotlin は仕事で少しだけ使って、あと少し前に 「<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/07/11/135045">標準入力を読んで行ごとに処理</a>」と「<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/07/11/120000">四則演算と剰余のみのexprコマンドをKotlinで作ってみた</a>」を書いたくらいのほぼ初心者</li> <li>Java と Scala の中間みたいな印象</li> </ul> <hr /> <p>今回の実験は配列変数の宣言。<a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-kotlin/compare/main...trial">trial ブランチ</a>で試してみました。</p> <p>確認用のコードです。今回の実験ではとりあえずこれだけコンパイルできれば OK。</p> <pre><code class="javascript">// declare_array.vg.txt func main() { var xs[22]; } </code></pre> <p>AST がこんな感じ。お試しなので適当。</p> <pre><code class="sh">$ ( \ > cat declare_array.vg.txt \ > | ./run.sh tokenize \ > | ./run.sh parse \ > ) 2>/dev/null [ "top_stmts", [ "func", "main", [ ], [ [ "var_array", "xs", 22 ] ] ] ] </code></pre> <p>アセンブリコードがこんな感じ。</p> <pre><code class="sh">$ ( \ > cat declare_array.vg.txt \ > | ./run.sh tokenize \ > | ./run.sh parse \ > | ./run.sh codegen \ > ) 2>/dev/null call main exit label main push bp cp sp bp # 関数の処理本体 sub_sp 22 # ... 配列のサイズ分の領域を確保する cp bp sp pop bp ret </code></pre> <p>これだけだったら少し修正するだけで済みました。</p> <p>次のようなコードはまだ正しく動くようにコンパイルできません。</p> <pre><code class="javascript">func main() { var xs[22]; var a = 11; } </code></pre> <p>配列がスタック上で占有している幅を考慮しないといけないのに、それをまだやってないからです。これをやろうとするとたぶんあちこち修正しないといけなくなるので、それはまた今度ということで。</p> sonota486 tag:crieit.net,2005:PublicArticle/16562 2021-01-07T23:57:33+09:00 2022-07-17T10:04:04+09:00 https://crieit.net/posts/Zig 素朴な自作言語のコンパイラをZigに移植した <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">移植一覧に戻る</a></p> <hr /> <p><strong>※ 一部内容が古いです。二重管理が煩雑なため<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/07/235019">オリジナルのブログ記事</a>の方だけ加筆修正しています。</strong></p> <p>やっつけなので汚いです。ライフゲームのコンパイルが通ったのでヨシ、というレベルのものです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://ziglang.org/">Zig(ziglang)</a>を触り始めて1週間くらいの人が雑に書いたものですので、「お手本にできそうな Zig のコード」「かっこいい Zig のコード」を求めている人には参考にならないと思います。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-zig">https://github.com/sonota88/vm2gol-v2-zig</a></p> <hr /> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">Rubyで素朴な自作言語のコンパイラを作った</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/05/04/232236">RubyでオレオレVMとアセンブラとコード生成器を2週間で作ってライフゲームを動かした話</a></li> </ul> <p>ベースになっているバージョン: <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/50">tag:50</a> のあたり</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li>C と Go の中間、GC のない Go みたいな印象</li> <li>sentinel-terminated な配列もなるほど便利そうと思って使ってみたが、通常の配列とは別の型になって煩雑なのと、サイズ固定な配列の出番が実はほとんどなかったので、後から普通の配列+スライスで書き直していった</li> <li>例外の扱いも Go とは違ったアプローチでおもしろい。最初は試しに使っていたが、お遊びプログラムなのでまじめにやる必要ないかと思い直し、これも後から使わないように(Java でいえば検査例外をその場で RuntimeException に包んで投げ直す感じに)書き変えた。単に煩雑さを避けるのを優先した形なのでほんとはちゃんとハンドリングすべきと思います。</li> <li>型まわりの理解がまだいまいち(配列や const が絡むあたりとか)</li> <li>あと細かい話がいろいろあった気がするけど、作った後このエントリを書くまで他のことをしていたら忘れてしまった……</li> <li>今回はあんまり余裕なかったので実験的な要素はなし</li> </ul> sonota486