如何访问递归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 })块将变量名保存到哈希中,但这与回溯无关(即,即使变量回溯过去,赋值的副作用仍然存在)。

有没有任何方法可以让给定的命名捕获组捕获所有内容,包括递归匹配?还是我需要手动挖掘单个片段(从而复制所有模式)?

必须添加捕获和回溯机制是Regexp::Grammars解决的缺点之一。

然而,您的问题中的语法是递归的,无论是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;
};

请注意,这个简单的语法赋予所有运算符同等的优先级,而不是"请原谅我亲爱的莎莉阿姨"。

您想要提取所有变量名,这样您就可以像中那样遍历AST

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 ];
}

并用打印结果

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

另一种方法是为Var规则安装一个自动操作,在成功解析变量时记录变量的名称。

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 matchn";
}

无论哪种方式,输出都是

[a][b][c] 

递归是Marpa::R2的本机,使用下面__DATA__部分中的BNF:

#!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

你的问题只涉及如何获得变量名,所以在这个答案中没有运算符关联性等概念。请注意,如果需要的话,Marpa对此没有任何问题。

最新更新