でぶ Advent Calendar 2022 21日目 shojinさん用の言語を作りました

この記事は「でぶ Advent Calendar 2022」向けのジョーク記事です。普段は硬派なマラソンブログをやっています。

adventar.org

初めに

shojinさんがDebuScriptという言語を欲していたので作りました。

DebuScriptについて

基本的にはBrainfxxkの各文字をshojinさんが使いやすいようになじみのある単語に置き換えました。

DebuScript(Brainfxxk)の仕様について

Brainfxxkは多くの人がわかりやすい解説を書いているので、詳細は割愛します。 詳しく勉強したい方はきちんと書かれた記事を参照していただければと思います。

大雑把な理解としては以下のような感じです:

  • 1byteの値(0~255)が入る配列(memory)があります
  • 現在が配列の何番目の要素を指しているかを記録する場所があります(pointer)
  • Brainfxxkは入力されたプログラムに従って、このmemoryとpointerに対して操作を行ないます:
    • pointerの値をインクリメント・デクリメントする
    • pointerの指すmemoryの値をインクリメント・デクリメント
    • pointerの指すmemoryの値を標準出力(asciiコード)
    • 標準入力から1byteの文字を取得してpointerの指すmemoryに代入する
    • 特定の条件でのコードジャンプ(while文に相当)

これだけでチューリング完全プログラミング言語になっているらしいです。すごい。 やる気を出せばchatGPTだって書けてしまいます。

DebuScriptの文法

BrainfxxkとDebuScriptの対応関係は

Brainfxxk DebuScript 内容
+ なんや memory[pointer]++
- でぶ memory[pointer]--
> でぶばんわー☆彡 pointer++
< でぶ㊙️情報 pointer--
. でぶでぶしてきた memory[pointer]を標準出力
, デブシャッ‼️😡✨シャ‼️😡✨シャ‼️😡✨シャ‼️😡✨シャ‼️😡✨シャッッ‼ハッピー😡スマイル 標準入力から1byteの入力を取得してmemory[pointer]に代入する
[ でぶでぶ memory[pointer] == 0 の時、次のでぶ我慢リーチまでジャンプする
] でぶ我慢リーチ memory[pointer] > 0 の時、前のでぶでぶまでジャンプする

また、以下のような特徴があります:

  • 空白文字と改行を全て無視
  • 文字の区切りとして😡を2文字以上(上限はない)を必ず入れないといけない
  • シャープ以降はコメントとして無視される

インタープリタ

pythonで動くインタープリタのコード(nannya.py)を用意しました。Python3.10以上で動作します。 以下のように使ってください:

python nannya.py main.debu
# [nannya.py] 
import re
import sys
from typing import List, Literal, get_args


MEMORY_SIZE = 100

DebuChar = Literal[
    "なんや", # pointerが指すメモリの値をインクリメント
    "でぶ", # pointerが指すメモリの値をデクリメント
    "でぶばんわー☆彡", # ポインタの値をインクリメント
    "でぶ㊙️情報", # ポインタの値をデクリメント
    "でぶでぶしてきた", # memory[pointer]の値を標準出力
    "デブシャッ‼️😡✨シャ‼️😡✨シャ‼️😡✨シャ‼️😡✨シャ‼️😡✨シャッッ‼ハッピー😡スマイル", # 1byteの入力を取得
    "でぶでぶ", # memory[pointer]==0の時、次のでぶ我慢リーチまでジャンプする
    "でぶ我慢リーチ", # memory[pointer]>0の時、前のでぶでぶまでジャンプする
]

debu_char_list = get_args(DebuChar)


class DebuParseError(Exception):
    pass

class DebuSyntaxError(Exception):
    pass


def parse(program: str) -> List[DebuChar]:
    # 区切り文字😡😡でプログラムを区切る。なお😡は2個以上ならばいくつあってもいい
    # 空白と改行は全て無視される
    debus = [debu for debu in (re.sub("😡{2,}", ",", program.replace("\n", "").replace(" ", "")).split(",")) if len(debu)>0]
    try:
        assert all([(debu in debu_char_list) for debu in debus])
    except: 
        error = ""
        for debu in debus:
            if debu not in debu_char_list:
                error += f"{debu} "
        raise DebuParseError(f"""
            なんや😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡
            文法が間違っているねん😡😡😡😡😡😡😡😡😡😡😡😡😡😡
            {error}
        """)
    return debus
    
def next_while_finish(debus: List[DebuChar], start) -> int:
    pos = start+1
    while pos<len(debus):
        if debus[pos] == "でぶ我慢リーチ":
            return pos
        pos += 1
        
    raise DebuSyntaxError("""
            なんや😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡
            でぶでぶに対応するでぶ我慢リーチがないねん😡😡😡😡
    """)
    
def prev_while_start(debus: List[DebuChar], start) -> int:
    pos = start-1
    while pos>=0:
        if debus[pos] == "でぶでぶ":
            return pos
        pos -= 1
        
    raise DebuSyntaxError("""
            なんや😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡
            でぶ我慢リーチに対応するでぶでぶがないねん😡😡😡😡
    """)
    

