- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
尊敬的灵气专家们。
我试过 Spirit Qi 中的 MiniC 示例,并注意到表达式语法中“空”AST 节点的问题。它将再次生成“表达式”节点,这些节点只包含一个“表达式”类型的操作数。
我认为这个问题是由于运算符优先级的递归规则定义造成的:
// expression.hpp
qi::rule<Iterator, ast::expression(), skipper<Iterator> >
expr, equality_expr, relational_expr,
logical_or_expr, logical_and_expr,
additive_expr, multiplicative_expr
;
// expression_def.hpp
expr =
logical_or_expr.alias()
;
logical_or_expr =
logical_and_expr
>> *(logical_or_op > logical_and_expr)
;
logical_and_expr =
equality_expr
>> *(logical_and_op > equality_expr)
;
// ast.hpp
typedef boost::variant<
nil
, bool
, unsigned int
, identifier
, boost::recursive_wrapper<unary>
, boost::recursive_wrapper<function_call>
, boost::recursive_wrapper<expression>
>
operand;
/*...*/
struct expression
{
operand first;
std::list<operation> rest;
};
当logical_or_expr 递归到logical_and_expr 时,logical_and_expr 将返回一个expression()。由于属性传播是 Spirit,logical_or_expr 然后将此表达式分配给它的表达式的“第一个”成员。
我认为这就是生成所有这些“空”表达式节点的原因。然而,我发现它令人讨厌,并想摆脱它们,但尚未成功。以前有没有人找到合适的解决方案?
我在想,如果一个表达式由二元操作和一元操作组成,那在某种程度上是可能的。这也将具有将操作和操作数保持在同一结构中的优势(伪代码):
struct binary_op
{
optype type;
operand op1;
operand op2;
}
struct unary_op
{
optype type;
operand op1;
}
struct eval_op
{
operand op1;
}
typedef boost::variant<binary_op,
unary_op,
eval_op>
expression;
typedef boost::variant<int,
bool,
boost::recursive_wrapper<expression> >
operand;
不过,我相信我在纸上玩完之后还是会遇到这个“空节点”的问题。好像我在追自己的尾部。
有人知道如何解决这个问题吗?
编辑
a > b
将解析为:
expression (from logical_or_op) // Empty expression (forward only)
(rest) -/ \- (first) expression (from logical_and_op) // Empty expression (forward only)
(rest) -/ \- (first) expression (from equality_op) // Empty expression (forward only)
(rest) -/ \- (first) expression (from relational_op) // "Correct" expression
(first) a -/ \- (rest)
[0] operator_: op_greater
operand_: b
期望的输出是:
expression (from relational_op)
(first) a -/ \- (rest)
[0] operator_: op_greater
operand_: b
EDIT2
我已经上传了一个修改过的 mini-c 版本,它打印了一个表达式生成的表达式树:
如果您查看包含的 A.avxh 文件,它包含一个要解析的表达式。在 main.cpp 的第 67 行设置一个断点,并观察它递归进入那里的频率。
最佳答案
这是因为所有规则都公开了一个“原始”ast::expression
这是固定类型。
在这个示例中显然是为了简化而选择的:好处是
要使 AST 更灵活更紧密地遵循语义,通常的方法是制作 expression
一个变体代替:这样每个表达式都可以直接包含实际的“具体”子表达式类型,而不是通过节点类型的中间级别“涉水”,只是为了模仿语法结构而不是< em>语义。
I think I have several examples of such asts and corresponding grammars on this site, will see if I can link one later.
In fact, I think the
typedef variant<...> statement
in the same example (ast.hpp
) is not a bad example of this approach.Relevant links:
- Boost::Spirit Expression Parser
Boost::spirit how to parse and call c++ function-like expressions this being a question which I answered by providing a fully featured expression parser (with 'builtin function' evaluation), in two ways:
- A 'bells and whistles' approach with a full AST (resembling what we're doing here with the mini_c sample expressions)
- A 'pragmatic' approach which evaluates on-the-fly using just semantic actions - note this is optimal for storage, but has some downsides, which I address in that post
现在,如果您不想改变语法(以免“失去”简单性),您可以改为对 AST 进行转换(可以说是对表达式的“简化”传递) .
我将看看在接下来的一个小时内我能想出什么。
我刚刚重构了语法,因此它不会导致如此深的嵌套。
首先,让我们将测试缩减为一个独立的测试台,它只解析表达式并展示一个简单的表达式 ("42"
) 如何解析为一个深度嵌套的 AST:http://coliru.stacked-crooked.com/a/5467ca41b0ac1d03
<expr>
...
<success></success>
<attributes>[[[[[[[42, []], []], []], []], []], []]]</attributes>
</expr>
接下来,让我们解决根本问题:语法返回一个不变类型 ( ast::expression
),这在很多情况下都太重了。相反,我们想返回一个 ast::operand
(这是变体,可以包含相同的 ast::expression
节点):http://coliru.stacked-crooked.com/a/00e43b1f61db018c
最后,我们希望所有规则也变成变体,并返回任一个 expression
iff 有尾部操作,或者只是一个单独的 operand
在另一种情况下,在伪代码中:
logical_or_expr =
(logical_and_expr >> +(logical_or_op > logical_and_expr)
| logical_and_expr
;
注意 if +(...)
的微妙用法而不是 *(...)
强制执行至少一个尾随 logical_or 操作
现在,必须告诉 Spirit 如何将第一个分支分配给 operand
。属性。 qi::attr_cast<ast::expression>(...)
应该是这里的修复,但遗憾的是我遇到了未定义的行为(这花费了最多的时间)。我选择了一个更详细的解决方案:
_logical_or_expr = logical_and_expr >> +(logical_or_op > logical_and_expr) ;
logical_or_expr = _logical_or_expr | logical_and_expr;
正如您可能看到的,它是一样的,但是第一个分支被提取到一个单独的规则中,所以我们可以只声明规则来公开所需的属性:
qi::rule<It, ast::operand(), Skipper> logical_or_expr;
qi::rule<It, ast::expression(), Skipper> _logical_or_expr;
确实对子表达式的每个优先级执行此操作,结果如下:
_logical_or_expr = logical_and_expr >> +(logical_or_op > logical_and_expr) ;
_multiplicative_expr = unary_expr >> *(multiplicative_op > unary_expr) ;
_additive_expr = multiplicative_expr >> +(additive_op > multiplicative_expr) ;
_relational_expr = additive_expr >> +(relational_op > additive_expr) ;
_equality_expr = relational_expr >> +(equality_op > relational_expr) ;
_logical_and_expr = equality_expr >> +(logical_and_op > equality_expr) ;
logical_or_expr = _logical_or_expr | logical_and_expr ;
logical_and_expr = _logical_and_expr | equality_expr ;
equality_expr = _equality_expr | relational_expr ;
relational_expr = _relational_expr | additive_expr ;
additive_expr = _additive_expr | multiplicative_expr ;
multiplicative_expr = _multiplicative_expr | attr_cast<ast::operand, ast::operand> (unary_expr) ;
最终结果在这里:http://coliru.stacked-crooked.com/a/8539757bb02fca34 (遗憾的是,这对 Coliru 来说太多了),并打印:
<expr>
...
</logical_or_expr>
<success></success>
<attributes>[[42, []]]</attributes>
</expr>
注意 请注意,这种调整不会使解析器更高效!事实上,它只会导致大量回溯(调试输出计数为 925 行,而不是 第 1 步 (!!) 中的 45 行)。
现在,使用前瞻断言和/或语义操作将有一些优化空间,但通常您将不得不考虑优化 AST 存储将花费 CPU 时间。
关于c++ - Spirit Qi MiniC 示例的语法树空节点问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19707254/
关于 B 树与 B+ 树,网上有一个比较经典的问题:为什么 MongoDb 使用 B 树,而 MySQL 索引使用 B+ 树? 但实际上 MongoDb 真的用的是 B 树吗?
如何将 R* Tree 实现为持久(基于磁盘)树?保存 R* 树索引或保存叶值的文件的体系结构是什么? 注意:此外,如何在这种持久性 R* 树中执行插入、更新和删除操作? 注意事项二:我已经实现了一个
目前,我正在努力用 Java 表示我用 SML 编写的 AST 树,这样我就可以随时用 Java 遍历它。 我想知道是否应该在 Java 中创建一个 Node 类,其中包含我想要表示的数据,以及一个数
我之前用过这个库http://www.cs.umd.edu/~mount/ANN/ .但是,它们不提供范围查询实现。我猜是否有一个 C++ 范围查询实现(圆形或矩形),用于查询二维数据。 谢谢。 最佳
在进一步分析为什么MySQL数据库索引选择使用B+树之前,我相信很多小伙伴对数据结构中的树还是有些许模糊的,因此我们由浅入深一步步探讨树的演进过程,在一步步引出B树以及为什么MySQL数据库索引选择
操作系统的那棵“树” 今天从一颗 开始,我们看看如何从小树苗长成一颗苍天大树。 运转CPU CPU运转起来很简单,就是不断的从内存取值执行。 CPU没有好好运转 IO是个耗费时间的活,如果CPU在取值
我想为海洋生物学类(class)制作一个简单的系统发育树作为教育示例。我有一个具有分类等级的物种列表: Group <- c("Benthos","Benthos","Benthos","Be
我从这段代码中删除节点时遇到问题,如果我插入数字 12 并尝试删除它,它不会删除它,我尝试调试,似乎当它尝试删除时,它出错了树的。但是,如果我尝试删除它已经插入主节点的节点,它将删除它,或者我插入数字
B+ 树的叶节点链接在一起。将 B+ 树的指针结构视为有向图,它不是循环的。但是忽略指针的方向并将其视为链接在一起的无向叶节点会在图中创建循环。 在 Haskell 中,如何将叶子构造为父内部节点的子
我在 GWT 中使用树控件。我有一个自定义小部件,我将其添加为 TreeItem: Tree testTree = new Tree(); testTree.addItem(myWidget); 我想
它有点像混合树/链表结构。这是我定义结构的方式 struct node { nodeP sibling; nodeP child; nodeP parent; char
我编写了使用队列遍历树的代码,但是下面的出队函数生成错误,head = p->next 是否有问题?我不明白为什么这部分是错误的。 void Levelorder(void) { node *tmp,
例如,我想解析以下数组: var array1 = ["a.b.c.d", "a.e.f.g", "a.h", "a.i.j", "a.b.k"] 进入: var json1 = { "nod
问题 -> 给定一棵二叉树和一个和,确定该树是否具有从根到叶的路径,使得沿路径的所有值相加等于给定的和。 我的解决方案 -> public class Solution { public bo
我有一个创建 java 树的任务,它包含三列:运动名称、运动类别中的运动计数和上次更新。类似的东西显示在下面的图像上: 如您所见,有 4 种运动:水上运动、球类运动、跳伞运动和舞蹈运动。当我展开 sk
我想在 H2 数据库中实现 B+ Tree,但我想知道,B+ Tree 功能在 H2 数据库中可用吗? 最佳答案 H2 已经使用了 B+ 树(PageBtree 类)。 关于mysql - H2数据库
假设我们有 5 个字符串数组: String[] array1 = {"hello", "i", "cat"}; String[] array2 = {"hello", "i", "am"}; Str
我正在处理树。每个节点都有带有 Tree * 值的对象。我读取的数据如下所示: 1 2 2 ... 这意味着,将 1 作为 0 的子节点,将 2 作为 1 的子节点,将 3 作为 o 2 的子节点。在
我正在寻找一个好的 JavaScript 树/树网格包。现在——在你回答之前: 它需要能够在大量节点上正常运行。可能有 1,000 个兄弟节点。它需要能够在 2 或 3 秒内绘制到 1,000 个节点
下面的代码块究竟是如何工作的?更具体地说,程序如何知道返回哪个选项? return ancestor (node1->left(), node2) || ancestor
我是一名优秀的程序员,十分优秀!