最近,人们对于领域特定语言F#中DSL原型设计的兴趣卷土重来。这些语言不仅能够为特定领域提供更好等级的提炼,从而有助于减少在通用语言中因低等级构造而造成的错误;而且通过提供额外配置、定制的业务逻辑等,它们为用户提供了一种有效的机制,用于细调你的应用程序。总之,DSL能够让你的应用程序更加多样化并具有更好的伸缩性。
大致来讲,领域特定语言的工作方式有两种——你可以通过对以源DSL编写的源文本进行转译来实施,或者通过将源文本编译为可执行代码。这两种方式都有着独特的优点和缺点。对于解释器和编译器的实施阶段,很多都是类似的,甚至一模一样;例如,语法和语义检查在两种方式中是共同的。在获得合适的内部重现(inner representation)之后,编译器实施包括几个阶段,逐步将这种重现分解为低等级的指令,生成汇编语言原生码,或管理代码(取决于目标平台)。解释器与之相反,很少执行这些阶段。作为替代,你可以实施所谓的DSL 的“操作语义”(operational semantic);例如,为内部重现编写一个评估器。
图1. 运行中的Simply
你可以在Simply上进行构建,来创建新的DSL并将其嵌入到你自己定制的开发外科中。此处演示的应用程序 SimplyLogo从零开始构建,F# 代码少于500 行。
在本文介绍的F#中DSL原型设计,我们将为一个小型的DSL(由于其类似 C 语言的语法和简洁,我将其称为“Simply”)编写一个解释器,然后使用与Logo 那种语言类似的内置函数将其实例化。你可以通过在表达式语法器上来构建以完成实例化。之前我们已经在相关文章中进行讲述,在这篇文章中,你可以看到活跃模式(active pattern)提供了一个完美的机制(虽然付出了一点速度的小代价),能够用于构建符合类型安全规则的语法器,它与用户语法中的正规的 BNF 句法非常相似;并且能够在增强的AST 重现上实施语言检查(本文)和评估器(下一篇文章)。使用这种语言,你可以快速生成图像,这些图像能够使用简单的画图命令来定义——并且你可以在所有你需要的语境中使用这个核心评估器。在图 1 中所示为该 DSL 的一种可能的嵌入。在本文中,我们主要关心的是构建 Simply 的语法器和检查源程序以确认语法的正确性。
51CTO译者注:为了学习这个系列的文章,你需要下载 F# May 2009 CTP 或Visual Studio 2010 Beta 1。
Simply 概述
Simply 是本文的DSL,它是一个具有静态作用域、嵌套变量(nested variable)和函数声明,以及简单循环构造的小型编程语言。下面是一个很短的 Simply 程序:
var x = 2
fun x(a b) { a + b + x }
fun x(y) { y + x(1 2) }
repeat 100 as i { x(i) }
这段程序很容易读懂,它包含四条命令,定义了一个变量、两个函数和一个循环。为了分析这些命令的语法,你需要对上文中讲述的语法器进行扩展。
对具有循环构造、变量和函数的Simply进行扩展
前文中实施的语法器使用函数调用对算术表达式进行语法分析并将其翻译为定制的 AST 类型。对于 Simply,你需要一个稍微更为高级的内部重现来对表达式进行语法分析,这些表达式包含了简单变量以及与其密切关联的少量语言扩展,用于定义变量和函数,以及用简单的循环构造(循环区块)来表达循环。
如果你已经将AST 定义放在其自身模块中,下面你可以看到新的扩展版本:
namespace IntelliFactory.Simply
module Ast =
type var = string
type Expr =
| Number of float
| BinOp of (float -
>
float -
>
float) * Expr * Expr
| Var of var
| FunApply of var * Expr list
static member Sum (e1, e2) = BinOp (( + ), e1, e2)
static member Diff (e1, e2) = BinOp (( - ), e1, e2)
static member Prod (e1, e2) = BinOp (( * ), e1, e2)
static member Ratio (e1, e2) = BinOp (( / ), e1, e2)