使用 Dragonfly CompoundRule 时重复额外的操作

Repeating an extra when using a Dragonfly CompoundRule

使用语音命令框架dragonfly2,您可以编写如下语法:

chrome_rules = MappingRule(
    name='chrome',
    mapping={
        'down [<n>]': actions.Key('space:%(n)d'),
    },
    extras=[
        IntegerRef("n", 1, 100)
    ],
    defaults={
        "n": 1
    }
)

这让我按 space n 次,其中 n 是某个整数。但是如果我想在同一个语法中多次使用同一个变量(n),我该怎么办?如果我在语法中重复它,例如'down <n> <n>' 然后说类似 "down three four" 的东西,Dragonfly 会正确解析它,但它只会执行 actions.Key('space:%(n)d')n=3,使用 n 的第一个值.我怎样才能让它执行 3 次,然后使用同一个变量执行 4 次?

理想情况下,我不想在 extras 和 defaults 中复制变量 n,因为这看起来像是冗余代码。

TL;DR:你的MappingRule以字典的形式将数据传递给你的Action(例如KeyText),所以它只能通过每增加一个价值。你现在最好的选择可能是创建多个附加功能。


这是蜻蜓解析识别方式的副作用。我会先用 Action 个对象来解释它,然后我们可以分解为什么会在 Rule 级别发生这种情况。

当 Dragonfly 收到一个识别时,它必须解构它并提取发生的任何额外内容。语音识别引擎本身不会遇到多次出现相同额外信息的问题,它确实将该数据传递给蜻蜓,但蜻蜓丢失了该信息。

所有Action对象都派生自ActionBase,这是蜻蜓要执行Action时调用的方法:

    def execute(self, data=None):
        self._log_exec.debug("Executing action: %s (%s)" % (self, data))
        try:
            if self._execute(data) == False:
                raise ActionError(str(self))
        except ActionError as e:
            self._log_exec.error("Execution failed: %s" % e)
            return False
        return True

这就是 Text 的工作原理,与 Key 相同。这里没有记录,但是 data 是映射到值的额外字典。例如:

{
    "n":    "3",
    "text": "some recognized dictation",
}

看到问题了吗?这意味着我们只能为每个 extra 传递一个值。即使我们组合多个动作,我们也会遇到同样的问题。例如:

{
    "down <n> <n>": Key("%(n)d") + Text("%(n)d"),
}

在幕后,这两个动作组合成一个 ActionSeries 对象 - 一个动作。它公开了相同的 execute 接口。一系列动作,一个data dict.

请注意,复合规则不会发生这种情况,即使每个基础规则共享一个具有相同名称的额外规则。那是因为 data 被解码并通过 per-rule。每个规则将不同的 data 字典传递给它希望执行的 Action


如果你想知道我们在哪里丢失了第二个额外的部分,我们可以向上导航调用链。

每个规则都有一个 process_recognition 方法。这是在发生识别时调用的方法。它接受当前规则的 node 并处理它。 node 可能是一棵规则树,也可能是较低级别的东西,例如 Action。让我们看看 MappingRule:

中的实现
    def process_recognition(self, node):
        """
            Process a recognition of this rule.

            This method is called by the containing Grammar when this
            rule is recognized.  This method collects information about
            the recognition and then calls *self._process_recognition*.

            - *node* -- The root node of the recognition parse tree.
        """
        # Prepare *extras* dict for passing to _process_recognition().
        extras = {
                  "_grammar":  self.grammar,
                  "_rule":     self,
                  "_node":     node,
                 }
        extras.update(self._defaults)
        for name, element in self._extras.items():
            extra_node = node.get_child_by_name(name, shallow=True)
            if extra_node:
                extras[name] = extra_node.value()
            elif element.has_default():
                extras[name] = element.default

        # Call the method to do the actual processing.
        self._process_recognition(node, extras)

我将跳过一些复杂的部分 - 您在此处看到的 extras 变量是 data 字典的早期形式。看到我们在哪里失去价值了吗?

extra_node = node.get_child_by_name(name, shallow=True)

看起来像:

    def get_child_by_name(self, name, shallow=False):
        """Get one node below this node with the given name."""
        for child in self.children:
            if child.name:
                if child.name == name:
                    return child
                if shallow:
                    # If shallow, don't look past named children.
                    continue
            match = child.get_child_by_name(name, shallow)
            if match:
                return match
        return None

所以,你看到了问题。 Dragonfly 尝试为每个 extra 提取一个值,并获得第一个值。然后,它将该值填充到字典中并将其向下传递给 Action。其他事件丢失。