2022-07-10に更新

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

ブログに書いていたものを引っ越してきました。元の記事公開日は 2020-09-18 です。


やっつけなので汚いです。ライフゲームのコンパイルが通ったのでヨシ、という程度の雑なものです。

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

移植元

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

大元は Ruby 版なのですが、実質的には Perl版からの移植です。

ベースになっているバージョン: tag:50 のあたり
(追記 2021-09-18: ステップ60 の修正まで適用しました)

作り方はここに全部書いています(Ruby 版のものですが): vm2gol v2 製作メモ

メモ

主な部分のサイズ(2021-09-18 現在):

$ wc -l {lexer,parser,codegen}.php lib/utils.php 
   66 lexer.php
  536 parser.php
  423 codegen.php
   42 lib/utils.php
 1067 合計

  • PHP を書くのは今回が初めて
  • さらっと文法を見た感じ Perl に近そうだったので、
    Perl版をコピーして書き換えていった
    • 割と機械的に置き換え
    • my を消して
    • sub => function
    • elsif => elseif
    • 正規表現はダブルクォートで囲んで preg_match にして
    • ……などなど
    • Perl → PHP という流れで進んだのは良かった
  • Emacs に php-mode を追加するのがめんどくさかったので perl-mode で書いてた
  • クラスも普通に使えるし、 int と string の型の判別もできるし、 Perl より楽ちんという印象。
    正直なところあまり書くことがないです……。

今回の実験コーナー。

これまで Dart への移植のときに set を不要にし、Java への移植のときに call を不要にし、Perl への移植のときに call_set を不要にしてきました。

これらを全部適用するとどうなるか trial ブランチ
でやってみました。

とりあえず parse_stmt() の分岐のとこだけ貼ってみます。

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

個別に試していたときはそれぞれの個別のことだけ考えてやればよかったのが、3つを同時に満たそうとして else に押し込めてとりあえずこうなった、というものです。ちょっと野暮ったいですが peek(n) で先読みしてやればなんとかなるようです。

funcall を expr に含めるとかするともうちょっとすっきりする気がします。

vgコードはこんな感じ。ここまでやるとかなり普通の言語っぽい(というか 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;
  }
}

他の言語への移植

| PHP | github | 2020-09-18 |
-->

記事 リポジトリ 日付
Haskell github 2021-06-28
OCaml github 2021-06-26
Pascal github 2021-05-22
Julia github 2021-05-03
Rust github 2021-04-07
Crystal github 2021-03-27
Pric(セルフホスト) github 2021-02-21
Kotlin github 2021-01-14
Zig github 2021-01-07
LibreOffice Basic github 2020-12-14
Go github 2020-09-25
C♭ github 2020-09-13
Perl github 2020-09-08
C github 2020-09-06
Java github 2020-08-30
Dart github 2020-08-22
Python github 2020-08-19
TypeScript (Deno) github 2020-08-15
Originally published at zenn.dev
ツイッターでシェア
みんなに共有、忘れないようにメモ

sonota486

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント