北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

左再帰を解決する

正規表現エンジンを作ろう(3)では、簡単のためすべて演算が右結合となるように構文解析を実装しました。ただ、ちょっとこれは乱暴過ぎたかなと反省もしてます。

そんなわけで、

なお、引き算、割り算等のような、5-3-2が5-(3-2)のように右結合になると結果が変わってしまう演算に予言的パーサを適応する場合は、工夫が必要です。

こちらの方法を紹介します。


Wikipediaにも書いてますが、左再帰は以下のように解決できます。

A → Aα | β

こんな規則を、

A  → βA'
A' → ε | αA'

こう書き換えられます。これでどちらも、βααα... を導出する文法となります*1。こいつを利用すれば、今回のコンパイラでも左再帰を解決できます。

ただし、左結合性を維持するためには、文法規則に含まれる「処理*2」まで含めて考える必要があります。連結演算の文法規則は、処理まで含めると以下のようになっています。

subseq -> subseq star {subseq.r = Concat(subseq.r, star.r)} 
        | star {subseq.r = star.r}

で、{}で表したのが処理であり、.rと言うのは、各生成規則に紐づく属性で、returnする値だと思って下さい。例えば、 subseq.r = star.r ってのは、 subseq()内から return star(); することを意味します。

さて、このように処理まで含めた表記を先の変換方法と比べますと、

A => subseq
α => star {subseq.r = Concat(subseq.r, star.r)}
β => star {subseq.r = star.r}

となりますので、A' を 「_subseq」 と言う名前で導入すると、完成するルールは以下のようになります。

subseq  -> star {subseq.r = star.r} _subseq
_subseq -> ε 
         | star {subseq.r = Concat(subseq.r, star.r)} _subseq

subseq.rを_subseqで使ってるので、こいつを_subseq()に渡せるようにします。_subseqが、subseqの戻り値(subseq.r)を変換してくようなイメージです。εに至ったら何もしません・・・つまり変換をしません。

コードにすると以下です。

    def subseq(self):
        # subseq -> star {star} subseq_
        node = self.star()  # ← {subseq.r = star.r}
        return self._subseq(node)

    def _subseq(self, node1):
        if self.look.kind == Token.LPAREN \
           or self.look.kind == Token.CHARACTER:
            # _subseq -> star {Concat()} _subseq
            node2 = self.star()
            node  = Concat(node1, node2) # ← {subseq.r = Concat(subseq.r, star.r)}
            return self._subseq(node)
        else:
            # _subseq -> ε
            return node1

_subseq()は末尾再帰なんで、ループに出来ます。そうすると、_subseq()は再帰呼び出しのない関数になるので、subseq()の中にまとめられます。

    def subseq(self):
        # subseq -> star {star} subseq_
        node = self.star()
        while True:
            if self.look.kind == Token.LPAREN \
               or self.look.kind == Token.CHARACTER:
                # _subseq -> star {Concat()} _subseq
                node2 = self.star()
                node  = Concat(node, node2)
                continue
            else:
                # _subseq -> ε
                break
        return node

これで、左結合的な文法となりました。

参考文献

ドラゴンブックの手法そのまんまです。

*1:αもβも終端記号ではなく、変数を含んでよいことに注意

*2:semantic actions