Marpa解析器库是否支持错误恢复



我知道Perl的"Marpa"Earley解析器具有非常好的错误报告

但我在它的文档或谷歌上找不到它是否有错误恢复

例如,大多数C/C++编译器都有错误恢复功能,它们用它来报告多个语法错误,而其他编译器通常会在第一个错误时停止。

我实际上是在解析自然语言,想知道是否有一种方法可以在输入的一部分失败后重新同步并恢复解析。


例如,对于那些可以摸索的人:

我在分析老挝语的音节。在老挝语中,一些元音是变音符号,它们被编码为单独的字符,并呈现在前一个辅音之上。在分析老挝语维基百科上的随机文章时,我遇到了一些这样的元音被加倍的文本。这在老挝语正字法中是不允许的,所以一定是拼写错误。但我知道,在几个字符内,文本又很好了。

无论如何,这是一个真实的例子,它激发了我对错误恢复或与令牌流重新同步的普遍兴趣。

Marpa中有两种处理错误的可能性。

"Ruby Slippers"解析

Marpa在扫描过程中保留了大量上下文。我们可以使用这个上下文,这样解析器就可以需要一些令牌,并且我们可以决定是否要将其提供给Marpa,即使它不在输入中。例如,考虑一种编程语言,它要求任何语句都以分号结尾。然后,我们可以使用Ruby Slippers技术在特定位置插入分号,例如在一行的末尾或右大括号之前:

use strict;
use warnings;
use Marpa::R2;
use Data::Dump 'dd';
my $grammar = Marpa::R2::Scanless::G->new({
    source => q{
        :discard ~ ws
        Block         ::= Statement+ action => ::array
        Statement     ::= StatementBody (STATEMENT_TERMINATOR) action => ::first
        StatementBody ::= 'statement'       action => ::first
                      |   ('{') Block ('}') action => ::first
        STATEMENT_TERMINATOR ~ ';'
        event ruby_slippers = predicted STATEMENT_TERMINATOR
        ws ~ [s]+
    },
});
my $recce = Marpa::R2::Scanless::R->new({ grammar => $grammar });
my $input = q(
    statement;
    { statement }
    statement
    statement
);
for (
  $recce->read($input);
  $recce->pos < length $input;
  $recce->resume
) {
    ruby_slippers($recce, $input);
}
ruby_slippers($recce, $input);
dd $recce->value;
sub ruby_slippers {
    my ($recce, $input) = @_;
    my %possible_tokens_by_length;
    my @expected = @{ $recce->terminals_expected };
    for my $token (@expected) {
        pos($$input) = $recce->pos;
        if ($token eq 'STATEMENT_TERMINATOR') {
            # fudge a terminator at the end of a line, or before a closing brace
            if ($$input =~ /G s*? (?: $ | [}] )/smxgc) {
                push @{ $possible_tokens_by_length{0} }, [STATEMENT_TERMINATOR => ';'];
            }
        }
    }
    my $max_length = 0;
    for (keys %possible_tokens_by_length) {
        $max_length = $_ if $_ > $max_length;
    }
    if (my $longest_tokens = $possible_tokens_by_length{$max_length}) {
        for my $lexeme (@$longest_tokens) {
            $recce->lexeme_alternative(@$lexeme);
        }
        $recce->lexeme_complete($recce->pos, $max_length);
        return ruby_slippers($recce, $input);
    }
}

ruby_slippers函数中,您还可以计算伪造令牌的频率。如果该计数超过某个值,则可以通过抛出错误来放弃解析。

正在跳过输入

如果您的输入可能包含不可解析的垃圾,如果找不到词法,您可以尝试跳过它。为此,$recce->resume方法采用了一个可选的位置参数,在这里将恢复正常的解析。

use strict;
use warnings;
use Marpa::R2;
use Data::Dump 'dd';
use Try::Tiny;
my $grammar = Marpa::R2::Scanless::G->new({
    source => q{
        :discard ~ ws
        Sentence ::= WORD+ action => ::array
        WORD ~ 'foo':i | 'bar':i | 'baz':i | 'qux':i
        ws ~ [s]+
    },
});
my $recce = Marpa::R2::Scanless::R->new({ grammar => $grammar });
my $input = '1) Foo bar: baz and qux, therefore qux (foo!) implies bar.';
try { $recce->read($input) };
while ($recce->pos < length $input) {
    # ruby_slippers($recce, $input);
    try   {       $recce->resume                    }  # restart at current position
    catch { try { $recce->resume($recce->pos + 1) } }; # advance the position
    # if both fail, we go into a new iteration of the loop.
}
dd $recce->value;

虽然使用与任何内容匹配的:discard词法可以实现相同的效果,但如果必须进行太多的篡改,则在客户端代码中跳过可以使我们中止解析。

相关内容

  • 没有找到相关文章

最新更新