tag:crieit.net,2005:https://crieit.net/tags/%E8%87%AA%E4%BD%9C%E8%A8%80%E8%AA%9E/feed 「自作言語」の記事 - Crieit Crieitでタグ「自作言語」に投稿された最近の記事 2023-06-25T08:41:34+09:00 https://crieit.net/tags/%E8%87%AA%E4%BD%9C%E8%A8%80%E8%AA%9E/feed tag:crieit.net,2005:PublicArticle/18485 2023-06-25T08:41:34+09:00 2023-06-25T08:41:34+09:00 https://crieit.net/posts/ruby-lrama-parser Lramaで簡単な自作言語のパーサを書いた <p><a href="https://crieit.now.sh/upload_images/36b6f940e2f434c979b5ada813c8206f64977e25696d7.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/36b6f940e2f434c979b5ada813c8206f64977e25696d7.png?mw=700" alt="image" /></a></p> <p>先日 Ruby にマージされた LALR(1)パーサジェネレータ Lrama を使ってみました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/ruby/lrama">https://github.com/ruby/lrama</a></p> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://techracho.bpsinc.jp/hachi8833/2023_05_15/130116">RubyにlramaがマージされてBison依存がなくなった(RubyKaigi 2023)|TechRacho by BPS株式会社</a></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-c/tree/alt-parser-lrama">https://github.com/sonota88/vm2gol-v2-c/tree/alt-parser-lrama</a></p> <p>alt-parser-lrama ブランチに <code>mrcl_parser_lrama.y</code> が入っています。</p> <h1 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h1> <p>Mini Ruccola は私がコンパイラ実装に入門するために作った自作言語とその処理系です。原始的だけどその分入門者(=私)視点では分かりやすい、という方向性のものです。私でも作れるコンパイラ。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">https://github.com/sonota88/vm2gol-v2</a></p> <p>作ったときに書いた備忘記事:</p> <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">(2週間ちょっとで)Rubyでかんたんな自作言語のコンパイラを作った</a></li> </ul> <hr /> <p>コンパイラのパーサ部分は元々手書きの再帰下降パーサだったのですが、他のパーサライブラリも試したくて Racc 版と Parslet 版のパーサを以前書きました。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/97ba1f0c377fbe86d5b1">Raccでかんたんな自作言語のパーサを書いた</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/edce05e86d3248f49401">Parsletでかんたんな自作言語のパーサを書いた</a></li> </ul> <p>今回の Lrama 版はこのシリーズの延長です。</p> <hr /> <p>Mini Ruccola コンパイラはレキサ・パーサ・コード生成器が独立しています。そのため、レキサとコード生成器は Ruby 製のものをそのまま使い、パーサだけ別の言語で書いて一緒に動かすなんてことも可能です。</p> <p>可能なのですが、C言語への移植版<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-c">github.com/sonota88/vm2gol-v2-c</a><br /> を使った方が楽なので今回はこっちを使いました。</p> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/06/043607">素朴な自作言語のコンパイラをCに移植した</a></p> <hr /> <p>Racc 版パーサ実装と C移植版がすでにありますから、あとは Racc 版パーサを Lrama + C 向けに書き直せば一丁あがり、という寸法です。</p> <p>たとえば以下は関数定義の規則とアクションの記述の比較です。</p> <pre><code class="ruby"># Racc func_def : "func" IDENT "(" args ")" "{" stmts "}" { _, fn_name, _, args, _, _, stmts, _, = val result = ["func", fn_name, args, stmts] } </code></pre> <pre><code class="c">// Lrama func_def : TS_KW_FUNC TS_IDENT TS_PAREN_L args TS_PAREN_R TS_SYM stmts TS_SYM // "func" fn_name "(" args ")" "{" stmts "}" { NodeList* func_def = NodeList_new(); NodeList_add_str(func_def, "func"); NodeList_add_str(func_def, $2); NodeList_add_list(func_def, $4); NodeList_add_list(func_def, $7); $$ = func_def; } </code></pre> <p>こんな感じで読み換えていきました。どちらも Yacc から派生した LALRパーサジェネレータなのでよく似ていますね。</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li>1日くらいでババッと書いたものなので雑だったり手抜きで済ませている部分があります。一応動いてる、という程度の出来です。 <ul> <li>警告もひとまず放置</li> </ul></li> <li>Yacc, Bison もそのうち触ってみようと思いつつ結局今まで触らずじまいだった <ul> <li>今回ちょっとやってみて雰囲気が知れてよかった</li> <li>Rubyソースコード完全解説の <a target="_blank" rel="nofollow noopener" href="https://i.loveruby.net/ja/rhg/book/yacc.html">第9章 速習yacc</a> を読んだらとりあえずなんとかなった</li> </ul></li> <li>Rubyには慣れているけどCは分からないという状態で LALR パーサジェネレータに入門したい人は、まず Racc で入門するのが良いのではないかと思います。Ruby の知識だけで使えるので、いきなり Yacc や Bison や Lrama に挑戦するよりはお手軽かと。 <ul> <li>Racc の作者(青木峰郎さん)による解説本『Rubyを256倍使うための本 無道編』もまだ中古で入手可能(<a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/dp/4756137091">Amazon</a>)</li> </ul></li> </ul> <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%B2%E3%82%87%E3%81%A3%E3%81%A8%E3%81%97%E3%81%9F%E3%82%89%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> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/6a96d2bcea9d134e38b7">Ruby/Racc: パース時のスタックの動きをFlameGraphっぽくビジュアライズする</a></li> </ul> <p>Racc の気持ちが分かるように↓こういう図を描かせてみたもの。</p> <p><a href="https://crieit.now.sh/upload_images/7a727bbde73b2e30d4a57505e10347d264977e43df695.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7a727bbde73b2e30d4a57505e10347d264977e43df695.png?mw=700" alt="image" /></a></p> <hr /> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/816b6b8d7a362ef4e332">Ruby: 入れ子の配列だけをパースできるパーサを作る(手書きの再帰下降パーサ)</a></li> </ul> <p>メソッドの再帰呼び出しについてすでに知っていれば1時間もかからず書ける内容。比較的簡単で時間がかからないので、LALR パーサにこだわる事情がなければ先に再帰下降パーサで入門するのも良いと思います。</p> <hr /> <ul> <li><p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/84e4c16e5602b9771e92">『RubyでつくるRuby』のMinRubyのパーサを書いた(手書きの再帰下降パーサ)</a></p></li> <li><p><a target="_blank" rel="nofollow noopener" href="https://zenn.dev/sonota88/articles/06c540eda79b98">四則演算と剰余のみのexprコマンドをRubyで作ってみた</a></p></li> <li><p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/5902f255e196b5b8b048">正規表現エンジン(ロブ・パイクのバックトラック実装)をRubyで写経した</a></p></li> </ul> sonota486 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/17743 2021-11-06T07:02:42+09:00 2021-11-06T07:02:42+09:00 https://crieit.net/posts/simple-compiler-parser-ruby-parslet Parsletでかんたんな自作言語のパーサを書いた <p><a href="https://crieit.now.sh/upload_images/8049aaf2f8f58278f22a5618f2f8293f6185a962805bf.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8049aaf2f8f58278f22a5618f2f8293f6185a962805bf.png?mw=700" alt="image" /></a></p> <p><自作言語処理系の説明用テンプレ></p> <p>自分がコンパイラ実装に入門するために作った素朴なトイ言語とその処理系です。簡単に概要を書くと下記のような感じ。</p> <ul> <li>リポジトリ: <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">github.com/sonota88/vm2gol-v2</a></li> <li>小規模: コンパイラ部分は 1,000 行程度</li> <li>pure Ruby / 標準ライブラリ以外への依存なし</li> <li>独自VM向けにコンパイルする</li> <li>ライフゲームのために必要な機能だけ <ul> <li>変数宣言、代入、反復、条件分岐、関数呼び出し</li> <li>演算子: <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/2020/08/30/132314">Ruby 以外の言語への移植</a>(コンパイラ部分のみ)</li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/1e683276541cf1b87b76">セルフホスト版</a>(別リポジトリ)</li> </ul> <p><説明用テンプレおわり></p> <hr /> <p>もともとパーサ部分は手書きの再帰下降パーサでしたが、PEGベースのパーサライブラリ <a target="_blank" rel="nofollow noopener" href="https://github.com/kschiess/parslet">Parslet</a> 版を作ってみました。</p> <h1 id="できたもの"><a href="#%E3%81%A7%E3%81%8D%E3%81%9F%E3%82%82%E3%81%AE">できたもの</a></h1> <p><code>vgparser_parslet.rb</code> を追加したブランチです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/alt_parser_parslet">https://github.com/sonota88/vm2gol-v2/tree/alt_parser_parslet</a></p> <p>ここでは雰囲気程度ということで Parser クラスのみ貼ります。全体は GitHub の方で見てください。transform なども合わせると全体は 340 行くらいです。</p> <p>Parslet を使うのは今回初めてで、まだ慣れてなくて、こなれていない感じがします。もっといい書き方ができそう。</p> <pre><code class="ruby">class Parser < Parslet::Parser rule(:comment) { str("//") >> (str("\n").absent? >> any).repeat >> str("\n") } rule(:spaces) { ( match('[ \n]') | comment ).repeat(1) } rule(:spaces?) { spaces.maybe } rule(:lparen ) { str("(") >> spaces? } rule(:rparen ) { str(")") >> spaces? } rule(:lbrace ) { str("{") >> spaces? } rule(:rbrace ) { str("}") >> spaces? } rule(:comma ) { str(",") >> spaces? } rule(:semicolon) { str(";") >> spaces? } rule(:equal ) { str("=") >> spaces? } rule(:ident) { ( match('[_a-z]') >> match('[_a-z0-9]').repeat ).as(:ident_) >> spaces? } rule(:int) { ( str("-").maybe >> ( (match('[1-9]') >> match('[0-9]').repeat) | str("0") ) ).as(:int_) >> spaces? } rule(:string) { str('"') >> ((str('"').absent? >> any).repeat).as(:string_) >> str('"') >> spaces? } rule(:arg) { ident | int } rule(:args) { ( ( arg.as(:arg_) >> (comma >> arg.as(:arg_)).repeat ).maybe ).as(:args_) } rule(:factor) { ( lparen >> expr.as(:factor_expr_) >> rparen ).as(:factor_) | int | ident } rule(:binop) { ( str("+") | str("*") | str("==") | str("!=") ).as(:binop_) >> spaces? } rule(:expr) { ( factor.as(:lhs_) >> (binop.as(:binop_) >> factor.as(:rhs_)).repeat(1) ).as(:expr_) | factor } rule(:stmt_return) { ( str("return") >> (spaces >> expr.as(:return_expr_)).maybe >> semicolon ).as(:stmt_return_) } rule(:stmt_var) { ( str("var") >> spaces >> ident.as(:var_name_) >> (equal >> expr.as(:expr_)).maybe >> semicolon ).as(:stmt_var_) } rule(:stmt_set) { ( str("set") >> spaces >> ident.as(:var_name_) >> equal >> expr.as(:expr_) >> semicolon ).as(:stmt_set_) } rule(:funcall) { ( ident.as(:fn_name_) >> lparen >> args.as(:args_) >> rparen ).as(:funcall_) } rule(:stmt_call) { ( str("call") >> spaces >> funcall >> semicolon ).as(:stmt_call_) } rule(:stmt_call_set) { ( str("call_set") >> spaces >> ident.as(:var_name_) >> equal >> funcall.as(:funcall_) >> semicolon ).as(:stmt_call_set_) } rule(:stmt_while) { ( str("while") >> spaces? >> lparen >> expr.as(:expr_) >> rparen >> lbrace >> stmts.as(:stmts_) >> rbrace ).as(:stmt_while_) } rule(:when_clause) { ( str("when") >> spaces? >> lparen >> expr.as(:expr_) >> rparen >> lbrace >> stmts.as(:stmts_) >> rbrace ).as(:when_clause_) } rule(:stmt_case) { ( str("case") >> spaces >> when_clause.repeat.as(:when_clauses_) ).as(:stmt_case_) } rule(:stmt_vm_comment) { ( str("_cmt") >> lparen >> string.as(:cmt_) >> rparen >> semicolon ).as(:stmt_vm_comment_) } rule(:stmt_debug) { ( str("_debug") >> lparen >> rparen >> semicolon ).as(:stmt_debug_) } rule(:stmt) { stmt_return | stmt_var | stmt_set | stmt_call | stmt_call_set | stmt_while | stmt_case | stmt_vm_comment | stmt_debug } rule(:stmts) { (stmt.repeat).as(:stmts_) } rule(:func_def) { ( str("func") >> spaces >> ident.as(:fn_name_) >> lparen >> args.as(:fn_args_) >> rparen >> lbrace >> stmts.as(:fn_stmts_) >> rbrace ).as(:func_def_) } rule(:top_stmt) { func_def.as(:top_stmt_) } rule(:program) { spaces? >> (top_stmt.repeat).as(:top_stmts_) } root(:program) end </code></pre> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li>PEGベースのパーサを使ったことがなかったので、今回触ってみて雰囲気が知れてよかった</li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/pocari/items/048b878575bb28a40732">[ruby] Parsletで構文解析する[その1] - Qiita</a> 〜 その3 までを読むと基本的な使い方はほぼ分かる。ありがとうございます :pray:</li> <li>他には <a target="_blank" rel="nofollow noopener" href="https://github.com/kschiess/parslet">github.com/kschiess/parslet</a> の <code>example/</code> に入っているサンプルを見て参考にしたり <ul> <li><code>json.rb</code> や <code>string_parser.rb</code> など</li> </ul></li> <li>もう少し大きめのサンプルとして <a target="_blank" rel="nofollow noopener" href="https://github.com/undees/thnad">thnad</a> を参考にしたり</li> </ul> <hr /> <p>「○○以外の文字の連続」</p> <p>今回書いたものでいえば、コメント(改行以外の文字の連続)や文字列(<code>"</code> 以外の文字の連続)の部分。</p> <p><code>match</code> で正規表現が使えるので最初はこのように書いていました:</p> <pre><code class="ruby">match('[^\n]').repeat </code></pre> <p>これでも動きます。ただ、公式のサンプルを見ると <code>absent?</code> と <code>any</code> を組み合わせていたので、それに倣って次のように書きました。こういうのは知らないと何をやってるのかわかりにくいかも。</p> <pre><code class="ruby">(str("\n").absent? >> any).repeat </code></pre> <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%E3%81%93%E3%81%A1%E3%82%89%E3%81%AE%E8%A8%98%E4%BA%8B%E3%82%82%E8%AA%AD%E3%82%93%E3%81%A7%E3%81%84%E3%81%BE%E3%81%99%EF%BC%88%E3%81%9F%E3%81%B6%E3%82%93%EF%BC%89">この記事を読んだ人はこちらの記事も読んでいます(たぶん)</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/97ba1f0c377fbe86d5b1">Raccでかんたんな自作言語のパーサを書いた</a></li> </ul> sonota486 tag:crieit.net,2005:PublicArticle/17735 2021-11-03T08:04:56+09:00 2021-11-03T08:04:56+09:00 https://crieit.net/posts/simple-compiler-parser-ruby-racc Raccでかんたんな自作言語のパーサを書いた <p><a href="https://crieit.now.sh/upload_images/856c469c026e97152123f812f323d67e6181c1b9a3869.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/856c469c026e97152123f812f323d67e6181c1b9a3869.png?mw=700" alt="image" /></a></p> <p><自作言語処理系の説明用テンプレ></p> <p>自分がコンパイラ実装に入門するために作った素朴なトイ言語とその処理系です。簡単に概要を書くと下記のような感じ。</p> <ul> <li>小規模: コンパイラ部分は 1,000 行程度</li> <li>pure Ruby / 標準ライブラリ以外への依存なし</li> <li>独自VM向けにコンパイルする</li> <li>ライフゲームのために必要な機能だけ <ul> <li>変数宣言、代入、反復、条件分岐、関数呼び出し</li> <li>演算子: <code>+</code>, <code>*</code>, <code>==</code>, <code>!=</code> のみ(優先順位なし)</li> <li>型なし(値は整数のみ)</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">Ruby 以外の言語への移植</a>(コンパイラ部分のみ)</li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/1e683276541cf1b87b76">セルフホスト版</a>(別リポジトリ)</li> </ul> <p>下記も参照してください。</p> <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> <p><説明用テンプレおわり></p> <hr /> <p>もともとパーサ部分は手書きの再帰下降パーサでしたが、Racc 版を作ってみました。</p> <p>Racc で四則演算のパーサを作る方法は分かったがもう少しプログラム言語らしきものを扱っているサンプルを見たいとか、パースしたそばから実行する方式(インタプリタ方式)ではなく構文木が欲しいんだけど、という感じの人には参考になるかもしれません(昔の自分に送ってあげたい)。</p> <h1 id="できたもの"><a href="#%E3%81%A7%E3%81%8D%E3%81%9F%E3%82%82%E3%81%AE">できたもの</a></h1> <p><code>vgparser_racc.y</code> を追加したブランチです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/alt_parser_racc">github.com/sonota88/vm2gol-v2/tree/alt_parser_racc</a></p> <p>300行弱なので全部貼ってもいいのですが、雰囲気程度ということで途中を省略したものを貼ります。全体は GitHub の方で見てください。</p> <pre><code class="ruby">class Parser prechigh left "+" "*" preclow rule program: top_stmts { top_stmts = val[0] result = ["top_stmts", *top_stmts] } top_stmts: top_stmt { top_stmt = val[0] result = [top_stmt] } | top_stmts top_stmt { top_stmts, top_stmt = val result = [*top_stmts, top_stmt] } top_stmt: func_def func_def: "func" IDENT "(" args ")" "{" stmts "}" { _, fn_name, _, args, _, _, stmts, _, = val result = ["func", fn_name, args, stmts] } args: # nothing { result = [] } | arg { arg = val[0] result = [arg] } | args "," arg { args, _, arg = val result = [*args, arg] } arg: IDENT | INT stmts: # nothing { result = [] } | stmt { stmt = val[0] result = [stmt] } | stmts stmt { stmts, stmt = val result = [*stmts, stmt] } stmt: stmt_var | stmt_set | stmt_return | stmt_call | stmt_call_set | stmt_while | stmt_case | stmt_vm_comment | stmt_debug stmt_var: "var" IDENT ";" { _, ident, _ = val result = ["var", ident] } | "var" IDENT "=" expr ";" { _, ident, _, expr = val result = ["var", ident, expr] } # ... 途中省略 ... ---- header require "json" require_relative "common" ---- inner def next_token @tokens.shift end def to_token(line) token = Token.from_line(line) return nil if token.nil? if token.kind == :int Token.new(token.kind, token.value.to_i) else token end end def read_tokens(src) tokens = [] src.each_line do |line| token = to_token(line) next if token.nil? tokens << token end tokens end def to_racc_token(token) kind = case token.kind when :ident then :IDENT when :int then :INT when :str then :STR else token.value end [kind, token.value] end def parse(src) tokens = read_tokens(src) @tokens = tokens.map { |token| to_racc_token(token) } @tokens << [false, false] do_parse() end ---- footer if $0 == __FILE__ ast = Parser.new.parse(ARGF.read) puts JSON.pretty_generate(ast) end </code></pre> <h1 id="実行の例"><a href="#%E5%AE%9F%E8%A1%8C%E3%81%AE%E4%BE%8B">実行の例</a></h1> <p>入力とするプログラムの例です。</p> <pre><code class="javascript">// sample.vg.txt func main() { return 1 + (2 * 3); } </code></pre> <p>次のように実行するとASTに変換できます。</p> <pre><code class="sh">## vgparser_racc.rb を生成 $ bundle exec racc -t -o vgparser_racc.rb vgparser_racc.y $ ruby vglexer.rb sample.vg.txt > tmp/tokens.txt $ ruby vgparser_racc.rb tmp/tokens.txt [ "top_stmts", [ "func", "main", [ ], [ [ "return", [ "+", 1, [ "*", 2, 3 ] ] ] ] ] ] </code></pre> <h1 id="スタックの動きを見てみる"><a href="#%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E5%8B%95%E3%81%8D%E3%82%92%E8%A6%8B%E3%81%A6%E3%81%BF%E3%82%8B">スタックの動きを見てみる</a></h1> <p>せっかく Racc を使っているので、適当なサンプルコード(下記)をパースさせてスタックの動きを図にしてみました。</p> <pre><code class="javascript">func add(a, b) { var c = a + b; return c; } func main() { var x = -1; var i = 0; while (i != 10) { case when (i == 1) { call_set x = add(i, x); } when (i == 2) { set x = 1 + 2; } when (1) { set x = 1 + (2 * 3); } set i = i + 1; } } </code></pre> <p>図の描き方については <a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/6a96d2bcea9d134e38b7">Ruby/Racc: パース時のスタックの動きをFlameGraphっぽくビジュアライズする</a> を参照。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234055/66f17460-1b71-e4bc-f82a-7a48d3fa269c.png" alt="image.png" /></p> <p>左端の、全体の 1/5 くらいの小さめの山が <code>add</code> 関数で、残りが <code>main</code> 関数ですね。</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li>レキサはすでにあるのでそれを流用した <ul> <li><code>to_racc_token</code> メソッドで Token オブジェクトを Racc が期待する形式に変換</li> </ul></li> <li>1時間くらいで書けた。これより大きめの SQL パーサを少し前にすでに書いていてある程度慣れていたのと、パーサのテストがそのまま使えたのが良かった。</li> </ul> <h1 id="関連"><a href="#%E9%96%A2%E9%80%A3">関連</a></h1> <p>他に Racc 関連で書いたもの。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/6a96d2bcea9d134e38b7">Ruby/Racc: パース時のスタックの動きをFlameGraphっぽくビジュアライズする</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/f55c1654fe101fa0e557">Ruby/Racc: パースに失敗した位置(行、桁)を得る</a></li> </ul> <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%E3%81%93%E3%81%A1%E3%82%89%E3%81%AE%E8%A8%98%E4%BA%8B%E3%82%82%E8%AA%AD%E3%82%93%E3%81%A7%E3%81%84%E3%81%BE%E3%81%99%EF%BC%88%E3%81%9F%E3%81%B6%E3%82%93%EF%BC%89">この記事を読んだ人はこちらの記事も読んでいます(たぶん)</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/edce05e86d3248f49401">Parsletでかんたんな自作言語のパーサを書いた</a></li> </ul> 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/17366 2021-06-07T18:46:10+09:00 2022-08-28T10:29:23+09:00 https://crieit.net/posts/ruby-virtual-machine-assembler-codegenerator (初心者が)RubyでオレオレVMとアセンブラとコード生成器を2週間で作ってライフゲームを動かした話 <p>(<a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/f9cb3fc4a496b354b729">Qiitaに書いた記事</a>のクロス投稿です。元の記事公開日は 2019-05-04 です。)</p> <p><a href="https://crieit.now.sh/upload_images/a720c941b54bca7a71d2622d8a0ff10260bde99044169.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a720c941b54bca7a71d2622d8a0ff10260bde99044169.png?mw=700" alt="image" /></a></p> <p><a href="https://crieit.now.sh/upload_images/ba4829289cc2f4f60aed09f716820b3e630ac4e6361cc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/ba4829289cc2f4f60aed09f716820b3e630ac4e6361cc.gif?mw=700" alt="image" /></a></p> <p>きれいにまとまってないですが、箇条書き+α程度で雑にメモ。</p> <hr /> <p>TechRacho さんの週刊Railsウォッチ(20210209後編)で紹介されました(ありがとうございます 🙏)。こちらもあわせて読んでいただけるとよいかと。</p> <p><a target="_blank" rel="nofollow noopener" href="https://techracho.bpsinc.jp/hachi8833/2021_02_09/103838">週刊Railsウォッチ(20210209後編)Rubyでミニ言語処理系を作る、Kernel#getsの意外な機能、CSSのcontent-visibilityほか|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社</a></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-v1">https://github.com/sonota88/vm2gol-v1</a></p> <ul> <li>2018年5月のゴールデンウィーク残り2日というところ(5/5)で思い立って初めて、 5/18 完成。 <ul> <li>もっと早く公開したかったんですが仕事が忙しくて燃え尽きたりしていて結局1年かかってしまいました。</li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2018/lang_dev">去年の言語実装 Advent Calendar</a> に超初心者枠で参加しようかと考えてたんですが、間に合わず……。</li> </ul></li> <li>それを原型を損ねない程度に名前を変えたり最終的に不要になった部分を消したりしたものです。</li> <li>実装言語は Ruby。いちばん慣れていてサッと始められるので。</li> </ul> <hr /> <p>完成直後(ライフゲームが最初に動いた時点)の状態も見られるようにタグを付けています。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v1/tree/20180518">https://github.com/sonota88/vm2gol-v1/tree/20180518</a></p> <p>汚いしひどいコードなので正直恥ずかしいんですが、この状態も生々しくてコンテンツとしては面白いかもしれません。やけくそか。ヒマな人向け。</p> <p>※ <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">その後作り直した v2</a> もあります。その後の改良も加わったものが見たい、パーサまで揃ったものが見たいという方はこちらを見てください。<br /> ※ (2021-02-21 追記) v2 にいくつか機能を足して<a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/pric/tree/main/selfhost">セルフホストできるようになりました</a>。</p> <h2 id="製作過程のメモ"><a href="#%E8%A3%BD%E4%BD%9C%E9%81%8E%E7%A8%8B%E3%81%AE%E3%83%A1%E3%83%A2">製作過程のメモ</a></h2> <p>どういうステップを踏んで何を実装していったか、具体的な話はこちらに書きました。あまり省略せずにその都度 diff を貼っていくスタイルです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/05/04/234516">https://memo88.hatenablog.com/entry/2019/05/04/234516</a></p> <h2 id="動かし方"><a href="#%E5%8B%95%E3%81%8B%E3%81%97%E6%96%B9">動かし方</a></h2> <p>サードパーティなライブラリへの依存はなく、Ruby(と標準ライブラリ)だけあれば動きます。</p> <pre><code class="sh"># コード生成 ruby vgcg.rb gol.vgt.json > gol.vga.yaml # アセンブル ruby vgasm.rb gol.vga.yaml > gol.vge.yaml # VMで実行 ruby vgvm.rb gol.vge.yaml </code></pre> <p>これらの処理をまとめて行うために <code>run.sh</code> というシェルスクリプトを使って動かしていました。</p> <pre><code class="sh">./run.sh gol.vgt.json </code></pre> <h1 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h1> <h2 id="VM"><a href="#VM">VM</a></h2> <ul> <li>600行弱</li> <li>CPU <ul> <li>レジスタは <code>a</code>, <code>b</code>, <code>c</code>, <code>d</code>, <code>pc</code>, <code>sp</code>, <code>bp</code>, <code>zf</code>, <code>of</code> <ul> <li><code>c</code>, <code>d</code>, <code>of</code> は途中で不要になった</li> </ul></li> <li>命令セット … x86 っぽいオレオレ</li> <li>数値演算は Ruby に丸投げ</li> </ul></li> <li>メモリ <ul> <li>ただの Ruby の配列。整数 or 文字列(!)のどちらかが入る。</li> <li>3つの領域に分けた <ul> <li>メイン領域(機械語コードを配置する場所)</li> <li>スタック領域</li> <li>VRAM領域</li> </ul></li> <li>配列の添字がそのままアドレス</li> </ul></li> </ul> <h3 id="機械語コード"><a href="#%E6%A9%9F%E6%A2%B0%E8%AA%9E%E3%82%B3%E3%83%BC%E3%83%89">機械語コード</a></h3> <p>こういう YAML ファイル。バイナリではなくテキストですが、機械語のつもり。</p> <pre><code class="yaml">--- - call - 1029 - exit - label - vram_set - push - bp - cp - sp - bp - sub_sp - 1 - set_reg_a - "[bp+4]" - set_reg_b - "[bp+2]" - mult_ab - cp - reg_a ... </code></pre> <ul> <li>これを <code>YAML.load_file</code> したものをそのままメインメモリ領域にズボッと入れる。 富豪的。</li> <li>こういうものなので、「バイトコード」とは言えないかなと思って「機械語コード」と呼んでます <ul> <li>実在するCPU向けではなくオレオレVM向けなので「仮想機械語〜」のように呼ぶのがより適切?</li> </ul></li> </ul> <h2 id="アセンブラ・アセンブリコード"><a href="#%E3%82%A2%E3%82%BB%E3%83%B3%E3%83%96%E3%83%A9%E3%83%BB%E3%82%A2%E3%82%BB%E3%83%B3%E3%83%96%E3%83%AA%E3%82%B3%E3%83%BC%E3%83%89">アセンブラ・アセンブリコード</a></h2> <p>アセンブリコードも YAML。</p> <pre><code class="yaml">- call main - exit - label to_vi - push bp - cp sp bp - sub sp 1 - sub sp 1 - sub sp 1 - set_reg_d [bp+4] - set_reg_a [bp+2] - cp reg_d reg_b - mult_ab - cp reg_a [bp-3] - set_reg_d [bp-3] - set_reg_a [bp+3] - cp reg_d reg_b - add_ab_v2 - cp reg_a [bp-1] ... </code></pre> <ul> <li>ジャンプ先などのアドレス指定が実アドレスでなくラベル名になっている他は、 内容的には上の機械語コードとほぼ同じ。</li> <li>アセンブラは 60行弱</li> <li>実アドレスへの変換以外にアセンブラでやっているのは入れ子の構造を flatten して1次元にしてるくらい。 <ul> <li>この 1次元化の処理も削っても良かったかも(そうすれば CPU 部分もさらに簡略化できる(そこまでやると簡略化しすぎという気も))</li> </ul></li> </ul> <hr /> <p>パースをサボるために YAML にしたものの、インデントなどで構造を把握しやすくした方が良かったかなと思います。こんな感じで:</p> <pre><code> call main exit label vram_set push bp cp sp bp sub_sp 1 ... </code></pre> <p>この程度なら行ごとに <code>line.strip.split(" ")</code> で済みそう(v2 ではそうしています)。</p> <h2 id="コード生成器・高水準言語部分"><a href="#%E3%82%B3%E3%83%BC%E3%83%89%E7%94%9F%E6%88%90%E5%99%A8%E3%83%BB%E9%AB%98%E6%B0%B4%E6%BA%96%E8%A8%80%E8%AA%9E%E9%83%A8%E5%88%86">コード生成器・高水準言語部分</a></h2> <p>コード生成器は 600行弱。</p> <p>コード生成器に与える高水準言語相当のコードは、フォーマットとしてはJSON(ただし、 <code>//</code> でのコメントあり)。</p> <pre><code class="javascript">["stmts" // バッファ配列用 ,["func" ,"to_vi" ,["w", "x", "y", "offset"] ,[ ["var", "vi"] // vram index ,["var", "vi_buf"] ... ,["func" // 関数名 ,"main" // 引数 ,[] // 本体 ,[ ["_debug", "start ----"] ,["var", "w"] ,["set", "w", 5] ,["var", "h"] ,["set", "h", 5] // 配列の初期化 5x5, グライダー / x, y, val ,["set", "vram[1]", 1] ,["set", "vram[7]", 1] ,["set", "vram[10]", 1] ,["set", "vram[11]", 1] ,["set", "vram[12]", 1] // メインループ ,["var", "cnt"] ,["while", ["eq", 1, 1], [ ["set", "cnt", ["+", "cnt", 1]] // gen_next_step ,["call", "gen_next_step_loop", "w", "h"] // バッファの内容で置換 ,["call", "replace_with_buf_v2"] ]] ] ] ] </code></pre> <ul> <li>これを <code>JSON.parse</code> すれば構文木がゲットできる。ずるい。</li> <li>S式ですねー</li> <li>C言語のような何かを想定したオレオレ</li> <li>変数の型は数値のみ</li> <li>中間コード生成はやってません。 素朴に構文木を辿ってアセンブリコードに変換していくだけ。</li> </ul> <h2 id="グラフィック"><a href="#%E3%82%B0%E3%83%A9%E3%83%95%E3%82%A3%E3%83%83%E3%82%AF">グラフィック</a></h2> <ul> <li>ライフゲームなので2次元のグラフィック表示が必要</li> <li>VRAM はサイズ 50 の配列で、これを半分に分けて(片方は次世代生成用のバッファ)、 それぞれを縦横 5x5 としてターミナルに print するだけ。かわいい。</li> </ul> <pre><code>---- memory (vram) ---- ..... ..... @.@.. ..@.. .@@.. @.@.. .@... .@@.. ..... ..... </code></pre> <ul> <li>VRAM といいつつ、1個の配列専用のヒープ領域みたいなものでもあり、 なんとなくずるい気がしますが、とにかくこれでやりました。 <ul> <li>VRAM ではなくヒープ領域的なものであり、 そこを覗き見ているだけだ、ということにしてもいい? どうなんでしょう。</li> <li>「VRAMっていうのはこういうものだ」という知識がないので……</li> <li>↑ この、よく分かってなさをお楽しみください……</li> </ul></li> </ul> <h1 id="方針"><a href="#%E6%96%B9%E9%87%9D">方針</a></h1> <ul> <li>期限は2週間</li> <li>理論とかを全部理解してから作ろうとしない</li> <li>CPU の専門家やアセンブラの専門家やコンパイラの専門家になりたいのではない</li> <li>とにかく動けばいい</li> <li>調べものは最小限 <ul> <li>これは意識的にそうした。極力調べずに(=どうしても分からなかったらそのとき最低限調べる)、それまでに聞きかじったボンヤリした知識を動員して憶測で適当にやる。</li> <li>寄り道しない・脇道にそれない</li> </ul></li> <li><p>不可欠でない部分はさくっと捨てる</p> <ul> <li>「不可欠」というのは、「今回自分が知りたいこと」「完走するのに最低限必要なもの」 「後からすげかえたりするのが難しそうなもの」くらいの意味</li> </ul></li> <li><p>VM〜コード生成部分を作る</p> <ul> <li>加算器部分からは作らない。前にちょっとやったので。</li> <li>高級言語のパース部分はやらない。なんとなくだけど知ってるので。</li> </ul></li> <li><p>汚くていい</p> <ul> <li>自分が学習して満足できればよい。自分の中に知見なり経験値なりが貯まればよい。</li> <li>できたものはゴミでよい。どうせずっと使い続けるものではない。</li> </ul></li> <li><p>作りつつその都度ブログ書いていくのもライブ感あって良さそうとも考えたけど、これもスピードが落ちそうなのでボツ。外部公開のことは考えないことに。</p> <ul> <li>途中で更新止まると悲しいし</li> </ul></li> <li><p>達人プログラマーでいうところの曳光弾。あれをやる。</p> <ul> <li>自分の性格的にもその方が向いてる(早く見通しを得て不安をなくして余裕を得たい)</li> </ul></li> </ul> <hr /> <blockquote> <p>少し例えが暴力的でしたが、この手は新規プロジェクト、特に今まで構築されたことがないようなものを実現する場合に適用することができます。あなたは射撃手のように暗闇の目標を狙わなければならないのです。元になるシステムが存在していない場合、ユーザーの要求は曖昧なものとなります。さらに、不慣れなアルゴリズム、開発技法、言語、ライブラリを使って、未知の世界に直面していくことになるのです。また、プロジェクトの完了まで長い期間がかかるため、作業環境も変化していく可能性が高いでしょう。</p> </blockquote> <p>(<a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/dp/4894712741">達人プログラマー</a> ピアソン・エデュケーション版 p48)</p> <hr /> <blockquote> <p>「正しく」やる時間がない人間は、重要な箇所だけに集中して枝葉を無視する。その結果、いくつかの細かい点は次のバージョンに回そうと計画する。ここで注意する点は、次のバージョンなど永久に作られないかもしれないということだ。しかし、「足りない部分は後でなんとかすればいい」と思い込むことで、脇道に迷い込むことを避けられる。それはまた、初期バージョンの細かな欠点についてのよい言い訳にもなる。</p> </blockquote> <p>(<a target="_blank" rel="nofollow noopener" href="https://www.ohmsha.co.jp/book/9784274064067/">UNIXという考え方</a> p34)</p> <hr /> <h2 id="なぜ2週間か"><a href="#%E3%81%AA%E3%81%9C2%E9%80%B1%E9%96%93%E3%81%8B">なぜ2週間か</a></h2> <ul> <li><p>頓挫するから。</p></li> <li><p>平日昼間は仕事だし、夜・朝もそんなに時間使えないし、土日もいろいろやることある。</p></li> <li><p>これまでの経験でも、長期化するとなんか仕事が忙しくなってきて中断してそれっきりとか、他におもしろそう(で簡単そうなもの)なものを見つけてそっちをやり始めて逃避したりで結局頓挫するパターンが多くて身に染みているから。絶対頓挫する。100%頓挫する。自信を持って断言できる。</p></li> <li><p>で、いったんオレオレの適当な方法で動くものを作れば、その後で「正しいやり方」の解説を見聞きしたときに「あーなるほど、似たようなことやってたわ~」とか「こうすれば良かったのかーっ」ってなるはず。それでよい。それは2週間を終えた後でよい。</p></li> <li><p>とにかく頓挫だけはしたくなかった。頓挫しないことが最も重要な意思決定方針。</p></li> </ul> <h1 id="感想"><a href="#%E6%84%9F%E6%83%B3">感想</a></h1> <ul> <li>おもしろかった!!!!</li> <li>楽しかった!!!!!</li> <li>!!!!!!!!</li> <li>十分自己満足できた(大事)!!!!</li> <li>コード生成~VM実行部分に対するイメージがだいぶ解像度上がった感じ</li> <li>全部いっぺんに作ったの良かった <ul> <li>たとえば既存の何か(たとえば x86 だとか GNU as だとか)に合わせて作ろうとすると調べるのに時間取られる</li> <li>下位レイヤーのことを全部知ってるし、何が起こってるか全部把握できる(なぜなら自分で作ってるから)</li> <li>謎挙動は発生するけど、自分が書いた1000行ちょっとのコードのどこかに必ず原因がある</li> <li>ダンプでも何でもできるし、なんなら下位レイヤーを自分の都合のよいように決めて変えてしまえばよい</li> </ul></li> <li>下から上に向かって作ったの良かった <ul> <li>低水準から高水準へ向かう発展の歴史を辿るような流れになっていて、おもしろい</li> <li>上位レイヤーがなぜこうなっているのか?(下位レイヤーによる制約) が自然な流れで分かる</li> <li>つねに下位レイヤーのドッグフーディングをしている感じ。<br /> 「動くものがなかなかできなくてつまらない」にはならなかった。</li> </ul></li> <li>目標をライフゲームにしたの良かった <ul> <li>仕様や機能が必要かの判断で悩まなくて済む・無駄なものを作らなくて済む</li> <li>ライフゲームを動かすのに必要そうだったらやる。必要なさそうだったらやらない。</li> <li>簡単すぎず、難しすぎず、ちょうどよい負荷(自分にとって)</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://turingcomplete.fm/">Turing Complete FM</a> の内容がちょびっと分かるようになった!!気がする!!!!!!!</li> <li>これでやっと nand2tetris やふつパイラの入り口に立ったぞ……</li> <li>10年前の自分に送りつけて「教材作ってあげたから今すぐやれ」「Ruby とエディタとターミナルだけあればできるから」と言いたい <ul> <li>他の人に薦められるかというと微妙(オレオレなので)</li> </ul></li> </ul> <h1 id="困ったこと"><a href="#%E5%9B%B0%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">困ったこと</a></h1> <h2 id="教材の問題"><a href="#%E6%95%99%E6%9D%90%E3%81%AE%E5%95%8F%E9%A1%8C">教材の問題</a></h2> <ul> <li>どれが(自分にとって)良い教材なのか分からない</li> <li>入門者だし、界隈の事情に詳しくないので判断基準がない</li> <li>現代の最先端のもの、専門家(を目指している人)向けのものは難しすぎる <ul> <li>いきなり 64bit の世界から始まるし</li> </ul></li> <li>昔のCPU向けのものだと今のものより素朴で入りやすいのでは? とか、現代のものでも組み込み向けはどうかな? などと思って調べたりもしたけど、資料の探し方もよく分からないし、ある程度知識のある人向けに書かれていたりしてよく分からない(効率がとても悪い) <ul> <li><a target="_blank" rel="nofollow noopener" href="https://turingcomplete.fm/29">TCFM #29</a> で hikalium さんが「道が見えるようになるためには一回そこに行ってみないと分からない」という話をされてましたが、ほんとそれ</li> <li>ブートストラップ問題</li> <li>昔のものだとそもそも実行環境どうするの? ってとこから始めないといけない。 エミュレータがあっても、まずはその使い方を知らないといけない。</li> </ul></li> <li>目移りする <ul> <li>「x86 でやることにしようかな」 → 「あ、なんか良さげな解説見つけた…けど ARM 向けだなあ…」みたいになってなかなかターゲットを決められない(例は適当です)</li> </ul></li> <li>結局エイヤでオレオレすることに</li> <li>どのみち自分に完全にぴったりな教材は存在しないので、教材を自分で作るみたいになった</li> </ul> <h2 id="入れ子の式"><a href="#%E5%85%A5%E3%82%8C%E5%AD%90%E3%81%AE%E5%BC%8F">入れ子の式</a></h2> <p><code>x = (2 * (3 + 4))</code> のように入れ子になっている式を</p> <pre><code class="javascript"> ,["var", "x"] // 変数の宣言 ,["set", "x", ["*", 2, ["+", 3, 4]]] </code></pre> <p>みたいに書きたくてあれこれ検討したものの、まじめに取り組むと何日か溶けそうな気がしたため</p> <pre><code class="javascript"> ,["var", "temp"] ,["set", "temp", ["+", 3, 4]] ,["var", "x"] ,["set", "x", ["*", 2, "temp"]] </code></pre> <p>のように一時変数を使う形にして回避しました。うーん、これは悔しい。</p> <h2 id="自動テスト"><a href="#%E8%87%AA%E5%8B%95%E3%83%86%E3%82%B9%E3%83%88">自動テスト</a></h2> <ul> <li>これはちょっとした賭けでしたが、テストコードは書かずに進めました <ul> <li>できあがりがどういう形になるのか分からない状態でのスタートだったため、下手にテストコードを書いてしまうとそっちの修正で時間取られそうな気がして……</li> <li>既存の仕様に準拠するならともかく、今回は上から下までオレオレで、作りながら仕様が変わっていくと予想された(実際はあまり変わらなかったけど、それも作ってみないと分からないので)</li> </ul></li> <li>バグが出ると「やっぱり書いておけばよかったかな……」とは思ったものの、結果的にはなんとかなった <ul> <li>運が良かった</li> </ul></li> <li>とはいえ、まったくの無策だった訳ではなく、↓ のようなことはしていました <ul> <li>動作確認程度のちょっとしたものはその都度書いたり</li> <li>生成されたアセンブリコード・機械語コードのファイルも git のトラッキング対象にしておいて、 壊れていないことを git diff で確かめたり</li> </ul></li> </ul> <h1 id="どういう状態から始めたか"><a href="#%E3%81%A9%E3%81%86%E3%81%84%E3%81%86%E7%8A%B6%E6%85%8B%E3%81%8B%E3%82%89%E5%A7%8B%E3%82%81%E3%81%9F%E3%81%8B">どういう状態から始めたか</a></h1> <h2 id="CPU"><a href="#CPU">CPU</a></h2> <p>CPU のことをよく知らなかったので 2016年末~2017年頭頃に何冊か流し読みして、半加算器動かして足し算できる、みたいなのは書いてみた(この頃はアセンブラ、コード生成器まで作る気はなかった)。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.ohmsha.co.jp/book/9784274050619/">マンガでわかるCPU</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://book.mynavi.jp/ec/products/detail/id=41347">自作エミュレータで学ぶx86アーキテクチャ</a> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://tatsu-zine.com/books/my-emulator-x86-architecture">達人出版会</a></li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://book.mynavi.jp/ec/products/detail/id=22065">CPUの創りかた</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://book.mynavi.jp/ec/products/detail/id=22078">30日でできる! OS自作入門</a> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://tatsu-zine.com/books/make-your-own-os-in30days">達人出版会</a></li> <li>CPUの本ではないですがなんとなく流れでこの頃に読みました。 アセンブリ部分もあるし今回のをやるのに参考になっていると思います。</li> </ul></li> </ul> <p>※なので、2週間でまったくのゼロから始めたわけではなくて、構想期間が1,2年くらいあり、その間に本やネットであれこれ読んだりいろいろ妄想したりしてました。その上で、考えても埒が明かないのでやっぱり実際に書いてみよう! となった、という流れです。</p> <h2 id="アセンブリ"><a href="#%E3%82%A2%E3%82%BB%E3%83%B3%E3%83%96%E3%83%AA">アセンブリ</a></h2> <ul> <li>聞きかじりでなんとなく知ってるくらい。</li> <li>アセンブリ言語でプログラムを書いたことはない。</li> <li>mov があって比較とジャンプとかするらしいぞ、くらい。</li> <li>なるほど分かったぞそれを組み合わせればいろいろできるんだな!? くらいの認識。</li> </ul> <h2 id="コンパイラ"><a href="#%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9">コンパイラ</a></h2> <ul> <li>パーサ部分はなんとなくは知ってる</li> <li>だいぶ前に読んだ(両方ともさらっと読んだだけで実際には自分で書いたりしてない)(どっちも青木さんの本ですね): <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/dp/4756137091">Rubyを256倍使うための本 無道編</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.sbcr.jp/products/4797337958.html">ふつうのコンパイラをつくろう</a> <ul> <li>これを読んだときは構文解析部分に興味があったので後半(コード生成の部分)は読んでなかった気がする</li> </ul></li> </ul></li> <li>BNFで文法定義して構文木作って何かやるんでしょ? …何かというのはつまり、えーとアセンブリ言語のコードを生成する(?)んですよね、 で、具体的にどうやるかというと…(分からない</li> </ul> <p>という感じ。つまり、半加算器~構文木の間で何が起こっているのかが分かっていなかった。</p> <h2 id="C言語どのくらい知ってる?"><a href="#C%E8%A8%80%E8%AA%9E%E3%81%A9%E3%81%AE%E3%81%8F%E3%82%89%E3%81%84%E7%9F%A5%E3%81%A3%E3%81%A6%E3%82%8B%EF%BC%9F">C言語どのくらい知ってる?</a></h2> <ul> <li>学生の頃は趣味プログラムをCで書いてた</li> <li>十数年前なのでもうだいぶ忘れた <ul> <li>当時入門書として読んでいたのは前橋さんの <a target="_blank" rel="nofollow noopener" href="https://gihyo.jp/book/2001/4-7741-1200-3">C言語 体当たり学習 徹底入門</a> で、この本+αくらいの知識</li> </ul></li> <li>OpenGL で3Dグラフィック描いたりとかはしてたけど クリティカルなメモリ管理が必要なものはあまり書いてなかったはず</li> <li>その後 Ruby を使うようになり、 趣味プログラムの99%くらいは Ruby で用が足りるのでC言語で書かなくなった</li> </ul> <h1 id="元ネタ・きっかけなど"><a href="#%E5%85%83%E3%83%8D%E3%82%BF%E3%83%BB%E3%81%8D%E3%81%A3%E3%81%8B%E3%81%91%E3%81%AA%E3%81%A9">元ネタ・きっかけなど</a></h1> <h2 id="nand2tetris"><a href="#nand2tetris">nand2tetris</a></h2> <ul> <li>https://www.nand2tetris.org/ <ul> <li>日本語版の書籍は <a target="_blank" rel="nofollow noopener" href="https://www.oreilly.co.jp/books/9784873117126/">コンピュータシステムの理論と実装</a></li> </ul></li> <li>企画というか発想の元はこれ</li> <li>たしか 2016年頃に知って、読んだ</li> <li>最初に nand2tetris のコンセプトを知ったときは、 2週間〜1ヶ月くらい根詰めればどうにかなるかなあと甘い期待を抱いたけど無理で、<br /> 流し読みだけでやめてしまっていた</li> <li>もっと簡略化した教材かなと思ってたけど、 思ったよりきっちり進めてくスタイルの、教科書っぽい本だった</li> <li>問題をもっと簡単にしてハードルを下げまくらないといけない</li> </ul> <p>ライフゲームならテトリスより簡単そう(ユーザからの入力もない)だし、簡単な割には面白い動きが生成されるので自己満足度高そう。「ライフゲームが動いたら嬉しいだろうなぁ…」と、完成したときの満足感をモワモワ~と妄想しつつ……</p> <h2 id="rebuild.fm るいさんゲスト回(2016-08-09)"><a href="#rebuild.fm+%E3%82%8B%E3%81%84%E3%81%95%E3%82%93%E3%82%B2%E3%82%B9%E3%83%88%E5%9B%9E%EF%BC%882016-08-09%EF%BC%89">rebuild.fm るいさんゲスト回(2016-08-09)</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://rebuild.fm/153/">Rebuild: 153: Connecting The Dots (rui314)</a></p> <p>40分あたりから 8cc の話。インクリメンタルに作ると良いという話。これを聞いたとき、やっぱりそうですよねえ、そうだろうなあと思った記憶があります。明らかに当企画のきっかけの一つです。</p> <p>その後 <a target="_blank" rel="nofollow noopener" href="https://turingcomplete.fm/">Turing Complete FM</a> が始まり、なんか楽しそうなのでやってみたい! という気分が盛り上がりました。</p> <h1 id="TODO"><a href="#TODO">TODO</a></h1> <p>時間とやる気が揃ったら続きをやりたい。下記は「今回やってないこと」「今回捨てた・削ったもの」のリストでもあります。</p> <ul> <li>nand から作って nand2gol にする <ul> <li>(2020-05-03) 1bit CPU 作るところまではやりました: <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/03/132253">リレー式論理回路シミュレータを自作して1bit CPUまで動かした</a></li> </ul></li> <li>VM <ul> <li><code>[bp+1]</code> とかを VM が直接(正規表現で…)解釈しているので、もうちょっと機械語っぽく</li> </ul></li> <li>高級言語・コンパイラ部分 <ul> <li>入れ子の式の計算をできるようにする <ul> <li>今回回避してしまったので、最低限ここまではちゃんとやりたい</li> <li>(2019-12-15) <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/12/15/142809">できるようにしました</a></li> </ul></li> <li>配列</li> <li>構造体</li> <li>ポインタ</li> <li>再帰</li> <li>型</li> <li>字句解析・構文解析(ちゃんとCっぽいフォーマットのソースをパースする) <ul> <li>(2020-05-04) <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">パーサも作りました</a></li> </ul></li> </ul></li> <li>グラフィック <ul> <li>あると楽しい</li> </ul></li> <li>リンカ</li> <li>OS <ul> <li>難しそう</li> <li>いろいろ削りまくればあるいは……?</li> </ul></li> </ul> <h1 id="ざっくり工程"><a href="#%E3%81%96%E3%81%A3%E3%81%8F%E3%82%8A%E5%B7%A5%E7%A8%8B">ざっくり工程</a></h1> <ul> <li>まず簡単なVM</li> <li>メモリにプログラムを直接書く</li> <li>ファイルからロードするようにする</li> <li>機械語コード書きつつ <ul> <li>命令を追加していく</li> <li>アセンブラを作ってラベルを実アドレスに変換</li> <li>条件分岐、ループ、サブルーチンなど、必要そうなものを用意</li> </ul></li> <li>ある程度できてきたらコード生成器を作りだす <ul> <li>変数、条件分岐、ループ、関数などをアセンブリコードに変換</li> </ul></li> <li>部品が揃ってきたらライフゲームを動かす</li> </ul> <p>ざっくり書くとこんな感じですが、実際は行きつ戻りつしていて、終盤でもライフゲーム書きながらVM部分を修正したりしてました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/05/04/234516">こちらで製作過程を書いています</a>ので詳しくはそっちを見ていただければと思います。</p> <h1 id="その他の参考書籍など"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96%E3%81%AE%E5%8F%82%E8%80%83%E6%9B%B8%E7%B1%8D%E3%81%AA%E3%81%A9">その他の参考書籍など</a></h1> <h2 id="v1 を作る前〜作っている時に読んだもの"><a href="#v1+%E3%82%92%E4%BD%9C%E3%82%8B%E5%89%8D%E3%80%9C%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E6%99%82%E3%81%AB%E8%AA%AD%E3%82%93%E3%81%A0%E3%82%82%E3%81%AE">v1 を作る前〜作っている時に読んだもの</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://tatsu-zine.com/books/scheme-in-ruby">つくって学ぶプログラミング言語 RubyによるScheme処理系の実装 - 達人出版会</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.lambdanote.com/products/ruby-ruby">RubyでつくるRuby ゼロから学びなおすプログラミング言語入門(紙書籍+PDF版) – 技術書出版と販売のラムダノート</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://tatsu-zine.com/books/hajimete-yomu-486">32ビットコンピュータをやさしく語る はじめて読む486【委託】 - 達人出版会</a></li> <li><p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2017/lowlayer">ひとりでCPUとエミュレータとコンパイラを作る Advent Calendar 2017 - Qiita</a></p> <ul> <li>下の方が半導体から始まっていて、すごいなーと思いながら読んでいました。</li> <li>> 最適化は理解の敵</li> </ul></li> <li><p><a target="_blank" rel="nofollow noopener" href="https://archive.org/details/MSXMachineLanguageIntroduction">くじけちゃいけない! マシン語入門 平塚憲晴 : 平塚憲晴 : Free Download, Borrow, and Streaming : Internet Archive</a></p> <ul> <li>MSX</li> </ul></li> </ul> <h2 id="v1 を作った後に読んだもの"><a href="#v1+%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%9F%E5%BE%8C%E3%81%AB%E8%AA%AD%E3%82%93%E3%81%A0%E3%82%82%E3%81%AE">v1 を作った後に読んだもの</a></h2> <p>※流し読み含む</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.sigbus.info/compilerbook">低レイヤを知りたい人のためのCコンパイラ作成入門</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://esumii.github.io/min-caml/">速攻MinCamlコンパイラ概説</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://notes.eatonphil.com/compiler-basics-lisp-to-assembly.html">Compiler basics (1): lisp to assembly | notes.eatonphil.com</a> <ul> <li>JavaScript</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.ohmsha.co.jp/book/contact.htm?bname=Ruby%e3%81%ae%e3%81%97%e3%81%8f%e3%81%bf%e3%80%80Ruby+Under+a+Microscope&isbn=978-4-274-05065-7">Rubyのしくみ Ruby Under a Microscope | Ohmsha</a> <ul> <li>この本に限らず、Ruby の VM の話もうっすら分かるようになって良かった</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://book.mynavi.jp/ec/products/detail/id=24268">Rubyで作る奇妙なプログラミング言語 プレミアムブックス版 | マイナビブックス</a> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://tatsu-zine.com/books/esoteric-language-programing-in-ruby">達人出版会</a></li> <li>2019-03 頃に読んだ</li> <li>この本の存在は昔から知ってはいましたが、 esoteric language というのは上級者の遊戯であって自分にはハードルが高そうというイメージがあり、 なんとなく読まずに過ごしてきました。 しかし、今実際に読んでみると、難解そうに見えるのは表面だけで、 中身は良い意味で普通の(汎用的な、使い回しの効く)言語処理系の話です。 コードもコンパクトですし、もっと早く読んでいればよかった。 先に読んで写経でもしていれば、 vm2gol の内容ももう少し違ったものになっていたかもしれません。</li> </ul></li> </ul> <h2 id="ブックマーク"><a href="#%E3%83%96%E3%83%83%E3%82%AF%E3%83%9E%E3%83%BC%E3%82%AF">ブックマーク</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="http://b.hatena.ne.jp/sonota88/CPU/">はてなブックマーク - CPUに関するsonota88のブックマーク</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://b.hatena.ne.jp/sonota88/Compiler/">はてなブックマーク - Compilerに関するsonota88のブックマーク</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://b.hatena.ne.jp/sonota88/Assembly/">はてなブックマーク - Assemblyに関するsonota88のブックマーク</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://b.hatena.ne.jp/sonota88/Emulator/">はてなブックマーク - Emulatorに関するsonota88のブックマーク</a></li> </ul> <h1 id="その他 Ruby 関連で書いたもの"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96+Ruby+%E9%96%A2%E9%80%A3%E3%81%A7%E6%9B%B8%E3%81%84%E3%81%9F%E3%82%82%E3%81%AE">その他 Ruby 関連で書いたもの</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/archive/category/Ruby">https://memo88.hatenablog.com/archive/category/Ruby</a></p> 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