7
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

難解言語Malbolgeで湯婆婆を実装してみた

はじめに

もう落ち着いた気はしますが「Javaで湯婆婆を実装してみる」が流行ってたので、絶対に誰もやらないであろう「Malbolge」で実装してみました。
正確には「Malbolge20」だとは思いますがよくわかりません。
色々妥協した点もあるのでその辺は許してください。
先に言っておくと完成品はなんと173.6 MBとなっており、まず間違いなく湯婆婆史上で一番長いでしょう。
(ただここまで大きくなったのは単純に僕が下手くそなだけだとは思います。)

Malbolgeについて

僕自身がプログラミングに詳しいわけではないので、特徴とかは全く理解してません。
とにかく難解プログラミング言語の中でも一番やばいらしいです。
こちらの記事なんかが参考になるんじゃないでしょうか。

準備

そんなやばい言語を自力で実装なんていくら時間をかけてもできません。
名古屋大学の研究室ではMalbolgeについて研究してるらしく、C風言語からMalbolge20という言語に変換するソースコードが載っています。
これを使うとMalbolgeなんて何も知らなくてもソースコードを生成することが可能です。
それでも中間言語を2回も経由する必要があるのでどれだけやばいのかわかると思います。

  1. C風言語から制御付き疑似命令列への変換 (ソースコード)
  2. 制御付き疑似命令列から低級アセンブリコードへの変換 (ソースコード)
  3. 低級アセンブリコードからMalbolge20への変換 (ソースコード)

この3回の変換を毎回行うのは面倒なのでシェルスクリプトを書いて自動で行うようにしました。

generater.sh
#!/bin/zsh

