Uniqfuck: Brainfuck Translator

Uniqfuck

Brainfuck プログラムと Brainfuck 派生言語プログラムを相互変換するプログラムを作った。 名前は Uniqfuck とした。 ソースコードは記事の最後に載せる。 ライセンスは MIT License とする。

使い方

Uniqfuck の使い方を簡単に説明する。 Python が読める方はソースを読んだほうがはやい。

必須入力ファイルはソースファイルと文法定義ファイルである。 文法定義は JSON で記述する。

例: Mukadefuck

使い方の例として新たな Brainfuck 派生言語を作った。 言語名は Mukadefuck とした。

文法定義: mukade.json

{
    "commands": {
        ">": "起立! 気を付け!",
        "<": "これがバーチャルYouTuberなんだよなあ……。",
        "+": "よいしょ。",
        "-": "わたくしで隠さなきゃ……!",
        ",": "クソ雑魚パンチやめてくださいwww",
        ".": "僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです!",
        "[": "ぜろ……。ぜろ……。ぜろ……。",
        "]": "おるやんけ! おるやんけ! どなたかおるやんけ!"
    },
    "special_characters": {
        "delimiter": " ",
        "r_delimiter": ""
    }
}

Brainfuck -> Mukadefuck

"Hello, world!" を出力する Brainfuck プログラムを Mukadefuck に変換してみる。 ソースコードニコニコ大百科から引用した。

helloworld.brainfuck

+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+
++.>-.------------.<++++++++.--------.+++.------.--------.>+.

出典: http://dic.nicovideo.jp/a/brainfuck

次のコマンドを実行すると

$ ./uniqfuck.py -o helloworld.mukadefuck mukade.json helloworld.brainfuck

次のファイルが得られる。

helloworld.mukadefuck

よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 ぜろ……。ぜろ……。ぜろ……。 起立! 気を付け! よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 起立! 気を付け! よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 起立! 気を付け! よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 これがバーチャルYouTuberなんだよなあ……。 これがバーチャルYouTuberなんだよなあ……。 これがバーチャルYouTuberなんだよなあ……。 わたくしで隠さなきゃ……! おるやんけ! おるやんけ! どなたかおるやんけ! 起立! 気を付け! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! 起立! 気を付け! よいしょ。 よいしょ。 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! よいしょ。 よいしょ。 よいしょ。 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! 起立! 気を付け! わたくしで隠さなきゃ……! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! これがバーチャルYouTuberなんだよなあ……。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! よいしょ。 よいしょ。 よいしょ。 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! 起立! 気を付け! よいしょ。 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです!

Mukadefuck -> Brainfuck

'X' を 8 個出力する Mukadefuck プログラムを Brainfuck に変換してみる。

x8.mukadefuck

# 'X' を 8 個出力するプログラムを作りましょう。

# ASCII コードの 'X' に対応する数値 88 を 3 番地に用意します。
# まずカウントダウン用に 0 番地に 8 を用意します。
「よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。」

# ここから 0 番地の値が 0 になるまでループします。
「ぜろ……。ぜろ……。ぜろ……。」
    # 2 番地に移動します。
    「起立! 気を付け! 起立! 気を付け!」
    # カウントダウン用 2 番地に 11 を用意します (8 * 11 = 88)。
    「よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。」
    # 2 番地が 0 になるまでループします。
    「ぜろ……。ぜろ……。ぜろ……。」
        # 3 番地に移動し、1 加えます。
        # ここに 88 が用意されます。
        「起立! 気を付け! よいしょ。」
        # 2 番地に移動し、1 引きます。
        「これがバーチャルYouTuberなんだよなあ……。わたくしで隠さなきゃ……!」
    # 2 番地が 0 であれば、次に進みます。
    「おるやんけ! おるやんけ! どなたかおるやんけ!」

    # 1 番地に移動し 1 加えます。
    # なぜかというと、'X' を 8 回表示するために、
    # カウントダウン用の 8 を用意する必要があるからです
    # (「僕はね音楽なんて(略)」を 8 回書くと大変)。
    # ここのループは 8 回繰り返されるので 8 が得られます。
    「これがバーチャルYouTuberなんだよなあ……。よいしょ。」
    # 0 番地に移動して 1 引きます。
    「これがバーチャルYouTuberなんだよなあ……。わたくしで隠さなきゃ……!」
# 0 番地が 0 であれば、次に進みます。
「おるやんけ! おるやんけ! どなたかおるやんけ!」

