Perlでかんたんな自作言語のコンパイラを書いた

[ブログに書いていたもの](https://memo88.hatenablog.com/entry/2020/09/08/053329)を引っ越してきました。元の記事公開日は 2020-09-08 です。

---

20年ぶりくらいに Perl のコードを書きました。やっつけなので汚いです。ライフゲームのコンパイルが通ったのでヨシ、という程度の雑なものです。


https://github.com/sonota88/vm2gol-v2-perl



----

# 移植元

自作言語の概要などについてはこちらを参照してください。

https://memo88.hatenablog.com/entry/2020/05/04/155425

ベースになっているバージョン: [tag:49](https://github.com/sonota88/vm2gol-v2/tree/49) のあたり
（追記 2021-09-11: ステップ60の修正まで適用しました）


作り方はここに全部書いています（Ruby 版のものですが）: [vm2gol v2 製作メモ](https://memo88.hatenablog.com/entry/2019/05/04/234516)

ただ、Ruby 版よりは主に [C言語版](https://memo88.hatenablog.com/entry/2020/09/06/043607) を見ながら書き写していました。クラスを使わなかったのもあり、C言語版に近いところも多いです。




# メモ

主な部分のサイズ（2021-09-18 現在）:

```
$ wc -l {lexer,parser,codegen}.pl lib/{token,utils,val}.pm 
   64 lexer.pl
  553 parser.pl
  528 codegen.pl
   34 lib/token.pm
   27 lib/utils.pm
   38 lib/val.pm
 1244 合計
```

----

- 20年ぶりくらいといっても、当時はちょっと入門した程度だったしもうほとんど忘れてるので
  ほぼゼロから再入門といった感じ
- でも 2日（日曜＋月曜）でなんとか完成した。殴り書きです。
- 配列・ハッシュとリファレンスが絡むあたりが最初よく分からなかった
  - まず配列の要素数の取り方が分からないとか、
    `for my $x (@$xs) { ... }` の `@` がなくて動かないとか、
    たぶん初心者あるある
- 数と文字列の区別がない（シェルスクリプトっぽい）
  のをどうするかちょっと悩んだ。
  データの型を見て処理を分岐しているところがあるので。
  - ハッシュでラッパーオブジェクトみたいなものを作ってどうにかした（ `lib/Val.pm` ）。
  - 最初は C言語版のときの
    NodeList, NodeItem 構造体を使う書き方にすれば何も考えずに機械的に移植できそうと思ったけど、
    あれはあれでリストの処理が煩雑・大げさで、
    配列で済むならその方がシンプルなので、最低限文字列と数だけ判別できるようにした。
- クラスの使い方もちょっと調べてみたけど、
  もうちょっと Perl 力が上がった状態で使わないと
  逆に回り道っぽくなりそうな気がして、
  C言語版の「構造体＋関数群のセット」方式でやってしまった


---

`my $foo = ...` をいっぱい書かないといけなくてさすがに面倒だったので途中で次のような Emacs Lisp を書いて `Ctrl + Alt + M` で入力できるようにしたり。yasnippet でも良かったかも。

```lisp
(add-hook
 'perl-mode-hook
 '(lambda ()
    (local-set-key (kbd "C-M-m")
                   (lambda ()
                     (interactive)
                       (insert "my $ = ;")
                       (backward-char 4)))))
```

---

[Dart版](https://memo88.hatenablog.com/entry/2020/08/22/162201)で `call`、[Java版](https://memo88.hatenablog.com/entry/2020/08/30/121437)で `set` を不要にした流れで、今回は `call_set` キーワードを不要にしてみました。同じ要領でできますね、ということが分かりました。

（追記 2021-04-09: この修正はいったん revert しましたが、一応 [trial ブランチ](https://github.com/sonota88/vm2gol-v2-perl/tree/trial)に残してあります。）

```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";
        }
    }
}
```

---

（追記 2021-08-30: その後の修正により、下記の部分はなくなりました）

それと、今回はコード生成処理のアレ、何度も似たようなのが出てきて鬱陶しかった部分をためしにサブルーチンに抽出して共通化してみました。うーん。どうでしょう。どうしようかな。とりあえず名前が微妙。

名前が微妙な上に上手い抽象でもない気がする……となるとこれはメソッド抽出すべきではないパターンのように思えます。いい解決法が見つかるまで本家の Ruby版には取り込めなさそう。とはいえコーディングの手間・退屈さは減らせるので、移植版では気軽に使っていこうかなと。

```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;
    }
}
```

呼び出し箇所は6箇所。それなりに使いまわせているようではあります。

```
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));
```


# 他の言語への移植

かんたんなコンパイラなのでいろんな言語に気軽に移植しています。


| 記事 | リポジトリ | 日付 |
| --- | --- | --- |
| [Haskell](https://memo88.hatenablog.com/entry/20210628_vm2gol_v2_haskell) | [github](https://github.com/sonota88/vm2gol-v2-haskell) | 2021-06-28 |
| [OCaml](https://memo88.hatenablog.com/entry/2021/06/26/054540) | [github](https://github.com/sonota88/vm2gol-v2-ocaml) | 2021-06-26 |
| [Pascal](https://qiita.com/sonota88/items/4d446b778ffbbaab267c) | [github](https://github.com/sonota88/vm2gol-v2-pascal) | 2021-05-22 |
| [Julia](https://qiita.com/sonota88/items/b80696f23a08c3c2fc5d) | [github](https://github.com/sonota88/vm2gol-v2-julia) | 2021-05-03 |
| [Rust](https://memo88.hatenablog.com/entry/20210407_vm2gol_v2_rust) | [github](https://github.com/sonota88/vm2gol-v2-rust) | 2021-04-07 |
| [Crystal](https://qiita.com/sonota88/items/275c2b5407986b100cc8) | [github](https://github.com/sonota88/vm2gol-v2-crystal) | 2021-03-27 |
| [Pric（セルフホスト）](https://qiita.com/sonota88/items/1e683276541cf1b87b76) | [github](https://github.com/sonota88/pric) | 2021-02-21 |
| [Kotlin](https://memo88.hatenablog.com/entry/2021/01/14/032813) | [github](https://github.com/sonota88/vm2gol-v2-kotlin) | 2021-01-14 |
| [Zig](https://memo88.hatenablog.com/entry/2021/01/07/235019) | [github](https://github.com/sonota88/vm2gol-v2-zig) | 2021-01-07 |
| [LibreOffice Basic](https://qiita.com/sonota88/items/31c127c42a9722892aa8) | [github](https://github.com/sonota88/vm2gol-v2-libreoffice-basic) | 2020-12-14 |
| [Go](https://memo88.hatenablog.com/entry/2020/09/25/073200) | [github](https://github.com/sonota88/vm2gol-v2-go) | 2020-09-25 |
| [PHP](https://memo88.hatenablog.com/entry/2020/09/18/193844) | [github](https://github.com/sonota88/vm2gol-v2-php) | 2020-09-18 |
| [C♭](https://memo88.hatenablog.com/entry/2020/09/13/133735) | [github](https://github.com/sonota88/vm2gol-v2-cflat) | 2020-09-13 |
| [C](https://memo88.hatenablog.com/entry/2020/09/06/043607) | [github](https://github.com/sonota88/vm2gol-v2-c) | 2020-09-06 |
| [Java](https://memo88.hatenablog.com/entry/2020/08/30/121437) | [github](https://github.com/sonota88/vm2gol-v2-java) | 2020-08-30 |
| [Dart](https://memo88.hatenablog.com/entry/2020/08/22/162201) | [github](https://github.com/sonota88/vm2gol-v2-dart) | 2020-08-22 |
| [Python](https://qiita.com/sonota88/items/5267dcd4750235680c8f) | [github](https://github.com/sonota88/vm2gol-v2-python) | 2020-08-19 |
| [TypeScript (Deno)](https://memo88.hatenablog.com/entry/2020/08/15/114754) | [github](https://github.com/sonota88/vm2gol-v2-typescript/tree/20200815) | 2020-08-15 |