Arantium Maestum

プログラミング、囲碁、読書の話題

PythonでLanguage Implementation Patternsのパーサと戯れる Packratパーサのキャッシュクリア

前回のコードだとparse関数を複数回呼び出すと前回のキャッシュが残っていてパースがうまくいかない問題があった。parse呼び出しごとにキャッシュをクリアするようにしたい。

キャッシュ

xparser.pyモジュールのトップレベルにキャッシュのリストとそのリストをループしてすべてのキャッシュをクリアする関数を定義:

_caches = []

def _clear_caches():
    for cache in _caches:
        cache.clear()

_memoデコレータで作成されるキャッシュを_cachesに追加するようにする:

def _memo(func):
    d = {}
    _caches.append(d) # ここを追加

    @functools.wraps(func)
    def f(tokens):
        match d.get(tokens.i, None):
            case int(n):
                tokens.i = n
            case ValueError(_) as e:
                raise e
            case None:
                current_pos = tokens.i
                try:
                    func(tokens)
                    d[current_pos] = tokens.i
                except ValueError as e:
                    d[current_pos] = e
                    raise
    return f

あとはparse関数が呼び出されるたびに_clear_cachesするようにする:

def parse(char_stream):
    _clear_caches() # ここを追加
    tokens = Backtrackable(xl.lex(char_stream), 2)
    _list(tokens)
    _match(tokens, t.Eof)

使ってみる

>>> parse("[x=a]")
>>> parse("[[[[[")
Traceback (most recent call last):
...
ValueError: Invalid token Lbrack()
>>> parse("[abc=xyz, [d, [e]=[x], f, g]]")

前回見られた「二回目以降は第一回とトークン数が合えばパース成功、合わなければ失敗する」という挙動は修正されているようだ。

今回のコード

Python Implementation of Cache-Clearing Packrat Recursive Descent Parser based loosely on Language Implementation Patterns · GitHub

次回

Backtrackableクラスがこれまで見たすべてのトークンを不必要に保持していて、パース終了時にはソースファイルのすべてのトークンがメモリに乗っているのを修正する。