ドキュメントのちら見で箸か棒にかかるなら入り口くらいは遊んでみるかモード。
一つ前、二つ前同様 Python Parsing Tools より。
リアルワールドなご用では parser はいらないけど lexer はちゃんとしたのが欲しい、というケースが結構多く、ワタシは学生時代は「GNU flex のみで」書くフィルタープログラムを書くのに凝ってた時期があった。C プログラムを「綺麗に印刷するためのフォーマッタ」なんて書いてたわ。今にして思えば、ワタシの Sphinx 好きは、この頃から芽はあったのかもな。
てなわけで plex:
元祖 lex と GNU flex との、目に見える一番大きな機能拡張が「状態遷移」を管理できること、だったのよね。そして ply とか rply でこれがないのが微妙にツラいかったりしてるわけ。そうなんだろ? と思ってすぐにこの例が目に留まる:
1 #
2 # Example 5
3 #
4
5 lex = Lexicon([
6 (name, 'ident'),
7 (number, 'int'),
8 (space, IGNORE),
9 (Str("(*"), Begin('comment')),
10 State('comment', [
11 (Str("*)"), Begin('')),
12 (AnyChar, IGNORE)
13 ])
14 ])
そうなんだよなぁ、これがあるなしで結構作りやすさ、違うんだわ。と、まずは好印象から始まってみる。
次に考えたのが「rply は lexer と parser が完全に分離してるので、plex の Scanner でリプレイスしちゃえんかなぁ?」だった。結論からはこれは「食べれない」、おいしくない:
To read tokens from the input stream, you call the read() method of the Scanner. Each time read() is called, it returns a tuple
1 (value, text)
where value is the value returned by the token’s action, and text is the text from the input stream that matched the token.
We can test out the Lexicon we defined earlier like this:
1 #
2 # Example 2
3 #
4
5 filename = "my_file.txt"
6 f = open(filename, "r")
7 scanner = Scanner(lexicon, f, filename)
8 while 1:
9 token = scanner.read()
10 print(token)
11 if token[0] is None:
12 break
ファイルライクオブジェクト前提なのね。rply 向けアダプタを作れなくないけどちとノリが違うのよね。あんまし嬉しかない。
と、ここまでは、インストールしてみることも動かしてみることもなく、のファーストインプレッション。棒にはかかっておるよさ。
そもそもオリジナル plex が v 2.0 12/2009 ととても古く、plex3 (Python3 port of Plex) (No official release) が存在してる、ってだけで臆してしまうんだけれど、まぁやろうとだけはしてみよう、と…:
1 from plex import *
2
3 #
4 # Example 6
5 #
6
7 lex = Lexicon([
8 (name, 'ident'),
9 (number, 'int'),
10 (space, IGNORE),
11 (Str("(*"), Begin('comment1')),
12 (Str("{"), Begin('comment2')),
13 State('comment1', [
14 (Str("*)"), Begin('')),
15 (AnyChar, IGNORE)
16 ]),
17 State('comment2', [
18 (Str("}"), Begin('')),
19 (AnyChar, IGNORE)
20 ])
21 ])
22 import sys
23 scanner = Scanner(lex, sys.stdin)
24 while True:
25 token = scanner.read()
26 print(token)
27 if token[0] is None:
28 break
1 [me@host: ~]$ echo 'a (* this is comment *)' | py -2 plex_exam5.py
2 Traceback (most recent call last):
3 File "plex1.py", line 8, in <module>
4 (name, 'ident'),
5 NameError: name 'name' is not defined
うーん、ドキュメントを前から読んでいかないとダメなパターンか…:
1 from plex import *
2
3 #
4 # Example 6
5 #
6
7 letter = Range("AZaz")
8 digit = Range("09")
9 name = letter + Rep(letter | digit)
10 number = Rep1(digit)
11 space = Any(" \t\n")
12
13 lex = Lexicon([
14 (name, 'ident'),
15 (number, 'int'),
16 (space, IGNORE),
17 (Str("(*"), Begin('comment1')),
18 (Str("{"), Begin('comment2')),
19 State('comment1', [
20 (Str("*)"), Begin('')),
21 (AnyChar, IGNORE)
22 ]),
23 State('comment2', [
24 (Str("}"), Begin('')),
25 (AnyChar, IGNORE)
26 ])
27 ])
28 import sys
29 scanner = Scanner(lex, sys.stdin)
30 while True:
31 token = scanner.read()
32 print(token)
33 if token[0] is None:
34 break
1 [me@host: ~]$ echo 'a (* this is comment *)' | py -2 plex_exam5.py
2 ('ident', 'a')
3 (None, '')
うん、期待通り。が…
1 [me@host: ~]$ echo 'a (* this is comment *)' | py -3 plex_exam5.py
2 Traceback (most recent call last):
3 File "plex1.py", line 1, in <module>
4 from plex import *
5 File "C:\Python35\lib\site-packages\plex\__init__.py", line 33, in <module>
6 from plex.lexicons import Lexicon, State
7 File "C:\Python35\lib\site-packages\plex\lexicons.py", line 117
8 if type(specifications) <> types.ListType:
9 ^
10 SyntaxError: invalid syntax
「<>
」なんていつの Python だよっ、て思うが、それ以前に、あらら、2.7 ってまだ「<>
」使えるんだ、て事実にも驚く。
やな予感、の通り、plex3 (Python3 port of Plex) が必要で、まぁこっち入れとけば Python 2.x でも使えるわけだが、PyPI に未登録なわけなので、自力でインストールな。しかるに:
1 # original plex:
2 # doesn't support python 3 (never work)
3 #
4 # plex3 (for python 3 fork):
5 # No official release, so visit https://github.com/uogbuji/plex3,
6 # download (or git clone), and do this:
7 # python setup.py install
8 #from plex import *
9 from plex3 import *
10
11 letter = Range("AZaz")
12 digit = Range("09")
13 name = letter + Rep(letter | digit)
14 number = Rep1(digit)
15 space = Any(" \t\n")
16
17 lexicon = Lexicon([
18 (name, 'ident'),
19 (number, 'int'),
20 (space, IGNORE),
21 (Str("(*"), Begin('comment1')),
22 (Str("{"), Begin('comment2')),
23 State('comment1', [
24 (Str("*)"), Begin('')),
25 (AnyChar, IGNORE)
26 ]),
27 State('comment2', [
28 (Str("}"), Begin('')),
29 (AnyChar, IGNORE)
30 ])
31 ])
32
33 import sys
34 scanner = Scanner(lexicon, sys.stdin)
35 while True:
36 token = scanner.read()
37 print(token)
38 if token[0] is None:
39 break
これで 2/3 両方動く。
いかんせん完全にストップしちゃってて今後の進化もあまり期待出来なさそうなので、少なくとも人にオススメは出来ない。結構好みではあるんだけどね。