2017-11-05 16 views
6

埋め込みコードを実行したPerl正規表現を使ってネストされたタプルの抽象構文木を作成する方法を学びたいと思います。私はPerl 6文法を使って簡単にプログラムすることができます。パーシングモジュールを使うとPerl 5のタスクが単純化されることに気付いていますが、このような単純なタスクでは、モジュールなしで機械的に翻訳する方法文法の定義。私は$^Rを逆参照する方法を見つけることができなかったので、私はTUPLEルール定義の終わりに不本意なネスティングを取り消そうとしましたが、出力は間違っています。一部の部分文字列が2回表示されます。

use v5.10; 
use Data::Dumper; 

while (<DATA>) { 
    chomp; 
    /(?&TUPLE)(?{$a = $^R}) 
    (?(DEFINE) 
     (?<TUPLE> 
      T \s (?&ELEM) \s (?&ELEM) 
      (?{ [$^R->[0][0],[$^R->[0][1],$^R[1]]] }) 
     ) 
     (?<ELEM> 
      (?: (a) (?{ [$^R,$^N] }) | \((?&TUPLE) \)) 
     ) 
    )/x; 
    say Dumper $a; 
} 

__DATA__ 
T a a 
T (T a a) a 
T a (T a a) 
T (T a a) (T a a) 
T (T (T a a) a) (T a (T a a)) 

予想される出力データ構造は、ネストされたリストです:使用方法を把握しようと

grammar Tuple { 
    token TOP { 'T ' <elem> ' ' <elem> } 
    token elem { 'a' | '(' <TOP> ')'} 
} 
class Actions { 
    method TOP($/) {make ($<elem>[0].made, $<elem>[1].made)} 
    method elem($/) {make $<TOP> ?? $<TOP>.made !! 'a'} 
} 

答えて

6

:私はまた私は、コードのPerl 6の作業を共有しましょう参考のため

['a','a']; 
['a',['a','a']]; 
[['a','a'],'a']; 
[['a','a'],['a','a']]; 
[[['a','a'],'a'],['a',['a','a']]] 

(?{ ... })のコンストラクトは、ほとんど常に努力する価値がありません。特に、これはバックトラックと一緒に予期しない動作をする可能性があります。このような正規表現をデバッグすることも非常に困難です。なぜなら、制御フローは自明ではない傾向があるからです。

代わりに、m//gcスタイルの字句解析を使用したアドホックな再帰的降下構文解析プログラムを書く方が簡単です。各Perl文字列には、最後の一致オフセットが格納されます。スカラーコンテキストでm/\G ... /gcの正規表現を適用すると、最後のオフセットにアンカーし、マッチが成功するとオフセットを進めることができます。

ここ

use strict; 
use warnings; 
use Test::More; 

sub parse { 
    my ($str) = @_; 
    pos($str) = 0; # set match position to beginning 
    return parse_tuple(\$str); 
} 

sub parse_tuple { 
    my ($ref) = @_; 
    $$ref =~ /\G T \s/gcx or die error($ref, "expected tuple start T"); 
    my $car = parse_element($ref); 
    $$ref =~ /\G \s /gcx or die error($ref, "expected space between tuple elements"); 
    my $cdr = parse_element($ref); 
    return [$car, $cdr]; 
} 

sub parse_element { 
    my ($ref) = @_; 
    return 'a' if $$ref =~ /\G a /gcx; 

    $$ref =~ /\G \(/gcx or die error($ref, "expected opening paren for nested tuple"); 
    my $tuple = parse_tuple($ref); 
    $$ref =~ /\G \) /gcx or die error($ref, "expected closing paren after nested tuple"); 
    return $tuple; 
} 

sub error { 
    my ($ref, $msg) = @_; 
    my $snippet = substr $$ref, pos($$ref), 20; 
    return "$msg just before '$snippet...'"; 
} 

is_deeply parse('T a a'), ['a','a']; 
is_deeply parse('T (T a a) a'), [['a','a'],'a']; 
is_deeply parse('T a (T a a)'), ['a',['a','a']]; 
is_deeply parse('T (T a a) (T a a)'), [['a','a'],['a','a']]; 
is_deeply parse('T (T (T a a) a) (T a (T a a))'), [[['a','a'],'a'],['a',['a','a']]]; 
done_testing; 
+0

このように書くと、簡単に書くことができます。 parse_tupleでstringパラメータを参照として渡すのはなぜですか? – rubystallion

+2

@rubystallion '\ G'アンカーが文字列値の一部である現在の' pos'のため、コピーを作成してはいけません(または、各サブに 'pos'をもう一度割り当てる必要があります)。 'parse_element()'は '$$ ref'が正しいposを持っているので、' parse_tuple() 'が終了したときにマッチを続けることに注意してください。また、大きな文書ではコピーが非効率的になります。 – amon

+1

これは、パラメータの代わりに '$ _'を使うのが理にかなっている場所の一つだと思います。 – ikegami

0

私は私の質問にコードを修正しました。私は偶然、$^R->[1]の代わりに$^R[1]を書きました。だから、私はなぜ、これらの構造がデバッグするのが難しいか、アモンが言った理由を理解しています;-)

use v5.10; 
use Data::Dumper; 

while (<DATA>) { 
    chomp; 
    /(?&TUPLE)(?{$a = $^R->[1]}) 
    (?(DEFINE) 
     (?<TUPLE> 
      T \s (?&ELEM) \s (?&ELEM) 
      (?{ [$^R->[0][0],[$^R->[0][1],$^R->[1]]] }) 
     ) 
     (?<ELEM> 
      (?: (a) (?{ [$^R,$^N] }) | \((?&TUPLE) \)) 
     ) 
    )/x; 
    say Dumper $a; 
} 

__DATA__ 
T a a 
T (T a a) a 
T a (T a a) 
T (T a a) (T a a) 
T (T (T a a) a) (T a (T a a)) 
関連する問題