Freewind @ Thoughtworks scala java javascript dart 工具 编程实践 月结 math python english [comments admin] [feed]

(2014-03-12) 自己动手写模板引擎 – SharkDart (1) – 原理

广告: 云梯:翻墙vpn (省10元) 土行孙:科研用户翻墙http proxy (有优惠)

近段时间在学习Dart,想用它多造几只轮子,以巩固自己的基本功。忙里偷闲,实现了一个小巧的模板引擎,名为SharkDart,前后花了大约一个月的时间。之所以叫Shark,是因为这段时间在客户这里做的项目也叫这个名字,所以以此作为纪念。

另外选择“模板引擎”这只轮子,是因为编译原理相关的东西,是相当基础且重要的东西,自己也比较感兴趣。

项目代码

https://github.com/freewind/SharkDart

外观

模板的语法,以字母@作为标识符,大体上看起来是这样的:

lang.html

@params(List<String> languages)

<ul>
@for(String lang: languages) {
   <li>Hello, @lang!</li>
}
</ul>


它将会编译为一个`lang.dart`文件,并且提供了一个`render({List<String> languages})`函数以供调用。如果我们这样调用它:

import "lang.dart" as lang;

main() {
    var langs = ["Dart", "Java", "Scala"];
    lang.render(languages: langs);
}


它生成的最终结果将会是:

<ul>
    <li>Hello, Dart!</li>
    <li>Hello, Java!</li>
    <li>Hello, Scala!</li>
</ul>

SharkDart是一个通用型的模板引擎,可以用于各种文本的生成,比如html, js, css等。配合一个web框架,就可以做出简单的网站了。

原理

对于一个简单的模板引擎来说,一般需要这几步:

  1. 定义模板语法,比如前面的@for() { ... }的结构和功能等
  2. 词法分析,在模板文本中识别出各语法成分,一般会利用第三方解析库或者纯手写
  3. 定义各元素对应的类,生成一棵语法树
  4. 对于编译型引擎来说,需要将该树转化为一段目标语言的代码

这几步里,第一步最费脑筋,因为需要创造性的思维,才能想出一套与别人不同的、让自己满意的语法。

第二步最难最需要耐心,因为不论采用哪种方案,都需要定义一套准确、完整的解析规则来识别各种语法成分,这需要思考和反复的调试。

第三步相对要简单一些。

第四步要看情况,如果语法定的很细,比如直接把@for@if作为语法成分,编译的难度要小一些;但如果语法比较灵活,比如只定义了一套通用的标签结构,如@tagname(paramType paramVariable: paramDescription) { ... },然后根据tagname的不同,实现不同的功能,灵活度大,但编译就麻烦一些。

SharkDart的选择

根据我的偏好,SharkDart做出了以下的选择:

  1. 使用Dart主要是我打算使用Dart来巩固一下基本功。用Java的话没什么激情,Scala又觉得有点难了。Dart比较轻量,语法也比较简单,虽然IDEA对它的支持还有点弱,但也基本够用了。另外在Dart社区现在正处于各种造轮子的阶段,大家一起造比较有气氛。

  2. 编译型由于Dart语言本身不支持动态执行代码,所以如果想实现一些较复杂的功能,比如在模板中直接使用Dart语法,则必须采用编译型,而不是解析型。我对于在模板是否允许嵌入代码持支持态度,因为一项功能用不用得好还是看使用者。另外采用编译型,在性能上有先天优势。

  3. 采用灵活的语法即定义了标签的结构为@tagname(paramType paramVariable: paramDescription) { ... },但不在语法层面支持某一个具体的标签,而是根据tagname去找到对应的handler,实现相应的功能。这样可以让语法的解析变得简洁,并且容易扩展新的标签。其实之前已经做过了一个类似的模板引擎了,叫RythmDart,它采用了详细的语法定义,在语法分析阶段就已经可以识别出各标签了。这次想使用一种不同的方式。

  4. 使用PetitParser作为解析库据我所知,在Dart中目前有四种方式实现语法解析。

    1. 纯手写 比如dart-xml,使用了自定义的语法解析,代码相当长。这种方式是多数编译原理课程上讲授的。
    2. PetitParser PetitPaserDart是一个简洁、灵活且强大的语法分析器,它本身也使用Dart写成。代码清晰易懂、文档和示例丰富,且直接内置了对json/xml/lisp/dart语法的解析程序。作者也非常热心,解答了我遇到的各种疑问,非常感谢他。还有一个好处是,PetitParser系列还有Java版与Smalltalk版,如果有一天想把该模板移植到java上,也很方便。
    3. parsers parsers是另一个解析器,使用了更多的符号来减化语法的定义。因为PetitParser让我很满意,所以暂未尝试它。
    4. antlr for dart 这里有两个,一个是dartlr,使用antlr3的语法;另一个是antlr4dart,使用antlr4的语法,都是同一个作者。对于antlr我一直没有尝试,也许以后会试试它。

在这四种方式中,我采用了PetitParser,因为对它比较熟悉,遇到问题也很容易向作者请教。事实证明,对于一个小型的模板引擎,采用它是相当方便的。

下面就让我们一起,自己动手来实现这个模板引擎吧!

comments powered by Disqus