if [ $# = 0 ]; then
    exit 1
fi

SECONDS=0
./interpreters/c2mg $1 > program.mg # C風言語から制御付き疑似命令列
./interpreters/mg2mc -di program.mg > program.mc # 制御付き疑似命令列から低級アセンブリコード
perl interpreters/mc2data.pl program.mc program # 低級アセンブリコードからMalbolge20 (1)
./interpreters/data2mb program.data > program.mb # 低級アセンブリコードからMalbolge20 (2)
echo $SECONDS

rm -f program.mg program.mc program.data* info # いらないファイル削除

./interpreters/malbolge20 program.mb # 実行

実装

それでは準備もできたので、湯婆婆できるようなC風言語を書いていきます。
まず、C風言語で利用可能な機能はREADMEを見ると以下のようになってます。

  • 制御構造:if / while
  • 型:int / bool
  • 演算:算術(+, -) / 論理AND(&&) / 論理OR(||) / 比較(==, !=, <, >, <=, >=) / 単項(+, -, !) / 複合代入(+=, -=
  • 入出力:putc / getc
  • 関数:再帰呼び出し可能
  • 変数:スカラ変数と配列変数が利用可能

しかし、これはあくまでもC風言語から制御付き疑似命令列への変換でエラーが発生せずにできるというだけです。
色々やってて気づきましたが最後のMalbolge20まで変換することを考慮すると以下の機能は利用しない方が良さそうです。

  • 引き算(制御付き疑似命令列から低級アセンブリコードへの変換でエラー)
  • 配列変数(実行が止まったりセグメンテーション違反が起きたり)
  • 再帰呼び出し(多分できない)

引き算については次の項目で詳しく説明します。

苦戦した点

苦戦した点というか実装上の注意点を説明します。

引き算ができない

はい、なんと引き算ができません。(正確には違うと思いますが)
このことに気づくのにどれだけ試行錯誤したかは省略しておきます...
他の2つはまあなくてもなんとかはなります。
配列が使えないので文字列を全て変数に代入することにはなりましたが。

色々試したら引き算に関して以下のような結果が得られました。

// 実行不可
a = 1 - 1;
a -= 1;
a = -b;

// 実行可能
a = -1;
a += -1;

a += -1が実行できるのをみたときは、ふざけるなと思いました。
まあ、符号の反転こそできないけど引き算はできるようになったので良しとします。

while文が不安定?

なんか実行できたりできなかったりして最初はよくわかりませんでした。
特に引き算みたいな問題とかと絡んでるのかと思って見当違いのことをやったりもしました。
それで「制御付き疑似命令列から低級アセンブリコード」のプログラムはオプションがあったなと思い確認したら

オプション

  • -m / -d
    連続するROTOPRモジュールを呼び出すスタイルを指定する.
    • -m:メインの制御フローに戻ってから次のモジュールを呼び出す
    • -d:直接次のモジュールを呼び出す
      どちらのオプションも指定されなかった場合はランダムに決定される
      モジュールの詳細については,文献[1] に記載.
  • -c / -i
    OUTPUTINPUTSETRESET 命令の変換スタイルを指定する.
    • -c:これらの命令についてもモジュールを生成する
    • -i:モジュールを生成せず,メインの制御フローに展開する
      どちらのオプションも指定されなかった場合はランダムに決定される

とのことでした。
それでオプションを4パターン試してみたら-diでwhile文が止まらずに動いてくれました。
エラーも出てなかったのでこれが原因だとは全く思ってませんでした...

機能

湯婆婆する上で必要な機能の実装方法を説明します。

名前入出力

一番大切な機能ですね。
ちゃんとgetcharputcharは使えるので問題なく実装できます。
ただし、なぜかgetcharに引数がないとエラーが発生するので適当な値を渡してください。

getchar例.c
a = getchar(0);

また配列が上手く使えなかったので最終的にこんな感じになりました。

名前入出力.c
// 入力
tmp = getchar(0);
while (tmp != 10) {
  name_length += 1;
  if (name_length == 1) {
    n1 = tmp;
  } else if (name_length == 2) {
    n2 = tmp;
  } else if (name_length == 3) {
    n3 = tmp;
  } else if (name_length == 4) {
    n4 = tmp;
  } else {
    is_end = 1;
    name_length += -1;
  }
  if (is_end == 1) { // 4文字を超えたら改行されるまでループ
    while (tmp != 10) {
      tmp = getchar(0);
    }
  } else {
    tmp = getchar(0);
  }
}

// 出力
index = 0;
while (index < name_length) {
  index += 1;
  if (index == 1) {
    putchar(n1);
  } else if (index == 2) {
    putchar(n2);
  } else if (index == 3) {
    putchar(n3);
  } else if (index == 4) {
    putchar(n4);
  }
}

ちなみにセリフは全て英語です。

乱数生成

名前をランダムに奪うために必要な機能ですね。
時間が取得できたらそれで終わりなんですが当然できません。
色々試しましたが処理に時間がかかるので一番シンプルな感じで実装しました。
rand += -name_lengthが実行できないせいで大変な感じになりましたね。
また、シードは悩みましたが名前の合計にしました。
同姓同名の人が油屋を訪れることはない筈です。

乱数.c
rand = seed;
while (rand >= name_length) {
  while (index < name_length) {
    rand += -1;
    index += 1;
  }
  index = 0;
}

改善点

実装を諦めたものです。

セリフの日本語化

無理
putcharだけで日本語出力の方法知ってる方いましたら、ぜひコメントお願いします。

配列の使用

最初に使えないとは言いましたが普通には使えないって感じだと思います。
エラーが出るわけでもないしもしかしたら使えるかも...?

完成品

試行錯誤して完成した湯婆婆がこちらです。


C風言語のソースコード
yubaba.c
int tmp, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, name_length = 0, is_end = 0;
int sn1, sn2, sn3, index = 0, rand = 0;
int main() {
  putchar('Y');
  putchar('o');
  putchar('u');
  putchar('r');
  putchar(' ');
  putchar('c');
  putchar('o');
  putchar('n');
  putchar('t');
  putchar('r');
  putchar('a');
  putchar('c');
  putchar('t');
  putchar('.');
  putchar(' ');
  putchar('S');
  putchar('i');
  putchar('g');
  putchar('n');
  putchar(' ');
  putchar('y');
  putchar('o');
  putchar('u');
  putchar('r');
  putchar(' ');
  putchar('n');
  putchar('a');
  putchar('m');
  putchar('e');
  putchar('.');
  putchar(10);


  tmp = getchar(0);
  while (tmp != 10) {
    name_length += 1;
    rand += tmp;
    if (name_length == 1) {
      n1 = tmp;
    } else if (name_length == 2) {
      n2 = tmp;
    } else if (name_length == 3) {
      n3 = tmp;
    } else if (name_length == 4) {
      n4 = tmp;
    } else if (name_length == 5) {
      n5 = tmp;
    } else if (name_length == 6) {
      n6 = tmp;
    } else if (name_length == 7) {
      n7 = tmp;
    } else if (name_length == 8) {
      n8 = tmp;
    } else if (name_length == 9) {
      n9 = tmp;
    } else if (name_length == 10) {
      n10 = tmp;
    } else if (name_length == 11) {
      n11 = tmp;
    } else if (name_length == 12) {
      n12 = tmp;
    } else {
      is_end = 1;
      name_length += -1;
    }
    if (is_end == 1) {
      while (tmp != 10) {
        tmp = getchar(0);
      }
    } else {
      tmp = getchar(0);
    }
  }


  putchar('Y');
  putchar('o');
  putchar('u');
  putchar(''');
  putchar('r');
  putchar('e');
  putchar(' ');

  while (index < name_length) {
    index += 1;
    if (index == 1) {
      putchar(n1);
    } else if (index == 2) {
      putchar(n2);
    } else if (index == 3) {
      putchar(n3);
    } else if (index == 4) {
      putchar(n4);
    } else if (index == 5) {
      putchar(n5);
    } else if (index == 6) {
      putchar(n6);
    } else if (index == 7) {
      putchar(n7);
    } else if (index == 8) {
      putchar(n8);
    } else if (index == 9) {
      putchar(n9);
    } else if (index == 10) {
      putchar(n10);
    } else if (index == 11) {
      putchar(n11);
    } else if (index == 12) {
      putchar(n12);
    }
  }

  putchar(',');
  putchar(' ');
  putchar('h');
  putchar('u');
  putchar('h');
  putchar('?');
  putchar(' ');
  putchar('W');
  putchar('h');
  putchar('a');
  putchar('t');
  putchar(' ');
  putchar('a');
  putchar('n');
  putchar(' ');
  putchar('e');
  putchar('x');
  putchar('t');
  putchar('r');
  putchar('a');
  putchar('v');
  putchar('a');
  putchar('g');
  putchar('a');
  putchar('n');
  putchar('t');
  putchar(' ');
  putchar('n');
  putchar('a');
  putchar('m');
  putchar('e');
  putchar('.');
  putchar(10);


  index = 0;
  while (rand >= name_length) {
    while (index < name_length) {
      rand += -1;
      index += 1;
    }
    index = 0;
  }
  if (rand < 3) {
    sn1 = n1;
    if (n2 != 10) {
      sn2 = n2;
      if (n3 != 10) {
        sn3 = n3;
      }
    }
  } else if (rand < 6) {
    sn1 = n4;
    if (n5 != 10) {
      sn2 = n5;
      if (n6 != 10) {
        sn3 = n6;
      }
    }
  } else if (rand < 9) {
    sn1 = n7;
    if (n8 != 10) {
      sn2 = n8;
      if (n9 != 10) {
        sn3 = n9;
      }
    }
  } else {
    sn1 = n10;
    if (n11 != 10) {
      sn2 = n11;
      if (n12 != 10) {
        sn3 = n12;
      }
    }
  }

  putchar('F');
  putchar('r');
  putchar('o');
  putchar('m');
  putchar(' ');
  putchar('n');
  putchar('o');
  putchar('w');
  putchar(' ');
  putchar('o');
  putchar('n');
  putchar(',');
  putchar(' ');
  putchar('y');
  putchar('o');
  putchar('u');
  putchar(''');
  putchar('l');
  putchar('l');
  putchar(' ');
  putchar('b');
  putchar('e');
  putchar(' ');
  putchar(sn1);
  putchar(sn2);
  putchar(sn3);
  putchar('.');
  putchar(' ');
  putchar('Y');
  putchar('o');
  putchar('u');
  putchar(' ');
  putchar('g');
  putchar('o');
  putchar('t');
  putchar(' ');
  putchar('t');
  putchar('h');
  putchar('a');
  putchar('t');
  putchar('?');
  putchar(10);

  putchar('Y');
  putchar('o');
  putchar('u');
  putchar(''');
  putchar('r');
  putchar('e');
  putchar(' ');
  putchar(sn1);
  putchar(sn2);
  putchar(sn3);
  putchar('.');
  putchar(' ');
  putchar('A');
  putchar('n');
  putchar('s');
  putchar('w');
  putchar('e');
  putchar('r');
  putchar(' ');
  putchar('m');
  putchar('e');
  putchar(',');
  putchar(' ');
  putchar(sn1);
  putchar(sn2);
  putchar(sn3);
  putchar('!');
  putchar(10);
}


これをMalbolge20に変換したコードは173.6 MBもあるため掲載はしません。
実行してみたい方は#準備を見て環境を作ってください。

実行例

実行例
% ./interpreters/malbolge20 yubaba.mb
Your contract. Sign your name.
荻野千尋
You're 荻野千尋, huh? What an extravagant name.
From now on, you'll be 野. You got that?
You're 野. Answer me, 野!

セリフは英語ですがちゃんと名前を奪われてます!!!
ちなみに実行してから名前を奪うのに20分弱かかりました。
湯婆婆もきっともう歳なんでしょう。

また、何文字入力しようがエラーにはなりません。
全角4文字(半角12文字)以上の文字は無視されます。
空文字の場合は空文字が返されるだけ...のはずです。
適当に放置してたのですが名前の確認をした後に出力がされることはありませんでした。

おまけ

湯婆婆のセリフですが日本語が使えなかったので英語のセリフを使っています。
それにあたって英語の字幕を探したのですが聴覚障害者向けと一般向けの2種類がありました。
今後何かに使えるかもしれない知識ですね。
ちなみに今回使ったのは一般向けです。
卒研中にこれを調べてくれたMさんには、この場を借りてお礼申し上げます。

終わりに

元々流れに乗ろうとか思ってたわけではないんですが「軽い気持ちでアセンブリで湯婆婆したらかなり大変だった件」を見て、ならMalbolgeで対抗しようと思って始めました。
何かきっかけがないとこんな言語触ることもないですしね。
おかげで卒研の時間も含めて丸2日間かかってしまいました...
指導教員がこの記事を見ないことを祈ります。
最後まで読んでいただきありがとうございました。

参考文献

Malbolge(名古屋大研究室)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
7
Help us understand the problem. What are the problem?