The FalconJx program is a prototype of a javascript cross compiler.

Q: Is FalconJx the same as FalconJS?

A: No, FalconJx has nothing to do with FalconJS other than it uses the same compile pattern, program startup and backend configuration type setup.

From the standpoint of a replacement, that is up for debate. From the standpoint of development, the project was started out of a necessity to deal in reality on a programming capability level.

The project doesn't even have a name, the FalconJx is to denote the implementation difference. Also note, this project may never be accepted as a solution for cross compiling ActionScript to JavaScript but, it's what I will be working on.

Q: How does it work?

A: Basically we are utilizing the fantastic AST IASNode structure, traversing the nodes and visiting them individually with an emitter. The difference here is the AST walker is a hand written implementation that can easily be maintained and optimized per individual specs.

Framework Design

The FalconJx javascript cross compiler relies on AST generated by the Falcon compiler when it parses ActionScript code. This AST is then traversed in a top down fashion from the IFileNode down into the leaf expression nodes of an ActionScript source file.

There are 3 abstract concepts employed here;

  1. A traversing class that is responsible for knowing the toplevel structure of an ActionScript file, in the default implementation of the compiler, this class is the ASBlockWalker.
  2. A node visitor that will handle each node based on it's type. This is the ASNodeSwitch class. The walker calls this class's handle() method when traversing the AST.
  3. An emitter class that is a strategy as well inside the walker class that gets called when a node is found that needs to be emitted.

One question you may be asking is, when I look at the source code of the ASEmitter, you seem to have coupled that with the walker in callbacks to the walker's walk() method.

A: Yes, this is true but to maintain a central walker class that could be overridden to provide hooks to generate extra code, delegating on a 1 to 1 relationship between these classes only rely on a public API, the emit and walk API.

It could be argued that there is redundant calls back and forth, but I guess it comes down to the designer and what was implemented. :) I really see this as a very good abstraction that will lead to easy code maintenance down the road as ALL emitting should be done in the base ASEmitter class. Plus, there are circumstances present and in the unforeseeable future that having the core traversing done in the walker with more fine grained traversal in the emitter really pays off.

The first implementation of the IASNodeStrategy handler of the walker uses a before and after handler which sandwiches the main AST node handler in the middle. The benefit of this is leaving the open and closing of blocks in an external class for good decoupling of the block generation. It may work out that this before and after strategy is not needed and affects performance but that is to be decided.

Compilation Steps

  • Main method is called with compile arguments like MXMLC
  • A backend instance is created that acts as a factory to create the source file handler, configurator, javascript target, AST walker, JavaScript emitter and node switch strategy and the JavaScript writer.
  • The configuration instance is created and the project is configured with the run-time arguments and defaults.
  • The main compile() method is called.
  • The file handler is setup for .as and .mxml files.
  • The target file is setup. The target file is the main application file used just like MXMLC expects. The target file is the entry point to the application.
  • Once everything is setup and there are no configuration and target errors present, the JavaScript target is built.
  • A JSTarget instance is created and the build() method is called.
  • Once the build() method is called, the compiler runs in multi-threaded mode finding all reachable compilation units and proceeds to call the ASParser on them created their AST trees.
  • If the JSApplication artifact is created successfully a JSWriter is created for each compilation unit and an AST walker is created to traverse each compilation unit's AST tree to emit JavaScript source code dependent on the visitor/emitter implementation.
  • After each compilation unit is traversed, the output bytes of the emitter are then saved to it's corresponding .js file in the source output directory.
  • If all is successful, the compiler will exit with a 0 exit code meaning success and all ActionScript files were cross compiled to JavaScript.

The above is a very minimized outline of the program flow within the compiler. I would say about 2/3 closely resembles the original FalconJS compiler with setup and compilation unit management. The difference as stated above is how the JavaScript is generated.

Unit Tests

Unit tests are an essential part of developing any software and when dealing with a cross compiler it becomes essential to test every facet of production.

From what I looked at it would be very hard to unit test the original FalconJS code. It's written with a Bottom Up Rewritter (JBurg) and is very hard for intermediate to even advanced programmers to understand.

Within 2 days I was able to generate over 80 unit tests for the prototype cross compiler. Most expressions and statements are unit tested already. (binary, unary, literals, statements such as if, while, switch, etc.).

For the first unit tests implemented the pattern follows close to what we are testing in MXML.

  • Create a string class with the expression or statement injected within.
  • Load up the configuration and flex-config.
  • Pass the arguments to the compiler.
  • Save the string class to a temporary file.
  • Load the temp file into the Falcon parser.
  • Parse the class for the root IFileNode.
  • Use a method called from the test method to get the specific node such as a + b, you will get an IBinaryOperatorNode.
  • Call the corresponding visitor method to write out the JavaScript string to the output buffer.
  • Test the buffer against the expected output.
@Test
public void testVisitBinaryOperatorNode_Plus()
{
    IBinaryOperatorNode node = getBinaryNode("a + b");
    visitor.visitBinaryOperator(node);
    assertOut("a + b");
}

The goal of this design is granularity. With the ability to test each expression, statement and block individually, virtually all spaghetti code messes are resolved.

Note: Most of these base test output JavaScript that is virtually identical to ActionScript. This means ActionScript could as easily be output from the emitter since it proves that the walker is semantically traversing the ActionScript AST correctly. Using this same framework, different language output could be implemented, would you want to do that? Only time will tell.

Composition

The main goal of this prototype is for composition of emitter strategies. There are specific levels of node handlers that have been implemented.

  1. Compilation unit level walker that traverses the whole Class/Interface AST tree. This API is represent with the IASNodeStrategy interface with one method handle(IASNode node).
  2. A before and after strategy that will be called before a node is handled and after a node is handled. This allows for correct block indenting, semi colon handling and pre and post symbol handling.
  3. An emitter API that has specific write methods that can be overridden.
  • No labels