Как правильно указать условия якорения в Spirit X3?



Я новичок в написании парсеров. Я пытаюсь создать парсер, который может извлекать почтовые индексы США из входного текста. Я создал следующие шаблоны синтаксического анализа, которые делают большую часть того, что я хочу. Я могу сопоставить 5-значные почтовые индексы или 9-значные почтовые индексы (90210-1234), как и ожидалось.



Однако это не позволяет мне избегать совпадений, таких как:




246764 (возвращает 46764)

578397 (возвращается 78397)




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



Тестовые данные (жирным шрифтом должны быть сопоставлены записи)




12345



Foo456



Ba58r



246764anc



578397



90210-

15206-1
15222-1825

15212-4267-53410-2807




Полный код:



using It = std::string::const_iterator;
using ZipCode = boost::fusion::vector<It, It>;

namespace boost { namespace spirit { namespace x3 { namespace traits {
template <>
    void move_to<It, ZipCode>(It b, It e, ZipCode& z)
    {
    z =
        {
        b,
            e
        };
}}}}}

void Parse(std::string const& input)
{
    auto start = std::begin(input);
    auto begin = start;
    auto end = std::end(input);

    ZipCode current;
    std::vector<ZipCode> matches;

    auto const fiveDigits = boost::spirit::x3::repeat(5)[boost::spirit::x3::digit];
    auto const fourDigits = boost::spirit::x3::repeat(4)[boost::spirit::x3::digit];
    auto const dash = boost::spirit::x3::char_('-');
    auto const notDashOrDigit = boost::spirit::x3::char_ - (dash | boost::spirit::x3::digit);

    auto const zipCode59 = 
        boost::spirit::x3::lexeme
        [
            -(&notDashOrDigit) >> 
            boost::spirit::x3::raw[fiveDigits >> -(dash >> fourDigits)] >> 
            &notDashOrDigit
        ];

    while (begin != end)
    {
        if (!boost::spirit::x3::phrase_parse(begin, end, zipCode59, boost::spirit::x3::blank, current))
        {
            ++begin;
        }
        else
        {
            auto startOffset = std::distance(start, boost::fusion::at_c<0>(current));
            auto endOffset = std::distance(start, boost::fusion::at_c<1>(current));
            auto length = std::distance(boost::fusion::at_c<0>(current), boost::fusion::at_c<1>(current));
            std::cout << "Matched ("" << startOffset
                << "", "" 
                << endOffset
                << "") => ""
                << input.substr(startOffset, length)
                << """
                << std::endl;
        }
    }
}


Этот код с приведенными выше тестовыми данными выдает следующий результат:




Согласовано ("0", "5") => "12345"

Совпало ("29", "34") => "46764"

Совпало ("42", "47") => "78397"

Совпало ("68", "78") => "15222-1825"




Если я изменю zipCode59 на следующий, я не получу ответных ударов:



auto const zipCode59 = 
    boost::spirit::x3::lexeme
    [
        &notDashOrDigit >> 
        boost::spirit::x3::raw[fiveDigits >> -(dash >> fourDigits)] >> 
        &notDashOrDigit
    ];


Я прочитал этот вопрос: остановить символы X3 от совпадения подстрок . Однако в этом вопросе используется таблица символов. Я не думаю, что это может сработать для меня, потому что я не могу указать жестко закодированные строки. Мне также неясно, как ответ на этот вопрос позволяет запретить ведущий контент.

666   1  

1 ответ:

Использование -(parser) просто делает (parser) необязательным. Использование его с -(&parser) не имеет буквально никакого эффекта.

Возможно, вы хотели получить отрицательное утверждение ("lookahead"), которое является !(parser) (противоположностью &(parser)).
Обратите внимание, что потенциальная путаница может быть вызвана разницей между унарным минусом (отрицательное утверждение) и двоичным минусом (уменьшение наборов символов).

Утверждение, что почтовый индекс начинается не с тире/цифры, кажется ... смущенный. Если вы хотите положительно утверждать что-то другое, чем тире или цифра, было бы &~char_("-0-9") (используя унарный ~ для отрицания набора символов), но это предотвратило бы совпадение в самом начале ввода.

Позитивный подход

Отбрасывая часть сложности влево и вправо, я бы наивно начал с чего-то вроде:

using It = std::string::const_iterator;
using ZipCode = boost::iterator_range<It>;

auto Parse(std::string const& input) {
    using namespace boost::spirit::x3;
    auto dig = [](int n) { return repeat(n)[digit]; };
    auto const zip59 = dig(5) >> -('-' >> dig(4));
    auto const valid = zip59 >> !graph;

    std::vector<ZipCode> matches;
    if (!parse(begin(input), end(input), *seek[raw[valid]], matches))
        throw std::runtime_error("parser failure");

    return matches;
}

Что, конечно, соответствует слишком многим:

Жить На Колиру

Matched '12345'
Matched '78397'
Matched '15222-1825'
Matched '53410-2807'

Героические Подвиги

Чтобы ограничить его (и все еще соответствовать в начале ввода) вы можете seek[&('-'|digit)], а затем потребовать допустимый zip.

Я свободно признаю, что мне пришлось немного повозиться с вещами, прежде чем сделать это "правильно". В процессе я создал отладочный помощник:
auto trace_as = [&input](std::string const& caption, auto parser) { 
    return raw[parser] [([=,&input](auto& ctx) { 
        std::cout << std::setw(12) << (caption+":") << " '";
        auto range = _attr(ctx);
        for (auto ch : range) switch (ch) {
            case '\0': std::cout << "\\0"; break;
            case '\r': std::cout << "\\r"; break;
            case '\n': std::cout << "\\n"; break;
            default: std::cout << ch;
        }
        std::cout << "' at " << std::distance(input.begin(), range.begin()) << "\n";
    })]; 
};

auto const valid = seek[&trace_as("seek", '-' | digit)] >> raw[zip59] >> !graph;

std::vector<ZipCode> matches;
if (!parse(begin(input), end(input), -valid % trace_as("skip", *graph >> +space), matches))
    throw std::runtime_error("parser failure");

, который выдает следующий дополнительный диагностический вывод:

Жить На Колиру

       seek: '1' at 0
       skip: '\n    ' at 5
       seek: '4' at 13
       skip: 'foo456\n    ' at 10
       seek: '5' at 23
       skip: 'ba58r\n    ' at 21
       seek: '2' at 31
       skip: '246764anc\n    ' at 31
       seek: '5' at 45
       skip: '578397\n    ' at 45
       seek: '9' at 56
       skip: '90210-\n    ' at 56
       seek: '1' at 67
       skip: '15206-1\n    ' at 67
       seek: '1' at 79
       skip: '\n    ' at 89
       seek: '1' at 94
Matched '12345'
Matched '15222-1825'

Теперь, когда выход-это то, что мы хотим, давайте снова разрежем леса:

Полный Листинг

Жить На Колиру

#include <boost/spirit/home/x3.hpp>

using It = std::string::const_iterator;
using ZipCode = boost::iterator_range<It>;

auto Parse(std::string const& input) {
    using namespace boost::spirit::x3;
    auto dig = [](int n) { return repeat(n)[digit]; };
    auto const zip59 = dig(5) >> -('-' >> dig(4));
    auto const valid = seek[&('-' | digit)] >> raw[zip59] >> !graph;

    std::vector<ZipCode> matches;
    if (!parse(begin(input), end(input), -valid % (*graph >> +space), matches))
        throw std::runtime_error("parser failure");

    return matches;
}

#include <iostream>
int main() {
    std::string const sample = R"(12345
foo456
ba58r
246764anc
578397
90210-
15206-1
15222-1825
15212-4267-53410-2807)";

    for (auto zip : Parse(sample))
        std::cout << "Matched '" << zip << "'\n";
}

Отпечатки пальцев:

Matched '12345'
Matched '15222-1825'

Comments

    Ничего не найдено.