JMindのソース例(本編は次から)
メインとは
30を 回数指定し
「」をつみ
回数を 3で 割った余りが ゼロ? ならば
「Fizz」を 合成し
つぎに
回数を 5で 割った余りが ゼロ? ならば
「Buzz」を 合成し
つぎに
複写し 空列? ならば
捨て
回数をつみ
つぎに
何でも表示することを
繰り返すこと。
これはなに
これは私がプログラミング言語MindのソースコードをJVM上で動く「.class」ファイルに変換するのを楽しんでいる、という自分語りと備忘録です。
※あくまで "Mindのソースコードを「.class」ファイルに変換するプログラム" ですが、長ったらしいので以下では恐れながら「コンパイラ」と言い換えます。
ソース:https://github.com/Snowman-s/JMind
きっかけ
PHP上でJVMを作成されていた某ちょうぜつエンジニアに感化されて僕もこんなことができるようになりたいなぁと思っていました。
そこで、個人的に気に入ってた日本語プログラミング言語Mindを、JVM上で動かせるようにしたいなと思いまして。
名前を先人たちに倣いJMindとしまして。
さぁ早速作りますよ。MindでMindのコンパイラを。
(※JVMを作る話では無くて、「.class」ファイルへのコンパイラを作る話です。)
苦労譚
UTF-8
これが最初で割と大きい壁でした。SourceTreeがうまくShiftJISを読み込んでくれなかったので仕方なくMindのソースコードをUTF-8で動かせるようなプログラムを書きました。**Mindで。**あとバッチ
内容はShiftJISにファイルを変換してからコンパイラを動かす簡単なものです。
ソース:https://github.com/Snowman-s/MindTools
中間コード(「.mco」ファイル)
MindもJavaと同じく現在は中間コードを採用しており、それが「.mco」ファイルです。ですがどれだけ探してもそのファイルのフォーマットが見つかりませんでした。終わり。
ということで自分で1から構文解析的なことをやることになりました。
「スタック」
「.class」ファイルの動作原理、およびMindの仕組みはスタック言語であり、一見親和性が高く見えます。
そんなことはない――でした。
私が読み取りが正しければ、JVMのスタックはメソッドが終了すると強制リセットがかかり、Mindにおける単語はそうではないという大きな違いがあります。(https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-6.html#jvms-6.5.return)
何とか使えないかと試行錯誤していましたが、僕の技術ではだめでしたので、しょうがなく一つのコレクションを使いまわすことにしました。
選ばれたのは、java.util.Stackでした。(のちにjava.util.LinkedListに変更。)
その結果、メインの単語で一つのコレクションをnewし、他のあらゆる単語はそのコレクションを受け取ってそれをスタックとするように設計されました。(まだ複数の単語定義は実装されていません。)
なお、この方法では数値や文字をスタックに積むためにいちいちInteger.valueOfやInteger.intValueを通さなければならないので、処理時間は見込めません。(いつか最適化を図っています。)
文字列およびファイル
このコンパイラは仕様上、中間ファイルにバリバリ書き出しバリバリ読み込みます。
Mindではローカルな文字列実体の中身は単語外では保証されず、ファイルの情報に上書きされることがあります。(そうと分かっていてもグローバルにすると名前空間が減る恐怖があり。)
このような大規模なプログラムを書いたことが無いのもあって、ファイル操作に関するソースコードは結構泥沼化しています。
明るい話(たのしかったこと)
スタック言語
スタック言語はコンパイラに非常に優しかったです。
一つ一つの単語だけ見てその都度実行すればいいのです。
構文解析的なことは非常に楽に終わらせることが出来ました。
但し「かつ」と「または」、お前は駄目だ。「かつ」と「または」はまだ実装されていません。
JVM上で動作可能に
「.class」ファイルにしたことでJVM上で動かすことができる、つまりJavaのコードからJMindのメソッドを呼ぶことが出来たときは僕の中でブレイクスルーが起きました。(JMind側からJavaのメソッドを呼ぶのはまだ実装されていません。)
以下に例をくっつけておきます。
※Mind側
PrintNumbersとは
(受け取った数を) 回数指定し
回数を 何でも表示することを
繰り返すこと。
import java.util.*;
public class JWithJava {
//Java側
//MWithJava.srcをコンパイルして、
//これを実行する。
public static void main(String[] args) {
LinkedList<Object> s = new LinkedList<>();
s.push(30);
//Mindソースの関数を呼ぶ。
MWithJava.PrintNumbers(s);
}
}
> jmindc MWithJava.src
> javac -encoding UTF-8 JWithJava.java
> java JWithJava
123456789101112131415161718192021222324252627282930
「何でも表示」単語
上記の例でも少し出てきましたが、JMindにはMindにはない「何でも表示する」という単語が追加されています。これは、あらゆるオブジェクト(数値、文字列など)を標準出力することができる単語です。
「.class」で実装する、つまり表示にSystem.outのprint(Ljava/lang/Object;)Vを使わせてもらったことで、このようなことが可能になりました。
回想のまとめ(結論)
色々ドタバタしながら作っていましたが、最終的には良い経験になったと思います。
今でも気まぐれにちょくちょく機能が追加されているので気になったらのぞいてみてください。(最近では「選択する」が追加されました。)
以上です。あとに続くのはサンプルソースなので見たい人は見てください。
#スクラップ(サンプルソース、GitHubにもあります)
仲間外れ
メインとは
クラスの人数は 変数
一グループあたりの人数は 変数
余った人数は 変数
39を クラスの人数に 入れ
2を 一グループあたりの人数に 入れ
クラスの人数を
一グループあたりの人数で 割った余りを
余った人数に 入れ
余った人数が ゼロ以外? ならば
「じゃあ先生とグループになろうか」を 一行表示し
つぎに。
突き出しFizzBuzz
メインとは
30を 回数指定し
回数を 15で 割った余りが ゼロ? ならば
「FizzBuzz」を
さもなければ
回数を 3で 割った余りが ゼロ? ならば
「Fizz」を
さもなければ
回数を 5で 割った余りが ゼロ? ならば
「Buzz」を
さもなければ
回数を
つぎに
つぎに
つぎに
何でも表示することを
繰り返すこと。
JMindにハローワールド
メインとは
「こんにちは! 世界!」を 一行表示すること。
スタック言語本領発揮
メインとは
0をつみ
ここから
一つ数値入力し 複写して
ゼロ? ならば
捨てて
打ち切る
つぎに
加えてゆくことを
繰り返して
「合計は」を 表示してから
何でも一行表示すること。
辛辣な人たち
メインとは
一つ数値入力し
選択する
「声小さいよ」
「良く聞こえなかったな」
「なんて言ったの?」
真
選択終わり
何でも一行表示する。