повысить дух грамматики для разбора столбцов заголовка - PullRequest
0 голосов
/ 08 октября 2018

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

#include <string>
#include <iostream>
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

template <typename Iterator, typename Skipper>
struct Grammar : qi::grammar<Iterator, void(), Skipper>
{
        static constexpr char colsep = '|';
        Grammar() : Grammar::base_type(header)
        {
                using namespace qi;
                using ascii::char_;
#define COL(name) (no_case[name] | ('"' >> no_case[name] >> '"'))
                header = (COL("columna") | COL("column_a")) >> colsep >>
                        (COL("columnb") | COL("column_b")) >> colsep >>
                        (COL("columnc") | COL("column_c")) >> eol >> eoi;
#undef COL
        }
        qi::rule<Iterator, void(), Skipper> header;
};

int main()
{
        const std::string s{"columnA|column_B|column_c\n"};
        auto begin(std::begin(s)), end(std::end(s));
        Grammar<std::string::const_iterator, qi::blank_type> p;
        bool ok = qi::phrase_parse(begin, end, p, qi::blank);

        if (ok && begin == end)
                std::cout << "Header ok" << std::endl;
        else if (ok && begin != end)
                std::cout << "Remaining unparsed: '" << std::string(begin, end) << "'" << std::endl;
        else
                std::cout << "Parse failed" << std::endl;
        return 0;
}

Возможно ли это без использования макроса?Кроме того, я бы хотел игнорировать любые подчеркивания.Можно ли это сделать с помощью специального шкипера?В конце концов было бы идеально, если бы можно было написать:

header = col("columna") >> colsep >> col("columnb") >> colsep >> column("columnc") >> eol >> eoi;

, где col будет подходящей грамматикой или правилом.

1 Ответ

0 голосов
/ 25 октября 2018

@ sehe, как я могу исправить эту грамматику для поддержки "\"Column_A\""? 6 часов назад

К этому времени вы, вероятно, уже поняли, что здесь происходят две разные вещи.

Отдельные проблемы Йо

С одной стороны, у вас есть грамматика (которая позволяет разделить | столбцы, такие как columna или "Column_A").

С другой стороны, у вас есть семантический анализ (фазагде вы проверяете, что проанализированное содержимое соответствует определенным критериям).

То, что делает вашу жизнь трудной, - это попытка связать их.Не поймите меня неправильно, могут быть (очень редкие) обстоятельства, когда объединение этих обязанностей абсолютно необходимо, но я чувствую, что это всегда будет оптимизацией.Если вам это нужно, Дух - это не ваше дело, и вам, скорее всего, будет предоставлен рукописный анализатор.

Синтаксический анализ

Итак, давайте разберемся с грамматикой просто:

static auto headers = (quoted|bare) % '|' > (eol|eoi);

Правила bare и quoted могут быть почти такими же, как и раньше:

static auto quoted  = lexeme['"' >> *('\\' >> char_ | "\"\"" >> attr('"') | ~char_('"')) >> '"'];
static auto bare    = *(graph - '|');

Как вы можете видеть, это неявно позаботится о цитировании и экранировании какхорошо пропуская пробелы вне лексем.При простом применении это приведет к чистому списку имен столбцов:

std::string const s = "\"columnA\"|column_B| column_c \n";

std::vector<std::string> headers;
bool ok = phrase_parse(begin(s), end(s), Grammar::headers, x3::blank, headers);

std::cout << "Parse " << (ok?"ok":"invalid") << std::endl;
if (ok) for(auto& col : headers) {
    std::cout << std::quoted(col) << "\n";
}

Печать Live On Coliru

Parse ok
"columnA"
"column_B"
"column_c"

INTERMEZZO: Стиль кодирования

Давайте структурируем наш код так, чтобы отражалось разделение интересов.Наш код синтаксического анализа может использовать X3, но наш код проверки не обязательно должен находиться в той же единице перевода (файл cpp).

Иметь заголовок, определяющий несколько основных типов:

#include <string>
#include <vector>

using Header = std::string;
using Headers = std::vector<Header>;

Определите операции, которые мы хотим выполнить над ними:

Headers parse_headers(std::string const& input);
bool header_match(Header const& actual, Header const& expected);
bool headers_match(Headers const& actual, Headers const& expected);

Теперь main можно переписать так:

auto headers = parse_headers("\"columnA\"|column_B| column_c \n");

for(auto& col : headers) {
    std::cout << std::quoted(col) << "\n";
}

bool valid = headers_match(headers, {"columna","columnb","columnc"});
std::cout << "Validation " << (valid?"passed":"failed") << "\n";

И, например, parse_headers.cpp может содержать:

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

namespace x3 = boost::spirit::x3;

namespace Grammar {
    using namespace x3;
    static auto quoted  = lexeme['"' >> *('\\' >> char_ | "\"\"" >> attr('"') | ~char_('"')) >> '"'];
    static auto bare    = *(graph - '|');
    static auto headers = (quoted|bare) % '|' > (eol|eoi);
}

Headers parse_headers(std::string const& input) {
    Headers output;
    if (phrase_parse(begin(input), end(input), Grammar::headers, x3::blank, output))
        return output;
    return {}; // or throw, if you prefer
}

Проверка

Это то, что известно как "семантические проверки".Вы берете вектор строк и проверяете их в соответствии с вашей логикой:

#include <boost/range/adaptors.hpp>
#include <boost/algorithm/string.hpp>

bool header_match(Header const& actual, Header const& expected) {
    using namespace boost::adaptors;
    auto significant = [](unsigned char ch) {
        return ch != '_' && std::isgraph(ch);
    };

    return boost::algorithm::iequals(actual | filtered(significant), expected);
}

bool headers_match(Headers const& actual, Headers const& expected) {
    return boost::equal(actual, expected, header_match);
}

Вот и все.Вся мощь алгоритмов и современного C ++ в вашем распоряжении, вам не нужно бороться с ограничениями из-за синтаксического анализа контекста.

Полная демонстрация

Выше, Live On Wandbox

Обе части стали значительно проще:

  • ваш синтаксический анализатор не должен иметь дело со странной логикой сравнения
  • ваша логика сравнения не имеетприходится иметь дело с грамматическими проблемами (кавычки, экранированные символы, разделители и пробелы)
...