赞
踩
DSL,全称为领域特定语言(Domain-Specific Language),是一种专门为特定问题领域设计的计算机语言。与通用编程语言(如Java,C++等)相比,DSL在特定领域内有更强的表达能力,可以让开发者更加高效地解决问题。这篇文章将深入探讨DSL的历史、类型、设计、应用以及面临的挑战。
DSL的起源可以追溯到20世纪60年代,那时计算机科学家们开始为特定问题领域创建专门的语言。随着时间的推移,DSL的种类和应用范围不断扩大,成为现代编程的重要组成部分。
DSL主要分为两类:内部DSL和外部DSL。
设计DSL需要深入理解问题领域,然后创建能准确描述这个领域问题和解决方案的语言。实现DSL通常需要使用语言工作框架,如ANTLR,或者利用现有编程语言的元编程特性。
DSL广泛应用于各种领域,如Web开发中的HTML和CSS,数据库查询中的SQL,以及构建系统中的Makefile等。这些DSL都大大提高了开发效率,简化了复杂问题的处理。
实现一个 DSL 来从 JSON 中提取数据, 只要简单三步即可
这边用一个第三方开源库 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())
运行以上代码,终端就会输出解析到的 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
接下来要实现一个可执行的算法。目前只是为了举例,写的比较粗暴。实现一个高阶函数 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
再实现个把 AST 转换成执行代码的转换器。 只要自底向上从左到右遍历树结构(后序遍历),根据节点的树结构类型去执行对应的转换函数,构建我们的执行算法。 比如 name 就只有一个子节点 CNAME;而 chained_path 就有两个子节点,表达式的历史路径,及表达式的下一个路径 name。
?path: path "." name -> chained_path
| name
name: CNAME
虽然我们需要遍历树结构,但我们没有必要去实现相应的算法。 这是因为 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()
验证
接下来我们写几个简单的测试,测试一下
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
之前 Executable 部分的代码有个 bug ,不知道大家能不能一眼看出来。我给个提示,执行以下代码就会抛出异常,这又该怎么修复呢?
assert parse(“info.author”)({}) is None
答案就是 getter 得处理参数 data=None 的情况,不然以上代码就会抛出 TypeError 异常。
DSL的主要优势在于其在特定领域内的强大表达能力,可以减少开发者的工作量,提高代码质量。然而,设计和实现一个好的DSL并非易事,需要深入理解领域知识,同时也需要精通语言设计和实现技术。
尽管DSL面临一些挑战,但其在现代编程中的重要性不容忽视。随着编程领域的发展,DSL将会越来越重要,我们也应该学习和掌握DSL,以便能更好地解决特定领域的问题。
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.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。