[PEG] PyMeta + DXF
List 1. PEG for DXF (spline only)
dxf ::= <header>:h (<spline> | <entity> | <entry>)+:c => (h, c)List 2. Output Python ObjectnewLine ::= ("\n" | "\r") number ::= <digit>+:c => int(''.join(c))
label ::= <spaces>? <digit>+:t => int(''.join(t)) attrib ::= <spaces>? (~<newLine> <anything>)+:c => ''.join(c) entry ::= <label>:l <newLine> (<newLine> | <attrib>):a => (l, a) entryX :x ::= <entry>:e ?(e[0]==x) => e[1] entryXX :xx ::= <entry>:e ?(e[0] in (xx)) => e[1] entryXY :x :y ::= <entry>:e ?(e==(x,y))
vector ::= <entryX 10>:v0 <entryX 20>:v1 <entryX 30>:v2 => (float(v0),float(v1),float(v2))
entityBegin ::= <entryX 0>:v => v nonEntityBegin ::= <entry>:e ?(e[0]!= 0)
entity ::= <entityBegin>:v <nonEntityBegin>* => v
spline ::= <entityBegin>:v ?(v=="SPLINE") <entryXX (5,100,8,370,390,6, 62, 100, 210, 220, 230)>* <entryX 70>:flags <entryX 71>:deg <entryXX (72, 73, 74, 42, 43)>* <splineKnot>+:knots <vector>+:CPs <nonEntityBegin>* => ("Spline", {'flags': int(flags), 'deg': int(deg), 'knots':knots, 'points':CPs}) splineKnot ::= <entryX 40>:v => float(v)
header ::= <entityBegin>:v ?(v=="SECTION") <entryXY 2 "HEADER"> (~<entryXY 9 "$EXTMIN">)+ <entryXY 9 "$EXTMIN"> <vector>:extmin <entryXY 9 "$EXTMAX"> <vector>:extmax <nonEntityBegin>* => {'min':extmin, 'max':extmax}
({'max': (41200.0, 850613.4907274337, 0.0), 'min': (-234117.469504671, 397400.0, 0.0)}, ['ENDSEC', 'SECTION', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'CLASS', 'ENDSEC', 'SECTION', 'TABLE', 'VPORT', 'ENDTAB', 'TABLE', 'LTYPE', 'LTYPE', 'LTYPE', 'LTYPE', 'LTYPE', 'LTYPE', 'LTYPE', 'LTYPE', 'LTYPE', 'LTYPE', 'LTYPE', 'LTYPE', 'LTYPE', 'LTYPE', 'ENDTAB', 'TABLE', 'LAYER', 'LAYER', 'LAYER', 'LAYER', 'LAYER', 'LAYER', 'LAYER', 'LAYER', 'LAYER', 'LAYER', 'LAYER', 'LAYER', 'LAYER', 'ENDTAB', 'TABLE', 'STYLE', 'ENDTAB', 'TABLE', 'ENDTAB', 'TABLE', 'ENDTAB', 'TABLE', 'APPID', 'ENDTAB', 'TABLE', 'DIMSTYLE', 'ENDTAB', 'TABLE', 'BLOCK_RECORD', 'BLOCK_RECORD', 'ENDTAB', 'ENDSEC', 'SECTION', 'BLOCK', 'ENDBLK', 'BLOCK', 'ENDBLK', 'ENDSEC', 'SECTION', ('Spline', {'knots': [0.0, 0.0, 0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 6.0, 6.0, 7.0, 7.0, ....], 'points': [(-12231.27794848819, 814215.11690494104, 0.0), (-10831.27794848819, 815215.11690494104, 0.0), ...], 'deg': 2} ....
I tried out PyMeta for extracting spline curves from DXF files as Python data. I know PyMeta is too powerful for that simple task, but I got really interesting brain training thorough out this experience. Though human being can understand a grammar intuitively somehow, writing down it in a pure logical format that computers can understand (like List.1 above) was painful job and kind of a new world for me. I was fully impressed by design philosophy of OMeta.
プログラミング話。こちらの記事がきっかけでOMeta及びPyMetaの存在を知りました。大雑把に言うとプログラミング言語の仕様をPEGという文法で書くためのフレームワークです。例えばOMetaのJavaScript実装=OMetaJSで自分のプログラミング言語を作ると、その自分言語で書いたプログラムを全部JavaScriptに変換してくれます。OMetaJS本体がJavaScriptだけで書かれいるだけでなく、そのPEG用パーサもPEGで書かれているという、かなり再帰的な感じです。 個人的にはPyMetaにある説明が一番実践に近くわかりやすかったです。自分の言語を作れる云々より、一種の洗練された正規表現という印象を受けました。実際に「OMeta is object-oriented pattern matching」とうたってますし、あながち間違えではないはず。現在丁度参加するかも知れないプロジェクトでDXF(建築図面のファイル形式の一つ)というテキストの羅列からスプライン曲線を読み取らなければならなかったため、折角だからと使ってみました。 List.1と言ったPEGを書いてDXFをつっこむと、List.2というPythonのデータが出てきます。結果は想像してた以上に奇麗。 上記のPEGを書くのはかなり頭の体操となりました。人はテキストの羅列を何となく読めちゃうのですが、それをコンピューターがわかる様に論理的に曖昧な点1つ無く文法を明白にするというのが大変だと言うのは、発見でした。逆に書き終わった後、この言語は結局はこういう事何だよとさらって言えちゃう感覚は気持ちいいです(PEG/PyMetaがよくできてるお陰)。以前DXFを扱った時は、switch文で場当たり的処理をしてたのですが、もっと言語の設計について全体的に考え、整理するという全く違う脳みその使い方をしました。 さらに、初めて関数型言語的な経験をする事ができたと思います。関数のみをデザインする、実際のワークフローはフレームワークが全て担当するので手出しできない、何か(ex.DXFファイル)をインプットすると結果(ex.Pythonデータ)だけが貴方の書いた関数(ex.文法とデータ構造の対応)に忠実に従って出てきますよという世界です。関数型言語が電子回路をデザインするのに似てるというのは結構上手い事言うなと思いました。 というわけでPyMeta、申し分無いのですが、DXFファイルが大きい事もあって1つのファイルの解析に10秒とかかかってしまうのだけが少し残念です。PEGを頑張って解いた価値が実用面でもあればいいのですが、明らかにDXFごときには豪華過ぎる様。 パターンマッチつながりで、Mathematicaはパターンマッチで数式を解いてるという「Mathematica開発者のウルフラム」と「ファインマン」という記事も興味深かったです。