悠然亂彈:「最好的模板引擎」Beetl 剖析及與Tiny模板引擎對比

Beetl的環境搭建

輸入命令 html

git clone https://git.oschina.net/xiandafu/beetl2.0.git
不一下子,輸出了下面的內容


Cloning into 'beetl2.0'...
remote: Counting objects: 5807, done.
remote: Compressing objects: 100% (2145/2145), done.
remote: Total 5807 (delta 3050), reused 5383 (delta 2733)
Receiving objects: 100% (5807/5807), 14.60 MiB | 684.00 KiB/s, done.
Resolving deltas: 100% (3050/3050), done.
Checking connectivity... done.
嗯嗯,好的開頭是成功的一半,不錯,代碼取下來了。
cd beetl2.0
mvn install
輸出結果:
[WARNING] 
[WARNING] Some problems were encountered while building the effective settings
[WARNING] 'servers.server.id' must be unique but found duplicate server with id tiny-nexus-releases @ /Users/luoguo/Develop/apache-maven-3.1.0/conf/settings.xml
[WARNING] 
[INFO] Scanning for projects...
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]   
[ERROR]   The project org.beetl:beetl-core:2.2.4-SNAPSHOT (/Users/luoguo/git/beetl2.0/beetl-core/pom.xml) has 1 error
[ERROR]     Non-resolvable parent POM: Could not find artifact org.beetl:beetl-parent:pom:2.2.4-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 4, column 10 -> [Help 2]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException
[ERROR] [Help 2] http://cwiki.apache.org/confluence/display/MAVEN/UnresolvableModelException
咦,這是什麼鬼?

猜測是因爲我用的是maven 3.1.x致使,因而升級到maven 3.3.3,執行 mvn install,能夠看到開始下載相關的資源文件了,OK,起步仍是不錯的,這裏須要耐心等待一段時間。 java

咦,中止了,看到一堆錯誤,再看看是什麼問題? git

[ERROR] /Users/luoguo/git/beetl2.0/beetl-core/src/main/java/org/beetl/ext/jodd/BeetlActionResult.java:[13,8] org.beetl.ext.jodd.BeetlActionResult不是抽象的, 而且未覆蓋jodd.madvoc.result.ActionResult中的抽象方法getResultType()
[ERROR] /Users/luoguo/git/beetl2.0/beetl-core/src/main/java/org/beetl/ext/jodd/BeetlActionResult.java:[60,9] 方法不會覆蓋或實現超類型的方法
[INFO] 2 errors 
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] beetl-core ......................................... FAILURE [ 44.926 s]
[INFO] beetl-parent ....................................... SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 45.061 s
[INFO] Finished at: 2015-07-28T14:08:38+08:00
[INFO] Final Memory: 18M/262M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project beetl-core: Compilation failure: Compilation failure:
[ERROR] /Users/luoguo/git/beetl2.0/beetl-core/src/main/java/org/beetl/ext/jodd/BeetlActionResult.java:[13,8] org.beetl.ext.jodd.BeetlActionResult不是抽象的, 而且未覆蓋jodd.madvoc.result.ActionResult中的抽象方法getResultType()
[ERROR] /Users/luoguo/git/beetl2.0/beetl-core/src/main/java/org/beetl/ext/jodd/BeetlActionResult.java:[60,9] 方法不會覆蓋或實現超類型的方法
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
看起來是Beetl繼承了jodd的類,可是有些方法沒有實現,

沒有辦法,只要增長fae指令再來執行: 程序員

mvn clean install -fae
結果仍是原樣的錯誤,至此已經沒法進行。

根據文件名分析,這個東東多是對jodd的一個擴展,理論上能夠刪除之,因而刪除了類BeetlActionResult,而後從新執行mvn install ajax

此次出來的結果是: shell

