原文刊登于:
http://www.bitwisemag.com/2/DLR-Build-Your-Own-Language,由Ray Linn翻译,如需转载,请站内告知。
-------------------------------------------------------------------
你曾经为学习一门计算机语言而苦恼么?那好,为啥不丢掉那些C++手册,动手按自己的想法去写一门称心的语言呢?这听起来无比困难,可能只有计算机科学的专家们才能完成,直到最近,两门新的技术让你我一样的普通人编写一门称心的语言成为了可能。首先是ANTLR3,这是广泛应用的ANTLR2解析系统的全新版本(http://www.antlr.org/)。另外一项技术则是微软的新革新“动态语言运行时”-DLR (http://www.codeplex.com/IronPython).
DLR是什么?
了解DLR是什么不是件很容易的事情。关于DLR并没有太多信息,至多只有些片段。开始我以为微软为现有的CLR添加了些新的指令来为动态语言提供帮助,但后来我发现这是错的,DLR压根儿没触及CLR的修改,实际上DLR只是个基于CLR的能运行你的动态语言的程序,这有点像解释器,但它要比通常的解释器聪明得多,也要快得多。事实上,它介于解释器和编译器之间,并带有一些很纯粹的特性来支持动态语言。
顺便说一下,CLR还是JVM谁比较适合支持动态语言是有些争论的。Sun有个实验性的项目叫"Da Vinci Machine“,它试图为JVM提供新指令以支持动态语言。但是,这条路走下坡了,应该在经过很长很长的研究和测试之后才能为JVM或CLR添加指令,因为,虚拟机必须完美瑕疵的工作。所以个人觉得,在新的动态语言指令出现在JVM或CLR之前,我们得等上一段时间了。微软将DLR层叠于CLR之上而不是内嵌于CLR看起来是个更好更实际的方法。
DLR区别于解释器或编译器的地方在于它如何处理动态语言中的“动态”。本质上,动态语言是指在运行时一个对象的类型可以被改变。下面的代码在Java或C#(至少是4.0之前)里都是错误的。
i = 1;
i = "hello";
当然,动态语言的特性远不只这么点:[url="http://en.wikipedia.org/wiki/Dynamic_programming_language"
]这篇文章[/url]很好的介绍了动态语言,不过这两行代码也总结了动态语言是怎么回事儿。上面代码中的"i"的类型从表示一个整数变成了表示一个字符串。这不是啥新东西,微软大肆将VB修改成VB.Net之前有‘variants’ 可变类型,而它的爷爷辈,Lisp语言,也足有50多年的历史了。随着时间的推移,可变类型现在被认为是个“好东西”。
如下所示,DLR保留了关于如何处理加法的规则,并更具类型选择了正确的规则。这些规则就是CLR里的“委托”--本质上就是方法指针-- DRL会在必要使调用。因此,如果变量的类型改变了,新的规则就被调用,如下所示
i = 1;
j = 2;
k = i + j; // integer addition rule invoked
i = "hello";
j = "world";
k = i + j; // string addition rule invoked?
现在问题是,你如何把这些规则告诉DLR?
ANTLR Detour
为了使用DLR,你必须创建抽象语法树AST,AST是程序的符号表达。有两种方法来产生AST,困难的方法或者使用ANTLR。困难的方法是使用传统的yacc/flex解析器和词法分析器。yacc是个LALR(自底向上)解析器能产生状态表然后建立语法树,我个人的感受是,浪费数年光阴在yacc状态表除错上,不如换点有趣的活法。
反之,ANTLR是一种‘递归下降’解析器, 术语叫LL(K). 事实上, ANTLR产生可读性很高的代码,意味着你可以理解和调试。这儿我谈的是ANTLR3。ANTLR3是自由使用的,由Java语言编写,但能够产生诸如java,C#,C++等等目标代码。现在的发布版是3.0.1,但我们真正需要的是下一个发布3.1(还在beta阶段),它能正确链接到DLR.
使用ANTLR来为DLR生成语法树包括三个步骤:
第一、词法分析。语汇分析器定义了语言的符号,例如C#的词法分析器会辨别关键字
“void“和整数"123"。第二、解析。解析器接受词法分析器的成组符号,并创建表达式(用某种语言如英文写就的句子)。解析器会分别正确与错误的表达式。比方说,解析器会接受诸如void abs(){}的表达式,但不会接受void(){}的表达式,即使词法分析器产生的符号是正确的。第三是树语法生成器. ANTLR解析器生成AST,而树语法(tree grammar)告诉ANTLR如何遍历AST并生成DLR代码。
这听起来好像很复杂,实际不然。 ANTLR3句法相当直白,并带丰富的例子,很容易上手。
链接到DLR
一旦你创建了ANTLR的词法分析器、解析器和生成器,接下来你需要创建DLR要使用的对象。DLR在两遍式操作中使用这些对象。首先,当DLR遍历ANTLR生成的AST时对象被创建,这是个标准的OO create操作,例如我们的示例语言的二进制操作符的构造函数:
public MyLBinary(SourceSpan span, int op, MyLExpression left, MyLExpression right)
: base(span) {
_op = op;
_left = left;
_right = right;
}
接下来是当遇到某个语言结构时所调用的“规则”:
protected internal override Expression Generate() {
Expression left = _left.Generate();
Expression right = _right.Generate();
Operators op;
switch (_op) {
case TestLexerLexer.PLUS:
op = Operators.Add;
break;
case TestLexerLexer.MINUS:
op = Operators.Subtract;
break;
case TestLexerLexer.MULTIPLY:
op = Operators.Multiply;
break;
case TestLexerLexer.DIVIDE:
op = Operators.Divide;
break;
default:
throw new System.InvalidOperationException();
}
return Ast.Action.Operator(op, typeof(object), left, right);
}
这儿,‘Generate’方法定义了DLR该为二进制操作做些什么。现在的DLR不需要每次都调用Generate方法,实际上,它通过你定义的规则产生了CLR代码并缓存在DLR规则缓存中。因此,通常,你的规则只被调用了一次,被DLR编译成字节码并被运行。这与普通的解析器每次都遇到二进制操作就再次调用上面的规则是完全不同的。
小结
在这篇文章里,我们了解:
. 用ANTLR3编写分析器-解析器-生成器定义了DLR所要遍历的AST结构。
. DLR遍历该结构偶并随之创建某语言对应操作的对象。
. 当语言被执行时,DLR调用这些对象以获取规则 - 当遇到某种指定的语言结构时,这些代码就被执行。这些规则只会被调用一次,并被编译。