前回、Pythonで簡単なExcel操作について書きました。
今回はもうちょっと踏み込んだものを、と考えた結果「brainf*ck」のインタプリタをPythonで作る事にしました。
ちなみに「brainf*ck」とは...
Brainfuck(ブレインファック)は難解プログラミング言語のひとつ。なお名称に卑語が含まれるため、Brainf*ckなどと表記されることがある。
(Wikipediaより)
だそうです。
ちなみに、下記命令文字列は「Hello,Python」と出力するものなのですが...なんのことやら、さっぱりですね。
+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.<<++++++++.>>>+++++++++++[>+++++++++++<-]>.-----.<++++[>---<-]>.<<<.-.
なお参考としては、先述のWikipedia(https://ja.wikipedia.org/wiki/Brainfuck)、
また実際の動きを見たい場合、以前ネットニュースでも話題になった、t-kihiraさんの動画が大変分かりやすくてよいと思います。
(こちらはjavascriptで作成してます。てかHTMLやDOM操作など全込みで1時間は凄すぎ!)
nicotter.net
どこをExcelと連携するか
ですが、brainf*ckには
- 命令文字列(><+-.,[])の読み込み
- メモリへの書き込み
があるので、前者をExcelシートから読み込み、後者をExcelシートに書き込もうと思います。
共通変数は?
共通変数(≒クラス変数)ですが、先述のWikipediaに、brainf*ckで管理が必要そうなものとして
がありますので、これを共通変数にすればよいかと。
また、「メモリのアドレスの個数」あたりも共通変数にしとくと便利そうです。
あ、肝心のExcel関連(ブック・シート)も共通変数にしたほうが便利そうですね。
で、まあこんな感じかと。
import sys
sys.path.append([openpyxlの格納パス])
import openpyxl as pyxl
from openpyxl import load_workbook
MEMORY = []
POINTER = 0
MEMORY_LENGTH = 30
RESULT_STRING=""
WORK_BOOK = load_workbook('brainf_ck.xlsx')
WORK_SHEET = WORK_BOOK.get_sheet_by_name('Sheet1')
実際の読み書き処理
ですが、これは例えば
というように決め打ちすれば、比較的楽ではないかと。
またメモリ書き込みは何回も呼び出すので、関数にしておきましょう。
で、コードとしてはこんな感じかな?
(てかpythonって、文字列の切り出しが'変数名[添字]'でいいんですね。
substring()とかいらないので、便利です。)
command_string = str(WORK_SHEET['B1'].value)
def writeMemoryToSheet(value):
memory_string_length = len("000" + str(value))
memory_string = ("000" + str(value))[memory_string_length -3:]
row = 0
col = (POINTER % 10) + 2
if(POINTER <= 9):
row = 4
elif(POINTER >= 10 and POINTER <= 19):
row = 5
else:
row = 6
WORK_SHEET.cell(row=row, column=col).value = memory_string
命令文本体
で、肝心の命令文ですが、Wikipediaによると、
- >: ポインタをインクリメントする。ポインタをptrとすると、C言語の「ptr++;」に相当する。
- <: ポインタをデクリメントする。C言語の「ptr--;」に相当。
- +: ポインタが指す値をインクリメントする。C言語の「(*ptr)++;」に相当。
- -: ポインタが指す値をデクリメントする。C言語の「(*ptr)--;」に相当。
- .: ポインタが指す値を出力に書き出す。C言語の「putchar(*ptr);」に相当。
- ,: 入力から1バイト読み込んで、ポインタが指す先に代入する。C言語の「*ptr=getchar();」に相当。
- [: ポインタが指す値が0なら、対応する ] の直後にジャンプする。C言語の「while(*ptr){」に相当。
- ]: ポインタが指す値が0でないなら、対応する [ (の直後[1])にジャンプする。C言語の「}」に相当[2]。
ということですが、下2つ以外は、特に問題ないかと思います。
他の「brainf*ck作ったよ!」というサイトなどでも説明されていますが、
- POINTERの値を加減する
- MEMORY[POINTER]の値を加減する...
といった感じですね。
MEMORY[POINTER]の値を加減した場合、先程の関数でExcelにも値を書きます。
また、コンソールからの入力にも対応してあげなくてはなりません(=input()関数)
※chr(value)関数は、valueの文字コードを文字に変換する関数。
逆にord(value)関数は、valueの文字を文字コードに変換する関数。
てかPythonって、switch case構文ないんですね...
i = 0
while i < len(command_string):
char = command_string[i]
if(char == ">"):
POINTER += 1
if(POINTER >= MEMORY_LENGTH):
print("エラー:不正な\'>\'があります。")
return
elif (char == "<"):
POINTER -= 1
if(POINTER < 0):
print("エラー:不正な\'<\'があります。")
return
elif(char == "+"):
MEMORY[POINTER] += 1
writeMemoryToSheet(MEMORY[POINTER])
elif(char == "-"):
MEMORY[POINTER] -=1
writeMemoryToSheet(MEMORY[POINTER])
elif(char == "."):
RESULT_STRING += chr(MEMORY[POINTER])
elif(char == ","):
while True:
input_char = input("半角英数1文字を入力してください。")
if(len(input_char) != 1):
print("エラー:1文字ではありません。")
else:
char_code = ord(input_char)
if(char_code < 0 or 127 < char_code):
print("エラー:半角文字ではありません。")
else:
MEMORY[POINTER] = char_code
writeMemoryToSheet(MEMORY[POINTER])
break
i += 1
問題の下二つ
ですが、これは「'['や']'があったら、対応する']'や'['を見つける」処理が必要です。
この処理は関数にしてしまい、「対応する'['や']'の位置」は、リスト(他言語でいう配列)で管理すると楽です。
てな訳で、ソースはこちら。
def checkKakkoRange(idx, target_string):
for i in range(idx + 1, len(target_string)):
kakko_count = 0
check_char = target_string[i]
if(check_char == "["):
kakko_count += 1
elif(check_char == "]"):
if(kakko_count == 0):
KAKKO[idx] = i
KAKKO[i] = idx
break
else:
kakko_count -= 1
で、先ほどの「命令文本体」のところに、こんな分岐処理を追加すればよいかと。
if(char == ">")
elif(char == "["):
if(KAKKO[i] == -1):
checkKakkoRange(i, command_string)
if(MEMORY[POINTER] == 0):
if(KAKKO[KAKKO[i]] == i):
i = KAKKO[i] + 1
continue
else:
print("エラー:\'[\' と \']\' の数が一致しません。")
return
elif(char == "]"):
if(MEMORY[POINTER] != 0):
if(KAKKO[KAKKO[i]] == i):
i = KAKKO[i] + 1
continue
else:
print("エラー:\'[\' と \']\' の数が一致しません。")
return
ソース全体
import sys
sys.path.append("C:\\Program Files\\Anaconda3\\Lib\\site-packages\\")
import openpyxl as pyxl
from openpyxl import load_workbook
MEMORY = []
KAKKO = []
POINTER = 0
MEMORY_LENGTH = 30
RESULT_STRING=""
WORK_BOOK = load_workbook('brainf_ck.xlsx')
WORK_SHEET = WORK_BOOK.get_sheet_by_name('Sheet1')
def init():
for i in range(MEMORY_LENGTH):
global POINTER
POINTER = i
MEMORY.append(0)
writeMemoryToSheet(MEMORY[i])
POINTER = 0
def interpreter():
global RESULT_STRING
global POINTER
command_string = str(WORK_SHEET['B1'].value)
if(command_string.count('[') != command_string.count(']')):
print("エラー:\'[\' と \']\' の数が一致しません。")
return
for j in range(len(command_string)):
KAKKO.append(-1)
i = 0
if(command_string[0] == "'"):
i = 1
while i < len(command_string):
char = command_string[i]
if(char == ">"):
POINTER += 1
if(POINTER >= MEMORY_LENGTH):
print("エラー:不正な\'>\'があります。")
return
elif (char == "<"):
POINTER -= 1
if(POINTER < 0):
print("エラー:不正な\'<\'があります。")
return
elif(char == "+"):
MEMORY[POINTER] += 1
writeMemoryToSheet(MEMORY[POINTER])
elif(char == "-"):
MEMORY[POINTER] -=1
writeMemoryToSheet(MEMORY[POINTER])
elif(char == "."):
RESULT_STRING += chr(MEMORY[POINTER])
elif(char == "["):
if(KAKKO[i] == -1):
checkKakkoRange(i, command_string)
if(MEMORY[POINTER] == 0):
if(KAKKO[KAKKO[i]] == i):
i = KAKKO[i] + 1
continue
else:
print("エラー:\'[\' と \']\' の数が一致しません。")
return
elif(char == "]"):
if(MEMORY[POINTER] != 0):
if(KAKKO[KAKKO[i]] == i):
i = KAKKO[i] + 1
continue
else:
print("エラー:\'[\' と \']\' の数が一致しません。")
return
elif(char == ","):
while True:
input_char = input("半角英数1文字を入力してください。")
if(len(input_char) != 1):
print("エラー:1文字ではありません。")
else:
char_code = ord(input_char)
if(char_code < 0 or 127 < char_code):
print("エラー:半角文字ではありません。")
else:
MEMORY[POINTER] = char_code
writeMemoryToSheet(MEMORY[POINTER])
break
i += 1
def checkKakkoRange(idx, target_string):
for i in range(idx + 1, len(target_string)):
kakko_count = 0
check_char = target_string[i]
if(check_char == "["):
kakko_count += 1
elif(check_char == "]"):
if(kakko_count == 0):
KAKKO[idx] = i
KAKKO[i] = idx
break
else:
kakko_count -= 1
def writeMemoryToSheet(value):
memory_string_length = len("000" + str(value))
memory_string = ("000" + str(value))[memory_string_length -3:]
row = 0
col = (POINTER % 10) + 2
if(POINTER <= 9):
row = 4
elif(POINTER >= 10 and POINTER <= 19):
row = 5
else:
row = 6
WORK_SHEET.cell(row=row, column=col).value = memory_string
init()
interpreter()
WORK_BOOK.save('brainf_ck.xlsx')
print("文字列:" + RESULT_STRING)
print("\n" + "終了")
ちなみに、実際に実行させてみた動画がこちら。
いや、やはりどんなものであれ、まずは1個何か作ってみると、(Pythonに限らず)その言語の理解がより深まりますね。
てかbrainf*ck、「難解言語」とか「ク*言語」とか言われるけど、文字列、メモリ、ポインタの考え方とか、むしろ初学者とかの教育にはかなり向いてるんじゃないの?とか思ったり...
ただ、もっとポインタやメモリの動きがリアルタイムに分かるといいなあ...
(本当はExcelを表示させながらやる予定だったが、起動してたら書き込み時にエラーになるので、断念...
てか、当然といえば当然ですよね...)