def interpret(commands: List[DebuChar]) -> None:
    char_count = 0
    pointer = 0
    memory = [0 for _ in range(MEMORY_SIZE)]

    while char_count<len(commands):
        # print(char_count, debus[char_count], len(debus))
        match commands[char_count]:
            case "なんや":
                memory[pointer] += 1
                memory[pointer] = (memory[pointer]+256)%256
            case "でぶ":
                memory[pointer] -= 1
                memory[pointer] = (memory[pointer]+256)%256
            case "でぶばんわー☆彡":
                pointer += 1
            case "でぶ㊙️情報":
                pointer -= 1
            case "でぶでぶしてきた":
                print(f"{chr(memory[pointer])}", end="")
            case "デブシャッ‼️😡✨シャ‼️😡✨シャ‼️😡✨シャ‼️😡✨シャ‼️😡✨シャッッ‼ハッピー😡スマイル":
                memory[pointer] = ord(sys.stdin.buffer.read(1))
            case "でぶでぶ":
                if memory[pointer] == 0:
                    char_count = next_while_finish(commands, char_count)
            case "でぶ我慢リーチ":
                if memory[pointer] > 0:
                    char_count = prev_while_start(commands, char_count)
            case _:
                raise Exception("でぶ")

        char_count += 1

        if pointer<0 or pointer>=MEMORY_SIZE:
            raise Exception("メモリエラーやねん😡😡😡😡😡😡😡😡😡")
        

if __name__ == "__main__": 
    file = sys.argv[1]
    program = ""
    with open(file, "r") as f:
        lines = f.readlines()
        for line in lines:
            program += re.sub("#.+$", "", line)
        
    commands = parse(program)
    interpret(commands)

サンプルコード

DEBUと標準出力をするには以下のようなコードを書けば良いです(インタープリタで動かしてみよう!)

# [debu.debu]
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡               # ここまでで68回memory[0]をインクリメント
でぶでぶしてきた😡😡                         # print(68) = Dを出力
なんや😡😡でぶでぶしてきた😡😡                # print(69) = Eを出力
でぶ😡😡でぶ😡😡でぶ😡😡でぶでぶしてきた😡😡   # print(66) = Bを出力
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡
でぶでぶしてきた😡😡                        # print(85) = Uを出力
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡でぶ😡😡
でぶでぶしてきた😡😡                        # print(10) = 改行コードを出力
python nannya.py debu.debu
> DEBU

うまくDEBUが標準出力されました。

コードの改善

前節のコードは動くものの若干fatな感じがしますね。 上のコードではpointer=0のままでしたが、pointerのインクリメント・デクリメントとwhileの仕組みを使うとコード量を削減することができます。 以下のような感じです:

なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡 # memory[0]=10

でぶでぶ😡😡 # loopのはじまり
でぶばんわー☆彡😡😡 # pointer=1
なんや😡😡なんや😡😡なんや😡😡なんや😡😡 
なんや😡😡なんや😡😡なんや😡😡 # 一回につきmemory[1]に7を足す

でぶばんわー☆彡😡😡 # pointer=2
なんや😡😡なんや😡😡なんや😡😡なんや😡😡 
なんや😡😡なんや😡😡なんや😡😡なんや😡😡 

でぶばんわー☆彡😡😡 # pointer=3
なんや😡😡

でぶ㊙️情報😡😡でぶ㊙️情報😡😡でぶ㊙️情報😡😡 # pointer=0
でぶ😡😡 # memory[0]を1減らす
でぶ我慢リーチ😡😡 # memory[0]>0の場合にはでぶでぶに戻る, memory[0]==0の場合にはループ終了

# 上記のループが終わった時点でmemory[1]=70, memory[2]=80, memory[3]=10

でぶばんわー☆彡😡😡 # pointer=1
でぶ😡😡でぶ😡😡 # memory[1]=68
でぶでぶしてきた😡😡                         # print(68) = Dを出力
なんや😡😡でぶでぶしてきた😡😡                 # print(69) = Eを出力
でぶ😡😡でぶ😡😡でぶ😡😡でぶでぶしてきた😡😡     # print(66) = Bを出力

でぶばんわー☆彡😡😡 # pointer=2
なんや😡😡なんや😡😡なんや😡😡なんや😡😡なんや😡😡 # memory[2] = 85
でぶでぶしてきた😡😡                        # print(85) = Uを出力

でぶばんわー☆彡😡😡 # pointer=3
でぶでぶしてきた😡😡                        # print(10) = 改行コードを出力

うまくwhile文を使ってコード量を減らせました。ダイエット成功です!

DebuScriptはチューリング完全なのでAtCoderの問題だって解けるぞ

(Brainfxxkで解きやすい問題とその答えは https://koboshi-kyopro.hatenablog.com/entry/2021/09/02/190613 こちらのブログから借りてきました)

最も簡単そうなのはABC151A(https://atcoder.jp/contests/abc151/tasks/abc151_a) です。1文字を読み取って、次の文字にすれば良いので、以下のようにプログラムを書けばいいです:

デブシャッ‼️😡✨シャ‼️😡✨シャ‼️😡✨シャ‼️😡✨シャ‼️😡✨シャッッ‼ハッピー😡スマイル😡😡
なんや😡😡でぶでぶしてきた😡😡

簡単ですね。

AIはDebuScriptを理解できなかった...

最後に

AtCoder言語アップデートの時にDebuScriptも入れてもらいましょう。