[INFO] Reactor Summary:
[INFO] 
[INFO] beetl-core ......................................... SUCCESS [03:52 min]
[INFO] beetl-parent ....................................... SUCCESS [  0.008 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 03:52 min
[INFO] Finished at: 2015-07-28T14:26:09+08:00
[INFO] Final Memory: 25M/309M
[INFO] ------------------------------------------------------------------------
從環境搭建的過程來看,只要是maven 3.3.3,搭建還算順利,美中不足是有一個Jodd的擴展 BeetlActionResult類有問題。經過直接刪除,編譯是經過了,有多大的影響,暫時還不清楚。

Beetl工程結構靜態分析

從這裏看,總體來講還能夠,把一些bak文件上傳上來,稍嫌不嚴謹,另外有些jpg文件直接放在根目錄也有一點點亂,若是整理一下就更好了。 express

接下來比較關心core apache

這裏面有幾個東東,就有點難理解了,爲何這裏放了個jar文件?爲何這裏放了個lib目錄?爲何這裏放了個performance工程?性能評測的代碼怎麼會放到core工程中?? 安全

上面這個應該就是關鍵工程了?core應該就是引擎核心代碼所在的位置,ext應該是它對各類開源框架方面的擴展或支持。有這些擴展仍是很是不錯的,方便使用者上手,贊一個。可是把ext和core放在一個工程裏仍是有點隨意了,若是能把ext單獨開個工程就更好了。 框架

從上面的目錄結構看仍是不錯的,可是很顯然下面的一些類和接口看起來就比較亂了,應該至關有改進的空間。

相對應的,能夠看看Tiny模板引擎的目錄結構:

就簡潔清爽多了。

再來看看beetl模板的代碼行數:

能夠看到core工程中的java代碼是20291行,不算空行,不算註釋行。

Tiny模板引擎的代碼行數,純純的java代碼只有4944行,也就是beetl的代碼整整是Tiny模板引擎4倍多。

上面是Beetl的sonar檢查狀況

上面的統計數據是Tiny模板引擎的統計數據:

這裏的數據和上面用Statistics統計的數據稍有區別,可是基本上差異不大。

從上面的數據能夠看出:

項目

Beetl

Tiny模板引擎

代碼行數 23087 4944
文件數 230 171 
重複 3.3%
0.0%
複雜度 2.8/方法
1.9/方法
包耦合指數 31.5%
31.6%
包耦合循環 >35
>18

從代碼規模來講,Tiny完勝,只有Beetl的不到1/4。代碼重複率方面Beetl也至關不錯了,固然Tiny的更好一點。複雜度Beetl方面也不錯,固然 Tiny的要更好一點。包耦合指數方面差很少,可是包耦合循環方面,tiny只有Beetl的一半。

從上面的數據來看,Tiny的方法更小,包依賴的長度更短,更容易維護。

OK,從上面的靜態分析來看,Beetl的包結構組織有進步的空間,有必定的代碼重複,總體代碼質量還不錯,可是包耦合度有點高,因此其可維護性較Tiny稍弱。

Beetl語法

到main/antlr中查找Beetl語法定義文件,竟然沒有找到,最後終於在下面的位置main/java/org/beetl/core/parser/BeetlParser.g4找到了,爲何不能徹底遵循Maven規範呢?

Tiny模板引擎則徹底遵照規範。
statement
  
    :   block   #blockSt
    |   textStatment    #textOutputSt
    |   constantsTextStatment #staticOutputSt
    |   COMMENT_TAG commentTypeTag  #commentTagSt
    |   If parExpression statement (Else statement)? #ifSt
    |   For LEFT_PAR forControl RIGHT_PAR statement  ( Elsefor statement)?  #forSt
    |   While parExpression statement   #whileSt
    |   Switch parExpression switchBlock    #siwchSt
    |   Select g_switchStatment #selectSt
    |   Try  block (Catch  LEFT_PAR Identifier? RIGHT_PAR  block   )?    #trySt
    |   Return expression? END  #returnSt
    |   Break END   #breakSt
    |   Continue END    #continueSt
    |   Var varDeclareList END  #varSt
    |   Directive  directiveExp #directiveSt 
    |   assignMent END  #assignSt
    |   functionTagCall #functionTagSt 
    |   statementExpression END   #statmentExpSt 
    |   Ajax Identifier COLON block   #ajaxSt 
    |   END   #end
上面是Beetl支持的語法。

tiny模板引擎支持的語法有:

directive   :   set_directive
            |   if_directive
            |   while_directive
            |   for_directive
            |   break_directive
            |   import_directive
            |   continue_directive
            |   stop_directive
            |   include_directive
            |   macro_directive
            |   layout_directive
            |   layout_impl_directive
            |   call_block_directive
            |   call_directive
            |   endofline_directive
            |   blank_directive
            |   tabs_directive
            |   indent_directive
            |   dent_directive
            |   call_macro_directive
            |   call_macro_block_directive
            |   bodycontent_directive
            |   invalid_directive
            ;
兩者作個對比:

語法體系的差別,Beetl採用的是相似jsp的方式,而Tiny模板引擎採用的是Velocity的方式,兩者各有優缺點,所以並沒有好壞之分,只是蘿蔔青菜上的差別。從我本人來講,是很是討厭相似於<% ... %>來方式圈定腳本,而更喜歡Velocity的直接用指令嵌入的方式來進行使用,因此我選擇了類 Velocity的方式。所以語法體系方面沒有什麼比如較的。

對於常規指令Beetl和Tiny模板引擎都有良好的支持

  • 循環指令二者都支持for和while,都支持break,contine,stop/return等。同時也都支持else,也就是當循環次數爲0時,執行一些操做,好比:有數據的時候在循環體內展現數據,沒有數據的時候顯示else中的默認內容。
  • 在條件判斷方面Beetl支持了if、switch、select等指令,而tiny模板引擎則是由強大的#if() ... #elseif()... #else...#end指令格式來完成全部的條件判斷,二者功能均可以互相覆蓋。
項目

Beetl

Tiny

定義臨時變量 var number=1 #set(number=1)
定義頁面變量 template.binding("number",1) #!set(number=1)
屬性引用 ${user.wife.name} ${user.wife.name}
算述表達式 <%
var a1 = 12;
var b1 = (a1+15)/3-2*a1;
var bc = -1-b1;
%>
${bc}

#set(a1=12,b1 = (a1+15)/3-2*a1,bc = -1-b1)
${bc}
固然,#set指令也能夠一行寫一個賦值指令

邏輯表達式 <%
var a1 = 12;
var b1 = a1==12;
var b2 = a1!=12;
%>
${b1}
${b2}
#set(a1 = 12,b1 = a1==12,b2 = a1!=12)
${b1}
${b2}

循環語句 <%
print("總共"+userList.~size+"<br>");
for(user in userList){
%>
${userLP.index}    ${user.name} <br>
<%}%>

總共${userList.size()}
#for(user in userList)
${userFor.index}    ${user.name}
#end

條件語句 <%
var user = map["001"];
if(user.name=="lijz"){
print(user.name);
}else{
return ;
}
%>

#set(user = map."001")
#if(user.name=="lijz")
     ${user.name}
#else
    #return
#end


函數調用 <%
print("hello");
println("hello");
printf("hello,%s,your age is %s","lijz",12+"");
%>

${format("hello")}
${format("hello\n")}

${format("hello,%s,your age is %s","lijz",12)}

格式化 <%
var now = date();
var date = date("2013-1-1","yyyy-MM-dd");
%>
now=${now,dateFormat='yyyy年MM月dd日'}
date=${date,dateFormat='yyyy年MM月dd日'}
or
now=${now,'yyyy年MM月dd日'}

tiny模板引擎不容許動態建立對象,可是容許經過自定義函數或SpringBean來獲取對象。
假設,這裏在上下文中在now和date兩個變量
now=${format(now,'yyyy年MM月dd日 HH:mm:SS')}
date=${format(date,'yyyy年MM月dd日')}


成員方法調用 <% 
 var list = [5,2,4];
%>
${ @java.util.Collections.max(list)}

#set( list = [5,2,4])
${list.get(1)}



安全輸出

<%
var user1 = null;
var user2 = null;
var user3 = {"name":"lijz",wife:{'name':'lucy'}};
%>

${user1.wife.name!"單身"}
${user2.wife.name!}
${user3.wife.name!"單身"}

#set(user1 = null,user2 = null,user3 = {"name":"lijz",wife:{'name':'lucy'}})
%>

${user1?.wife?.name?:"單身"}
${user2?.wife?.name?:"單身"}
${user3?.wife?.name?:"單身"}
註釋 <% 

//最大值是12;
/*最大值是12*/
var max = 12;
%>

##最大值是12;
#*最大值是12*#
#set( max = 12)


上面作了兩個模板引擎的常規指令的示例和對比,基本上採用Beetl在線示例中的示例而後用Tiny模板引擎的語法來一樣實現的功能。
下面來講說一些有意思的高級功能

項目 Beetl Tiny模板引擎
異常處理

<%
try{
        callOtherSystemView()
}catch(error){
        print("暫時無數據");
}
%>

Tiny模板引擎的設計者認爲若是讓模板引擎來處理異常,其實是有點過分設計的意味,而應該是系統的異常處理框架去處理之。模板只參與展現層的處理,不參與業務邏輯處理。

虛擬屬性

${user.~genderShowName}


${user.toJson()}

Tiny支持爲某種類增長一些擴展的成員函數,和Beetl的虛擬屬性的意思是相同的,可是在函數調用過程當中,使用方式與原生成員函數沒有區別。若是擴展的方法是getXxx,那麼就能夠直接調用object.xxx的方式按屬性的方式來進行調用。

函數擴展

<%
var date = date();
var len = strutil.len("cbd");
println("len="+len);
%>

Tiny也提供了函數擴展體系,也徹底能夠添加相似的函數擴展,調用方式也差很少。
#set(date =date(),len=strutil.len("cbd"))

標籤的支持

public class CmsContentTag extends GeneralVarTagBinding {
public void render(){
Object id= this.getAttributeValue("id");
try
{ctx.byteWriter.writeString("當前定義了一個竄上:"+id.toString());
}catch (IOException e){
e.printStackTrace();
}
}
}


Tiny沒有提供標籤的擴展功能,卻提供了強大的宏定義功能
簡單宏定義 

#macro cmsContent(id)
當前定義了一個內容:${id}
#end
調用方式:
#cmsContent("abc")
帶內容宏定義
前置信息
#macro contentFrame()
前置信息
#bodyContent
後置信息
#end
調用方式:
#@contentFrame()
這裏是一些信息
#end

運行結果:

後置信息
這裏是一些信息
調用方式:
因爲Tiny採用的是所有在模板語言中實現的方式,所以定義和使用文本內容更方便,同時在定義和使用時的嵌套支持能力會使得DRY原則得以全面實施,能夠整個頁面沒有重複內容的出現。
佈局支持 content.html內容以下:
<%
 //content.html內容以下:
 layout("/inc/layout.html"){%>
 this is 正文
 ..........
 <%%}%>
layout.html 是佈局文件
<%
 <%include("/inc/header.html"){} %>
 this is content:${layoutContent}
 this is footer:

 <%%}%>
