我一直在使用 Qi 和 Karma 对几种小语言进行一些处理。大多数语法都很小(20-40 条规则)。我几乎完全可以使用自动规则,所以我的解析树完全由变体、结构和 std::vectors 组成。
此设置非常适用于常见情况:
1) 解析某些东西 (Qi),
2) 对解析树 (访问者) 进行少量操作,以及
3) 输出某些东西 (Karma)。
但是,我担心如果我想对语法树进行复杂的结构更改(例如移动大子树)会发生什么。考虑以下玩具示例:
使用自动规则的 s-expr 样式逻辑表达式的语法...
// Inside grammar class; rule names match struct names...
pexpr %= pand | por | var | bconst;
pand %= lit("(and ") >> (pexpr % lit(" ")) >> ")";
por %= lit("(or ") >> (pexpr % lit(" ")) >> ")";
pnot %= lit("(not ") >> pexpr >> ")";
...这导致解析树表示看起来像这样...
struct var {
std::string name;
};
struct bconst {
bool val;
};
struct pand;
struct por;
struct pnot;
typedef boost::variant<bconst,
var,
boost::recursive_wrapper<pand>,
boost::recursive_wrapper<por>,
boost::recursive_wrapper<pnot> > pexpr;
struct pand {
std::vector<pexpr> operands;
};
struct por {
std::vector<pexpr> operands;
};
struct pnot {
pexpr victim;
};
// Many Fusion Macros here
假设我有一个看起来像这样的解析树:
pand
/ ... \
por por
/ \ / \
var var var var
(省略号的意思是“有更多类似形状的孩子pand
。”)
现在,假设我想否定每个por
节点,所以最终结果是:
pand
/ ... \
pnot pnot
| |
por por
/ \ / \
var var var var
对于每个por
子树,直接的方法是:
- 创建pnot
节点
(构造中的副本por
);- 在节点
中重新分配适当的向量槽
(复制节点及其子树)。 pand
pnot
por
或者,我可以构建一个单独的向量,然后pand
批量替换(交换)向量,从而消除第二轮复制。
与基于指针的树表示相比,所有这些似乎都很麻烦,这将允许在pnot
不复制现有节点的情况下插入节点。我的问题:
有没有办法使用符合自动规则的数据结构来避免复制繁重的树操作?我是否应该硬着头皮只使用非自动规则来构建基于指针的 AST(例如,http ://boost-spirit.com/home/2010/03/11/s-expressions-and-variants/ )?