Поддерживает ли библиотека анализатора Marpa исправление ошибок?

Я знаю, что в парлере Perl "Marpa" Earley есть очень хорошие сообщения об ошибках.

Но я не могу найти в его документации или через Google, есть ли у него восстановление после ошибок.

Например, большинство компиляторов C/C++ имеют исправление ошибок, которое они используют для сообщения о множественных синтаксических ошибках, где часто другие компиляторы останавливаются при первой ошибке.

Я на самом деле разбираю естественный язык и задаюсь вопросом, есть ли способ повторной синхронизации и возобновления разбора после сбоя одной части ввода.


Например, для тех, кто может это сделать:

Я разбираю слоги на лаосском языке. В лаосском языке некоторые гласные являются диакритическими знаками, которые закодированы как отдельные символы и переданы выше предыдущего согласного. При разборе случайных статей из лаосской Википедии я натолкнулся на какой-то текст, в котором такой гласный был удвоен. Это не разрешено в лаосской орфографии, поэтому должно быть опечатка. Но я знаю, что через пару символов текст снова хорош.

В любом случае, это реальный пример, который пробудил мой общий интерес к восстановлению после ошибок или повторной синхронизации с потоком токенов.

1 ответ

В 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 Лексема, которая соответствует чему-либо, делая пропуск в нашем клиентском коде, позволяет нам прервать синтаксический анализ, если нужно было сделать слишком много фальсификаций.

Другие вопросы по тегам