Advanced Virtual Tours

An Actionscript Compiler written in Actionscript


Zephyr Renner - Posted on 17 September 2009

I have been writing an AS3 compiler written in AS3 itself. There is of course ESC at Mozilla, but I don't really know that it works (it probably does, and very well at that; I just don't know), but then what fun is that? I'd rather learn how to do it. And there is a lot to learn, including some fascinating under-documented features of actionscript bytecode and the swf file format: static initializers, the difference between a typed class property and a typed local register, where initial values of class properties are actually assigned. More on that eventually. Oh, and I have a project which is an AIR app that needs to be able to compile AS3 and embed various binary blobs in the SWF as well, which is a pretty good excuse.

So I took Nicholas Cannasse's work on Haxescript, compiled it to AS3, fixed a couple of bugs that wouldn't let it compile, and thus I had a parser for a Haxe which is really, really similar to AS3. The only problem with this is that the "code" is interpreted at runtime with a lot of reflection and is slow. And I have inherited a couple of as yet unfixed minor problems from this, primarily that object literals don't parse.

And then I took a look at Chase Kernan's work on FInterp which is a drop in replacement for HScript's Interp class, which compiles the parsed expressions to ABC and uses loader.loadBytes() on it to load the ABC as a SWF and execute the code. This is really impressive stuff. Really! However, it is limited for what I want to do, since the underlying hxformat library (Nicholas Canasse's work again) would not write out a class that extends anything other than "Object". And Chase Kernan used it to compile your "code" into one static function (if you have functions in your code, they become anonymous functions). And, due to the fact that all hscript vars were untyped, so was the ABC that FInterp outputted, which isn't the end of the world, but typing is pretty important, so I definitely needed to add typing.

And then finally I found a blog post: Mr Sketch about modifying hxformat so that you can extend classes other than Object. Which is actually really simple. But I don't know that I ever would have figured it out back then.

So with all of that, I had the vague idea that I might just be able to do it.

And, amazingly, it works! I have an AIR ActionScript3 Compiler. (That comes with a lot of caveats, of course).

So, here is some boring AS3:

package {
import flash.display.Sprite;
import flash.text.TextField;
import flash.events.Event;
public class Test extends Sprite
{
[Embed(source="/Users/zleifr/Desktop/testing.txt", mimeType="application/octet-stream")]
public var testing : Class;

public var tf:TextField = new TextField();

public function run():void
{
this.tf.text = new testing();
this.addChild(tf);
}

public function Test()
{
super();
this.run();
}
}
}

And here is what my compiler outputs, disassembled with Nemo 440 (omitting the stub classes for the embedded binary blob, which is in this case just a text file):
class Test extends flash.display::Sprite
{

function Test():* /* disp_id -1*/
{
// local_count=1 max_scope=1 max_stack=5 code_len=28
0 getlocal0
1 pushscope
2 getlocal0
3 findpropstrict flash.text::TextField
5 constructprop flash.text::TextField (0)
8 dup
9 getlocal0
10 swap
11 setproperty tf
13 getlocal0
14 getlex Test_testing
16 initproperty testing
18 getlocal0
19 constructsuper (0)
21 getlocal0
22 getproperty run
24 getlocal0
25 call (0)
27 returnvoid
}

var testing:Class /* slot_id 1 */
var tf:flash.text::TextField /* slot_id 2 */

function getString(String):String /* disp_id 0*/
{
// local_count=2 max_scope=1 max_stack=1 code_len=4
0 getlocal0
1 pushscope
2 getlocal1
3 returnvalue
}

function run():void /* disp_id 0*/
{
// local_count=1 max_scope=1 max_stack=5 code_len=25
0 getlocal0
1 pushscope
2 getlocal0
3 findpropstrict testing
5 constructprop testing (0)
8 dup
9 getlocal0
10 getproperty tf
12 swap
13 setproperty text
15 getlocal0
16 getproperty addChild
18 getlocal0
19 getlocal0
20 getproperty tf
22 call (1)
24 returnvoid
}

static function Test$cinit():* /* disp_id 0*/
{
// local_count=1 max_scope=1 max_stack=1 code_len=3
0 getlocal0
1 pushscope
2 returnvoid
}

}

And here for reference is what the MXMLC "official" compiler outputs:
class Test extends flash.display::Sprite
{

function Test():* /* disp_id -1*/
{
// local_count=1 max_scope=1 max_stack=2 code_len=23
0 getlocal0
1 pushscope
2 getlocal0
3 getlex Test_testing
5 initproperty testing
7 getlocal0
8 findpropstrict flash.text::TextField
10 constructprop flash.text::TextField (0)
13 initproperty tf
15 getlocal0
16 constructsuper (0)
18 getlocal0
19 callpropvoid run (0)
22 returnvoid
}

var testing:Class /* slot_id 0 */
var tf:flash.text::TextField /* slot_id 0 */

function getString(String):String /* disp_id 0*/
{
// local_count=2 max_scope=1 max_stack=1 code_len=4
0 getlocal0
1 pushscope
2 getlocal1
3 returnvalue
}

function run():void /* disp_id 0*/
{
// local_count=1 max_scope=1 max_stack=2 code_len=19
0 getlocal0
1 pushscope
2 getlocal0
3 getproperty tf
5 getlocal0
6 constructprop testing (0)
9 setproperty text
11 getlocal0
12 getlocal0
13 getproperty tf
15 callpropvoid addChild (1)
18 returnvoid
}

static function Test$cinit():* /* disp_id 0*/
{
// local_count=1 max_scope=1 max_stack=1 code_len=3
0 getlocal0
1 pushscope
2 returnvoid
}

}

Are they exactly the same? Not quite, but the SWF executes just fine. If you download the compiler, I am sure you can throw something interesting at it which it can't parse or interpret correctly, but the amazing part is that it works at all.

So if you want to try it, here is the known list of things that don't work:
- object literals {}, they don't parse right
- optional arguments in function definitions
- (condition) ? expression : expression syntax might be buggy
- try{ expression } catch(e){expression} syntax might be buggy
- only binary blobs can be embedded (i.e. must have mimeType='application/octet-stream')
- for embedding path must be absolute (can be fixed relatively easily)
- assignments to static class properties in the class {}
- anonymous functions might be buggy
- the order of functions is still important: make sure references are declared before you use them. (Note the unusual order of Test.as
!!- you can only extend or compile classes that built in to the Flash Player right now. So you can extend Sprite, MovieClip, Object, etc, but you can't write your own class or use somebody's library with it, because it doesn't have code to load the imported classes and compile them. Of course this is not hard to fix.

And it is all of course open source. I will be posting the source code in the next couple of days, after I clean it up a bit.

The attached SWF is the compilation of the above AS3, and even though it is just a textfield with text from an embedded binary file.