運行結果:
運行content.html模板文件後,,正文文件的內容將被替換到layoutContent的地方,變成以下內容
this is header
this is content:this is 正文
............
this is footer:

Tiny的作法是:

首先新建content.layout文件

this is header
this is content:#pageContent
this is footer
再新建content.page文件
this is 正文
而後訪問content.page,運行結果就是:
this is header
this is content:this is 正文
this is footer
實際上Tiny模板引擎還支持默認佈局,多重佈局各類花樣玩樣,因爲採用了COC的方式,因此不須要在模板語言中顯式引入佈局,而是經過目錄結構的方式來肯定佈局渲染方式。在進行重構的時候更也加方便,好比:一樣一個文件,放在不一樣的目錄結構中,因爲渲染的佈局不一樣,就會出現徹底不同的效果,這在進行重構的時候也更加方便。

Tiny在.layout中還支持指令#layout,以下:

#layout(aaaInfo)
this is aaaInfo
#end

#layout(bbbInfo)
this is bbbInfo
#end
上面就定義了兩個佈局佔位,一個叫aaaInfo,一個叫bbbInfo,

在具體的頁面文件中,能夠用:

#@layout(aaaInfo)
this is new aaaInfo
#end

#@layout(aaaInfo)
this is new aaaInfo
#end
來覆蓋默認的定義,轉而顯示新的內容,若是不覆蓋的話,就顯示默認的信息,這裏經過引入Java的OverRide的機制,提供了更靈活多變的佈局能力。
宏引入

