2013-07-11 17 views
16

私はperl正規表現で簡単な文法を練習しようとしています(これはプロダクション用ではなく、エディタのヒント/補完を提供するための簡単な分析です)。例えば、再帰的perl正規表現によって捕捉されたグループにアクセスするには?

my $GRAMMAR = qr{(?(DEFINE) 
    (?<expr> \((?&expr) \) | (?&number) | (?&var) | (?&expr) (?&op) (?&expr)) 
    (?<number> \d++) 
    (?<var> [a-z]++) 
    (?<op> [-+*/]) 
)}x; 

は私が

$expr =~ /$GRAMMAR(?&expr)/; 

として、これを実行し、すべての変数名にアクセスできるようにしたいと思います。しかし、perlreによれば、

再帰の内側に一致捕捉基は再帰戻った後にアクセス可能ではないことに注意してくださいので、キャプチャグループの追加の層が必要です。したがって、$ + {NAME}があっても$ + {NAME_PAT}は定義されません。

これは明らかに不可能です。 (?{ code })ブロックを使用して変数名をハッシュに保存してみることもできますが、これはバックトラックを考慮していません(変数が過去に戻っても割り当ての副作用が持続します)。

再帰的なマッチを含む、指定された名前のキャプチャグループによってキャプチャされたものをすべて取得する方法はありますか。あるいは、個々の部分を手作業で掘り下げて(そしてすべてのパターンを複製する)必要がありますか?

+7

[Parse :: RecDescent](http://p3rl.org/Parse::RecDescent)または[Marpa :: R2](http://p3rl.org/Marpa::R2)を使用してください。 – choroba

+0

観測: '} x;'の前の ')'は正規表現にマッチしません。 –

+0

おっと、私は最初の行に開いたparenを忘れてしまった。私はそれを追加しました。 – Steve

答えて

8

キャプチャおよびバックトラックのメカニズムを追加する必要があることは、Regexp::Grammarsが解決する欠点の1つです。

しかし、あなたの質問の文法はleft-recursiveであり、Perl正規表現と再帰下降解析はどちらも解析されません。

Regexp::Grammarsにあなたの文法を適応と左再帰を因数分解は、私の親愛なるおばさんサリーを許しなさいよりも、この単純な文法ではなく、すべての演算子に同じ優先順位を与えること

my $EXPR = do { 
    use Regexp::Grammars; 
    qr{ 
    ^<Expr> $ 

    <rule: Expr>  <Term> <ExprTail> 
       |  <Term> 

    <rule: Term>  <Number> 
       |  <Var> 
       |  \(<MATCH=Expr> \) 

    <rule: ExprTail> <Op> <Expr> 

    <token: Op>   \+ | \- | \* | \/ 

    <token: Number>  \d++ 

    <token: Var>  [a-z]++ 
    }x; 
}; 

注意を生成します。あなたは

sub all_variables { 
    my($root,$var) = @_; 

    $var ||= {}; 
    ++$var->{ $root->{Var} } if exists $root->{Var}; 
    all_variables($_, $var) for grep ref $_, values %$root; 

    wantarray ? keys %$var : [ keys %$var ]; 
} 

のようにASTを歩くと

if ("(a + (b - c))" =~ $EXPR) { 
    print "[$_]\n" for sort +all_variables \%/; 
} 
else { 
    print "no match\n"; 
} 

別のアプローチで結果を印刷することができるように、すべての変数名を抽出したい

Varルールのautoactionをインストールすることです変数の名前が正常に解析されたときにその名前を記録します。

package JustTheVarsMaam; 

sub new { bless {}, shift } 

sub Var { 
    my($self,$result) = @_; 
    ++$self->{VARS}{$result}; 
    $result; 
} 

sub all_variables { keys %{ $_[0]->{VARS} } } 

1; 

コール

my $vars = JustTheVarsMaam->new; 
if ("(a + (b - c))" =~ $EXPR->with_actions($vars)) { 
    print "[$_]\n" for sort $vars->all_variables; 
} 
else { 
    print "no match\n"; 
} 

のようにこのいずれかいずれにせよ、出力は

[a] 
[b] 
[c]
7

再帰は、以下__DATA__セクションにBNFを用いMarpa::R2とネイティブである:

#!env perl 
use strict; 
use diagnostics; 
use Marpa::R2; 

my $input = shift || '(a + (b - c))'; 

my $grammar_source = do {local $/; <DATA>}; 
my $recognizer = Marpa::R2::Scanless::R->new 
    (
    { 
    grammar => Marpa::R2::Scanless::G->new 
    (
    { 
     source => \$grammar_source, 
     action_object => __PACKAGE__, 
    } 
    ) 
    }, 
); 
my %vars =(); 
sub new { return bless {}, shift;} 
sub varAction { ++$vars{$_[1]}}; 

$recognizer->read(\$input); 
$recognizer->value() || die "No parse"; 

print join(', ', sort keys %vars) . "\n"; 

__DATA__ 
:start ::= expr 
expr ::= NUMBER 
     | VAR action => varAction 
     | expr OP expr 
     | '(' expr ')' 
NUMBER ~ [\d]+ 
VAR ~ [a-z]+ 
OP ~ [-+*/] 
WS ~ [\s]+ 
:discard ~ WS 

出力は次のようになります。

a, b, c 

ご質問は、変数名を取得する唯一の方法adressingないので、オペレータ連想の概念など、この答えでました。必要に応じてMarpaには問題はないことに注意してください。

関連する問題