埋め込みPython?プリプロセッサ?テンプレートエンジン?


MUGENと言う格闘ゲームエンジンで遊んでいるのですが、

キャラクターやステージを自作しようとすると、出来はともかく、

スクリプトの記述に繰り返しが非常に多い。



何十回も書くのは面倒なので、Pythonでどうにかできないか、と、作ってみました。



まず、埋め込んだPythonコードを実行するクラス。

#preprocessor.py
import re

class Preprocessor(object):
    def __init__(self, vars, debug=False):
        self.vars = vars
        self.debug = debug
        self._regex = None
    
    def regex(self):
        pat = r"""
            {exec_begin}
            (?P.*?)
            {exec_end}(\\(\n|$))?
            |
            {eval_begin}
            (?P.*?)
            {eval_end}
            |
            {raw_begin}
            (?P.*?)
            {raw_end}(\\(\n|$))?
            |
            (?P.+?(?={exec_begin}|{eval_begin}|{raw_begin}|$))
        """.format(
            exec_begin  &#061;re.escape("<["),
            exec_end    &#061;re.escape("]>"),
            eval_begin  &#061;re.escape("$"),
            eval_end    &#061;re.escape("$"),
            raw_begin   &#061;re.escape("<!"),
            raw_end     &#061;re.escape("!>"),
        )
        return re.compile(pat, re.DOTALL | re.VERBOSE | re.MULTILINE)
    
    def process(self, src, **kw):
        pp = self.vars["preprocessor"]
        pp.__dict__.update(**kw)
        
        def iter_strs():
            for m in self.regex().finditer(src):
                if self.debug:
                    pprint(m.groupdict())
                
                if m.group("exec"):
                    ss = m.group("exec")
                    pp.clear()
                    exec ss in self.vars
                    yield pp.getvalue()
                elif m.group("eval"):
                    ss = m.group("eval")
                    yield eval(ss, self.vars)
                elif m.group("raw"):
                    yield m.group("raw")
                elif m.group("others"):
                    yield m.group("others")
        
        return "".join(map(unicode, iter_strs()))



次に、埋め込んだPythonから使うコード

#macro.py
from StringIO import StringIO
import codecs

class _PPWriter(object):
    def __init__(self):
        self._fp = StringIO()
        self.encoding = "utf8"
        
    def write(self, *a, **kw):
        self._fp.write(*a, **kw)
    
    def getvalue(self):
        return self._fp.getvalue()
    
    def clear(self):
        self._fp = StringIO()
    
preprocessor = _PPWriter()
del _PPWriter

def pw(s):
    preprocessor.write(s)

def include(path, encoding=None):
    if encoding is None:
        encoding = preprocessor.encoding
    with codecs.open(path, encoding) as fp:
        return fp.read()


最後にPreprocessorを操作する本体

#main.py
from __future__ import print_function
from preprocessor import Preprocessor

with open(sys.argv[1]) as fp:
    src = unicode(fp.read())

vars = {}
exec "from macro import *" in vars
pp = Preprocessor(vars)

print(pp.process(src))



それで、たとえば、次のようなステージ用スクリプトを書く

;spam_stage_.def
[Info]
name = "Spam Stage"

<[
size = 10
temp = '''
[BG python]
type  = normal
spriteno = 1, {index}
start =  {p[0]}, {p[1]}
'''

def bg(index):
    y = index * size
    x = 0
    return temp.format(index=index, p=(x, y))

for i in xrange(3):
    pw(bg(i))
]>



実行すると、次のように出力されます。

D:\Owner\temp>python main.py "spam_stage_.def"
;spam_stage_.def
[Info]
name = "Spam Stage"


[BG python]
type  = normal
spriteno = 1, 0
start =  0, 0

[BG python]
type  = normal
spriteno = 1, 1
start =  0, 10

[BG python]
type  = normal
spriteno = 1, 2
start =  0, 20



"<[ ]>"で囲んだ部分が、Pythonコードとして認識されて、execされます。



"<[ ... ]>"の中でpw関数を呼び出すと、

引数の文字列がその場所に書き出されます。


また、"$ $" で囲むと、evalされて、結果が書き出されます。


たとえば、spam_stage_.defはこう書いても良い

;spam_stage_.def
[Info]
name = "Spam Stage"

<[
size = 10
temp = '''
[BG python]
type  = normal
spriteno = 1, {index}
start =  {p[0]}, {p[1]}
'''

def bg(index):
    y = index * size
    x = 0
    return temp.format(index=index, p=(x, y))
]>
$pw(0)$
$pw(1)$
$pw(2)$



あと、""で囲んだ部分は、そのまま書き出されます。


まぁ、実際にMUGENユーザーに使ってもらえるかどうかはわかりませんが、
そのうち、Windows用の実行ファイルにして出してみようと思います。


ちなみに、テンプレートエンジンは、本格的なものもありますが、

軽量のものとしては、

inforno::埋め込みPythonを実装してみました

のものがあるようです。





実際、わざわざ自分で作らなくても・・・いやいや!作ってみる事に意味があるのです!