前置き
Pythonにはスライスと呼ばれる非常に便利な構文がある。
>>> src = 'spamhamegg'
>>>
>>> src[2:]
'amhamegg'
>>> src[:-3]
'spamham'
>>> src[::-1]
'ggemahmaps'
それを悪用して、変なコードを書いてみた話。
去年も全く生産的でない記事を書いたので、ハロウィンネタ第二段とも言える。
成果物
こんなコードを書いてみた。
Pythonのコードとして実行可能であることはもちろん、PEP8にもある程度準拠している。1
from brainbite import Biter
_ = Biter()[::...][::...][::...][::...][::...][::...][::...][::...][:...]
_ = _[:...:...][::...][::...][::...][::...][:...][:...:...][::...][::...]
_ = _[:...:...][::...][::...][::...][:...:...][::...][::...][::...]
_ = _[:...:...][::...][...:...][...:...][...:...][...:...][...:][...::...]
_ = _[:...:...][::...][:...:...][::...][:...:...][...:][:...:...][:...:...]
_ = _[::...][:...][...:...][...::...][...:...][...:][...::...][:...:...]
_ = _[:...:...][:][:...:...][...:][...:][...:][:][::...][::...][::...]
_ = _[::...][::...][::...][::...][:][:][::...][::...][::...][:][:...:...]
_ = _[:...:...][:][...:...][...:][:][...:...][:][::...][::...][::...][:]
_ = _[...:][...:][...:][...:][...:][...:][:][...:][...:][...:][...:][...:]
_ = _[...:][...:][...:][:][:...:...][:...:...][::...][:][:...:...][::...]
_ = _[::...][:]()
実行結果
Hello World!
ここでインポートしているbrainbiteパッケージは、ちゃっかりPyPIに登録済みだ。
試してみたかったらpip install brainbite
で入る。2
brainbiteの実装はこちら: GitHub - LouiS0616/brainbite
コアな部分だけここにも貼っておく。
import sys
class BFDataModel:
"""
This holds cells what behaves as the Turing Machine's tape.
"""
def __init__(self, size=30000):
self._data = [0 for _ in range(size)]
self._index = 0
@property
def pointer(self):
return self._index
@pointer.setter
def pointer(self, value):
self._index = value % len(self._data)
@property
def pointee(self):
return self._data[self._index]
@pointee.setter
def pointee(self, value):
self._data[self._index] = value
def increment_pointer(self):
"""This corresponds to operation '>'."""
self.pointer += 1
def decrement_pointer(self):
"""This corresponds to operation '<'."""
self.pointer -= 1
def increment_pointee(self):
"""This corresponds to operation '+'."""
self.pointee += 1
def decrement_pointee(self):
"""This corresponds to operation '-'."""
self.pointee -= 1
def conditional_skip(self) -> bool:
"""This corresponds to operation '['."""
return self.pointee == 0
def unconditional_jump(self):
"""This corresponds to operation ']'."""
pass
def print_pointee(self):
"""This corresponds to operation '.'."""
sys.stdout.write(
chr(self.pointee)
)
def input_to_pointee(self):
"""This corresponds to operation ','."""
self.pointee = ord(sys.stdin.read(1))
from .data_model import BFDataModel as Model
class _BFLikeInterpreter:
"""
This class should be considered as abstract one.
"""
def __init__(self, parent):
self._parent = parent
self._op_list = []
self._op_dict = {
(None, None, None): Model.print_pointee,
(None, None, ...): Model.increment_pointee,
(None, ..., None): 'begin of brace',
(None, ..., ...): Model.increment_pointer,
(..., None, None): Model.decrement_pointee,
(..., None, ...): 'end of brace',
(..., ..., None): Model.decrement_pointer,
(..., ..., ...): Model.input_to_pointee,
}
@property
def op_list(self):
return self._op_list
def __getitem__(self, arg):
if not isinstance(arg, slice):
raise TypeError(
f'BFLikeInterpreter indices must be slices, not {type(arg).__name__}'
)
operation = self._op_dict[
arg.start, arg.stop, arg.step
]
# begin of brace
if operation == 'begin of brace':
child = _BFLikeInnerInterpreter(parent=self)
child.op_list.append(
Model.conditional_skip
)
self._op_list.append(child)
return child
# end of brace
if operation == 'end of brace':
self._op_list.append(
Model.unconditional_jump
)
return self._parent
# other
self._op_list.append(operation)
return self
class _BFLikeRootInterpreter(_BFLikeInterpreter):
"""
This class handles whole of program.
So this must be instantiated just one time, no more no less.
"""
def __init__(self):
super().__init__(parent=None)
self._model = Model()
def __call__(self):
for operation in self._op_list:
operation(self._model)
self._op_list = []
return self
class _BFLikeInnerInterpreter(_BFLikeInterpreter):
"""
This class handles part of program what is located between '[' and ']'.
"""
def __call__(self, model):
conditional_skip, *op_list, unconditional_jump = self._op_list
assert conditional_skip is Model.conditional_skip
assert unconditional_jump is Model.unconditional_jump
while True:
if conditional_skip(model):
return self._parent
for operation in op_list:
operation(model)
Biter = _BFLikeRootInterpreter
仕組み
パッケージ名で察している読者もいると思うが、結局Brainf*ckの焼き直しである。
Wikipedia - Brainfuck
Brainf*ckの各命令が、次のようにスライスに対応している。
機能 (Wikipediaより引用) | ||
---|---|---|
> |
[:...:...] |
ポインタをインクリメントする。 |
< |
[...:...] |
ポインタをデクリメントする。 |
+ |
[::...] |
ポインタが指す値をインクリメントする。 |
- |
[...:] |
ポインタが指す値をデクリメントする。 |
. |
[:] |
ポインタが指す値を出力に書き出す。 |
, |
[...:...:...] |
入力から1バイト読み込んで、ポインタが指す先に代入する。 |
[ |
[:...] |
ポインタが指す値が0なら、対応する ] の直後にジャンプする。 |
] |
[...::...] |
ポインタが指す値が0でないなら、対応する [ (の直後)にジャンプする。 |
...
はあまり見慣れないかもしれないが、Ellipsisと呼ばれる立派な組み込み変数である。
>>> ...
Ellipsis
もう少し詳しい仕組み [スライス編]
メソッド__getitem__
を適切に用意すれば、スライス操作を受け付けるクラスを作ることができる。
class SliceableClass:
def __getitem__(self, key):
print(key)
sliceable = SliceableClass()
sliceable[1:] # slice(1, None, None)
sliceable[::2] # slice(None, None, 2)
sliceable[...:] # slice(Ellipsis, None, None)
スライスオブジェクトには三つの値を指定できるが、省略された場合Noneが渡される。
もちろん明示的に渡しても良い。
>>> src
'spamhamegg'
>>>
>>> src[1:]
'pamhamegg'
>>> src[1:None:None]
'pamhamegg'
もう少し詳しい仕組み [モデル編]
brainbiteの実行モデルの核となるクラスは次の二つである。
Biter
スライスを命令に変換し、キューに押し込んでいく。
命令はcallされたときにまとめて実行される。
実際にはもうちょっと抽象的な作りにしているが、ここでは割愛。3
BFDataModel
実際のデータを保持しているモデルで、Brainf*ckの各命令に対応するメソッドを持つ。
コーディング例
100までの素数を出力するコード。
このコードの大元となるbfコードは、[白月のIT入門 - Brainfuck -] (http://hakugetu.so.land.to/program/brainfuck/1-4.php) からお借りした。
from brainbite import Biter
_ = Biter()[:...:...][::...][::...][::...][::...][:...][...:...][::...]
_ = _[::...][::...][::...][::...][::...][::...][::...][:...:...][...:]
_ = _[...::...][:...:...][::...][::...][::...][::...][::...][::...][::...]
_ = _[::...][:...][...:...][::...][::...][::...][::...][::...][::...]
_ = _[:...:...][...:][...::...][...:...][::...][::...][:][...:...][:]
_ = _[:...:...][::...][:][...:...][:][:...:...][::...][::...][:][...:...][:]
_ = _[:...:...][::...][::...][:][...:...][:][:...:...][...:][...:][...:]
_ = _[...:][...:][...:][:][:][...:...][:][:...:...][:][::...][::...][:]
_ = _[...:...][:][:...:...][...:][...:][:][::...][::...][::...][::...]
_ = _[::...][::...][:][...:...][:][:...:...][...:][...:][...:][...:][...:]
_ = _[...:][:][:...:...][::...][::...][::...][:...][...:...][::...][::...]
_ = _[::...][:...:...][...:][...::...][...:...][...:][:][...:...][:]
_ = _[:...:...][...:][...:][...:][...:][...:][...:][...:][:][::...][:]
_ = _[...:...][:][:...:...][...:][:][::...][::...][::...][::...][::...]
_ = _[::...][::...][:][...:...][:][:...:...][...:][...:][...:][...:][...:]
_ = _[...:][:][...:][...:][:][...:...][:][:...:...][::...][::...][:][::...]
_ = _[::...][::...][::...][:][...:...][:][:...:...][...:][...:][...:][:]
_ = _[...:][...:][...:][:][...:...][:][:...:...][::...][::...][::...][:]
_ = _[...:][:][...:...][:][:...:...][::...][:][::...][::...][::...][:]
_ = _[...:...][:][:...:...][...:][...:][:][...:][...:][:][...:...][:]
_ = _[:...:...][::...][::...][:][::...][::...][::...][::...][:][...:...][:]
_ = _[:...:...][...:][...:][...:][:][...:][...:][...:][...:][...:][:]
_ = _[...:...][:][:...:...][::...][::...][::...][::...][::...][:][::...][:]
_ = _[...:...][:][:...:...][:][...:][...:][...:][...:][...:][...:][:]
_ = _[...:...][:][:...:...][::...][::...][::...][::...][::...][::...][:]
_ = _[...:][...:][...:][...:][:][...:...][:][:...:...][::...][::...][::...]
_ = _[::...][:][::...][::...][:][...:...][:][:...:...][...:][:][...:][...:]
_ = _[...:][...:][...:][:][...:...][:][:...:...][::...][::...][::...][::...]
_ = _[::...][:][::...][:][...:...][:][:...:...][:][...:][...:][:]()
実行結果
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
後書き
相も変わらず、誰の役にも立たないコードを書いてしまった。
しかし書いていてなかなか楽しかったし、パッケージ配布の練習にもなったので良かったこととする。
パッケージのmainモジュールやドキュメントが中途半端なのは、飽きたからである。