当前位置:   article > 正文

DSL初探

dsl

I. 引言

DSL,全称为领域特定语言(Domain-Specific Language),是一种专门为特定问题领域设计的计算机语言。与通用编程语言(如Java,C++等)相比,DSL在特定领域内有更强的表达能力,可以让开发者更加高效地解决问题。这篇文章将深入探讨DSL的历史、类型、设计、应用以及面临的挑战。

II. DSL的历史和发展

DSL的起源可以追溯到20世纪60年代,那时计算机科学家们开始为特定问题领域创建专门的语言。随着时间的推移,DSL的种类和应用范围不断扩大,成为现代编程的重要组成部分。

III. DSL的类型和特点

DSL主要分为两类:内部DSL和外部DSL。

  • 内部DSL是在现有的通用编程语言中创建的,如Ruby中的RSpec;
  • 而外部DSL是完全独立创建的,如HTML和SQL。相比通用编程语言,DSL更专注于特定问题领域,语法更简洁,易于理解和使用。

IV. DSL的设计和实现

设计DSL需要深入理解问题领域,然后创建能准确描述这个领域问题和解决方案的语言。实现DSL通常需要使用语言工作框架,如ANTLR,或者利用现有编程语言的元编程特性。

V. DSL的应用示例

DSL广泛应用于各种领域,如Web开发中的HTML和CSS,数据库查询中的SQL,以及构建系统中的Makefile等。这些DSL都大大提高了开发效率,简化了复杂问题的处理。

实现一个 DSL 来从 JSON 中提取数据, 只要简单三步即可

  1. 实现解析器 Parser,把 DSL 转换成 AST(Abstract Syntax Tree 抽象语法树)
  2. 实现可执行程序 Executable,把数据从 JSON 中提取出来
  3. 实现转换器 Transformer,把 AST 转成可执行可执行程序 (Code Generation)

V.I实现解析器 Parser

这边用一个第三方开源库 lark-parser/lark 去实现。它基于 EBNF 实现了自己的扩展写法。支持多种解析算法。接下来用个例子来介绍一下,它是如何使用的。

from lark import Lark

parser = Lark('''?start: path
                 ?path: path "." name          -> chained_path
                     | name
                 name: CNAME

                 %import common.CNAME         -> CNAME
                 %import common.WS_INLINE

                 %ignore WS_INLINE
              ''')

tree = parser.parse("info.author.name")
print(tree)
print(tree.pretty())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

运行以上代码,终端就会输出解析到的 AST 结果。

Tree(chained_path, [Tree(chained_path, [Tree(name, [Token(CNAME, 'info')]), Tree(name, [Token(CNAME, 'author')])]), Tree(name, [Token(CNAME, 'name')])])
chained_path
  chained_path
    name    info
    name    author
  name  name
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

V.II 实现执行程序 Executable

接下来要实现一个可执行的算法。目前只是为了举例,写的比较粗暴。实现一个高阶函数 getter 来创建相应的提取函数,在函数中先粗暴地拦截掉所有 KeyError 异常,返回成 None。再实现一个高阶函数 chain 来创建级联提取函数。

import operator

def getter(name):

    def _getter(data):
        try:
            return operator.getitem(data, name)
        except KeyError:
            return None

    return _getter

def chain(current, next):

    def pair(data):
        return next(current(data))

    return pair
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

V.III 实现转换器 Transformer

再实现个把 AST 转换成执行代码的转换器。 只要自底向上从左到右遍历树结构(后序遍历),根据节点的树结构类型去执行对应的转换函数,构建我们的执行算法。 比如 name 就只有一个子节点 CNAME;而 chained_path 就有两个子节点,表达式的历史路径,及表达式的下一个路径 name。

?path: path "." name -> chained_path
    | name
name: CNAME
  • 1
  • 2
  • 3

虽然我们需要遍历树结构,但我们没有必要去实现相应的算法。 这是因为 lark-parser 提供特别方便的 visitors 模块去让开发者使用。以类方法来转换 AST 上对应的结构。就是把 CNAME token 转换成字符串,把 name 结构转换成提取函数,把 chained_path 转换成级联(链式)提取函数。代码如下

from lark.visitors import Transformer, v_args

@v_args(inline=True)
class JSONPathTransformer(Transformer):
    CNAME = str

    def name(self, cname):
        return getter(cname)

    def chained_path(self, previous_path, name):
        return chain(previous_path, name)

transformer = JSONPathTransformer()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

验证
接下来我们写几个简单的测试,测试一下

def parse(text):
    return transformer.transform(parser.parse(text))

assert parse("info.author.name")(
        {"info": {"author": {"name": "Jack"}}}
    ) == "Jack"
assert parse("data")({"info":"boo"}) is None
assert parse("info")({"info":"boo"}) == "boo
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

之前 Executable 部分的代码有个 bug ,不知道大家能不能一眼看出来。我给个提示,执行以下代码就会抛出异常,这又该怎么修复呢?

assert parse(“info.author”)({}) is None
答案就是 getter 得处理参数 data=None 的情况,不然以上代码就会抛出 TypeError 异常。

VI. DSL的优点和挑战

DSL的主要优势在于其在特定领域内的强大表达能力,可以减少开发者的工作量,提高代码质量。然而,设计和实现一个好的DSL并非易事,需要深入理解领域知识,同时也需要精通语言设计和实现技术。

VII. 结论

尽管DSL面临一些挑战,但其在现代编程中的重要性不容忽视。随着编程领域的发展,DSL将会越来越重要,我们也应该学习和掌握DSL,以便能更好地解决特定领域的问题。

VIII. 参考文献

Mernik, M., Heering, J., & Sloane, A. M. (2005). When and how to develop domain-specific languages. ACM computing surveys (CSUR), 37(4), 316-344.
Fowler, M. (2010). Domain-specific languages. Pearson Education.
Van Deursen, A., Klint, P., & Visser, J. (2000). Domain-specific languages: An annotated bibliography. Sigplan Notices, 35(6), 26-36.

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/941686
推荐阅读
相关标签
  

闽ICP备14008679号