6

Perl の "Marpa" Earley パーサーが非常に優れたエラー レポート機能を備えていることは知っています。

しかし、ドキュメントやグーグルでエラー回復があるかどうかを見つけることができません。

たとえば、ほとんどの C/C++ コンパイラにはエラー回復機能があり、これを使用して複数の構文エラーを報告しますが、他のコンパイラは最初のエラーで停止することがよくあります。

私は実際に自然言語を解析していますが、入力の一部が失敗した後に再同期して解析を再開する方法があるかどうか疑問に思っています。


例、それを理解できる人のために:

ラオス語の音節を解析しています。ラオス語では、いくつかの母音は分音記号であり、別個の文字としてエンコードされ、前の子音の上に表示されます。ラオス語ウィキペディアのランダムな記事を解析しているときに、そのような母音が二重になっているテキストに出くわしました。これはラオス語の正書法では許可されていないため、タイプミスに違いありません。しかし、数文字以内でテキストが再び優れていることがわかります。

とにかく、これは、エラー回復またはトークン ストリームとの再同期に対する私の一般的な関心を刺激した実際の例です。

4

1 に答える 1

7

Marpa でエラーを処理するには、2 つの可能性があります。

「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、クライアント コードでスキップを行うと、あまりにも多くのファッジを行う必要がある場合に解析を中止できます。

于 2014-09-06T12:13:36.547 に答える