# 1 番地に移動します (1 番地の値は 8 です)。
「起立! 気を付け!」
# ここから 1 番地の値が 0 になるまでループします。
「ぜろ……。ぜろ……。ぜろ……。」
    # 3 番地まで移動します。
    「起立! 気を付け!起立! 気を付け!」
    # 3 番地の値 88、つまり 'X' を出力します。
    「僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです!」
    # 1 番地に移動して、1 引きます。
    「これがバーチャルYouTuberなんだよなあ……。これがバーチャルYouTuberなんだよなあ……。わたくしで隠さなきゃ……!」
# 1 番地が 0 であればプログラムは終了します。
「おるやんけ! おるやんけ! どなたかおるやんけ!」

# これでプログラムは終了です。楽しんでいただけましたか?
# 気を付け! 着席!
# 以上、Mukadefuck のプログラムの例をお送りしました。

'-r' オプションを付けて実行すると

$ ./uniqfuck.py  -r -o x8.brainfuck mukade.json x8.mukadefuck

次のファイルが得られる。

x8.brainfuck

++++++++[>>+++++++++++[>+<-]<+<-]>[>>.<<-]

ソースコード

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Copyright (C) 2018 Vanaestea

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import sys
import argparse
import json

def is_uniquely_decodable(codewords):
    def get_residual_words(prefixes, words):
        residual_words = set()
        if len(prefixes) == 0:
            return words

        for prefix in prefixes:
            for word in words:
                if word.find(prefix) == 0:
                    residual_words.add(word[len(prefix):])

        return residual_words

    s = [set()]
    for word in get_residual_words(codewords, codewords):
        s[0].add(word)
    s[0].remove('')

    i = 0
    while True:
        if len(s[i]) == 0:
            return True

        for word in s[i]:
            if word == '' or word in codewords:
                return False

        for t in s[:i]:
            if t == s[i]:
                return True

        s.append(set())
        for word in get_residual_words(codewords, s[i]):
            s[i + 1].add(word)
        for word in get_residual_words(s[i], codewords):
            s[i + 1].add(word)

        i += 1


def main():
    parser = argparse.ArgumentParser(description='Uniqfuck: Meta(?) Brainfuck')
    parser.add_argument('-r', '--reverse', action='store_true', help='convert source code to Brainfuck code')
    parser.add_argument('-s', '--stdout', action='store_true', help='set output to stdout')
    parser.add_argument('-i', '--stdin', action='store_true', help='read source code from stdin')
    parser.add_argument('-o', '--output', nargs='?', default='a.out.txt', help='set output file name')
    parser.add_argument('grammar', metavar='grammar_file', nargs='?', help='grammar definition file')
    parser.add_argument('source', metavar='src_file', nargs='?', help='source file')

    args = parser.parse_args()

    reverse = args.reverse
    stdout = args.stdout
    stdin = args.stdin
    output = args.output
    if args.grammar is None or stdin == False and args.source is None:
        print('must specify files')
        sys.exit()
    grammar = args.grammar
    source = args.source

    commands = None
    special_characters = None
    with open(grammar, 'r') as f:
        grammar_dict = json.load(f)
        commands = grammar_dict['commands']
        special_characters = grammar_dict['special_characters']

    if is_uniquely_decodable(set(v for v in commands.values())) == False:
        print('not uniquely decodable')
        sys.exit()

    if reverse == True:
        commands = dict(((v, k) for k, v in commands.items()))

    source_code = None
    if stdin is True:
        source_code = ''.join(sys.stdin.readlines())
    else:
        with open(source, 'r') as f:
            source_code = ''.join(f.readlines())

    compiled_code = None
    if reverse == True:
        commands_lengths = sorted([len(k) for k in commands.keys()], reverse=True)
        compiled_commands = []
        i = 0

        while i + commands_lengths[-1] < len(source_code):
            for length in commands_lengths:
                if i + length > len(source_code):
                    continue
                command = source_code[i:i + length]
                if command in commands:
                    i += length
                    compiled_commands.append(commands[command])
                    break
            else:
                i += 1
        compiled_code = special_characters['r_delimiter'].join(compiled_commands)
    else:
        compiled_code = special_characters['delimiter'].join(commands[command] for command in source_code if command in commands)

    if stdout == True:
        print(compiled_code)
    else:
        with open(output, 'w') as f:
            f.write(''.join([compiled_code, '\n']))

if __name__ == '__main__':
    main()