Python 将声明性DSL转换为嵌套函数调用

Python 将声明性DSL转换为嵌套函数调用,python,parsing,dsl,behavior-tree,Python,Parsing,Dsl,Behavior Tree,我有一个python库,它从中构建特殊的迭代器(行为树)。虽然API有一个相当好的、轻量级的语法(因为它是python),但它实际上可以使用声明性DSL 下面是我设想的大致情况: DSL(使用YAML): 将导致以下嵌套函数调用: visit( sequence( do_action1(), do_action2(), select( do_action3(), sequence(

我有一个python库,它从中构建特殊的迭代器(行为树)。虽然API有一个相当好的、轻量级的语法(因为它是python),但它实际上可以使用声明性DSL

下面是我设想的大致情况:

DSL(使用YAML):

将导致以下嵌套函数调用:

visit(
    sequence(
        do_action1(),
        do_action2(),
        select(
            do_action3(),
            sequence(
                do_action4(),
                do_action5(),
                ),
            do_action6(),
            )
        )
    )

我很难想象到底该怎么做。因为DSL必须表示一棵树,所以简单的深度优先遍历似乎是合适的。但为了构建嵌套函数调用,我必须以某种方式将其从内到外转换。它可能涉及一些巧妙的中间堆栈或类似的东西,但我不能完全理解它。执行此转换的正确方法是什么?

我认为可以让python跟踪函数调用和参数,而不是自己使用堆栈

假设您有一个YAML解析树,其中每个节点表示一个函数调用,该节点的每个子节点都是一个参数(这也是一个函数调用,因此它可能有自己的参数)

然后定义函数
evaluate
,该函数计算此树的节点,如下所示(伪代码):

最后,调用
evaluate
将YAML树的根作为参数传递,您应该会得到所需的行为


稍有不同的eval apply结构

def evaluate(node):
    # evaluate parameters of the call
    params = [ evaluate(child) for child in node ]

    # apply whatever function this node represents
    return node.function.call(*params)

“它真的可以使用声明性DSL”?真正地Python代码比DSL更具可读性。DSL有什么帮助?在这个非常简单的例子中,括号特别让我感到困扰。在更复杂的树中,尤其是当涉及关键字arg时,要求子项(
*args
)位于选项(
**kwargs
)之前会严重降低可读性。演示了这一点--
parallel
节点的
policy
参数很容易丢失。如果“真实示例”实际显示了问题,那么您可能应该在此处包含类似的内容。DSL是一个有吸引力的麻烦。我没有看到一个有任何价值。如果你有一个例子,请修正这个问题来说明增加复杂性的一些令人信服的原因。这个问题不是关于DSL的优点或缺点。一个更复杂的例子会偏离如何将解析树转换为特定构造的真正问题。想象一下,select意味着“do_one_of”,编写的python实际上调用了所有子级,然后返回一个结果。你的意思可能是只做其中一个动作。在这种情况下,python变得比DSL丑陋得多(IMO)。我不知道为什么这很难想象,“为什么这很难想象”。另一个例子说明了为什么DSL没有多大帮助。Python很容易可视化。坚持使用easy。我确信你对DSL的评价很低,我真心希望你永远不会发现自己需要DSL。我想把我的自行车棚漆成蓝色,我现在就去油漆店。这很难,零价值,而且仍然很重要。有趣。顺便说一句,这正是Python进行表达式求值的方式。你自相矛盾。它的值为零,但我刚刚确切地了解了Python如何进行表达式求值。你的意思是说,通过应用来学习计算机科学的基本概念是没有价值的,还是说在30年的编程经验中,你从来没有建造过任何类似于编译器的东西,或者从没有发现任何价值?请,我感谢你花宝贵的时间来尝试和帮助,但我在这里努力学习。关于DSL,我可能会得出类似的结论,但我需要在这件事上得出自己的结论。
def evaluate(node):
    # evaluate parameters of the call
    params = []
    for child in node:
        params.append(evaluate(child))

    # now make the call to whatever function this node represents,
    # passing the parameters
    return node.function.call(*params)
def evaluate(node):
    # evaluate parameters of the call
    params = [ evaluate(child) for child in node ]

    # apply whatever function this node represents
    return node.function.call(*params)