記一個腳本解釋器的開發

最近能夠有1個月左右的空閒,能夠稍微整理一下這個腳本解釋器的開發過程。java

1、原因
  2014年左右,咱們使用AIR技術,開發了一個3D戰爭類型的手遊。那時候手遊開發技術主要是cocos2d,unity,Air稍微小衆一些,可是也有。那個時候正是AS3走下坡路的時候,BOSS耳軟心活,一會要改用cocos,一會要改用unity,因而萌生了一個本身寫一個as 3.0腳本解釋器的想法。git

2、關於actionscript3
  As3腳本語言,實際上就是ecmascript 262 V4的增強版,也就是說基本上js有的它都有,另外還有java的特性,包含完整的類繼承,接口系統,還可使用jsprototype原型鏈繼承,2方面互不干擾,又能夠互爲補充,靈活又不失嚴謹。當年adobeMozilla提議將as3做爲ecmascript 262 v4,可是受到了巨頭公司(主要是微軟)的反對,最終ecma沒有發佈 EcmaScript V4,而是發佈了一個和諧版 V3.1。可是V4仍然保留了下來。固然現在已是ecmascript 已是6了,中間發生了蘋果,安卓的崛起,wp的衰落,年年都是h5遊戲元年這些事情你們都知道就不談了。github

  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
  
  
  

  
  

 

 

 

3、龍書。
  編譯原理號稱有龍,虎,鯨三本聖經。我參考的是龍書。要寫腳本解釋器,網上確實有許多參考文章,可是大多都是簡單的告訴你怎麼用簡單的技巧去人肉寫代碼解析,再或者就是叫你去用相似yacc這樣的工具,我買了2本書,一本叫「自制編程語言」,一本叫「兩週自制腳本語言」。這兩本書我讀了一下,確實能夠自制語言,可是確定是沒法自制如as3這樣的大型的語言的。我也嘗試使用人肉代碼解析,發現這根本就沒辦法進行下去,稍有地方出錯,就要大量修改而後本身也搞不清了。所以,最後我決定懟龍書。在這裏,我就直接說出我懟龍書的心得了c#

a) 龍書有中文版pdf。內容很是豐富,文字也易懂,我我的感受,值得一讀不愧聖經之名後端

b) 對於腳本解釋器而言,只要看到LL(1)就好了。龍書提供了一個極度詳細的算法,詳細到幾乎是一步一步的指導你構建一個FirstFollow翻譯算法。有了這個算法就能夠本身構建文法分析器!閉包

c) 關於LL(1)文法。確實LL(1)文法有許多限制的地方,好比左遞歸,二義性等,可是這些都是能夠解決的,左遞歸手工慢慢消除,二義性書裏也介紹瞭解決的方案,只要嘗試一下,就能夠過去。app

 

  d) 作出文法分析工具後,就只要不斷的嘗試去寫文法說明,最終就能獲得目標語言的文法分析器,進而生成語法樹!這就是看龍書的收穫ecmascript

 

4、從語法樹到運行時
  我用了3個月的時間,作到了能夠解析幾乎任何as3代碼的語法樹。從通常意義上說,這時候只要順着語法樹執行,就能夠跑起代碼來了。但事實是,作到這一步後,發現後面還有一個更大的坑在等着:自動垃圾收集。你們都知道js也好.net也好,都有垃圾收集器的,那麼咱們若是要本身實現完整的as3,勢必也要本身實現垃圾收集器。這一步我想了很長時間,也沒想出太好的辦法,除非本身擼個垃圾收集器。。。。。當時BOSS要求用cocos開發新的項目,用C++的話,自動垃圾收集這個麻煩實在太大了。
      可是時隔不久,cocos項目作了一半,BOSS突發奇想,又決定用Unity山寨某世面熱門遊戲一款。因而解釋器暫停了,咱們全力進行Unity的開發。一年後,遊戲所有開發完成,稍有空閒,因而我準備繼續將這個解釋器進行完成。回到垃圾收集的問題,這個最簡單的就是直接用C#的垃圾收集器代勞。所以,說幹就幹,解釋器使用純.net2.0開發,不用任何3.5開始的語法和類庫,好比linq,hashset啊 這樣能夠,嗯,避免未來沒必要要的麻煩,懂得天然懂:)有了C#的高生產力,奮鬥了幾個月,解釋器大體出爐了

5、解釋器的能力

a) 編譯時類型檢查。對象訪問權限控制,包括public ,private,protected等。若是使用類繼承,或者編碼時指定了變量類型,就能擁有編譯時檢查。行爲和Adobe AIR編譯器保持一致。

b) 原型鏈繼承。和js相似,行爲與Adobe AIR保持一致。對於封閉的類,可使用原型鏈進行擴展。很是相似.net的擴展方法(真的很是像)

c) 閉包。任何函數都是一等對象,因此閉包支持瓜熟蒂落。

d) 完整的類繼承,接口系統和AIR編譯器徹底一致。對於類的成員method,使用function.apply不能改變this指針。而其餘的函數,則使用applycalljs一致,和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的一些類庫,特製做操做符重載。

 

6、還未完成的部分:

  a) 目前須要手工將.net類連接到腳本對象,這部分的代碼生成器還需開發

  b) 目前沒有將編譯的結果序列化 / 反序列化。這部分工做難度不大,但須要細心和時間。完成後,就能夠將編譯和執行分離了,每次執行只需加載二進制字節碼執行便可,沒必要編譯。

7、解釋器能幹什麼
  嗯,這還用問嗎?純.net2.0,連linq都沒有使用,不依賴任何第三方庫的腳本解釋器,天然是能夠嵌入Unity了,並且有靜態編譯檢查,還特地加入了yield和結構體,就是爲這個作準備的

8、遊戲項目從開發到跑路
  咱們項目開所有完成了,除了UI改了一遍又一遍。。咱們滿心期待的等着項目上線,總算能夠看到結果了。而後端午節事後的中午,BOSS召集咱們宣佈,他關門了!跑路了!跑路了,跑路了 其實我當時內心想的是,好吧,歷經數年沒日沒夜的加班日子,我終於能夠休息了。

9、休息中

  。。寫點什麼吧。嗯。正好又一段時間休息,繼續完善腳本解釋器。展現一些執行結果

下面展現的是和現有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 */

 

 

10、最後

a) 解釋器目前進度的代碼地址:https://github.com/asheigithub/ASTool 歡迎測bug

b) 也可直接下載編譯好的demo

c) 有心情的話,後續記錄一些開發中的心得。嘛,看找工做的狀況了,若是一直失業的話恐怕也不會太有心情哈哈

相關文章
相關標籤/搜索