提升用于解析标题列的精神语法
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_cn"};
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小时前
到这个时候,你可能已经意识到这里有两件不同的事情。
单独的哟问题
一方面,您有一个语法(允许|
分隔的列,例如columna
或"Column_A"
(。
另一方面,您有语义分析(检查解析的内容是否与某些条件匹配的阶段(。
让你的生活变得艰难的事情是试图将两者混为一谈。现在,不要误会我的意思,在某些情况下,绝对需要将这些责任融合在一起(非常罕见的(情况 - 但我觉得这始终是一种优化。如果你需要它,Spirit不是你的菜,你更有可能得到一个手写的解析器。
解析
因此,让我们对语法进行简单的脑死亡:
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";
}
在科里鲁现场印刷
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++的所有功能都可供您使用,无需因解析上下文而与约束作斗争。
完整演示
以上,活在魔杖盒上
这两个部分都变得更加简单:
- 您的解析器不必处理古怪的比较逻辑
- 您的比较逻辑不必处理语法问题(引号、转义、分隔符和空格(
相关文章:
- 1d 智能指针不适用于语法 (*)++
- 助记符和指向成员语法的指针
- 有人能分解一下这个c++模板的语法吗
- C++避免重复声明的语法是什么
- QMetaObject invokeMethod的基于函数指针的语法
- 为什么在Windows上的VS 2019和Clang 9中"size_t"在没有标题的情况下工作
- 这个语法std::class<>{}(arg1, arg2) 在C++中是什么意思?
- 为什么包含windows.h会产生语法错误,从而阻止类的实例化?(C2146,C2065)
- 对于C++,方括号中的标题必须总是在引号中的标题之上吗
- 库标题在标题中不可见,但在 cmake build 下.cpp文件中完全可见.为什么?
- 单独定义模板化嵌套类方法的正确语法
- 共享指针和具有自定义删除程序的唯一指针之间的语法差异背后的任何原因
- 错误 C2760:语法错误:映射迭代器上意外的标记"标识符",预期的";"
- 为什么我不应该把所有东西都放在标题中?
- 为什么我会收到错误 C2143 语法错误:缺少"*"之前的';'?
- 奇怪的代码抛出编译错误模板< J,int aSize=10> C2143:语法错误:在"<"之前缺少";"
- 提升用于解析标题列的精神语法
- 如何在源文件中定义类并将其声明在标题文件中(而不必使用`class :: method'语法定义类方法)
- (C )节点标题的语法错误
- Std库标题中的奇怪枚举语法