コンビネータとパラメタライズ

近頃はパーサコンビネータに関心を持っていて、 Scheme を用いて CASLⅡ や PL/0 のパーサを書いたりした。

そんなことをしている内に、プログラミング言語 TL/1 のパーサが微妙な出来なまま放置していたことを思い出した。 TL/1 では予約語をシャドウすることが出来るという性質を実装していなかったのだ。

これを適切に処理するには変数 (などの名前) 宣言を記憶して残りの部分のパースに使用する必要がある。 柔軟な Scheme であれば楽に出来るだろうと思ったのだがそうでもなかった。 コンビネータとパラメタライズの相性が悪い。

例として簡単化した事例を見せる。 識別子の並びをふたつ入力し、ひとつめの列を変数宣言、ふたつめの列にある識別子の内で宣言済みのものを #t そうでなければ #f と置換えたものを出力とするような処理を期待して以下のように書いた。

#!/usr/bin/env gosh
(use parser.peg)
(use gauche.parameter)

(define vars (make-parameter '()))
(define %ws ($skip-many ($one-of #[\s\t])))
(define %ident
  ($do (ident ($many ($one-of #[a-zA-Z]))) %ws
       ($return (rope->string ident))))
(define %comma ($seq ($char #\,) %ws))
(define %declare-variables ($sep-by %ident %comma))
(define %var
  ($do (ident %ident)
       ($return (boolean (member ident (vars))))))
(define %main
  ($do (vs %declare-variables)
       (parameterize ((vars vs))
         ($sep-by %var %comma))))

(write (peg-parse-string %main "a,b,c,d  a,c,e,r,t,b"))

これを実行すると (#f #f #f #f #f #f) という結果が出力されてしまう。 パーサの途中で parameterize を狭んでもその動的区間ではパーサを構築して返すだけで、値を適用する段階では既に動的区間を抜けているからだ。

うまく環境を取り回す方法が思い付かないでいるのだが、良い方法があるだろうか?

Document ID: f7288e2b5a5da4fd311c096aaed11290