pyparsing’s result tokens does not contain the token type…

むむ…

pyparsing、非常に広く使われてるんで、安心して使えるもんだと思い込んでいたが、非常にイケてない、しかも「たった一つだけの致命的ダメ仕様」がある。なんだかなぁ。

ふつー、パース処理をするには、「欲しい単位に分解出来たらハッピーねっ」つぅわけではなくて、「分類された分解」が欲しいわけなんである。何言ってるかわかる?

たとえばこんな文字列があるとする:

1 function xyz('(', ')')

そして、

1 '('

部分が「quotedString」として「よきに計らってもらえる」とする。こんな状況で、「トークンリストが欲しいんだぜっ」と頼んだら

1 ["function", "xyz", "(", "(", ")", ")"]

ヲィ。「関数の引数リストを囲む括弧」なのか、「文字列(quotedString)に入ってた括弧」なのか区別でけんじゃないか。馬鹿げてる。

…と、バカだと断言するためにはもう一声必要。「種類も一緒に取れる術が「バカな方法でのみ」ある」、というのが致命傷。なんなのよ、「asXML()」メソッドでのみ欲しい「トークンの種類とトークン文字列がセットになったリスト」が取れる。これでしか得られないとなりゃぁ、「パース処理が欲しくて使ってるのにさらに XML パーサが必要」なんて不毛なことに。

この問題除けば pyparsing は使いやすいんで、非常にもったいない。時間と気力があったらちゃんと整理しようと思うけど、とりあえず、「トークンの種類とトークン文字列がセットになったリスト」を返すメソッドを動的に追加することで逃げてみている:

 1 import types
 2 
 3 from pyparsing import *
 4 
 5 
 6 def _ParseResults_asListWithNames(self):
 7     names = []
 8 
 9     namedItems = dict(
10         (v[1], k)
11         for (k, vlist) in self._ParseResults__tokdict.items()
12         for v in vlist)
13     for i, res in enumerate(self._ParseResults__toklist):
14         #if isinstance(res, ParseResults):
15         #    pass
16         if i in namedItems:
17             names.append(namedItems[i])
18         else:
19             names.append("ITEM")
20 
21     return self._ParseResults__toklist, names
22 
23 
24 def wrap_ParseResults(toks):
25     toks.asListWithName = types.MethodType(_ParseResults_asListWithNames, toks)
26     return toks
27 
28 # ParseResults を受け取る箇所で
29 #     wr_toks = wrap_ParseResults(toks)
30 #     print(wr_toks.asListWithName())
31 # とする。asList() で返るものとそれに対応する名前リストが返る。
32 # ただし、名前は構文定義時に setResultsName で明示的に指定しなければ
33 # ならない。

わかる人はわかると思うけど当然「self._ParseResults__toklist」は「世間一般的にはやっちゃダメ」である。作者は利用者が決して触れてはならないものとしているわけなのだから。けど今の場合はどうしたって必要。ま…こういう乱暴な逃げ道があるてのが python の良さでもあるのよね。(つまり「絶対にオレが正しいから逆らうな」という、場合によってはデンジャラスなポリシーに逆らうことが出来る、てハナシ。)