Perl中的递归成对大括号匹配

时间:2015-08-10 18:52:17

标签: regex perl

让我们考虑一下这段不属于任何已知语言的代码:

foo() {
    bar() {
       bar();
    }

    baz() {
        // Content baz
        qux() {
           // Content qux
        }
    }
}

我想通过调用子例程来迭代处理每个function,子例程接收:函数名,参数,缩进级别和内容。

到目前为止,我已经写过:

#!/usr/bin/env perl
use 5.010;
$_ = do {local $/; <>};       
s/([{}])/$1.($1 eq '{'?++$i:$i--).$1/eg;     
parse($_);

sub parse {
    local $_ = shift;
    while (/(?<name>\w+)\s*\((?<args>.*?)\)\s*\{(\d)\{(?<content>.*?)\}(?<level>\3)\}/gs) {
        parse($+{content});
        process($+{content}, $+{args}, $+{level}, $+{content});
    }
}

sub process {
    my ($name, $args, $level, $content) = @_;
    #...
}

棘手的想法是使用缩进编号替换每个匹配的大括号{。所以这个:

{
    {
    }
}

将成为这个:

{1{
    {2{
    }2}
}1}

它允许轻松编写简单的解析正则表达式:

qr/
  \w+          # name
  \s* \(.*?\)  # arguments
  \s* \{(\d)\{ # opening brace
  .*?          # content
  \s* \}\1\}   # closing brace
/x
  

如果没有这个技巧我怎么能重写呢?

请注意,{1{的选择可以是{(1){1-{[1]甚至{1✈

2 个答案:

答案 0 :(得分:2)

您可以尝试使用递归正则表达式。例如:

/(\{(?:[^{}]++|(?1))*\})/

将匹配一组平衡的大括号。有关更多信息,请参阅

答案 1 :(得分:2)

使用递归正则表达式时,您将很难做到这一点,因为您只能获取每个捕获组的最后一个值,并且会丢失所有干预值。此任务更适合像Parse::RecDescent这样的解析器:

use strict;
use warnings;
use 5.010;

use Parse::RecDescent;

sub process {
    my ($name, $args, $depth) = @_;
    say "$depth - $name($args)";
}

my $grammar = q{
    { my $indent = 0; }
    startrule     : expression(s /;/)
    expression    : function_call
                  | function_def[$indent]
    function_call : identifier '(' arglist ')' ';'
    function_def  : identifier '(' arglist ')' '{' expression[ $arg[0]++ ](s?) '}'
                  { main::process( $item{identifier}, join(',', @$item{arglist}), $arg[0] ) }
    arglist       : identifier(s? /,/)
    identifier    : /\w+/
};

# Tell parser to ignore spaces and C99 one-line comments
my $skip_spaces_and_comments = qr{ (?: \s+ | // .*?$ )* }mxs;
$Parse::RecDescent::skip = $skip_spaces_and_comments;

my $parser = Parse::RecDescent->new($grammar) or die 'Bad grammar';

my $text = do { local $/; <DATA> };

defined $parser->startrule($text) or die 'Failed to parse';

__DATA__
foo() {
    bar() {
       bar();
    }

    baz() {
        // Content baz
        qux() {
           // Content qux
        }
    }
}

输出:

2 - bar()
1 - qux()
2 - baz()
3 - foo()

请注意,深度是反转的(1是嵌套最多的),这不会返回函数定义的内容,但它应该让您开始。