因爲Tiny支持把公用的宏用獨立的文件來進行存放,至關於Library,可是因爲不一樣的人定義的庫有可能有宏名衝突。所以Tiny引入了#import指令來優先使用先import進來的庫中的宏,以下:

#import("/a/b/liba.component")
#import("/a/b/libb.component")
若是出現同名的宏,那麼liba中的會被執行
安全調用 Beetl採用的是安全表達式的方式來處理安全讞用 Tiny的在調用屬性或成員函數時,能夠顯式用「?.」來表示安全屬性調用,而用「.」來表示非安全屬性調用,這樣寫模板時須要明確使用哪一個,這樣能夠及時發現應用中的問題。
錯誤提示
<%
var a = 1;
var b = a/0;
%>
錯誤提示以下:
>>DIV_ZERO_ERROR:0 位於3行 資源:/org/beetl/sample/s0125/error1.txt
1|<%
2|var a = 1;
3|var b = a/0;
4|%>
beetl只給出了具體的位置在哪一行,以及整個模板(或者比較近位置的模板)內容。
#set(a=1,b=1/0)
錯誤提示以下:
路徑:/a.page
位置[1行11列]-[1行13列]
===================================================================
1/0
===================================================================
Tiny則明確給出了精確的座標,x1,y1-x2,y2,同時還給出了具體出問題的內容,相對來講程序員查找問題更加迅捷。




