提升标题列的精神分析语法

时间:2018-10-08 10:26:35

标签: c++ boost-spirit-qi

我想解析文本文件的标题列。列名应允许加引号,以及任何大小写字母。目前,我正在使用以下语法:

public class PlayerBall : MonoBehaviour {

    public float xSpeed = 10;

    // Update is called once per frame
    void FixedUpdate () {
        this.transform.position = new Vector3(this.transform.position.x + Time.fixedDeltaTime * xSpeed, 
            this.transform.position.y , transform.position.z);
    }

}

在不使用宏的情况下是否有可能?此外,我想完全忽略任何下划线。可以使用自定义船长来实现吗?最后,理想的情况是可以写:

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

其中col是适当的语法或规则。

1 个答案:

答案 0 :(得分:1)

  

@sehe我如何解决此语法以同时支持"\"Column_A\""6 hours ago

这一次您可能应该已经意识到这里发生了两种不同的事情。

单独的Yo关注点

一方面,您有一个语法(它允许|分隔的列,例如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";
}

打印 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

两个部分都变得非常简单:

  • 您的解析器不必处理古怪的比较逻辑
  • 您的比较逻辑不必处理语法问题(引号,转义符,定界符和空白)