Как правильно указать условия якорения в 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
[
-(¬DashOrDigit) >>
boost::spirit::x3::raw[fiveDigits >> -(dash >> fourDigits)] >>
¬DashOrDigit
];
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
[
¬DashOrDigit >>
boost::spirit::x3::raw[fiveDigits >> -(dash >> fourDigits)] >>
¬DashOrDigit
];
Я прочитал этот вопрос: остановить символы X3 от совпадения подстрок . Однако в этом вопросе используется таблица символов. Я не думаю, что это может сработать для меня, потому что я не могу указать жестко закодированные строки. Мне также неясно, как ответ на этот вопрос позволяет запретить ведущий контент.
1 ответ:
Использование
Возможно, вы хотели получить отрицательное утверждение ("lookahead"), которое является-(parser)просто делает(parser)необязательным. Использование его с-(&parser)не имеет буквально никакого эффекта.!(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