脳姦は良くないと思います。
・Brainfuckとは?
難解クソプログラミング言語。<>+-,.[]
の8つの命令だけを使って記述する可読性皆無の言語だが、こんなに簡潔でもチューリング完全なので、どんなアルゴリズムでも記述できてしまう。詳しくはWikipediaとかを参照して欲しい。
・どうせならオリジナリティを出したい
今回はこのBrainfuckのコードを実行するインタプリタを作りたいのだが、もういろんな人が作ってるし今更感があるので、少し改変して新たな類似言語を作り出すことにした。相違点としては、
- ストリームは出力だけにする。入力からの読み込み命令
,
は削除 - ポインタの指す値を左ビットシフトする命令
^
と、右ビットシフトする命令v
を追加 - 出力ストリームのエンコードはUTF-8にする(マルチバイト文字を出力できるようにする)
の3点。インタプリタはPython3で組む。
・できた
そもそもが簡潔な言語なのでアッサリ実装できた。
import io,sys,re
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
#実行時エラーの例外クラス定義
class BFError(Exception):
pass
class PointerUnderFlowError(BFError):
def __init__(self,message):
self.message=message
class InvalidTokenError(BFError):
def __init__(self,message):
self.message=message
#インタプリタ
def execBF(code):
#命令以外の文字を無視
pat=r"[^]><+^v.[-]"
code=re.sub(pat,"",code)
clen=len(code)
ptr=0 #データポインタ
cp=0 #インストラクションポインタ
rg={} #データ領域
out=[] #出力ストリーム
while cp<clen:
token=code[cp]
#データ領域初期化
if ptr not in rg:
rg[ptr]=0
if token==">":
ptr+=1
elif token=="<":
ptr-=1
if ptr<0:
raise PointerUnderFlowError("Pointer Underflow at %d"%cp)
elif token=="+":
rg[ptr]+=1
rg[ptr]%=256
elif token=="-":
rg[ptr]-=1
rg[ptr]%=256
elif token=="^":
rg[ptr]<<=1
rg[ptr]%=256
elif token=="v":
rg[ptr]>>=1
elif token==".":
out.append(rg[ptr])
elif token=="[":
if rg[ptr]==0:
cop=cp
depth=0
while True:
cp+=1
if cp>=clen:
raise InvalidTokenError("Invalid Token '%s' at %d"%(token,cop))
tn=code[cp]
if tn=="[":depth+=1
elif tn=="]":
if depth==0:break
else:depth-=1
elif token=="]":
if rg[ptr]!=0:
cop=cp
depth=0
while True:
cp-=1
if cp<0:
raise InvalidTokenError("Invalid Token '%s' at %d"%(token,cop))
tp=code[cp]
if tp=="[":
if depth==0: break
else: depth-=1
elif tp=="]":depth+=1
cp+=1
bs=bytes(out) #バイト列に変換
print(bs.decode()) #UTF-8でデコードして表示
rcode=input()
execBF(rcode)
だいたい秒間180万ステップは実行できるみたい。
・ためしてガッテン
まずは簡単に、「a」を出力するコードを試してみる。「a」の文字コードは97
なので、ビットシフトを駆使して97
を出力すればよい。
- 入力:
+^+^^^^^+.
- 出力:
a
ちゃんと出た。
次はマルチバイトである。「あ」を試してみよう。「あ」をUTF-8でエンコードすると\xe3\x81\x82
なので、1バイト目に0xe3 = 227
、2バイト目に0x81 = 129
、3バイト目に0x82 = 130
を出力する。
- 入力:
+^+^+^^^^+^+.>+^^^^^^^+.+.
- 出力:
あ
イケてる。最後はちょっと長いコードにトライして「Brainfuckはクソ」を出力してみる。
- 入力:
+^^^^^+^.>>+^+^+^^^+^.<+^^^^+[>-<-]>.++++++++.+++++.--------.<+^^^^-[>+<-]>.<+^^^+^[>-<-]>.++++++++.>+^+^+^^^^+^+.<<+^+^^^--[>+<-]>.>>>+^^+^^+^+^+^+.<<.<+.>>>.<<.<.>>+^^^^--[>+<-]>.
- 出力:
Brainfuckはクソ
完璧。
・どうせならネタ言語にしたい
この9つの命令を別の9つの字句に置き換えることでネタ言語ができる。例えば、
命令 | 字句 |
---|---|
> | あくしろよ |
< | そうだよ(便乗) |
+ | こ↑ |
- | こ↓ |
^ | ファッ!? |
v | まあ多少はね? |
. | ンアーッ! |
[ | いいよ! |
] | 来いよ! |
とか対応させておいて、インタプリタ側で
tokens=[r'あくしろよ', r'そうだよ(便乗)', r'こ↑', r'こ↓', r'ファッ!?',
r'まぁ多少はね?', r'ンアーッ!', r'いいよ!', r'来いよ!']
def preprocess(ocode):
repls=[">","<","+","-","^","v",".","[","]"]
rcode=ocode
for v in range(9):
rcode=re.sub(tokens[v],repls[v],rcode)
return rcode
といった感じで命令文字に置換するだけである。この方法だと、字句に正規表現のメタ文字が入ると正常に動作しないので、?
や ()
などは全角にしておくのが無難だろう。
これでさっきの「Brainfuckはクソ」を表示するコードを書いてみると、こうなる。
こ↑ファッ!?ファッ!?ファッ!?ファッ!?ファッ!?こ↑ファッ!?ンアーッ!あくしろよあくしろよこ↑ファッ!?こ↑ファッ!?こ↑ファッ!?ファッ!?ファッ!?こ↑ファッ!?ンアーッ!そうだよ(便乗)こ↑ファッ!?ファッ!?ファッ!?ファッ!?こ↑いいよ!あくしろよこ↓そうだよ(便乗)こ↓来いよ!あくしろよンアーッ!こ↑こ↑こ↑こ↑こ↑こ↑こ↑こ↑ンアーッ!こ↑こ↑こ↑こ↑こ↑ンアーッ!こ↓こ↓こ↓こ↓こ↓こ↓こ↓こ↓ンアーッ!そうだよ(便乗)こ↑ファッ!?ファッ!?ファッ!?ファッ!?こ↓いいよ!あくしろよこ↑そうだよ(便乗)こ↓来いよ!あくしろよンアーッ!そうだよ(便乗)こ↑ファッ!?ファッ!?ファッ!?こ↑ファッ!?いいよ!あくしろよこ↓そうだよ(便乗)こ↓来いよ!あくしろよンアーッ!こ↑こ↑こ↑こ↑こ↑こ↑こ↑こ↑ンアーッ!あくしろよこ↑ファッ!?こ↑ファッ!?こ↑ファッ!?ファッ!?ファッ!?ファッ!?こ↑ファッ!?こ↑ンアーッ!そうだよ(便乗)そうだよ(便乗)こ↑ファッ!?こ↑ファッ!?ファッ!?ファッ!?こ↓こ↓いいよ!あくしろよこ↑そうだよ(便乗)こ↓来いよ!あくしろよンアーッ!あくしろよあくしろよあくしろよこ↑ファッ!?ファッ!?こ↑ファッ!?ファッ!?こ↑ファッ!?こ↑ファッ!?こ↑ファッ!?こ↑ンアーッ!そうだよ(便乗)そうだよ(便乗)ンアーッ!そうだよ(便乗)こ↑ンアーッ!あくしろよあくしろよあくしろよンアーッ!そうだよ(便乗)そうだよ(便乗)ンアーッ!そうだよ(便乗)ンアーッ!あくしろよあくしろよこ↑ファッ!?ファッ!?ファッ!?ファッ!?こ↓こ↓いいよ!あくしろよこ↑そうだよ(便乗)こ↓来いよ!あくしろよンアーッ!
控えめに言って地獄だ。錯視すら見える。
なお、実行したら正常に動作した。
というわけで、無事Brainfuck系のインタプリタができた。単純ゆえになかなか奥が深い言語なので、暇があればいろいろやってみたい(そんな暇はない)。
あと、記事中の例に淫夢を使うのをそろそろやめたい。
追記
せっかく言語を作ったので名前を付けようと思って適当に考えた結果、「INSTEP」という名前に決定した。
多少強引だが、INterpreter for Supplementary Tokens and Encodable Printing (追加トークンとエンコード可能な出力のためのインタプリタ) の略ということにしておく。
追記
インタプリタを実行できるページを作成した→ここ。テストコードを実行して遊ぼう。