簡體   English   中英

用於解析標題列的提升精神語法

[英]boost spirit grammar for parsing header columns

我想解析文本文件的標題列。 應允許引用列名和任何字母的大小寫。 目前我正在使用以下語法:

#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 將是適當的語法或規則。

@sehe 我該如何修復這個語法以支持"\\"Column_A\\"" 6小時前

到這個時候,您可能應該已經意識到這里發生了兩種不同的事情。

分離喲關注點

一方面,您有一個語法(允許| "Column_A"列,如columna"Column_A" )。

另一方面,您有語義分析(您檢查解析的內容是否符合某些標准的階段)。

使您的生活變得艱難的事情是試圖將兩者混為一談。 現在,不要誤會我的意思,在(非常罕見的)情況下,絕對需要將這些職責融合在一起——但我覺得這總是一種優化。 如果你需要它,Spirit 不是你的東西,你更有可能得到一個手寫的解析器。

解析

因此,讓我們簡單地了解一下語法:

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

barequoted規則可能與以前幾乎相同:

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";
}

在 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

兩個部分都變得更加簡單:

  • 您的解析器不必處理古怪的比較邏輯
  • 您的比較邏輯不必處理語法問題(引號、轉義符、分隔符和空格)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM