我正在使用C ++ 11中的shift / reduce解析器生成器,我不知道如何指定输入生成和缩减动作函数的接口类型,以便它们保存我想要放入的信息它们。
我想静态指定语法但使用C ++类型(不是单独的构建工具)。
对于每个符号(终端和非终端),用户提供字符串名称和类型。
然后每个产品指定一个头部符号名称和一个或多个身体符号名称。
对于每个生产,由用户(硬部件)提供动作功能,该动作功能返回头部非终端类型并具有与生产主体符号(其相应类型)相对应的参数。
主要问题是静态绑定参数类型并将这些操作函数的返回类型返回到相应的符号类型
例如:
假设我们有非终结符X
,A
B
C
他们的名字/类型可能是:
"X" Foo
"A" string
"B" string
"C" int
在语法中可能有一个制作:
X -> A B C
用户将为该产品提供一个动作功能:
Foo f(string A, string B, int C)
如果生产减少,则应使用生产体参数调用函数f。然后存储f返回的值,以便在较高的向上缩减中使用该符号时。
所以要为解析器生成器指定语法,我需要提供类似的东西:
(我知道以下内容无效)
struct Symbol
{
string name;
type T;
}
struct Production
{
string head;
vector<string> body;
function<head.T(body[0].T, body[1].T, ..., body[n].T)> action;
}
struct Grammar
{
vector<Symbol> symbols;
vector<Production> productions;
}
并指明前面的例子是:
Grammar example =
{
// symbols
{
{ "X", Foo },
{ "A", string },
{ "B", string },
{ "C", int }
},
// productions
{
{
"X",
{ "A", "B", "C" },
[](string A, string B, int C) { ... return Foo(...); }
}
}
}
当然,这不起作用,你不能将类型参数与那样的运行时参数混合。
一种解决方案是拥有一些通用基础:
struct SymbolBase
{
...
}
template<class SymbolType>
struct SymbolDerived<SymbolType> : SymbolBase
{
SymbolType value;
}
然后创建类型的所有动作函数:
typedef function<SymbolBase(vector<SymbolBase>)> ActionFunction;
并在运行时对其进行排序。但是这使得使用更加困难,并且所有的投射都很慢。我宁愿在编译时检查函数签名,并保持机制对用户隐藏。
如何重构Symbol,Production和Grammar类型以传递我在法律C ++ 11中传达的信息?
(是的,我看过Boost Spirit和朋友,它是一个很好的框架,但它是递归下降,因此它在一次传递中可以处理的语言少于LALR解析器,并且因为它使用回溯,所以减少操作将得到多次调用,等等)
答案 0 :(得分:1)
我一直在玩这个问题。一旦我看到它看起来应该工作的可能性,就是使用一堆变体对象,可能是boost :: variant或boost :: any。由于每次减少都知道它对堆栈的期望,因此访问将是类型安全的;不幸的是,类型检查将在运行时,但它应该非常便宜。这样做的好处就是可以捕获bug :)它还会在对象从堆栈中弹出时正确地破坏对象。
我将一些示例代码汇总为PoC,可根据要求提供。编写缩减规则的基本样式是这样的:
parse.reduce<Expression(Expression, _, Expression)>
( [](Expression left, Expression right){
return BinaryOperation(Operator::Times, left, right);
});
对应规则:
expression: expression TIMES expression
此处,BinaryOperation
是AST节点类型,必须可转换为Expression
;模板参数Expression(Expression, _, Expression)
正好是生产的左侧和右侧,表示为类型。 (因为第二个RHS类型是_,模板不打算将值提供给缩减规则:使用适当的解析器生成器,实际上没有理由甚至首先将标点符号推入堆栈。)I使用Expression
实现了标记的union boost::variant
和解析器堆栈的标记类型。如果您尝试这样做,值得知道使用变体作为另一个变体的选项类型之一并不真正起作用。最后,将较小的联合包装为struct
是最简单的。您还必须阅读有关递归类型的部分。