为什么 qi::skip 会因来自词法分析器的标记而失败?
Why does qi::skip fail with tokens from the lexer?
我正在使用 boost::spirit lex 和 qi 来解析一些源代码。
我已经使用词法分析器从输入字符串中跳过了空格。我想做的是根据解析器中的上下文切换跳过注释。
这是一个基本的演示。我的问题见Grammar::Grammar()中的评论:
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <iostream>
namespace lex = boost::spirit::lex;
namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;
typedef lex::lexertl::token<char const*, boost::mpl::vector<std::string>, boost::mpl::false_ > token_type;
typedef lex::lexertl::actor_lexer<token_type> lexer_type;
struct TokenId
{
enum type
{
INVALID_TOKEN_ID = lex::min_token_id,
COMMENT
};
};
struct Lexer : lex::lexer<lexer_type>
{
public:
lex::token_def<std::string> comment;
lex::token_def<std::string> identifier;
lex::token_def<std::string> lineFeed;
lex::token_def<std::string> space;
Lexer()
{
comment = "\/\*.*?\*\/|\/\/[^\r\n]*";
identifier = "[A-Za-z_][A-Za-z0-9_]*";
space = "[\x20\t\f\v]+";
lineFeed = "(\r\n)|\r|\n";
this->self = space[lex::_pass = lex::pass_flags::pass_ignore];
this->self += lineFeed[lex::_pass = lex::pass_flags::pass_ignore];
this->self.add
(comment, TokenId::COMMENT)
(identifier)
(';')
;
}
};
typedef Lexer::iterator_type Iterator;
void traceComment(const std::string& content)
{
std::cout << " comment: " << content << std::endl;
}
class Grammar : public qi::grammar<Iterator>
{
typedef token_type skipped_t;
qi::rule<Iterator, qi::unused_type, qi::unused_type> m_start;
qi::rule<Iterator, qi::unused_type, qi::unused_type, skipped_t> m_variable;
qi::rule<Iterator, std::string(), qi::unused_type> m_comment;
public:
Lexer lx;
public:
Grammar() :
Grammar::base_type(m_start)
{
// This does not work (comments are not skipped in m_variable)
m_start = *(
m_comment[phx::bind(&traceComment, qi::_1)]
| qi::skip(qi::token(TokenId::COMMENT))[m_variable]
);
m_variable = lx.identifier >> lx.identifier >> ';';
m_comment = qi::token(TokenId::COMMENT);
/** But this works:
m_start = *(
m_comment[phx::bind(&traceComment, qi::_1)]
| m_variable
);
m_variable = qi::skip(qi::token(TokenId::COMMENT))[lx.identifier >> lx.identifier >> ';'];
m_comment = qi::token(TokenId::COMMENT);
*/
}
};
void test(const char* code)
{
std::cout << code << std::endl;
Grammar parser;
const char* begin = code;
const char* end = code + strlen(code);
tokenize_and_parse(begin, end, parser.lx, parser);
if (begin == end)
std::cout << "-- OK --" << std::endl;
else
std::cout << "-- FAILED --" << std::endl;
std::cout << std::endl;
}
int main(int argc, char* argv[])
{
test("/* kept */ int foo;");
test("int /* ignored */ foo;");
test("int foo /* ignored */;");
test("int foo; // kept");
}
输出为:
/* kept */ int foo;
comment: /* kept */
-- OK --
int /* ignored */ foo;
-- FAILED --
int foo /* ignored */;
-- FAILED --
int foo; // kept
comment: // kept
-- OK --
skipped_t有什么问题吗?
你所描述的行为是我从我的经历中所期望的。
写的时候
my_rule = qi::skip(ws) [ foo >> lit(',') >> bar >> lit('=') >> baz ];
这和写法本质上是一样的
my_rule = *ws >> foo >> *ws >> lit(',') >> *ws >> bar >> *ws >> lit('=') >> *ws >> baz;
(假设 ws
是没有属性的规则。如果它在您的语法中有一个属性,那么该属性将被忽略,就像使用 qi::omit
一样。)
值得注意的是,船长不会在 foo
规则内传播。所以 foo
、bar
和 baz
在上面仍然可以是空白敏感的。 skip 指令所做的是导致语法不关心此规则中的前导空格,或此规则中 ','
和 '='
周围的空格。
更多信息在这里:http://boost-spirit.com/home/2010/02/24/parsing-skippers-and-skipping-parsers/
编辑:
此外,我认为 skipped_t
并没有像您认为的那样发挥作用。
当您使用自定义跳过器时,最直接的方法是指定解析器的实际实例作为该规则的跳过解析器。当您使用类型而不是对象时,例如qi::skip(qi::blank_type)
,即 shorthand,其中标记类型 qi::blank_type
已通过先前的模板声明链接到类型 qi::blank
,并且 qi 知道当它看到 qi::blank_type
在某些地方它应该实例化一个 qi::blank
解析器对象。
我没有看到任何证据表明您确实设置了该机器,您只是将 skipped_t
类型定义为 token_type
。如果你想让它以这种方式工作,你应该做什么(如果它甚至可能,我不知道)阅读关于 qi 定制点而不是声明 qi::skipped_t
作为一个空结构,它通过一些模板样板链接规则 m_comment
,这大概是您实际想要跳过的内容。 (如果你跳过所有类型的所有标记,那么你不可能匹配任何东西,所以这没有意义,所以我不确定你让 token_type
成为船长的意图是什么。)
我的猜测是,当 qi
在您的参数列表中看到 typedef token_type
时,它要么忽略它,要么将其解释为规则的 return 值的一部分,或者类似这样的东西,不确定它到底会做什么。
我正在使用 boost::spirit lex 和 qi 来解析一些源代码。
我已经使用词法分析器从输入字符串中跳过了空格。我想做的是根据解析器中的上下文切换跳过注释。
这是一个基本的演示。我的问题见Grammar::Grammar()中的评论:
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <iostream>
namespace lex = boost::spirit::lex;
namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;
typedef lex::lexertl::token<char const*, boost::mpl::vector<std::string>, boost::mpl::false_ > token_type;
typedef lex::lexertl::actor_lexer<token_type> lexer_type;
struct TokenId
{
enum type
{
INVALID_TOKEN_ID = lex::min_token_id,
COMMENT
};
};
struct Lexer : lex::lexer<lexer_type>
{
public:
lex::token_def<std::string> comment;
lex::token_def<std::string> identifier;
lex::token_def<std::string> lineFeed;
lex::token_def<std::string> space;
Lexer()
{
comment = "\/\*.*?\*\/|\/\/[^\r\n]*";
identifier = "[A-Za-z_][A-Za-z0-9_]*";
space = "[\x20\t\f\v]+";
lineFeed = "(\r\n)|\r|\n";
this->self = space[lex::_pass = lex::pass_flags::pass_ignore];
this->self += lineFeed[lex::_pass = lex::pass_flags::pass_ignore];
this->self.add
(comment, TokenId::COMMENT)
(identifier)
(';')
;
}
};
typedef Lexer::iterator_type Iterator;
void traceComment(const std::string& content)
{
std::cout << " comment: " << content << std::endl;
}
class Grammar : public qi::grammar<Iterator>
{
typedef token_type skipped_t;
qi::rule<Iterator, qi::unused_type, qi::unused_type> m_start;
qi::rule<Iterator, qi::unused_type, qi::unused_type, skipped_t> m_variable;
qi::rule<Iterator, std::string(), qi::unused_type> m_comment;
public:
Lexer lx;
public:
Grammar() :
Grammar::base_type(m_start)
{
// This does not work (comments are not skipped in m_variable)
m_start = *(
m_comment[phx::bind(&traceComment, qi::_1)]
| qi::skip(qi::token(TokenId::COMMENT))[m_variable]
);
m_variable = lx.identifier >> lx.identifier >> ';';
m_comment = qi::token(TokenId::COMMENT);
/** But this works:
m_start = *(
m_comment[phx::bind(&traceComment, qi::_1)]
| m_variable
);
m_variable = qi::skip(qi::token(TokenId::COMMENT))[lx.identifier >> lx.identifier >> ';'];
m_comment = qi::token(TokenId::COMMENT);
*/
}
};
void test(const char* code)
{
std::cout << code << std::endl;
Grammar parser;
const char* begin = code;
const char* end = code + strlen(code);
tokenize_and_parse(begin, end, parser.lx, parser);
if (begin == end)
std::cout << "-- OK --" << std::endl;
else
std::cout << "-- FAILED --" << std::endl;
std::cout << std::endl;
}
int main(int argc, char* argv[])
{
test("/* kept */ int foo;");
test("int /* ignored */ foo;");
test("int foo /* ignored */;");
test("int foo; // kept");
}
输出为:
/* kept */ int foo;
comment: /* kept */
-- OK --
int /* ignored */ foo;
-- FAILED --
int foo /* ignored */;
-- FAILED --
int foo; // kept
comment: // kept
-- OK --
skipped_t有什么问题吗?
你所描述的行为是我从我的经历中所期望的。
写的时候
my_rule = qi::skip(ws) [ foo >> lit(',') >> bar >> lit('=') >> baz ];
这和写法本质上是一样的
my_rule = *ws >> foo >> *ws >> lit(',') >> *ws >> bar >> *ws >> lit('=') >> *ws >> baz;
(假设 ws
是没有属性的规则。如果它在您的语法中有一个属性,那么该属性将被忽略,就像使用 qi::omit
一样。)
值得注意的是,船长不会在 foo
规则内传播。所以 foo
、bar
和 baz
在上面仍然可以是空白敏感的。 skip 指令所做的是导致语法不关心此规则中的前导空格,或此规则中 ','
和 '='
周围的空格。
更多信息在这里:http://boost-spirit.com/home/2010/02/24/parsing-skippers-and-skipping-parsers/
编辑:
此外,我认为 skipped_t
并没有像您认为的那样发挥作用。
当您使用自定义跳过器时,最直接的方法是指定解析器的实际实例作为该规则的跳过解析器。当您使用类型而不是对象时,例如qi::skip(qi::blank_type)
,即 shorthand,其中标记类型 qi::blank_type
已通过先前的模板声明链接到类型 qi::blank
,并且 qi 知道当它看到 qi::blank_type
在某些地方它应该实例化一个 qi::blank
解析器对象。
我没有看到任何证据表明您确实设置了该机器,您只是将 skipped_t
类型定义为 token_type
。如果你想让它以这种方式工作,你应该做什么(如果它甚至可能,我不知道)阅读关于 qi 定制点而不是声明 qi::skipped_t
作为一个空结构,它通过一些模板样板链接规则 m_comment
,这大概是您实际想要跳过的内容。 (如果你跳过所有类型的所有标记,那么你不可能匹配任何东西,所以这没有意义,所以我不确定你让 token_type
成为船长的意图是什么。)
我的猜测是,当 qi
在您的参数列表中看到 typedef token_type
时,它要么忽略它,要么将其解释为规则的 return 值的一部分,或者类似这样的东西,不确定它到底会做什么。