工具的支持

beetl的插件功能

Beetl插件如約而來!

安裝說明:
本插件是beetl模板語言插件,請放到dropins目錄下重啓便可。若是之前安裝過,須要刪除之前保本
若是文件以.btl結尾,則自動以插件方式打開,不然,能夠經過右鍵此文件,選擇open-with,並選擇beetl editor,不建議使用btl結尾,請儘可能使用原有編輯器,參考使用說明4快捷使用beetl editor

使用說明:

1 工程屬性裏有個beetl屬性,能夠指定定界符號等,默認是<%%> ${}。也能夠指定模板根目錄(可選,沒必要手工填寫,在模板單擊定位裏會提示你選擇)
2 ctrl-2 定位到下一個beetl 塊
3 ctrl-3 定位到上一個beetl塊
4 ctrl-4 將普通文件以beetl editor方式打開,並保持同步編輯 
5 ctrl-5 靜態文本所有摺疊和打開靜態文本摺疊
6 能夠ctrl+單擊字符串定位到字符串對應的模板文件,第一次使用的時候,須要選擇模板根目錄,隨後,也能夠在project屬性的beetl配置裏配置模板根目錄
7 alt-/ 進行上下文提示。也能夠鍵入此快速輸入定界符號和佔位符號
8 alt-shift-p 從{ 快速移動到 匹配的},或者反之亦然。若是隻單擊{ 則會框選住匹配的} 而光標不移動
9 選中任何id,都能全文框選住一樣的id。
10 ctrl-/ 單行註釋,或者取消註釋
11 一般eclipse具備的快捷操做方式,beetl仍然予以保留不變 
12 具有必定的錯誤提示,目前只提示第一個發現的錯誤。

