素朴な自作言語のコンパイラをPerlに移植した


移植まとめに戻る


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

github.com


移植元

memo88.hatenablog.com

ベースになっているバージョン: tag:49 のあたり

ただ、Ruby 版よりは主に C言語版 を見ながら書き写していました。 クラスを使わなかったのもあり、C言語版に近いところも多いです。

メモ

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

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

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

Dart版callJava版set を不要にした流れで、 今回は call_set キーワードを不要にしてみました。 同じ要領でできますね、ということが分かりました。

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

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

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

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