【IT168 技术文档】
摘要:
总的说来,有两种创建DSL 的方法。 一种方法是从零开始发明一个新的语法,并为之构造一个编译器或解释器。 另一种方法是裁剪一个现存的通用目的语言, 增加或修改方法(methods), 操作符(operators),以及缺省的行为。 本文讲述使用第二种方法来在 Ruby 之上创建DSL.
一个DSL, 是一个针对相对窄的应用范围设计的编程或描述语言。相对于通用目的语言, 它被设计来处理特定的计算任务, DSL 仅适用于特定领域。 你可以用两种方式创建一个DSL.
从零开始发明一个新的语法,并为之构造一个编译器或解释器。
裁剪一个现存的通用目的语言,增加或修改方法(method), 操作符(operator),以及缺省的行为。
第二种方法的优势是你节省了时间, 因为你不需要生成和调试一个新的语言, 这样你有更多的时间用于解决最终用户面临的问题。缺点是DSL 会受限于其下的通用目的语言的语法和能力。 更进一步来说,基于另一个语言来构造DSL 常常意味着最终用户可以获得这个基础语言所有的威力,这可能是优点也可能是缺点, 它取决于特定的应用环境。 这篇文章讲述怎么使用第二种方法来在Ruby之上创建DSL.
描述堆叠模型(stackup models)
我是一个互连模型(interconnect modeling) 工程师,在工作中, 我们需要一种方式来描述半导体晶片上的电路的垂直几何结构(vertical geometric profile)。这些描述被保存在一个堆叠模型(stackup model) 文件中。(我们造了一个词stackup,因为金属连线是在装配过程(fabrication process)中通过层层堆叠形成的)。现在的问题是,每一个供货商有他们自己的描述堆叠模型(stackup) 的格式, 而我们想要一个公共的格式使得我们可以在不同的格式间进行转换。 换句话说, 我们需要定义一个公共的堆叠模型(stackup) DSL, 并且写一个程序用来从我们格式转换为不同供货商的特定的格式。
供货商没有使用一个复杂的DSL 语言,他们的语言仅仅包含静态的数据元素于一个基本平坦的文本数据库中。他们的文件格式不允许有参数化类型(parameterized type), 变量, 常量, 以及等式(equation)。 只有静态数据。更进一步来说,格式非常简单。它是基于行的,或者基于只有一个层次的块结构。
我们开始描述我们的堆叠模型格式时,要求并不高,因为我们仅仅需要达到供货商实现的程度就行。但我们很快扩展了我们的格式。为什么我们可以做到这一点, 而我们的供货商却做不到。我相信这是因为我们使用了 Ruby, 而不象我们的供货商用 C 从零开始。我相信也可以用其他的语言, 但是我不认为最终的产品可以同样出色。通用语言的选择是关键的一步。
我也相信供货商的开发速度因为使用C 而受到阻碍, 因为他们要保持他们的堆叠模型的语法尽量简单以便于分析(parse). 可能并非偶然,很多供货商在他们的文件格式中使用一些共同的简单的语法结构。 因为这些简单的语法结构出现频繁, 我们将先在 Ruby 中仿制它们, 然后再转到更复杂的语法结构。
基于行和块级别的 DSL 结构(constructs)
基于行的结构是一种将一个值或一个范围的值赋予一个参数(parameter)的方式。 在我们所考察的供货商的文件中,使用了下面的格式。
1. parameter = value
2. parameter value
3. parameter min_value max_value step_value
除了2 中没有显式出现’=’ , 格式1和2 是等价的。 格式3 将一个范围的值赋给了一个参数。
更为复杂的格式包含一个块结构。有两种格式如下所示。 这两种块结构可以使用一个基于行的分析器和一个堆栈 ,或一个带堆栈的关键字分析器(key-letter and word parser)来手动分析。
begin type = TYPE name = NAME param1 = value1 param2 = value2 ... end
下面一种块格式使用 C 风格的大括号{}来标识一个块, 但参数/值对用空格来分隔。
TYPE NAME {param1 = value1 param2 = value2 }