我正在尝试解析像树表达式这样的 C 函数,如下所示(使用 Spirit Parser Framework ):
F( A() , B( GREAT( SOME , NOT ) ) , C( YES ) )
为此,我尝试在以下语法上使用三个规则:
template< typename Iterator , typename ExpressionAST >
struct InputGrammar : qi::grammar<Iterator, ExpressionAST(), space_type> {
InputGrammar() : InputGrammar::base_type( ) {
tag = ( qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z_0-9") )[ push_back( at_c<0>(qi::_val) , qi::_1 ) ];
command = tag [ at_c<0>(qi::_val) = at_c<0>(qi::_1) ] >> "(" >> (*instruction >> ",")
[ push_back( at_c<1>(qi::_val) , qi::_1 ) ] >> ")";
instruction = ( command | tag ) [qi::_val = qi::_1];
}
qi::rule< Iterator , ExpressionAST() , space_type > tag;
qi::rule< Iterator , ExpressionAST() , space_type > command;
qi::rule< Iterator , ExpressionAST() , space_type > instruction;
};
请注意,我的标记规则只是试图捕获表达式中使用的标识符(“函数”名称)。另请注意,标记规则的签名返回 ExpressionAST
而不是 std::string
,就像在大多数示例中一样。我想这样做的原因实际上很简单:我讨厌使用变体,如果可能的话我会避免使用它们。我想,如果能把蛋糕留着吃,那就太好了。
命令应该以标签(当前节点的名称,AST 节点的第一个字符串字段)和括号括起来的可变数量的参数开始,每个参数可以是标签本身或另一个命令。
但是,这个例子根本行不通。它编译一切,但在运行时它无法解析我所有的测试字符串。真正让我烦恼的是我不知道如何修复它,因为我无法真正调试上面的代码,至少在这个词的传统意义上是这样。基本上,我认为可以修复上述代码的唯一方法是知道我做错了什么。
所以,问题是我不知道上面的代码有什么问题。你会如何定义上面的语法?
我使用的 ExpressionAST
类型是:
struct MockExpressionNode {
std::string name;
std::vector< MockExpressionNode > operands;
typedef std::vector< MockExpressionNode >::iterator iterator;
typedef std::vector< MockExpressionNode >::const_iterator const_iterator;
iterator begin() { return operands.begin(); }
const_iterator begin() const { return operands.begin(); }
iterator end() { return operands.end(); }
const_iterator end() const { return operands.end(); }
bool is_leaf() const {
return ( operands.begin() == operands.end() );
}
};
BOOST_FUSION_ADAPT_STRUCT(
MockExpressionNode,
(std::string, name)
(std::vector<MockExpressionNode>, operands)
)
最佳答案
就调试而言,可以使用正常的中断和监视方法。但是,您格式化规则的方式使这变得困难。如果您按照精神示例(〜每行一个解析器,每行一个 phoenix 语句)格式化,断点将提供更多信息。
你的数据结构没有办法区分A()
来自 SOME
因为它们都是树叶(如果我遗漏了什么,请告诉我)。从您的变体评论来看,我认为这不是您的意图,因此为了区分这两种情况,我添加了一个 bool commandFlag
。 MockExpressionNode 的成员变量(对于 A()
为真,对于 SOME
为假),具有相应的融合适配器线。
对于具体的代码,您需要将开始规则传递给基本构造函数,即:
InputGrammar() : InputGrammar::base_type(instruction) {...}
这是语法的入口点,也是您没有得到任何数据解析的原因。我很惊讶它在没有它的情况下编译,我认为语法类型需要匹配第一条规则的类型。即便如此,这是一个方便遵循的约定。
对于 tag
规则,实际上有两个解析器 qi::char_("a-zA-Z_")
,这是类型为 char
的 _1和 *qi::char_("a-zA-Z_0-9")
这是 _2 类型(基本上)vector<char>
.在没有自动规则的情况下无法将它们强制转换为字符串,但可以通过将规则附加到每个已解析的字符来完成:
tag = qi::char_("a-zA-Z_")
[ at_c<0>(qi::_val) = qi::_1 ];
>> *qi::char_("a-zA-Z_0-9") //[] has precedence over *, so _1 is
[ at_c<0>(qi::_val) += qi::_1 ]; // a char rather than a vector<char>
但是,让精神进行这种转换要干净得多。所以定义一个新规则:
qi::rule< Iterator , std::string(void) , ascii::space_type > identifier;
identifier %= qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z_0-9");
不用担心 ;)。那么标签就变成了
tag = identifier
[
at_c<0>(qi::_val) = qi::_1,
ph::at_c<2>(qi::_val) = false //commandFlag
]
对于命令,第一部分没问题,但是 (*instruction >> ",")[ push_back( at_c<1>(qi::_val) , qi::_1 ) ]
有几个问题.这将解析后跟“,”的零个或多个指令规则。它还尝试 push_back vector<MockExpressionNode>
(不知道为什么这个编译,可能因为缺少开始规则而没有实例化?)。我认为您需要以下内容(修改了标识符):
command =
identifier
[
ph::at_c<0>(qi::_val) = qi::_1,
ph::at_c<2>(qi::_val) = true //commandFlag
]
>> "("
>> -(instruction % ",")
[
ph::at_c<1>(qi::_val) = qi::_1
]
>> ")";
这使用可选运算符 -
和列表运算符 %
, 后者相当于 instruction >> *("," >> instruction)
.然后 phoenix 表达式只是将 vector 直接分配给结构成员,但您也可以将操作直接附加到指令匹配并使用 push_back。
指令规则很好,我只是提一下它等同于instruction %= (command|tag)
.
最后一件事,如果A()
之间实际上没有区别的话和 SOME
(即您的原始结构没有 commandFlag
),您可以仅使用自动规则编写此解析器:
template< typename Iterator , typename ExpressionAST >
struct InputGrammar : qi::grammar<Iterator, ExpressionAST(), ascii::space_type> {
InputGrammar() : InputGrammar::base_type( command ) {
identifier %=
qi::char_("a-zA-Z_")
>> *qi::char_("a-zA-Z_0-9");
command %=
identifier
>> -(
"("
>> -(command % ",")
>> ")");
}
qi::rule< Iterator , std::string(void) , ascii::space_type > identifier;
qi::rule< Iterator , ExpressionAST(void) , ascii::space_type > command;
};
这是使用紧密模拟输入的融合包裹结构的一大好处。
关于c++ - 使用 Boost Spirit 解析语法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3078162/