【问题标题】:Writing a compiler for a DSL in python在 python 中为 DSL 编写编译器
【发布时间】:2008-12-04 00:17:14
【问题描述】:

我正在用 python 编写一个游戏,并决定为地图数据文件创建一个 DSL。我知道我可以用正则表达式编写我自己的解析器,但我想知道是否有现有的 python 工具可以更轻松地做到这一点,比如 PHP 引擎中使用的 re2c。

一些额外的信息:

  • 是的,我确实需要 DSL,即使我不需要,我仍然想要在项目中构建和使用 DSL。
  • DSL 只包含数据(声明性的?),它不会被“执行”。大多数行看起来像:

    SOMETHING: !abc @123 #xyz/123

    我只需要读取数据树。

【问题讨论】:

    标签: python dsl dsl-tools


    【解决方案1】:

    pyparsing 一直给我留下深刻印象。作者 Paul McGuire 在python list/comp.lang.python 上很活跃,并且一直对任何有关它的查询都非常有帮助。

    【讨论】:

      【解决方案2】:

      这是一种非常有效的方法。

      abc= ONETHING( ... )
      xyz= ANOTHERTHING( ... )
      pqr= SOMETHING( this=abc, that=123, more=(xyz,123) )
      

      声明性的。易于解析。

      还有……

      它实际上是 Python。一些类声明,工作就完成了。 DSL 实际上是类声明。

      重要的是 DSL 只是创建对象。定义 DSL 时,首先必须从对象模型开始。稍后,您围绕该对象模型添加了一些语法。你不是从语法开始,而是从模型开始。

      【讨论】:

      • 我知道你在说什么,但是编写所有这些 cmets、括号、等号、前缀会混淆实际数据。此外,此方法不能很好地移植到 PHP 或 Java 等更冗长的语言。
      • @彼得。不同意。您可以使用位置参数并消除标签和 =。它完美地翻译成Java。已经在生产应用程序中使用它来定义声明性 DSL。
      • 我已经看到你建议的被称为内部 DSL 的东西。我喜欢这种方法。一个问题可能是,虽然该方法确实移植到其他语言(我已经看到在 C# 中实现了类似的东西),但您的 DSL 的精确语法可能会有所改变。地图文件不可移植。
      • @Mendelt:不认为可移植性是一项要求。你是对的,但它似乎不适用于这种情况。
      【解决方案3】:

      是的,有很多——太多了——解析工具,但标准库中没有。

      据我所知,PLY 和 SPARK 很受欢迎。 PLY 就像 yacc,但你用 Python 做所有事情,因为你用 docstrings 编写语法。

      就我个人而言,我喜欢解析器组合器的概念(取自函数式编程),我也非常喜欢pyparsing:你直接在 python 中编写你的语法和动作,而且很容易上手。我最终生成了我自己的带有动作的树节点类型,而不是使用它们默认的 ParserElement 类型。

      否则,您也可以使用现有的声明性语言,例如YAML

      【讨论】:

      • +1 用于 YAML 参考。任何能够处理任意嵌套的复合数据结构的系统都可以满足绝大多数需求。 YAML 非常符合要求,而且无需重新发明轮子。
      【解决方案4】:

      我在工作中编写了类似的东西来读取 SNMP 通知定义并从中自动生成 Java 类和 SNMP MIB 文件。使用这个小 DSL,我可以编写 20 行规范,它会生成大约 80 行 Java 代码和一个 100 行 MIB 文件。

      为了实现这一点,我实际上只是使用了直接的 Python 字符串处理(split()、切片等)来解析文件。我发现 Python 的字符串功能足以满足我的大多数(简单)解析需求。

      除了其他人提到的库之外,如果我正在编写更复杂的东西并且需要适当的解析能力,我可能会使用支持 Python(和其他语言)的ANTLR

      【讨论】:

        【解决方案5】:

        对于您所描述的“小型语言”,我使用简单的拆分、shlex(请注意 # 定义注释)或正则表达式。

        >>> line = 'SOMETHING: !abc @123 #xyz/123'
        
        >>> line.split()
        ['SOMETHING:', '!abc', '@123', '#xyz/123']
        
        >>> import shlex
        >>> list(shlex.shlex(line))
        ['SOMETHING', ':', '!', 'abc', '@', '123']
        

        以下是一个示例,因为我不知道您在寻找什么。

        >>> import re
        >>> result = re.match(r'([A-Z]*): !([a-z]*) @([0-9]*) #([a-z0-9/]*)', line)
        >>> result.groups()
        ('SOMETHING', 'abc', '123', 'xyz/123')
        

        【讨论】:

          【解决方案6】:

          DSL 是一个好东西,所以你不需要为自己辩护 :-) 但是,您是否考虑过内部 DSL ?与外部(解析的)DSL 相比,它们有很多优点,至少值得考虑。将 DSL 与本机语言的强大功能相结合确实可以为您解决很多问题,而且 Python 在内部 DSL 方面也不是很差,with 语句很方便。

          【讨论】:

          • withstatement 对内部 DSL 有何用处?我很好奇
          【解决方案7】:

          在声明性 python 的行中,我编写了一个名为“bpyml”的辅助模块,它允许您以更 XML 结构化的方式在 python 中声明数据,而无需冗长的标签,它也可以转换为 XML 或从 XML 转换,但它是有效的蟒蛇。

          https://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts/modules/bpyml.py

          示例使用 http://wiki.blender.org/index.php/User:Ideasman42#Declarative_UI_In_Blender

          【讨论】:

            【解决方案8】:

            这里有一个更简单的方法来解决它

            如果我可以使用新的运算符扩展 python 语法以在语言中引入新的功能怎么办?例如,一个新的运算符<=> 用于交换两个变量的值。

            如何实现这种行为? AST 模块来了。 最后一个模块是处理抽象语法树的便捷工具。这个模块很酷的地方在于它允许我编写生成树然后将其编译为 python 代码的 python 代码。

            假设我们要将超集语言(或类 Python 语言)编译为 Python:

            来自:

                a <=> b
            

            到:

                a , b = b , a
            
            1. 我需要将我的“类似 python”的源代码转换为令牌列表。 所以我需要一个分词器,一个 Python 源代码的词法扫描器。 Tokenize module

            2. 我可以使用相同的元语言来定义新的'python-like'语言的语法,然后构建抽象语法树的结构AST

            为什么要使用 AST?

            1. 在评估不受信任的代码时,AST 是一种更安全的选择
            2. 在执行代码之前操作树Working on the Tree
            from tokenize import untokenize, tokenize, NUMBER, STRING, NAME, OP, COMMA
            import io
            import ast
            
            s = b"a <=> b\n" # i may read it from file
            b = io.BytesIO(s)
            g = tokenize(b.readline)
            result = []
            for token_num, token_val, _, _, _ in g:
                # naive simple approach to compile a<=>b to a,b = b,a
                if token_num == OP and token_val == '<=' and next(g).string == '>':
                    first  = result.pop()
                    next_token = next(g)
                    second = (NAME, next_token.string)
                    result.extend([
                        first,
                        (COMMA, ','),
                        second,
                        (OP, '='),
                        second,
                        (COMMA, ','),
                        first,
                    ])
                else:
                    result.append((token_num, token_val))
            
            src = untokenize(result).decode('utf-8')
            exp = ast.parse(src)
            code = compile(exp, filename='', mode='exec')
            
            
            def my_swap(a, b):
                global code
                env = {
                    "a": a,
                    "b": b
                }
                exec(code, env)
                return env['a'], env['b']
            
            print(my_swap(1,10))
            
            

            其他使用 AST 的模块,其源代码可能是有用的参考:

            • textX-LS:用于描述形状集合并为我们绘制的 DSL。

            • pony orm:您可以使用 Python 生成器和带有转换为 SQL 查询字符串的 lambdas 编写数据库查询——pony orm 在后台使用 AST

            • osso:基于角色的访问控制框架句柄权限。

            【讨论】:

            • 我认为这是一个答案,而您并没有提出新问题。能否请您删除修辞问题,因为它看起来不是真正的答案。
            猜你喜欢
            • 2012-12-27
            • 2011-06-29
            • 2016-07-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-11-10
            相关资源
            最近更新 更多