Tiny模板引擎的插件功能

  1. 大綱支持:支持在大綱當中顯示一些關鍵內容,並能夠快速定位
  2. 語法高亮:支持在編輯器中,根據語法進行着色,使得代碼更容易閱讀和排錯
  3. 錯誤提示:若是模板語言存在錯誤,則能夠在工程導航、錯誤視圖及編輯窗口進行錯誤提示
  4. 代碼摺疊:支持對代碼塊進行代碼摺疊,方便查閱
  5. 語法提示:支持Tiny模板引擎語法提示及Html語法提示方便快速錄入
  6. 快速定位:支持Tiny模板中開始語句與結束語句間快速切換
  7. 變量快速提示:點鼠標點擊某變量時,會高亮顯示文件中的全部同名變量
  8. 宏定義對應位置顯示:在tiny塊處理的標籤頭部按ctrl時,會高亮顯示與其對應的#end,反之亦然
  9. 格式化:能夠按快捷鍵ctrl+shift+F進行格式化了
  10. 註釋處理:能夠按快捷鍵ctrl+/來進行快速設置單行註釋或取消單行註釋,能夠按ctrl+shift+/來進行快速設置塊註釋或取消塊註釋

因爲篇幅太長,所以這裏不貼完整內容,詳細請看連接:http://my.oschina.net/tinyframework/blog/365370

OK,工具上徹底不在一個等級上。

代碼質量對比

代碼質量這個自己沒有惟一標準,這裏貼一下相似的功能的代碼對比,不作評論:

for語句實現 

Beetl版

public final class ForStatement extends Statement implements IGoto
{
	public Expression idNode;
	public Expression exp;
	public Statement forPart;
	public Statement elseforPart;
	public boolean hasGoto = false;
	public short itType = 0;
	public boolean hasSafe;

	/**
	 * for(idNode in exp) {forPath}elsefor{elseforPart}
	 * @param idNode
	 * @param exp
	 * @param forPart
	 * @param elseforPart
	 * @param token
	 */
	public ForStatement(VarDefineNode idNode, Expression exp, boolean hasSafe, Statement forPart,
			Statement elseforPart, GrammarToken token)
	{
		super(token);
		this.idNode = idNode;
		this.exp = exp;
		this.hasSafe = hasSafe;
		this.elseforPart = elseforPart;
		this.forPart = forPart;

	}
	public final void execute(Context ctx)
	{
		// idNode 是其後設置的
		int varIndex = ((IVarIndex) idNode).getVarIndex();
		Object collection = exp.evaluate(ctx);
		IteratorStatus it = null;
		if (collection == null)
		{
			if (!this.hasSafe)
			{
				BeetlException ex = new BeetlException(BeetlException.NULL);
				ex.pushToken(exp.token);
				throw ex;
			}
			else
			{
				it = new IteratorStatus(Collections.EMPTY_LIST);
			}
		}
		else
		{
			it = IteratorStatus.getIteratorStatusByType(collection, itType);
			if (it == null)
			{
				BeetlParserException ex = new BeetlParserException(BeetlParserException.COLLECTION_EXPECTED_ERROR);
				ex.pushToken(exp.token);
				throw ex;
			}
		}
		ctx.vars[varIndex + 1] = it;
		// loop_index
		//		ctx.vars[varIndex+2] = 0;
		//		ctx.vars[varIndex+3] = it.getSize();
		//		
		if (this.hasGoto)
		{

			while (it.hasNext())
			{
				ctx.vars[varIndex] = it.next();
				forPart.execute(ctx);
				switch (ctx.gotoFlag)
				{
					case IGoto.NORMAL:
						break;
					case IGoto.CONTINUE:
						ctx.gotoFlag = IGoto.NORMAL;
						continue;
					case IGoto.RETURN:
						return;
					case IGoto.BREAK:
						ctx.gotoFlag = IGoto.NORMAL;
						return;
				}
			}
			if (!it.hasData())
			{
				if (elseforPart != null)
					elseforPart.execute(ctx);
			}
			return;
		}
		else
		{
			while (it.hasNext())
			{
				ctx.vars[varIndex] = it.next();
				forPart.execute(ctx);

			}
			if (!it.hasData())
			{
				if (elseforPart != null)
					elseforPart.execute(ctx);
			}
		}
	}
	@Override
	public final boolean hasGoto()
	{
		// TODO Auto-generated method stub
		return hasGoto;
	}
	@Override
	public final void setGoto(boolean occour)
	{
		this.hasGoto = occour;
	}
	@Override
	public void infer(InferContext inferCtx)
	{
		exp.infer(inferCtx);
		if (exp.getType().types != null)
		{
			if (Map.class.isAssignableFrom(exp.getType().cls))
			{
				idNode.type = Type.mapEntryType;
			}
			else
			{
				//list or array
				idNode.type = exp.getType().types[0];
			}
		}
		else
		{
			idNode.type = Type.ObjectType;
		}
		int index = ((IVarIndex) idNode).getVarIndex();
		inferCtx.types[index] = idNode.type;
		inferCtx.types[index + 1] = new Type(IteratorStatus.class, idNode.type.cls);
		forPart.infer(inferCtx);
		if (elseforPart != null)
		{
			elseforPart.infer(inferCtx);
		}
	}
}

