[转译]Flash10中的泛型类型名

kid:之前在解析一个flash官方的.swf文件的时候遇到了一个莫名其妙的问题,我在解析Multiname时遇到了一个未在官方文档中定义的标签0x1D。不过前几天毕业超忙,所以就耽搁下来了。今天重新Google,发现了解决方案,顺手转过来并翻译了一下,原文在此:http://laan.doswf.com/genericname-in-flash-10/


随着DoSWF的开发,我在解析abc数据的时候遇到了一个问题。有时候,会出现一个类型为0x1D(29)的Multiname。然而在adobe的官方文档avm2overview.pdf中,却没有对这种Multiname有所描述。因此,DoSWF将会在遇到这种类型的Multiname时抛出一个错误。

然而今天,当我在Google上搜索关键词:multiname 0x1d的时候,我找到了一个非常有用的页面:http://bugs.adobe.com/jira/browse/FP-1474:

现有的AVM2文档并没有包含flash player 10中的更新,特别是:

  • 类型定义为0x1D的multiname(我觉着这是和Vector类型有关的东东?)
  • 关于Vector类型的文档(如果与上述的无关):对它的引用(with type)以及它是如何被声明的(without type)
  • 用以访问bytearray的指令

实际上,有人已经遇到了相同的问题,并向adobe提了bug。这非常有趣。最后,这个问题并不是一个bug。不过adobe的工程师依旧对这个问题进行了回复:

0x1D可以被认为是一个泛型multiname,它有以下的声明:

[Kind] [TypeDefinition] [ParamCount] [Param1] [Param2] [ParamN]

其中:
[TypeDefinition] 是一个U30类型的对multiname表的索引
[ParamCount] 是一个U8(U30?)类型的参数个数
[ParamX] 是一个U30类型的对multiname表的索引

显然AS3当前并没有广泛地支持泛型(当前仅有Vector.<*>),因此ParamCount目前总是1(对于Vector.<*>来说)。

更多的信息可以参考这里:http://blog.richardszalay.com/2009/02/generics-vector-in-avm2.html

因此,我新增了一个名为GenericName的类用以解析泛型multiname。这很有效。不过另一个问题出现了:一个名为(0x53)的指令无法被解析。这依旧是在flash10中加入的,与Vector有关的新内容。我找到了另一个页面:http://stackoverflow.com/questions/553445/how-do-generics-vector-work-inside-the-avm:

Flash 10中新加入了一个opcode(0x53),我把它称作MakeGenericType。MakeGenericType有着如下的栈声明:

TypeDefinition, ParameterType1, ParameterTypeN -> GenericType

同时拥有一个U8(U30?)类型的参数,用以说明在栈中有多少个参数。通常你会看到MakeGenericType被这样使用:

GetLex [TypeDefinitionMultiname]
GetLex [ParameterTypeMultiname]
MakeGeneric [ParamCount]
Coerce [GenericNameMultiname]
Construct [ConstructorParamCount]

因此如果你像这样……

GetLex __AS3__.vec::Vector
GetLex int
MakeGeneric 1
Coerce __AS3__.vec::Vector.<int>
Construct 0

你将会拥有一个 Vector.<int>的实例。

使用FlexPMD构建AS3语法树

最近由于工作的原因,需要从一个AS3文件中抽取一个类的包名、类名、函数名、属性名等。虽然使用正则表达式可以勉强完成这项工作,不过最靠谱的方法还是使用一个AS3编译器前端帮我完成语法分析。

万能的Google告诉我目前有两个开源项目可以用,一个是metaas, 另一个是FlexPMD。呃呃,这两个项目居然都是用Java写的,好吧……

我首先尝试的是metaas,如你在官网所见,这个项目的最新版本是在……2008年发布的。好吧,虽然旧是旧了点,能用就行。

首先随手写了个简单的HelloWorld程序让它去解析,Good!貌似解析成功,很好。接下来尝试一个稍微复杂点的源码:

package kid.shape.circle
{
    import kid.shape.Shape;

    public class Circle extends Shape
    {
        override public function setpos(x:int, y:int):void
        {
            this.x = x - 10;
            this.y = y - 10;
        }

        override public function draw():void
        {
            super.draw();
            graphics.lineStyle(1);
            graphics.drawCircle(x, y, 20);
        }

    }
}

我满怀期望地运行了解析器,叮咚,Runtime Exception:Unexpected token in line 15:.

不愧是个08年的老项目啊,我估计那个时候super关键字还没有被加入AS3吧。既然是这样那就用FlexPMD吧,怎么说也是Adobe官方的项目,应该比较靠谱。

首先去这里下载FlexPMD,下载完后解压缩,可以看见里面内容是这样的:

QQ截图20130610205830

实际上FlexPMD的真正用途是代码质量检测工具,我们可以用它来检查我们AS3代码中不符合规范的地方。如果你想知道如何使用FlexPMD检测你的AS3代码(这才是FlexPMD的正确用法),你可以看看这篇文章。不过,由于FlexPMD的工作原理是首先为代码生成语法树,再应用规则,因此我们可以简单地将它用作一个编译器前端。

我们真正需要使用到的jar包只有以下4个:

  1. as3-parser-1.2.jar
  2. as3-parser-api-1.2.jar
  3. flex-pmd-files-1.2.jar
  4. as3-plugin-utils-1.2.jar

将这四个jar包加入到我们Java项目的构建路径后,我们就可以这样使用它来生成一颗语法树:

AS3Parser parser = new AS3Parser();
IParserNode root = parser.buildAst(fileName);

之后就可以通过根节点遍历整个语法树了。比如,我上面贴出来的那个文件(Circle.as)生成出来的语法树是这样子的:

compilation-unit
    package
        name kid.shape.circle
        content
            import kid.shape.Shape
            class
                name Circle
                mod-list
                    mod public
                extends Shape
                content
                    function
                        mod-list
                            mod override
                            mod public
                        name setpos
                        parameter-list
                            parameter
                                name-type-init
                                    name x
                                    type int
                            parameter
                                name-type-init
                                    name y
                                    type int
                        type void
                        block
                            dot
                                primary this
                                assign
                                    primary x
                                    op =
                                    add
                                        primary x
                                        op -
                                        primary 10
                            dot
                                primary this
                                assign
                                    primary y
                                    op =
                                    add
                                        primary y
                                        op -
                                        primary 10
                    function
                        mod-list
                            mod override
                            mod public
                        name draw
                        parameter-list null
                        type void
                        block
                            dot
                                primary super
                                call
                                    primary draw
                                    arguments null
                            dot
                                primary graphics
                                call
                                    primary lineStyle
                                    arguments
                                        primary 1
                            dot
                                primary graphics
                                call
                                    primary drawCircle
                                    arguments
                                        primary x
                                        primary y
                                        primary 20
    content null

至此,工作完成,撒花!