记一个脚本解释器的开发

最近能够有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) 有心情的话,后续记录一些开发中的心得。嘛,看找工做的状况了,若是一直失业的话恐怕也不会太有心情哈哈

相关文章
相关标签/搜索