Parse indent sensitive grammar in Rust with Pest

以前、パーサーを雑に比較した。
e-tipsmemo.hatenablog.com

結局Left-recursionは書き方で何とか対応できる範囲だと思われるので
Introducing pest into glsl and hindsight about nom vs. pest (part 1) – phaazon.net

(それ以外はどうしようもない)
インデントセンシティブなパース方法についてテストしていく。

pestでindent sensitiveな文法(pythonのような)をハンドルするには。
pest/lists.pest at master · pest-parser/pest · GitHub
PEEK_ALLとPUSHとDROPを使えばいいらしい。


まずはindentをスペースかタブが連続したものとして定義しておく。
(あとは適当なものを定義する)

indentation = _{(" " | " ")+}
id = {ASCII_ALPHANUMERIC+}
oneline = { (!" " ~ ANY)* }

今回の場合、同じインデントの深さを持つ連続するonelineが1つのブロックとして認識されてほしいのでLeft-recursiveの書き換えと組み合わせて

oneline_first = _{oneline}
oneline_continue = _{PEEK_ALL~oneline}
onelines = _{oneline_first~(" "~oneline_continue)*}
oneline_children = {PEEK_ALL~PUSH(indentation)~onelines~DROP}

oneline_firstが1個とoneline_continueが複数並ぶと書き換えることで、left-recursiveを避ける。

同じ要領で、インデントされた連続するmoduleを定義する。

moduleはonline_childrenを持つ。

circuit = {"circuit "~id~" :"~"\n"~module_children~"\n"?~EOI}
module = {"module "~id~" :"~("\n"~oneline_children)?}
modules = _{module_first~("\n"~module_continue)*}

module_first = _{module}
module_continue = _{PEEK_ALL~module}

module_children = {PEEK_ALL~PUSH(indentation)~modules~DROP}

これに

circuit hoge :
 module piyo :
  foo
  bar
 module baz :
  one
  two


こんな入力をいれたら

よさそう?
f:id:katakanan:20210624211033p:plain
pest. The Elegant Parser

まだこれだけではwhen ~ else ~のようなインデントの位置が戻るが、1つのブロックとなっている文法には対応できていないがそれはまた後でやる。

インデントスコープを区切る言語/設定ファイルは作る側にとっては楽かもしれんが、パースすることを考えたら面倒くさいんじゃないかなと思う。が、割と溢れている気がする。
とにかく自分の使いたいことを実現できそうなパーサーがあってよかった。