前回、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')
実際の読み書き処理
ですが、これは例えば
- B1セルは命令文字列
- B4~K6セルはメモリの値
というように決め打ちすれば、比較的楽ではないかと。
またメモリ書き込みは何回も呼び出すので、関数にしておきましょう。
で、コードとしてはこんな感じかな?
(てかpythonって、文字列の切り出しが'変数名[添字]'でいいんですね。
substring()とかいらないので、便利です。)
## 命令文字列の読み込み command_string = str(WORK_SHEET['B1'].value)
## 命令文字列の書き込み関数 ## valueは書き込む値(数値) def writeMemoryToSheet(value): #global POINTER 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
問題の下二つ
ですが、これは「'['や']'があったら、対応する']'や'['を見つける」処理が必要です。
この処理は関数にしてしまい、「対応する'['や']'の位置」は、リスト(他言語でいう配列)で管理すると楽です。
てな訳で、ソースはこちら。
## idxはcommand_string内での'['の位置 ## target_stringはcommand_string。 ## KAKKOは対応する位置を管理するlist(共通関数にする) 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 == "["): ## 2重以上の'['があったら、kakko_countを+1する kakko_count += 1 elif(check_char == "]"): if(kakko_count == 0): ##kakko_countが0の場合の']'は、対応する']'である。 KAKKO[idx] = i KAKKO[i] = idx break else: ##そうじゃない場合の']'は、対応する']'ではないので、kakko_countを-1する。 kakko_count -= 1
で、先ほどの「命令文本体」のところに、こんな分岐処理を追加すればよいかと。
if(char == ">") ## 中略 elif(char == "["): ## 初回のみ、checkKakkoRange()を呼び出す。 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 == "]"): ## ']'の場合、checkKakkoRange()は呼び出し不要。(対応する'['の時点で呼び出されてるはずなので) 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 #print(pyxl.__file__) 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): #global POINTER 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" + "終了")
ちなみに、実際に実行させてみた動画がこちら。
昨日「brainf*ckがマイブーム」ってツイートしたから、pythonでインタプリタ作った!(2.75時間で)。命令文字列とメモリ内容はExcelと連携してる。てか、ガッツリ作ってみると、pythonにもアラ的な所がいろいろあるんだなあ、とよく分かる。 #Python pic.twitter.com/10IfM7SJEc
— マッキー (@makky12) 2018年3月3日
いや、やはりどんなものであれ、まずは1個何か作ってみると、(Pythonに限らず)その言語の理解がより深まりますね。
てかbrainf*ck、「難解言語」とか「ク*言語」とか言われるけど、文字列、メモリ、ポインタの考え方とか、むしろ初学者とかの教育にはかなり向いてるんじゃないの?とか思ったり...
ただ、もっとポインタやメモリの動きがリアルタイムに分かるといいなあ...
(本当はExcelを表示させながらやる予定だったが、起動してたら書き込み時にエラーになるので、断念...
てか、当然といえば当然ですよね...)