Mindを「.class」ファイルに変換する動作原理

# これはなに

僕が趣味で作っているMindソースコードを「.class」ファイルへ変換するプログラムの動作原理を説明します。

このプログラムの名前は「JMind」です。

※少し前に[回想](https://qiita.com/Snowman-s/items/196c2b4348854e26b95e)を書きましたが、あれは日記のノリだったのでここではどのように動作するかについて説明します。

※あくまで "Mindのソースコードを「.class」ファイルに変換するプログラム" ですが、長ったらしいので以下では**恐れながら**「コンパイラ」と言い換えます。

ソースコード：https://github.com/Snowman-s/JMind

# 「.class」ファイルについて

この記事を読んでいるような酔狂な方ならもうご存じのように、Javaではソースコードをまずコンパイルして、その生成されたファイルを起動引数に渡して「java」コマンドを実行する、という手順でプログラムが実行されます。

このソースコードをコンパイルしたものが「.class」ファイルですね。詳細な仕様は[ここ](https://docs.oracle.com/javase/specs/jvms/se14/html/)(公式ドキュメント、Java14)に書いてあります。

このコンパイラはMindソースコードを**Java言語を一切介さずに**「.class」ファイルに変換します。

言い換えれば、ソースコードの構文を読み取って「.class」ファイルにバイナリを書き込みます。

# スタック言語

Mindはスタック言語です。
したがって基本的には、このコンパイラは一つ一つの単語を読み取りオペコードを生成する、という手順で解決できました。

例：単語「終わり」はオペコード「return」に対応する。

JVMにもMindと同じようにスタックの概念がありますが、JVMのスタックはMindと違ってメソッド終了時にスタックを破棄します。(https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-6.html#jvms-6.5.return)

私の技術ではJVMのスタックをMindのスタックと同じように扱うことはできなかったので、
苦肉の策として一つのコレクションを使いまわすことにしました。

すなわち、メイン関数にて一つのコレクションをnewし、他の単語はそのコレクションを第一引数に受け取るように実装されました。
これであらゆる単語でスタックを共有でき、分割も簡単です。

現在(2020/7)はjava.util.LinkedListがその役目を担っています。

**例：単語定義「メインとは」は**

```java
public static void main(String[] args){
    LinkedList<Object> = new LinkedList<>(); //Mindにおけるスタック
    //other opecodes...
}
```

に対応する。

**例：単語定義「sayとは」は**

```java
public static void say(LinkedList<Object> list)[
   //opecodes...
}
```

に対応する。(Mindでは一定のルールで送り仮名は無視されます。)

# 実際の動作

## 空のソースコード

```test.src
メインとは。
```

これをこのコンパイラでコンパイルしてjavapで中身を見るとこうなります。

```terminal
> jmindc test.src

> javap -v test

(前略...)

public class test
  minor version: 0
  major version: 52
  flags: (0x0001) ACC_PUBLIC
  this_class: #25                         // test
  super_class: #23                        // java/lang/Object
  interfaces: 0, fields: 0, methods: 1, attributes: 0
Constant pool:
   #1 = Class              #2             // java/util/LinkedList
   #2 = Utf8               java/util/LinkedList
   #3 = Methodref          #1.#4          // java/util/LinkedList.addFirst:(Ljava/lang/Object;)V
   #4 = NameAndType        #5:#6          // addFirst:(Ljava/lang/Object;)V
   #5 = Utf8               addFirst
   #6 = Utf8               (Ljava/lang/Object;)V
   #7 = Methodref          #1.#8          // java/util/LinkedList.removeFirst:()Ljava/lang/Object;
   #8 = NameAndType        #9:#10         // removeFirst:()Ljava/lang/Object;
   #9 = Utf8               removeFirst
  #10 = Utf8               ()Ljava/lang/Object;
  #11 = Methodref          #1.#12         // java/util/LinkedList."<init>":()V
  #12 = NameAndType        #13:#14        // "<init>":()V
  #13 = Utf8               <init>
  #14 = Utf8               ()V
  #15 = Class              #16            // "[Ljava/lang/String;"
  #16 = Utf8               [Ljava/lang/String;
  #17 = Methodref          #1.#18         // java/util/LinkedList.remove:(I)Ljava/lang/Object;
  #18 = NameAndType        #19:#20        // remove:(I)Ljava/lang/Object;
  #19 = Utf8               remove
  #20 = Utf8               (I)Ljava/lang/Object;
  #21 = Utf8               main
  #22 = Utf8               ([Ljava/lang/String;)V
  #23 = Class              #24            // java/lang/Object
  #24 = Utf8               java/lang/Object
  #25 = Class              #26            // test
  #26 = Utf8               test
  #27 = Utf8               Code
{
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=256, locals=2, args_size=1
         0: new           #1                  // class java/util/LinkedList
         3: dup
         4: invokespecial #11                 // Method java/util/LinkedList."<init>":()V
         7: astore_1
         8: return
}

```

まず、ランタイム定数フィールドを見ると分かるように、最初にランタイム定数フィールドに「スタックを使うのに必要な情報」を無条件で書き込みます。
つぎに「main」メソッドの中身も見ると、Mindのスタックに相当する`LinkedList`を無条件で生成しているのが分かります。

~~全体を見ると、最低限動くだけの情報しかないですね、これはいけません。~~

## JMindにおけるスタック

```test.src
メインとは　「こんにちは！　世界！」を　一行表示すること。
```

これをこのコンパイラでコンパイルしてjavapで中身を見るとこうなります。

```terminal
> jmindc test.src

> javap -v test

(前略...)

      stack=256, locals=2, args_size=1
         0: new           #1                  // class java/util/LinkedList
         3: dup
         4: invokespecial #11                 // Method java/util/LinkedList."<init>":()V
         7: astore_1
         8: aload_1
         9: ldc           #21                 // String こんにちは！　世界！
        11: invokevirtual #3                  // Method java/util/LinkedList.addFirst:(Ljava/lang/Object;)V
        14: getstatic     #36                 // Field java/lang/System.out:Ljava/io/PrintStream;
        17: aload_1
        18: invokevirtual #7                  // Method java/util/LinkedList.removeFirst:()Ljava/lang/Object;
        21: checkcast     #29                 // class java/lang/String
        24: invokevirtual #28                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: return

(...後略)

```

オペコードの8バイトから11バイト目が「こんにちは！　世界！」、14バイトから24バイトまでが「一行表示」に対応するオペコードです。

8-11をみると、スタックが入っている変数を参照し、Stringオブジェクトを`addFirst()`、つまり積んでいることが分かります。
17-18をみると、スタックが入っている変数を参照し、オブジェクトを`removeFirst()`、つまり降ろしていることが分かります。

このようにして、スタック言語であるMindは、**スタックにオブジェクトを積んだり降ろしたりしながら**進んでいきます。

> これをみてわかったように、JMindには必要ないオペコードがたくさん増えるという重大な欠点があります。

## 数値(int)の扱い

```test.src
メインとは　１。
```

(これでも立派?なMindコードです。)
これをこのコンパイラでコンパイルしてjavapで中身を見るとこうなります。

```terminal
> jmindc test.src

> javap -v test

(前略...)

      stack=256, locals=2, args_size=1
         0: new           #1                  // class java/util/LinkedList
         3: dup
         4: invokespecial #11                 // Method java/util/LinkedList."<init>":()V
         7: astore_1
         8: aload_1
         9: ldc           #27                 // int 1
        11: invokestatic  #26                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        14: invokevirtual #3                  // Method java/util/LinkedList.addFirst:(Ljava/lang/Object;)V
        17: return

(...後略)

```

このように、整数はスタックに積むためにいちいちInteger.valueOf()やInteger.intValue()を通すので実行が遅くなります。
(iconst_1も使ってないし。)
最適化を図っていますがいったいいつになるのか。

> ただし、1程度のこの場合では動作に影響はほぼないと思われます。

# おわりに

~~ここまで読んでくださった有難い人はいるのか~~

とりあえず動かすのに必死になっていてJMindのソースコードはまさにフランケンシュタインみたいにつぎはぎですけれど、この記事が誰かの参考になれば幸いです。(なるとは思えない)

以上です。