Top 8
Outer WildsストーリーMODを色々やってみた
June 18, 2023, 10:04 a.m.表面符号と戯れる【量子コンピューター Advent Calendar 2023 23 日目】
Dec. 23, 2023, 3:28 a.m.位数発見アルゴリズム ~Quantum Zooやっていく【特別編】~
Jan. 27, 2023, 2:50 p.m.ストーリー追加 Mod: The Outsider やっていく日記【Outer Wilds】
Feb. 19, 2023, 6:33 a.m.意識が量子効果で生じることを示す実験結果についてちょっと調べただけのメモ
April 21, 2022, 3:09 p.m.ストーリー追加 MOD: Astral Codec やっていく日記【Outer Wilds】
Feb. 25, 2024, 8:47 a.m.Outer Wilds の量子は計算能力が(ある程度)すごいのではという話
Jan. 15, 2022, 8:35 a.m.MacでAge of Empires 2 DE (AoE2DE)をCrossOverで動かす
May 31, 2021, 11:52 a.m.Pythonでa++(インクリメント)を実現する
March 20, 2022, 3:29 p.m. edited March 21, 2022, 5:22 a.m.Python では a++
ができないのはよく知られる事実。
だが、 a++
したい!!!
という邪悪な話。最終的には
# coding: mylang
a = 5
print(a++)
print(a)
5
6
とできるようになる。なお、今回の実験は Python 3.9.1
かつ venv
環境下でおこなっている。
まずは観察
a++
を普通にやるとどうなるか。
a = 5
print(a++)
と書いて実行。
File "main.py", line 2
a++
^
SyntaxError: invalid syntax
というように構文エラーとなる。うん、知ってた。
新しい構文の追加
a++
は普通の Python では構文エラーとなるので、つまり Python に新たな構文を追加すれば良いということになる。そんな方法はあるのだろうかとググったら Can you add new statements to Python's syntax? の回答にこんなものが。これなら C にまで降りずとも Python で完結できそうだ。
つまり、ファイル冒頭にマジックコメントとして # coding: mylang
として書いておき、この mylang
として従来の utf8
Python に拡張した自作構文を追加するという戦略である(回答の Another fairly neat (albeit hacky) solution の方)。
ということで書いてみるのだが、様々な問題とぶつかることになった。
そもそも mylang
が見つからない
普通は新たな codecs
を追加する場合は、そのファイルを import
して codecs.register()
が通ることで認識されるのだが、マジックコメントの場合は import
の前なので認識できないのである。回答では .pythonrc
か site.py
に書くようにとのことだったが、前者の .pythonrc
は環境変数 PYTHONSTARTUP
に指定する必要があり、というかそもそも対話型での実行でしか動かない。後者の site.py
は Python の実行時に自動でインポートされるものであることを利用してここに書き込んでしまうということだが、さすがにそこまで環境を汚したくはない。
ここで、後者をもう少し追求すると、 usercustomize.py
(もしくは sitecustomize.py
) に行き着く。これは site.py
が実行してくれるスクリプトであり、ユーザが置いておくものらしい。その置き場所は基本的には Python ライブラリフォルダ内であり、やはり環境を汚しかねない1。が、さらに参照場所のパスを追加することができ、 PYTHONPATH
に追加すれば良い。また、 ENABLE_USER_SITE
が False
の環境だと sitecustomize.py
しか見てくれない。
したがって、 mylang
の定義を sitecustomize.py
に書いて、これをプロジェクトのフォルダに置いたうえで対象の main.py
を PYTHONPATH=. python main.py
で実行すれば良いということになる。
StreamReader
が呼ばれない
回答のコードを参考に色々試していたのだが、まったく動く気配がない。というか StreamReader
が呼ばれない。なぜだろうと思ってよく見たらこの回答は 2008 年のものだった。それはもう色々仕様が変わっていてもおかしくない。
そこで、もう少し調べると gist のコードが見つかった。 IncrementalDecoder
を継承してコードを差し込む形である。もっとも、このコードでもまだ微妙に動かなかったので、 encodings.utf_8.IncrementalDecoder
が継承している codecs.BufferedIncrementalDecoder
の decode
を参考に修正し、そのうえでインクリメントのコードを入れた。
なお、 untokenize
はトークンタイプおよびトークン文字列のイテレータさえ返せば問題なく動く。
どうやって実装しよう
a++
を置き換えてインクリメントとするシンタックスシュガーを実装しなければならない。単純に
a; a += 1
としてしまうと 2 つの文になってしまうので print
内部などで使えなくなってしまう。そこで、 Python 3.8 で導入されたセイウチ演算子を使う。どのように実装するか悩んだが、素晴らしい回答を見つけたので、
(a:=a+1)-1
とする。
sitecustomize.py
の内容
上記の文献に加えて日本語資料も見つつ、以下の sitecustomize.py
ができた。
import tokenize
from tokenize import TokenInfo
from token import tok_name
import codecs, encodings
from io import StringIO
from encodings import utf_8
from typing import NamedTuple
UTF8 = encodings.search_function('utf8')
class Token(NamedTuple):
type: int
name: str
def inject(a):
l = []
var = None
one_plus = False
for type, name, _, _, _ in a:
#print(tok_name[type], name)
l.append(Token(type, name))
#print(l)
if l[0].type == tokenize.NAME:
if len(l) > 1:
if l[1].name != '+' and l[1].name != '-':
for t, n in l:
yield t, n
l = []
continue
if len(l) < 3:
continue
if l[1].name == '+' and l[2].name == '+':
yield tokenize.OP, '('
yield tokenize.OP, '('
yield tokenize.NAME, l[0].name
yield tokenize.OP, ':='
yield tokenize.NAME, l[0].name
yield tokenize.OP, '+'
yield tokenize.NUMBER, '1'
yield tokenize.OP, ')'
yield tokenize.OP, '-'
yield tokenize.NUMBER, '1'
yield tokenize.OP, ')'
l = []
continue
if l[1].name == '-' and l[2].name == '-':
yield tokenize.OP, '('
yield tokenize.OP, '('
yield tokenize.NAME, l[0].name
yield tokenize.OP, ':='
yield tokenize.NAME, l[0].name
yield tokenize.OP, '-'
yield tokenize.NUMBER, '1'
yield tokenize.OP, ')'
yield tokenize.OP, '+'
yield tokenize.NUMBER, '1'
yield tokenize.OP, ')'
l = []
continue
for t, n in l:
yield t, n
l = []
continue
elif l[0].name == '+':
if len(l) > 1:
if l[1].name != '+' and l[1].name != '-':
for t, n in l:
yield t, n
l = []
continue
if len(l) < 3:
continue
if l[1].name == '+' and l[2].type == tokenize.NAME:
yield tokenize.OP, '('
yield tokenize.NAME, l[2].name
yield tokenize.OP, ':='
yield tokenize.NAME, l[2].name
yield tokenize.OP, '+'
yield tokenize.NUMBER, '1'
yield tokenize.OP, ')'
l = []
continue
for t, n in l:
yield t, n
l = []
continue
elif l[0].name == '-':
if len(l) > 1:
if l[1].name != '+' and l[1].name != '-':
for t, n in l:
yield t, n
l = []
continue
if len(l) < 3:
continue
if l[1].name == '-' and l[2].type == tokenize.NAME:
yield tokenize.OP, '('
yield tokenize.NAME, l[2].name
yield tokenize.OP, ':='
yield tokenize.NAME, l[2].name
yield tokenize.OP, '-'
yield tokenize.NUMBER, '1'
yield tokenize.OP, ')'
l = []
continue
for t, n in l:
yield t, n
l = []
continue
for t, n in l:
yield t, n
l = []
for t, n in l:
yield t, n
l = []
def transform(stream):
#print("call transform")
a = tokenize.generate_tokens(StringIO(stream).readline)
return tokenize.untokenize(inject(a))
def decode(input, errors='strict'):
#print('call decode')
if isinstance(input, memoryview):
input = input.tobytes().decode('utf-8')
return UTF8.decode(transform(StringIO(input)), errors)
class IncrementalDecoder(utf_8.IncrementalDecoder):
def decode(self, input, final=False):
#print('decode in incrementaldecoder')
return transform(super().decode(input, final))
class StreamReader(utf_8.StreamReader):
def __init__(self, *args, **kwargs):
codecs.StreamReader.__init__(self, *args, **kwargs)
self.stream = StringIO(transform(self.stream))
def search_function(s):
#print('call search_function')
if s != 'mylang':
return None
#print('mylang is used')
return codecs.CodecInfo(
name='mylang',
encode=UTF8.encode,
decode=decode,
incrementalencoder=UTF8.incrementalencoder,
incrementaldecoder=IncrementalDecoder,
streamreader=StreamReader,
streamwriter=UTF8.streamwriter)
codecs.register(search_function)
これをプロジェクトフォルダ(適当に ~/hoge/
)に置いたうえで ~/hoge/main.py
に
# coding: mylang
a = 5
print(a++)
print(a)
b = ++a
print(b--)
print(--b)
print(b)
と書いて ~/hoge
内で PYTHONPATH=. python main.py
と実行すると
5
6
7
5
5
と期待していた出力が得られる。
今後の展望
これせっかくなのでライブラリにしたいなぁ・・・。
-
python -m site
で確認できる。このsys.path
に入っていれば良いみたい ↩
Top 8
Outer WildsストーリーMODを色々やってみた
June 18, 2023, 10:04 a.m.表面符号と戯れる【量子コンピューター Advent Calendar 2023 23 日目】
Dec. 23, 2023, 3:28 a.m.位数発見アルゴリズム ~Quantum Zooやっていく【特別編】~
Jan. 27, 2023, 2:50 p.m.ストーリー追加 Mod: The Outsider やっていく日記【Outer Wilds】
Feb. 19, 2023, 6:33 a.m.意識が量子効果で生じることを示す実験結果についてちょっと調べただけのメモ
April 21, 2022, 3:09 p.m.ストーリー追加 MOD: Astral Codec やっていく日記【Outer Wilds】
Feb. 25, 2024, 8:47 a.m.Outer Wilds の量子は計算能力が(ある程度)すごいのではという話
Jan. 15, 2022, 8:35 a.m.MacでAge of Empires 2 DE (AoE2DE)をCrossOverで動かす
May 31, 2021, 11:52 a.m.Tags
- #Python (26)
- #量子力学 (25)
- #量子情報 (23)
- #Unity (11)
- #Outer Wilds (11)
- #数学 (9)
- #Mac (9)
- #AoE2 (8)
- #Linux (7)
- #Quantum Zoo (6)
- #意識 (5)
- #シミュレーション (5)
- #NumPy (5)
- #Bash (5)
- #相対論 (4)
- #Docker (4)
- #Android (4)
- #Qiskit (4)
- #Rust (3)
- #PyO3 (3)
- #GitHub (3)
- #Django (2)
- #情報理論 (2)
- #LaTeX (2)
- #AR (2)
- #Git (2)
- #iOS (2)
- #C++ (2)
- #正規表現 (2)
- #論文 (2)
- #電磁気学 (1)
- #Google Drive (1)
- #Overleaf (1)
- #Let's Encrypt (1)
- #ポケモン (1)
- #AdMob (1)
- #Autoya (1)
- #docopt (1)
- #SymPy (1)
- #AWS (1)
- #Twitter (1)
- #URP (1)
- #iMovie (1)
- #PyTorch (1)
- #C# (1)
- #Vim (1)