最近可以有1個月左右的空閑,可以稍微整理一下這個腳本解釋器的開發過程。
一、緣由
2014年左右,我們使用AIR技術,開發了一個3D戰爭類型的手游。那時候手游開發技術主要是cocos2d,unity,Air稍微小眾一些,但是也有。那個時候正是AS3走下坡路的時候,BOSS耳軟心活,一會要改用cocos,一會要改用unity,於是萌生了一個自己寫一個as 3.0腳本解釋器的想法。
二、關於actionscript3。
As3腳本語言,實際上就是ecmascript 262 V4的加強版,也就是說基本上js有的它都有,另外還有java的特性,包含完整的類繼承,接口系統,還可以使用js的prototype原型鏈繼承,2方面互不干擾,又可以互為補充,靈活又不失嚴謹。當年adobe和Mozilla提議將as3作為ecmascript 262 v4,但是受到了巨頭公司(主要是微軟)的反對,最終ecma沒有發布 EcmaScript V4,而是發布了一個和諧版 V3.1。但是V4仍然保留了下來。當然如今已經是ecmascript 已經是6了,中間發生了蘋果,安卓的崛起,wp的衰落,年年都是h5游戲元年這些事情大家都知道就不談了。
a) As3的類繼承 見代碼,一看就懂吧,都不用解釋,和c#基本沒區別
package { [Doc] public class FuncTest{ public function FuncTest() ; } } /* 類是唯一可實現接口的 ActionScript 3.0 語言元素。在類聲明中使用 implements 關鍵字可實現一個或多個接口。 下面的示例定義兩個接口 IAlpha 和 IBeta 以及實現這兩個接口的類 Alpha: */ interface IAlpha { function foo(str:String):String; } interface IBeta { function bar():void; } class Alpha implements IAlpha, IBeta { public function foo(param:String):String { trace("foo", param); return null; } public function bar():void { trace("bar");} } var a=new Alpha(); var alpha:IAlpha = a; var beta:IBeta = IBeta(alpha); alpha.foo("call foo"); beta.bar();
b) As3的原型鏈 見代碼,會js的一看就明白,不用解釋了吧.
package { [Doc] public class FuncTest{ public function FuncTest() { } } } /* 類繼承 -- 是主要的繼承機制,並支持固定屬性的繼承。固定屬性是聲明為類定義一部分的變量、常量或方法。現在,可通過存儲相關類信息的特殊類對象表示每個類定義。 原型繼承 -- 每種類都有一個關聯的原型對象,而原型對象的屬性由該類的所有實例共享。 在創建一個類實例時,它具有對其類的原型對象的引用,這將作為實例及與其關聯的類原型對象間的鏈接。 運行時,如果在類實例中找不到某屬性, 則會檢查委托(該類的原型對象)中是否有該屬性。 如果原型對象不包含這種屬性, 此過程會繼續在層次結構中連續的更高級別上對原型對象進行委托檢查,直到找到該屬性為止。 */ //類繼承和原型繼承可同時存在,如下例所示: class A { var x = 1 public function A() { A.prototype.px = 2 } } dynamic class B extends A { var y = 3 public function B() { B.prototype.py = 4 } } var b = new B() trace(b.x) // 1 via class inheritance trace(b.px) // 2 via prototype inheritance from A.prototype trace(b.y) // 3 trace(b.py) // 4 via prototype inheritance from B.prototype B.prototype.px = 5 trace(b.px) // now 5 because B.prototype hides A.prototype b.px = 6 trace(b.px) // now 6 because b hides B.prototype var b2=new B() trace(b2.px) // ==5
三、龍書。
編譯原理號稱有龍,虎,鯨三本聖經。我參考的是龍書。要寫腳本解釋器,網上確實有許多參考文章,但是大多都是簡單的告訴你怎么用簡單的技巧去人肉寫代碼解析,再或者就是叫你去用類似yacc這樣的工具,我買了2本書,一本叫“自制編程語言”,一本叫“兩周自制腳本語言”。這兩本書我讀了一下,確實可以自制語言,但是肯定是無法自制如as3這樣的大型的語言的。我也嘗試使用人肉代碼解析,發現這根本就沒辦法進行下去,稍有地方出錯,就要大量修改然后自己也搞不清了。因此,最后我決定懟龍書。在這里,我就直接說出我懟龍書的心得了
a) 龍書有中文版pdf。內容非常豐富,文字也易懂,我個人感覺,值得一讀不愧聖經之名
b) 對於腳本解釋器而言,只要看到LL(1)就行了。龍書提供了一個極度詳細的算法,詳細到幾乎是一步一步的指導你構建一個First和Follow翻譯算法。有了這個算法就可以自己構建文法分析器!
c) 關於LL(1)文法。確實LL(1)文法有許多限制的地方,比如左遞歸,二義性等,但是這些都是可以解決的,左遞歸手工慢慢消除,二義性書里也介紹了解決的方案,只要嘗試一下,就可以過去。
d) 做出文法分析工具后,就只要不斷的嘗試去寫文法說明,最終就能得到目標語言的文法分析器,進而生成語法樹!這就是看龍書的收獲
四、從語法樹到運行時
我用了3個月的時間,做到了可以解析幾乎任何as3代碼的語法樹。從一般意義上說,這時候只要順着語法樹執行,就可以跑起代碼來了。但事實是,做到這一步后,發現后面還有一個更大的坑在等着:自動垃圾收集。大家都知道js也好.net也好,都有垃圾收集器的,那么我們如果要自己實現完整的as3,勢必也要自己實現垃圾收集器。這一步我想了很長時間,也沒想出太好的辦法,除非自己擼個垃圾收集器。。。。。當時BOSS要求用cocos開發新的項目,用C++的話,自動垃圾收集這個麻煩實在太大了。
但是時隔不久,cocos項目做了一半,BOSS突發奇想,又決定用Unity山寨某世面熱門游戲一款。於是解釋器暫停了,我們全力進行Unity的開發。一年后,游戲全部開發完成,稍有空閑,於是我准備繼續將這個解釋器進行完成。回到垃圾收集的問題,這個最簡單的就是直接用C#的垃圾收集器代勞。因此,說干就干,解釋器使用純.net2.0開發,不用任何3.5開始的語法和類庫,比如linq啊,hashset啊 這樣可以,嗯,避免將來不必要的麻煩,懂得自然懂:)有了C#的高生產力,奮斗了幾個月,解釋器大致出爐了
五、解釋器的能力
a) 編譯時類型檢查。對象訪問權限控制,包括public ,private,protected等。如果使用類繼承,或者編碼時指定了變量類型,就能擁有編譯時檢查。行為和Adobe AIR編譯器保持一致。
b) 原型鏈繼承。和js類似,行為與Adobe AIR保持一致。對於封閉的類,可以使用原型鏈進行擴展。非常類似.net的擴展方法(真的非常像)
c) 閉包。任何函數都是一等對象,所以閉包支持順理成章。
d) 完整的類繼承,接口系統和AIR編譯器完全一致。對於類的成員method,使用function.apply不能改變this指針。而其他的函數,則使用apply和call和js一致,和AIR編譯器保持一致。
e) 完整的語法支持。支持除了 with {} 和 namespace 之外的所有語法。(namespace不是C#的namespace, as3中類似的是package。)因為with實在是沒法搞,玩js的大家都知道蛤蛤。
f) IDE。由於語法等和AIR完全一致,所以大體上可以直接使用flash develop。
g) 擴展語法。擴展as3的語法,加入了yield 也就是說,同樣試用yield就可以直接返回一個ienumerator,和C#學的:)
h) 支持結構體。准確的說,是可以將.net的結構體對象鏈接過來在腳本中使用。大致上是一個nullable的結構體。
i) 操作符重載。為了更好的鏈接.net的一些類庫,特制作操作符重載。
六、還未完成的部分:
a) 目前需要手工將.net類鏈接到腳本對象,這部分的代碼生成器還需開發
b) 目前沒有將編譯的結果序列化 / 反序列化。這部分工作難度不大,但需要細心和時間。完成后,就可以將編譯和執行分離了,每次執行只需加載二進制字節碼執行即可,不必編譯。
七、解釋器能干什么
嗯,這還用問嗎?純.net2.0,連linq都沒有使用,不依賴任何第三方庫的腳本解釋器,自然是可以嵌入Unity了,而且有靜態編譯檢查,還特意加入了yield和結構體,就是為這個做准備的
八、游戲項目從開發到跑路
我們項目開全部完成了,除了UI改了一遍又一遍。。我們滿心期待的等着項目上線,總算可以看到結果了。然后端午節過后的中午,BOSS召集我們宣布,他關門了!跑路了!跑路了,跑路了 其實我當時心里想的是,好吧,歷經數年沒日沒夜的加班日子,我終於可以休息了。
九、休息中
。。寫點什么吧。嗯。正好又一段時間休息,繼續完善腳本解釋器。展示一些執行結果
下面展示的是和現有IDE的結合。
下面展示的是yield語句。
下面是結構體TimeSpan的一些鏈接:展示了操作符重載
下面展示的是getter,setter。沒錯as3是支持屬性的
package { [Doc] public class FuncTest { } } //下例定義 Team 類。Team 類包括用於在該類內檢索和設置屬性的 getter 和 setter 方法: class Team { var teamName:String; var teamCode:String; var teamPlayers:Array = new Array(); public function Team(param_name:String, param_code:String) { teamName = param_name; teamCode = param_code; } public function get name():String { return teamName; } public function set name(param_name:String):void { teamName = param_name; } } //在腳本中輸入下面的代碼: var giants:Team = new Team("San Fran", "SFO"); trace(giants.name); giants.name = "San Francisco"; trace(giants.name); /* San Fran San Francisco */
十、最后
a) 解釋器目前進度的代碼地址:https://github.com/asheigithub/ASTool 歡迎測bug
b) 也可直接下載編譯好的demo
c) 有心情的話,后續記錄一些開發中的心得。嘛,看找工作的情況了,如果一直失業的話恐怕也不會太有心情哈哈