Parse::RecDescent (2)

単純な足し算だけに続いて,引き算もしたいということで,単純に拡張します.

use strict;
use warnings;

use Parse::RecDescent;

my $grammar=q(
expression : atom "+" expression
    { $return = $item[1] + $item[3] ;}

#引き算を追加
expression : atom "-" expression
    { $return = $item[1] - $item[3] ;}
####

expression : atom
    { $return = $item[1] ;}

atom : /\d+/
    { $return = $item[1] ;  }

);

my $parser = new Parse::RecDescent ($grammar) or die "Bad grammar!\n";

my $text = "8 - 2";
my $result = $parser->expression($text) or die "Bad text!\n";
print "$text = $result\n";

今回は「8-2」を与えてみます.もちろん結果は「6」となります.めでたい.しかし,これはすぐに破綻してしまいます.

$text="8-4-2";

としたらどうなるでしょう.これは

8-4-2 = 6

となってしまいます.「8-4-2」は「(8-4)-2」でなければいけないのに,「8-(4-2)」となってしまっています.足し算の場合は順序は関係ないので問題はおきません.しかし,引き算は順序が関係あり,いわゆる「左結合」の演算なので,そもそもexpressionの段階で間違っています.

expression : atom "-" expression

というのは,「右結合」であり,今の例では,「8-4-2」はatomに「8」,次に「-」,そして次のexpressionが「4-2」ということを意味しています.つまり「8-4-2」が「8-(4-2)」となるのはまさにルール通りです.したがってルールが誤っているのです.

そこで,単純な変更を試みます.atom "-" expressionが「右結合」なら,順番を変えてみる,つまり

expression : expression "-" atom

と書き換えます.これを実行すると,

               ERROR: Rule "expression" is left-recursive.
               (Hint: Set $::RD_HINT (or -RD_HINT if you're using "perl -s")
              for hints on fixing these problems.)

悲しいかな,エラーです.-s -RD_HINTで実行するとヒントがあるというのでやってみます.

perl -s  cal.pl -RD_HINT

                ERROR: Rule "expression" is left-recursive.
                (Hint: Redesign the grammar so it's not left-recursive. That
               will probably mean you need to re-implement repetitions
               using the '(s)' notation. For example:
               "expression(s)".)

「左再帰になってるから文法を見直せ,繰り返しの(s)を使ってる部分を実装しなおせ」といわれますが,そもそも「(s)」なんてものは使ってません.ですので,キーワードは「左再帰」ということです.

今,"8-2"をexpressionルールにマッチさせるとどうなるでしょうか.8-2はatomではないですので,expression "-" atomにマッチしようとします.そこで,さらに最初にexpressionがでてきます.これはatomか「expression "-" atom」ですが,同様の理由で,「expression "-" atom」が対応します.これを延々繰り返してしまうので,左側の項が再帰的にループしてしまうことになり,それがあらかじめ検出されてエラーになっているのです.

これを文法そのものから排除して,なおかつ「左結合」を実現する方策を考えなければいけません.Higher-order Perlの8.4節に助けてもらうことにします.