最外层访问策略在 Rascal 中到底做了什么?

What exactly does the outermost visit strategy do in Rascal?

我写了下面的 Rascal 代码,它应该从节点名称到节点的映射中构建一棵树,从 "top" 映射的节点开始。它应该重复将 result 中具有字符串作为子节点的所有节点的子节点替换为 nodeMap 映射到的节点,直到不再发生任何变化(固定点)。

getNode returns 节点 a map[str,node] 将它映射到,或者键本身,如果它不作为键出现在映射中。这很好用,证明了这个问题底部的其他代码确实有效。然而,下面的代码似乎 运行 即使在非常小的输入上也是如此。

node nodeMapToNode(map[str, node] nodeMap) {
    node result = nodeMap["top"];
    return outermost visit(result) {
        case node n: {
            if ([*str children] := getChildren(n)) {
                insert makeNode(getName(n), [getNode(child, nodeMap) | child <- children]);
            }
        }
    }
}

下面的代码确实有效,并且 returns 如我所料,在输入小的时候瞬间完成。然而,这正是我从流氓导师那里了解到的最外层访问应该做的。

任何人都可以向我解释这些代码片段之间的区别是什么(除了它们的编写方式之外)以及我因此误解了 outermost visit 的效果吗?另外,我想知道是否存在一种更短 and/or 更好的编写此代码的方法 - 使用诸如最外层访问之类的东西而不是手动编写固定点 - 是否存在。

node nodeMapToNode(map[str, node] nodeMap) {
    node result = nodeMap["top"];
    node lastResult;
    do {
        lastResult = result;
        result = visit(lastResult) {
            case node n: {
                if ([*str children] := getChildren(n)) {
                    insert makeNode(getName(n),
                        [getNode(child, nodeMap) | child <- children]);
                }
            }
        }
    } while (result != lastResult);
    return result;
}

最外层是什么?

流氓导师的解释非常紧凑,但让我们从那里开始吧。

repeat a top-down traversal as long as the traversal changes the resulting value (compute a fixed-point).

这在流氓术语中意味着:

r = outermost visit(x) {
  case str s => s + "."
    when size(s) < 3
};

是语法糖:

r = x;
solve(r) {
  r = top-down visit(r) {
    case str s => s + "."
      when size(s) < 3
  };
}

我认为有两种常见情况outermost/innermost是有道理的:

  • 你的替换应该在同一个节点上重复多次
  • 您的替换生成与其他模式匹配的新节点

你的具体例子

关于你问题中的例子。另一个手动改写的outermost实际上是一个innermost。默认的访问策略是bottom-up.

一般来说,树的自下而上访问比自上而下更快。特别是当你重写它的时候,因为 Rascal 是不可变的,自下而上构建一个新的树会更快。

那么,也许用 innermost 访问而不是最外层访问替换您的代码?