Tiny版

public class ForProcessor implements ContextProcessor<TinyTemplateParser.For_directiveContext> {
    public Class<TinyTemplateParser.For_directiveContext> getType() {
        return TinyTemplateParser.For_directiveContext.class;
    }
    public boolean processChildren() {
        return false;
    }
    public Object process(TemplateInterpreter interpreter, TemplateFromContext templateFromContext, TinyTemplateParser.For_directiveContext parseTree, TemplateContext pageContext, TemplateContext context, TemplateEngineDefault engine, Writer writer, String fileName) throws Exception {
        String name = parseTree.for_expression().IDENTIFIER().getText();
        Object values = interpreter.interpretTree(engine, templateFromContext, parseTree.for_expression().expression(),pageContext, context, writer,fileName);
        ForIterator forIterator = new ForIterator(values);
        context.put(name + "For", forIterator);
        boolean hasItem = false;
        while (forIterator.hasNext()) {
            TemplateContext forContext=new TemplateContextDefault();
            forContext.setParent(context);
            hasItem = true;
            Object value = forIterator.next();
            forContext.put(name, value);
            try {
                interpreter.interpretTree(engine, templateFromContext, parseTree.block(),pageContext, forContext, writer,fileName );
            } catch (ForBreakException be) {
                break;
            } catch (ForContinueException ce) {
                continue;
            }
        }
        if (!hasItem) {
            TinyTemplateParser.Else_directiveContext elseDirectiveContext = parseTree.else_directive();
            if (elseDirectiveContext != null) {
                interpreter.interpretTree(engine, templateFromContext, elseDirectiveContext.block(), pageContext,context, writer,fileName);
            }
        }
        return null;
    }
}

解釋引擎核心處理代碼

Beetl版

beetl版源代碼,因爲太長,因此就不貼內容了,詳細請點擊查看源碼

Tiny版

public class TemplateInterpreter {
    TerminalNodeProcessor[] terminalNodeProcessors = new TerminalNodeProcessor[200];
    Map<Class<ParserRuleContext>, ContextProcessor> contextProcessorMap = new HashMap<Class<ParserRuleContext>, ContextProcessor>();
    OtherTerminalNodeProcessor otherNodeProcessor = new OtherTerminalNodeProcessor();


    public void addTerminalNodeProcessor(TerminalNodeProcessor processor) {
        terminalNodeProcessors[processor.getType()] = processor;
    }

    public void addContextProcessor(ContextProcessor contextProcessor) {
        contextProcessorMap.put(contextProcessor.getType(), contextProcessor);
    }

