我正在尝试用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 })
块将变量名保存到哈希中,但这与回溯无关(即,即使变量回溯过去,赋值的副作用仍然存在)。
有没有任何方法可以让给定的命名捕获组捕获所有内容,包括递归匹配?还是我需要手动挖掘单个片段(从而复制所有模式)?
然而,您的问题中的语法是递归的,无论是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对此没有任何问题。