【问题标题】:Parsing nested tuples using perl regexp with $^R使用带有 $^R 的 perl 正则表达式解析嵌套元组
【发布时间】:2018-02-07 09:47:40
【问题描述】:

我想学习如何使用带有嵌入式代码执行的 Perl 正则表达式为嵌套元组创建抽象语法树。我可以使用 Perl 6 语法轻松地进行编程,并且我知道使用解析模块会简化 Perl 5 中的任务,但我认为对于如此简单的任务,我应该能够通过学习如何从语法定义。我找不到取消引用 $^R 的方法,所以我尝试撤消 TUPLE 规则定义末尾的非自愿嵌套,但输出不正确,例如一些子串出现两次。

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))

预期的输出数据结构是一个嵌套列表:

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

作为参考,我还将分享我的工作 Perl 6 代码:

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'}
}

【问题讨论】:

    标签: regex perl


    【解决方案1】:

    试图弄清楚如何使用(?{ ... }) 构造几乎总是不值得付出努力。特别是,这可能会与回溯一起出现意想不到的行为。调试此类正则表达式也非常困难,因为控制流往往不明显。

    相反,使用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;
    

    【讨论】:

    • 酷,这样写更容易。为什么在 parse_tuple 中将字符串参数作为引用传递?
    • @rubystallion 因为当前pos 其中\G 锚点是字符串值的一部分,所以我们不能复制(否则我们必须在每个子项中再次分配pos )。请注意parse_element() 可以继续匹配parse_tuple() 结束的位置,因为$$ref 具有正确的位置。对于较大的文档,副本也会变得低效。
    • 我认为这是使用$_而不是参数的地方之一。
    • 我认为pos $foo = 0pos $foo = undef 之间存在细微差别,但我想出了什么办法;可能是我记错了
    • @ysth 根据perldoc -f pos,一个 undef pos 表示匹配失败(这里由于/gc 正则表达式标志而避免)或者到目前为止没有在字符串上执行正则表达式。我将 pos 设置为零是为了使这一重要步骤更加明确,以便在 parse() 获取 ref 而不是复制时重置 pos,更重要的是:这样 error() 可以在没有警告的情况下运行,即使第一次匹配失败,否则 pos 仍然是 undef。
    【解决方案2】:

    我修复了问题中的代码。结果我不小心写了$^R[1] 而不是$^R-&gt;[1]。所以现在我明白了为什么 amon 说这些构造很难调试 ;-)

    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))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多