    public TinyTemplateParser.TemplateContext parserTemplateTree(String sourceName, String templateString) {
        char[] source = templateString.toCharArray();
        ANTLRInputStream is = new ANTLRInputStream(source, source.length);
        // set source file name, it will be displayed in error report.
        is.name = sourceName;
        TinyTemplateParser parser = new TinyTemplateParser(new CommonTokenStream(new TinyTemplateLexer(is)));
        return parser.template();
    }

    public void interpret(TemplateEngineDefault engine, TemplateFromContext templateFromContext, String templateString, String sourceName, TemplateContext pageContext, TemplateContext context, Writer writer, String fileName) throws Exception {
        interpret(engine, templateFromContext, parserTemplateTree(sourceName, templateString), pageContext, context, writer,fileName );
        writer.flush();
    }

    public void interpret(TemplateEngineDefault engine, TemplateFromContext templateFromContext, TinyTemplateParser.TemplateContext templateParseTree, TemplateContext pageContext, TemplateContext context, Writer writer, String fileName) throws Exception {
        for (int i = 0; i < templateParseTree.getChildCount(); i++) {
            interpretTree(engine, templateFromContext, templateParseTree.getChild(i), pageContext, context, writer,fileName );
        }
    }

    public Object interpretTree(TemplateEngineDefault engine, TemplateFromContext templateFromContext, ParseTree tree, TemplateContext pageContext, TemplateContext context, Writer writer, String fileName) throws Exception {
        Object returnValue = null;
        if (tree instanceof TerminalNode) {
            TerminalNode terminalNode = (TerminalNode) tree;
            TerminalNodeProcessor processor = terminalNodeProcessors[terminalNode.getSymbol().getType()];
            if (processor != null) {
                returnValue = processor.process(terminalNode, context, writer);
            } else {
                returnValue = otherNodeProcessor.process(terminalNode, context, writer);
            }
        } else if (tree instanceof ParserRuleContext) {
            try {
                ContextProcessor processor = contextProcessorMap.get(tree.getClass());
                if (processor != null) {
                    returnValue = processor.process(this, templateFromContext, (ParserRuleContext) tree, pageContext, context, engine, writer,fileName);
                }
                if (processor == null || processor != null && processor.processChildren()) {
                    for (int i = 0; i < tree.getChildCount(); i++) {
                        Object value = interpretTree(engine, templateFromContext, tree.getChild(i), pageContext, context, writer,fileName );
                        if (value != null) {
                            returnValue = value;
                        }
                    }
                }
            } catch (StopException se) {
                throw se;
            } catch (TemplateException te) {
                if (te.getContext() == null) {
                    te.setContext((ParserRuleContext) tree,fileName);
                }
                throw te;
            } catch (Exception e) {
                throw new TemplateException(e, (ParserRuleContext) tree,fileName);
            }
        } else {
            for (int i = 0; i < tree.getChildCount(); i++) {
                Object value = interpretTree(engine, templateFromContext, tree.getChild(i), pageContext, context, writer,fileName );
                if (returnValue == null && value != null) {
                    returnValue = value;
                }
            }
        }
        return returnValue;
    }

    public static void write(Writer writer, Object object) throws IOException {
        if (object != null) {
            writer.write(object.toString());
        }
    }
}

嗯嗯,不到100行的規模

固然整個通讀下來,就會慢慢發現爲何Tiny的代碼行數這麼少功能卻又多的緣由之所在了。

總結

Beetl算得上是較好的模板語言框架和不錯的開源項目,可是距離「最好的」三個字仍是有必定差距的,做爲@閒.大賦 的粉絲,偶會持續支持他,也但願他能再積再累,真正當得起「最好的」三個字。

補充說明

beetl裏面有4014行由antlr生成的代碼,實際統計中,因爲Beetl的目錄結構沒有按標準化的來,致使統計中包含了這部分代碼,所以實際上,應該是在16000+,所以規模是Tiny模板引擎的3倍左右,特此糾正。

相關文章
相關標籤/搜索