Repository: Cleod9/as3js
Branch: master
Commit: ea426d1289a2
Files: 26
Total size: 209.3 KB
Directory structure:
gitextract_llgholf1/
├── .gitattributes
├── .gitignore
├── AS3JS.as3proj
├── AS3JS.lxml
├── LICENSE
├── Readme.md
├── bin/
│ └── as3jsc.js
├── build.js
├── index.js
├── lib/
│ └── as3.js
├── package.json
├── runtime.js
├── snippets/
│ ├── class-snippet.js
│ └── main-snippet.js
└── src/
└── com/
└── mcleodgaming/
└── as3js/
├── Main.as
├── enums/
│ ├── AS3Encapsulation.as
│ ├── AS3MemberType.as
│ ├── AS3ParseState.as
│ └── AS3Pattern.as
├── parser/
│ ├── AS3Class.as
│ ├── AS3Parser.as
│ └── AS3Token.as
└── types/
├── AS3Argument.as
├── AS3Function.as
├── AS3Member.as
└── AS3Variable.as
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.js text
*.as text
*.html text
*.less text
*.json text
*.text text
*.bat text
*.sh text
# Declare files that will always have CRLF line endings on checkout.
#*.sln text eol=crlf
# Denote all files that are truly binary and should not be modified.
#*.png binary
#*.jpg binary
================================================
FILE: .gitignore
================================================
node_modules/
runtime-compiled.js
================================================
FILE: AS3JS.as3proj
================================================
node build.js
================================================
FILE: AS3JS.lxml
================================================
false
false
false
true
false
false
None
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 Greg McLeod
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: Readme.md
================================================
# AS3JS (deprecated) #
----------
### NOTICE: Due to lack of interest this project is no longer maintained. ###
I appreciate everyone who starred the project and participated in the discussions. Maybe some day we'll see true strictly typed JavaScript in some other form :)
----------
**NEW:** **Try AS3JS [live in your browser!](https://jsfiddle.net/cleod9/r1kn2cxf/12/embedded/result)**
AS3JS is a tool written for Node.js that converts ActionScript 3.0 to vanilla JavaScript (originally based on [ImportJS](https://github.com/Cleod9/importjs)). This allows you to write your code using the standard AS3 package structure, and have it automatically converted into a standalone JavaScript file. There are many IDE's out there that can easily parse ActionScript files, so why would you pass up this chance at smart JS code-completion in a program such as [FlashDevelop](http://www.flashdevelop.org/wikidocs/index.php?title=Features:Completion) or [FDT](http://fdt.powerflasher.com/)? **AS3JS even compiles its own source code from AS3 to JS!** :)
So this tool was created with the following goals in mind:
- Write your code in ActionScript
- Output to **coherent, debugabble** JavaScript!
The best part about AS3JS is that even if you aren't familiar with AS3 you can still use this tool with a very small learning curve. The only real difference between AS3JS and normal JS code is what I'd like to call **"outer syntax"**. The majority of your code stays the same, you just need to change what "surrounds" your code. This should hopefully encourage building a much more organized code base in a large application.
## Features ##
- Converts ActionScript 3.0 code into readable JavaScript output (Structure based on [ImportJS](https://github.com/Cleod9/importjs))
- Recursively parses directories for ActionScript files and automatically resolves import dependencies
- Concatenation into a single .js file
- Support for Vector type syntax (transpiles to a standard Array)
- Support for the '*' wildcard symbol for imports
- Support for AS3's default argument values and the "...rest" argument
- Support for the "super" keyword up to the parent level
- Moderate support for getter/setter methods
- Mix and match with traditional JS code via the global namespace at your leisure
- Ultra fast compilation!
### Experimental features ###
- Allows `require "module_name"` at the package level (do not include semicolon, variable `module_name` will be assigned)
## Setup Instructions ##
**Installation Requirements:**
- [Node.js](http://nodejs.org/)
So the first thing you need in order to use this application is Node.js:
[http://nodejs.org/](http://nodejs.org/)
Once installed, then install AS3JS as a global module via your command line:
```
$ npm install as3js -g
```
If you just want to install as3js into a local project, you can omit the `-g` to run via `./node_modules/.bin/as3js`:
```
$ npm install as3js
```
## Usage ##
### CLI ###
AS3JS can be run as a CLI via the `as3js` command, which has the following parameters:
`-o`, `--output`: Path where the output file will be written (e.g. path/to/output.js)
`-src`, `--sourcepath`: Comma-delimited list of paths to pull source .as files from. It is expected that this path be the root of your project's package directory. So for example, if you defined a package such as `com.myproject`, you would want this value to be the folder that contains the `com` directory. Note that AS3JS processes folders recursively, so you only need to put the path to your top-level folders.
`-h`, `--help`: Outputs help information.
`-v`,`--version`: Outputs version information.
`-s`,`--silent`: Flag to completely silence AS3JS output.
`--verbose`: Flag to enable verbose output. Use to help debug transpiler errors.
`-d`, `--dry`: Perfoms a dry-run of the compilation. This will perform all of the usual compilation steps but skip writing the final output file.
`-e`, `--entry`: This is the entry package class for your application. Uses the format `[mode]:path.to.package.Class`. You replace `[mode]` with either `"instance"` to have AS3JS instantiate the class once your compiled script loads, or `"static"` to have AS3JS return the class function as-is.
`--safe-require` - Puts a try-catch around require statements. Useful for code that may run in both the browser and Node.js
`--ignore-flash` - Ignores imports of flash.* packages (helps silence errors when porting Flash code)
Here is an example command:
```
$ as3js -src ./myas3 -o ./output.js -e new:com.example.MyClass
```
The above example recursively browses through the directory `myas3` finding all `.as` files, converts them to JS, and finally combines the results into a file called `output.js` in the working directory. This script contains your entire application, and will initialize `MyClass` as your entry point. Simple as that!
### Node Script ###
AS3JS can also be initialized manually within a Node.js script like so:
```js
// Import the compiler
var AS3JS = require('as3js');
// Instantiate the compiler
var as3js = new AS3JS();
var result = as3js.compile({
srcPaths: ['./src'], // --sourcepath
silent: false, // --silent
verbose: false, // --verbose
entry: "com.my.App", // Entry point class path
entryMode: "instance", // "instance" or "static"
safeRequire: false, // --safe-require
ignoreFlash: false // --ignore-flash
packages: [] // Provide an array of raw text strings to be parsed as "files"
});
// Gets the compiled source text and do what you want with it
var sourceText = result.compiledSource;
// Example: Prepending the loader source code to the program
var as3jslib = fs.readFileSync('node_modules/as3js/lib/as3.js');
fs.writeFileSync('app.js', as3jslib + '\n' + sourceText, "UTF-8", {flags: 'w+'});
```
## Examples ##
- **[Live browser demo](https://jsfiddle.net/cleod9/r1kn2cxf/12/embedded/result)** - Test out AS3JS right in your browser!
- **[Elevator Engine](https://github.com/cleod9/elevatorjs)** - I wrote this elevator simulator a long time ago in JavaScript and converted it to AS3. What's unique about this one is that the code can also compile to SWF simply by swapping out a single file.
## Limitations ##
Of course since AS3JS is still in alpha it comes with its limitations. See below:
### Event Listeners ###
AS3JS does not enforce class function binding when using them as callbacks. This is commonly an issue when dealing with event listeners. This simply means you will have to manage binding any event listeners on your own. A simple workaround for this is as follows:
```actionscript
//Write a global helper somewhere that anyone can access
var eventHelper = function (context, fn) {
//Returns a function with the proper binding
return function () {
return fn.apply(context, Array.prototype.slice.call(arguments));
};
};
```
```actionscript
//Usage in AS3
package {
public class Main {
public var myFuncBinded:Function;
public function Main():void {
//Allows you to use myFuncBinded for guaranteed scope
myFuncBinded = eventHelper(this, myFunc);
window.addEventListener("click", myFuncBinded);
}
public function myFunc(e:* = null):void {
//When window is clicked
console.log("clicked");
}
}
}
```
### No True Privates ###
While you can use any of the encapsulation keywords you'd like, there is currently no "true" encapsulation support in AS3JS. Private/protected class properties and methods remain publicly accessible on any instantiated objects. I gave a lot of thought to this and went over many potential solutions. I came to the conclusion that while encapsulation is convenient, in the open world of JavaScript all of this data is easily accessible through basic browser debugging tools. As such, I have no plans to add true encapsulation to AS3JS. The good news is that you can still use the keywords and AS3JS will simply strip them out.
### No Chaining super() ###
AS3JS does not currently support chaining `super` (i.e. `super.super.super.fn()`). If you need such a feature, you can achieve this by using JavaScript in your code:
```actionscript
GreatGrandfather.prototype.fn.call(this, arg1, arg2... etc);
```
### No type validation ###
AS3JS will not validate your types during compile time or runtime. I may add some compile time type checking in the future, but there are no plans for runtime type checking due to unnecessary overhead.
### Typed variable declarations cannot be all on one line ###
I hope to work on this soon, but currently you can't write statements like this:
```actionscript
var a:Type, b:Type, c:Type = 4;
```
If you remove the types it will work fine, but I have not yet implemented anything to strip the types from this type of statement.
### Class-level member variable assignments must be on one line ###
Currently AS3JS doesn't support breaking out something like this into separate lines:
```actionscript
public static var foo:Object = { a: 1, b: 2, c: 3, d: 4, e: 5 };
```
Hopefully you aren't writing such large assignments on class level properties, but for now please write these types of assignments as one-liners.
### Getter/Setter limitations ###
While the getter/setters work pretty well, there are a couple of things you should avoid:
- Assigning a value to a setter that spans multiple lines;
- Accessing a getter from within a getter: (e.g. `myGetter['key'].someOtherGetter`)
AS3JS isn't able to recognize those situations, so it will likely export invalid JS.
### No support for package-level functions ###
This isn't something I've seen used all that often anyway, but if you want to read up on package-level functions see [here](http://blogs.adobe.com/digitalmedia/2011/01/as3-package-level-functions-and-java-static-imports/)
### No casting types ###
I have not implemented the `as` operator, nor can you case with the `Caster(castee)` syntax. The only workaround for now is to re-assign values to a variable that has the proper Type:
```actionscript
var other:SomeOtherType = new SomeOtherType();
var foo:TypeIWant = other;
```
### No `is` support ###
Currently there is no support for type checking via the `is` operator (e.g. `val is Type`) Just stick with `instanceof` for now.
### No `Embed` support ###
Resource embedding is specific to the Flash platform, so I have no plans to implement it at this time.
### Restricted regex support ###
The parser is currently unable to recognize the start and end of a regular expression literal (e.g. `/pattern/`). As such, characters such as `"`, `'`, `{`, `}`, and other patterns may confuse the parser. A simple workaround for this is to use the `RegExp` constructor to define regular expressions that contain these characters (e.g. `new RegExp("pattern")`)
## \*Disclaimer\* ##
**AS3JS cannot currently convert *all* AS3 to proper JS.** While I have put a ton of effort into allowing it to convert 99% of AS3 syntax into JavaScript, the languages are still fundamentally different. There are several things that I have yet to handle, such as casting via the `as` operator, or forcefully binding event callbacks to class instances. This tool is not perfect, however it is quite able to handle a full-fledged personal project. You'll find that sometimes after compiling without errors there may still be some minor syntax issues in the output, however nearly all of these issues can be avoided very easily with a few code tweaks in your AS3 and are easy to catch (See "Limitations" listed above).
Also I would like to note that **this is not an all-in-one solution** like [FlashJS](http://flashjs.com/), [FlexJS](http://flex.apache.org/download-flexjs.html), [OpenFL](http://www.openfl.org/), or [Randori](http://randoriframework.com/). This is more like what [Jangaroo](http://www.jangaroo.net/home/) was meant to do, but a trillion times simpler. Although AS3JS can be used to create code that is somewhat cross-compatible with Flash, it is still designed with the average JavaScript developer in mind. The philosophy of AS3JS is to greatly simplify the organization of your JavaScript code using AS3 as syntax, not to re-create Flash. You have the freedom to implement Flash AS3 features if you want, but they will not come built into AS3JS.
Lastly, I fully acknowledge the ActionScript name as the property of [Adobe](http://www.adobe.com/). I do not claim ownership of the language nor do I have any affiliation with Adobe, but I do encourage you to check out the [documentation](http://www.adobe.com/devnet/actionscript/learning.html) if you are unfamiliar with ActionScript 3.0. Just remember that AS3JS is made for JavaScript, so many features of Flash AS3 will not be implemented unless you create them yourself.
## Building Source ##
The source code for AS3JS is written in ActionScript 3 under the `src/` folder, and is also set up as a FlashDevelop project. You can compile the source code one of two ways:
- Clicking the Build button in the toolbar of FlashDevelop
OR
- Executing `node build.js` via the command-line
Either of these steps will output a file called `runtime-compiled.js`. Replace `runtime.js` with the contents of `runtime-compiled.js` to update the runtime with your changes.
### Finalizing A Build ###
Since AS3JS's source is written to be compiled by AS3JS itself, if your changes affect the output of compiled files it's important to run the build again and replace `runtime.js` a second AND third time. This ensures that the runtime is using your code as opposed to an outdated runtime. If something is wrong with the build, it will likely fail the third time you attempt to build. It also can't hurt to build a fourth time to ensure the final build is stable.
## Upgrade Notes ##
**Upgrading from v0.1.**: [ImportJS](https://github.com/Cleod9/importjs) and [OOPS.js](https://github.com/Cleod9/oopsjs) are no longer dependencies of this project, so be sure to follow the new setup instructions carefully)
**Upgrading from v0.2.**: AS3JS's responsibilities have been split into two functions: The *compiler*, and the *loader*. The compiler is what converts your AS3 into vanilla JS, but with a few extra features that depend on a separate loader library included in this repo. In browser environments, this is just a matter of using the `./lib/as3.js` as a global script on the page to load your program. For Node.js environments, you'll need to attach AS3JS to the `global` object (details later on below)
## Version History ##
**0.3.3** (Final build)
- Added $cinit() and $init() logic to mimic Flash
- Inferring imports from top-scope member assignments
**0.3.2**
- Handle dictionary class
- Const keyword Removal
- Implicit Static Assignments
**0.3.1**
- Updated Readme
- Improved error messaging when class paths are missing
**0.3.0**
- Documented the Node.js interface for loading the compiler manually
- Split AS3JS roles into "compiler" and "program" (while still maintaining mostly vanilla code)
- Added safeRequire option to allow browser to load code with Node require statements
- Added ignoreFlash option to ignore **flash.*** packages
- Fixed several issues with transpiling classes in the top-level package
- Experimental package-level `require` feature
- New `packages` option that can be used when compiling directly in Node.js (allows injecting raw text packages into the compiler)
- Shipped new live editor with 0.3.* support: https://jsfiddle.net/cleod9/r1kn2cxf/12/embedded/result
**0.2.***
-Created new Vanilla output format that no longer requires external libraries
-Removed ImportJS and OOPS.js as dependencies
**0.1.***
-Initial alpha release
**0.0.1**
-First commit (before conversion of the source code itself to AS3)
----------
Copyrighted © 2017 by Greg McLeod
GitHub: [https://github.com/cleod9](https://github.com/cleod9)
================================================
FILE: bin/as3jsc.js
================================================
#!/usr/bin/env node
/**
* This is the global AS3JS compiler for running 'as3js' as a CLI
**/
var pjson = require('../package.json');
var fs = require('fs');
var path = require('path');
global.AS3JS = require(path.resolve(__dirname, '..', 'lib/as3.js'));
var AS3JS = require(path.resolve(__dirname, '..', 'runtime.js'));
var VERSION = pjson.version;
//AS3JS options
var srcPaths = [];
var output = null;
var silent = false;
var verbose = false;
var entry = '';
var dry = false;
var safeRequire = false;
var ignoreFlash = false;
//Command line args
var arg = null;
var option = null;
var command = null;
//Misc options
//Parse arguemnts
for(var i = 0; i < process.argv.length; i++) {
arg = process.argv[i];
if(command) {
//Commands will go here if implemented
command = null;
} else if(option) {
//Options are set here
if(option == 'o') {
output = arg; //File output
} else if(option == 'src') {
srcPaths = srcPaths.concat(arg.split(",")); //Source path(s) to parse
} else if(option == 'e') {
entry = arg;
}
option = null;
} else {
if(arg == '--verbose') {
verbose = true;
} else if(arg == '-d' || arg == '--dry') {
dry = true;
} else if(arg == '-s' || arg == '--silent') {
silent = true;
} else if(arg == '--safe-require') {
safeRequire = true;
} else if(arg == '-o' || arg == '--output') {
option = 'o'; //File output
} else if(arg == '-src' || arg == '--sourcepath') {
option = 'src'; //Source path(s)
} else if(arg == '-e' || arg == '--entry') {
option = 'e'; //Entry point
} else if(arg == '--ignore-flash') {
ignoreFlash = true;
} else if(arg == '-h' || arg == '--help') {
//Help text
console.log("Options:");
console.log("\t[-o|--output]\t\tOutput file");
console.log("\t[-src|-sourcepath]\tSource Path(s) (comma-separated)");
console.log("\t[-d|--dry]\tDry-run mode");
console.log("\t[-e|--entry]\t\tEntry point (ex. \"[instance|static]:com.example.MyClass\")");
console.log("\t[-h|--help]\t\tView Help");
console.log("\t[-v|--version]\t\tView Version information");
console.log("\t[--verbose]\t\tVerbose console output");
console.log("\t[--safe-require]\t\tTry-catch require() statements");
console.log("\t[--ignore-flash]\t\tIgnore flash.* imports");
return;
} else if(arg == '-v' || arg == '--version') {
//Version info
console.log("AS3JS for Node.js");
console.log("Created by Greg McLeod (c) 2017");
console.log("Version: " + VERSION);
return;
}
}
}
if(srcPaths.length <= 0) {
console.log("Error, must supply source path (-src)");
} else if(!output) {
console.log("Error, must supply output path (-o)");
} else {
var as3js = new AS3JS();
var sourceText = as3js.compile({
srcPaths: srcPaths,
silent: silent,
verbose: verbose,
entry: entry.split(':')[1],
entryMode: entry.split(':')[0],
safeRequire: safeRequire,
ignoreFlash: ignoreFlash
}).compiledSource;
//Remove old output file if it exists
if (output && !dry)
{
if (fs.existsSync(output))
{
fs.unlinkSync(output);
}
fs.writeFileSync(output || 'output.js', sourceText, "UTF-8", {flags: 'w+'});
}
}
================================================
FILE: build.js
================================================
var fs = require('fs');
var beautify = require('js-beautify').js_beautify;
// Pull in loader library first
global.AS3JS = require('./lib/as3.js');
// Now Pull in the actual AS3JS program
var AS3JS = require('./runtime.js');
// Load the program
var as3js = new AS3JS();
// Execute the program
var sourceText = as3js.compile({
srcPaths: ['./src'],
silent: false,
verbose: false,
safeRequire: true,
entry: 'com.mcleodgaming.as3js.Main',
entryMode: 'static'
}).compiledSource;
// Output the resulting source code
if (fs.existsSync('runtime-compiled.js'))
{
fs.unlinkSync('runtime-compiled.js');
}
fs.writeFileSync('runtime-compiled.js', beautify(sourceText, { indent_size: 2, max_preserve_newlines: 2 }), "UTF-8", {flags: 'w+'});
================================================
FILE: index.js
================================================
global.AS3JS = require('./lib/as3.js');
module.exports = require('./runtime.js');
================================================
FILE: lib/as3.js
================================================
/*******************************
AS3JS Version 0.3.3
AS3 to JS converter for use with ImportJS and OOPS.js.
The MIT License (MIT)
Copyright (c) 2017 Greg McLeod
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*******************************/
(function ( ) {
var AS3JS = {
Utils: {
// Helper for default args
getDefaultValue: function getDefaultValue(value, fallback) {
return (typeof value != 'undefined') ? value : fallback;
},
// Helper for Vector/Array constructors
createArray: function (size, val) {
var arr = [];
for (var i = 0; i < size; i++) {
arr.push(val);
}
return arr;
}
},
load: function ( params ) {
// Loads program specified by params
params = params || {};
params.entryMode = params.entryMode || 'instance';
// Shim just in case
if (typeof Object.create !== 'function') {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
}
};
// Some temps / helper
var i, j, tmpPkg;
var getPackageInfo = function ( name ) {
// Splits package path into separate package and class name
var pkg = name.split('.');
var className = pkg[pkg.length-1];
pkg.splice(pkg.length-1, 1);
var packageName = pkg.join('.');
return {
packageName: packageName,
className: className
};
};
// This hash map contains each package, each package contains its classes
var packages = {};
// Converts supplied package hash to packageName.className.{ source: moduleFn }
for (i in params.program) {
tmpPkg = getPackageInfo(i);
packages[tmpPkg.packageName] = packages[tmpPkg.packageName] || {};
packages[tmpPkg.packageName][tmpPkg.className] = { compiled: false, source: params.program[i] };
}
// This helper will execute the module source specified by "name" and return its exports object
var imports = function ( packageName, className ) {
// Only run source() if it hasn't been compiled yet
if (!packages[packageName][className].compiled) {
packages[packageName][className].compiled = true;
packages[packageName][className].module = { exports: null, inject: null, import: imports };
//This next line actually compiles the module
packages[packageName][className].source(packages[packageName][className].module, packages[packageName][className].module.exports);
}
// Returns the compiled module
return packages[packageName][className].module.exports;
};
// Compiles all packages
for (i in packages) {
for (j in packages[i]) {
imports(i, j);
}
}
// Run inject() and $cinit() functions as the final step (this trivializes circular dependencies)
for (i in packages) {
// Execute the injection functions
for (j in packages[i]) {
if (typeof packages[i][j].module.inject === 'function') {
packages[i][j].module.inject();
}
}
}
for (i in packages) {
// Execute the $cinit functions
for (j in packages[i]) {
if (typeof packages[i][j].module.exports.$cinit === 'function') {
packages[i][j].module.exports.$cinit();
}
}
}
// Initializes application
var entryPkgInfo = getPackageInfo(params.entry);
var entryPoint = imports(entryPkgInfo.packageName, entryPkgInfo.className);
if (params.entryMode === "instance") {
return new entryPoint();
} else if (params.entryMode === "static") {
return entryPoint;
}
}
};
if (typeof module !== 'undefined') {
//CommonJS
module.exports = AS3JS;
} else {
//Browser Global
window.AS3JS = AS3JS;
}
})();
================================================
FILE: package.json
================================================
{
"name": "as3js",
"version": "0.3.3",
"author": "Greg McLeod ",
"description": "AS3 to JS converter.",
"bin": {
"as3js": "./bin/as3jsc.js"
},
"devDependencies": {
"js-beautify": "1.5.10"
},
"main": "./index.js",
"repository": {
"type": "git",
"url": "https://github.com/cleod9/as3js"
},
"keywords": [
"as3",
"js",
"converter",
"import",
"oop"
],
"license": "MIT"
}
================================================
FILE: runtime.js
================================================
(function() {
var Program = {};
Program["com.mcleodgaming.as3js.enums.AS3Encapsulation"] = function(module, exports) {
var AS3Encapsulation = function AS3Encapsulation() {};
AS3Encapsulation.PUBLIC = null;
AS3Encapsulation.PRIVATE = null;
AS3Encapsulation.PROTECTED = null;
AS3Encapsulation.$cinit = function() {
AS3Encapsulation.PUBLIC = "public";
AS3Encapsulation.PRIVATE = "private";
AS3Encapsulation.PROTECTED = "protected";
};
AS3Encapsulation.prototype.$init = function() {}
module.exports = AS3Encapsulation;
};
Program["com.mcleodgaming.as3js.enums.AS3MemberType"] = function(module, exports) {
var AS3MemberType = function AS3MemberType() {};
AS3MemberType.VAR = null;
AS3MemberType.CONST = null;
AS3MemberType.FUNCTION = null;
AS3MemberType.$cinit = function() {
AS3MemberType.VAR = "var";
AS3MemberType.CONST = "const";
AS3MemberType.FUNCTION = "function";
};
AS3MemberType.prototype.$init = function() {}
module.exports = AS3MemberType;
};
Program["com.mcleodgaming.as3js.enums.AS3ParseState"] = function(module, exports) {
var AS3ParseState = function AS3ParseState() {};
AS3ParseState.START = null;
AS3ParseState.PACKAGE_NAME = null;
AS3ParseState.PACKAGE = null;
AS3ParseState.CLASS_NAME = null;
AS3ParseState.CLASS = null;
AS3ParseState.CLASS_EXTENDS = null;
AS3ParseState.CLASS_IMPLEMENTS = null;
AS3ParseState.COMMENT_INLINE = null;
AS3ParseState.COMMENT_MULTILINE = null;
AS3ParseState.STRING_SINGLE_QUOTE = null;
AS3ParseState.STRING_DOUBLE_QUOTE = null;
AS3ParseState.STRING_REGEX = null;
AS3ParseState.MEMBER_VARIABLE = null;
AS3ParseState.MEMBER_FUNCTION = null;
AS3ParseState.LOCAL_VARIABLE = null;
AS3ParseState.LOCAL_FUNCTION = null;
AS3ParseState.IMPORT_PACKAGE = null;
AS3ParseState.REQUIRE_MODULE = null;
AS3ParseState.$cinit = function() {
AS3ParseState.START = "start";
AS3ParseState.PACKAGE_NAME = "packageName";
AS3ParseState.PACKAGE = "package";
AS3ParseState.CLASS_NAME = "className";
AS3ParseState.CLASS = "class";
AS3ParseState.CLASS_EXTENDS = "classExtends";
AS3ParseState.CLASS_IMPLEMENTS = "classImplements";
AS3ParseState.COMMENT_INLINE = "commentInline";
AS3ParseState.COMMENT_MULTILINE = "commentMultiline";
AS3ParseState.STRING_SINGLE_QUOTE = "stringSingleQuote";
AS3ParseState.STRING_DOUBLE_QUOTE = "stringDoubleQuote";
AS3ParseState.STRING_REGEX = "stringRegex";
AS3ParseState.MEMBER_VARIABLE = "memberVariable";
AS3ParseState.MEMBER_FUNCTION = "memberFunction";
AS3ParseState.LOCAL_VARIABLE = "localVariable";
AS3ParseState.LOCAL_FUNCTION = "localFunction";
AS3ParseState.IMPORT_PACKAGE = "importPackage";
AS3ParseState.REQUIRE_MODULE = "requireModule";
};
AS3ParseState.prototype.$init = function() {}
module.exports = AS3ParseState;
};
Program["com.mcleodgaming.as3js.enums.AS3Pattern"] = function(module, exports) {
var AS3Pattern = function AS3Pattern() {};
AS3Pattern.IDENTIFIER = null;
AS3Pattern.OBJECT = null;
AS3Pattern.IMPORT = null;
AS3Pattern.REQUIRE = null;
AS3Pattern.CURLY_BRACE = null;
AS3Pattern.VARIABLE = null;
AS3Pattern.VARIABLE_TYPE = null;
AS3Pattern.VARIABLE_DECLARATION = null;
AS3Pattern.ASSIGN_START = null;
AS3Pattern.ASSIGN_UPTO = null;
AS3Pattern.VECTOR = null;
AS3Pattern.ARRAY = null;
AS3Pattern.DICTIONARY = null;
AS3Pattern.REST_ARG = null;
AS3Pattern.$cinit = function() {
AS3Pattern.IDENTIFIER = [/\w/g, /\w/g];
AS3Pattern.OBJECT = [/[\w\.]/g, /[\w(\w(\.\w)+)]/g];
AS3Pattern.IMPORT = [/[0-9a-zA-Z_$.*]/g, /[a-zA-Z_$][0-9a-zA-Z_$]([.][a-zA-Z_$][0-9a-zA-Z_$])*\*?/g];
AS3Pattern.REQUIRE = [/./g, /["'](.*?)['"]/g];
AS3Pattern.CURLY_BRACE = [/[\{|\}]/g, /[\{|\}]/g];
AS3Pattern.VARIABLE = [/[0-9a-zA-Z_$]/g, /[a-zA-Z_$][0-9a-zA-Z_$]*/g];
AS3Pattern.VARIABLE_TYPE = [/[a-zA-Z_$<>.*][0-9a-zA-Z_$<>.]*/g, /[a-zA-Z_$<>.*][0-9a-zA-Z_$<>.]*/g];
AS3Pattern.VARIABLE_DECLARATION = [/[0-9a-zA-Z_$:<>.*]/g, /[a-zA-Z_$][0-9a-zA-Z_$]*\s*:\s*([a-zA-Z_$<>\.\*][0-9a-zA-Z_$<>\.]*)/g];
AS3Pattern.ASSIGN_START = [/[=\r\n]/g, /[=\r\n]/g];
AS3Pattern.ASSIGN_UPTO = [new RegExp("[^;\\r\\n]", "g"), /(.*?)/g];
AS3Pattern.VECTOR = [/new[\s\t]+Vector\.<(.*?)>\((.*?)\)/g, /new[\s\t]+Vector\.<(.*?)>\((.*?)\)/];
AS3Pattern.ARRAY = [/new[\s\t]+Array\((.*?)\)/g, /new[\s\t]+Array\((.*?)\)/];
AS3Pattern.DICTIONARY = [/new[\s\t]+Dictionary\((.*?)\)/g];
AS3Pattern.REST_ARG = [/\.\.\.[a-zA-Z_$][0-9a-zA-Z_$]*/g, /\.\.\.[a-zA-Z_$][0-9a-zA-Z_$]*/g];
};
AS3Pattern.prototype.$init = function() {}
module.exports = AS3Pattern;
};
Program["com.mcleodgaming.as3js.Main"] = function(module, exports) {
var path = (function() {
try {
return require("path");
} catch (e) {
return undefined;
}
})();
var fs = (function() {
try {
return require("fs");
} catch (e) {
return undefined;
}
})();
var AS3Parser;
module.inject = function() {
AS3Parser = module.import('com.mcleodgaming.as3js.parser', 'AS3Parser');
};
var Main = function() {
this.$init();
};
Main.DEBUG_MODE = false;
Main.SILENT = false;
Main.debug = function() {
if (Main.SILENT) {
return;
}
if (Main.DEBUG_MODE) {
console.log.apply(console, arguments);
}
};
Main.log = function() {
if (Main.SILENT) {
return;
}
console.log.apply(console, arguments);
};
Main.warn = function() {
if (Main.SILENT) {
return;
}
console.warn.apply(console, arguments);
};
Main.$cinit = function() {
Main.DEBUG_MODE = false;
Main.SILENT = false;
};
Main.prototype.$init = function() {};
Main.prototype.compile = function(options) {
options = AS3JS.Utils.getDefaultValue(options, null);
var packages = {}; //Will contain the final map of package names to source text
var i;
var j;
var k;
var m;
var tmp;
options = options || {};
var srcPaths = options.srcPaths || {};
var rawPackages = options.rawPackages || [];
var parserOptions = {
safeRequire: options.safeRequire,
ignoreFlash: options.ignoreFlash
};
//Temp classes for holding raw class info
var rawClass;
var rawParser;
var pkgLists = {};
for (i in srcPaths) {
pkgLists[srcPaths[i]] = this.buildPackageList(srcPaths[i]);
}
Main.DEBUG_MODE = options.verbose || Main.DEBUG_MODE;
Main.SILENT = options.silent || Main.SILENT;
var classes = {};
var buffer = "";
//First, parse through the file-based classes and get the basic information
for (i in pkgLists) {
for (j in pkgLists[i]) {
Main.log('Analyzing class path: ' + pkgLists[i][j].classPath);
classes[pkgLists[i][j].classPath] = pkgLists[i][j].parse(parserOptions);
Main.debug(classes[pkgLists[i][j].classPath]);
}
}
// Now parse through any raw string classes
for (i = 0; i < rawPackages.length; i++) {
Main.log('Analyzing class: ' + i);
rawParser = new AS3Parser(rawPackages[i]);
rawClass = rawParser.parse(parserOptions);
classes[rawParser.classPath] = rawClass;
}
//Resolve all possible package name wildcards
for (i in classes) {
//For every class
for (j in classes[i].importWildcards) {
Main.debug('Resolving ' + classes[i].className + '\'s ' + classes[i].importWildcards[j] + ' ...')
//For every wild card in the class
for (k in srcPaths) {
//For each possible source path (should hopefully just be 1 most of the time -_-)
tmp = srcPaths[k] + path.sep + classes[i].importWildcards[j].replace(/\./g, path.sep).replace(path.sep + '*', '');
tmp = tmp.replace(/\\/g, '/');
tmp = tmp.replace(/[\/]/g, path.sep);
if (fs.existsSync(tmp)) {
Main.debug('Searching path ' + tmp + '...')
//Path exists, read the files in the directory
var files = fs.readdirSync(tmp);
for (m in files) {
//See if this is an ActionScript file
if (fs.statSync(tmp + path.sep + files[m]).isFile() && files[m].lastIndexOf('.as') == files[m].length - 3) {
//See if the class needs the file
if (classes[i].needsImport(classes[i].importWildcards[j].replace(/\*/g, files[m].substr(0, files[m].length - 3)))) {
Main.debug('Auto imported ' + files[m].substr(0, files[m].length - 3));
classes[i].addImport(classes[i].importWildcards[j].replace(/\*/g, files[m].substr(0, files[m].length - 3))); //Pass in package name with wild card replaced
}
}
}
} else {
Main.warn('Warning, could not find directory: ' + tmp);
}
}
// Must do again for classes in case there we
for (k in classes) {
if (classes[i].needsImport(AS3Parser.fixClassPath(classes[k].packageName + '.' + classes[k].className))) {
Main.debug('Auto imported ' + AS3Parser.fixClassPath(classes[k].packageName + '.' + classes[k].className));
classes[i].addImport(AS3Parser.fixClassPath(classes[k].packageName + '.' + classes[k].className)); //Pass in package name with wild card replaced
}
}
}
}
//Add extra imports before registring them (these will not be imported in the output code, but rather will provide insight for AS3JS to determine variable types)
for (i in classes) {
for (j in classes) {
classes[i].addExtraImport(AS3Parser.fixClassPath(classes[j].packageName + '.' + classes[j].className));
}
}
//Resolve import map
for (i in classes) {
classes[i].registerImports(classes);
}
//Resolve parent imports
for (i in classes) {
classes[i].findParents(classes);
}
//Walk through the class members that had assignments in the class scope
for (i in classes) {
classes[i].checkMembersWithAssignments();
}
//Process the function text to comply with JS
for (i in classes) {
Main.log('Parsing package: ' + AS3Parser.fixClassPath(classes[i].packageName + "." + classes[i].className));
classes[i].process(classes);
}
// Load stringified versions of snippets/main-snippet.js and snippets/class-snippet.js
var mainTemplate = "(function(){var Program={};{{packages}}if(typeof module !== 'undefined'){module.exports=AS3JS.load({program:Program,entry:\"{{entryPoint}}\",entryMode:\"{{entryMode}}\"});}else if(typeof window!=='undefined'&&typeof AS3JS!=='undefined'){window['{{entryPoint}}']=AS3JS.load({program:Program,entry:\"{{entryPoint}}\",entryMode:\"{{entryMode}}\"});}})();";
var classTemplate = "Program[\"{{module}}\"]=function(module, exports){{{source}}};";
var packageObjects = [];
var classObjects = null;
var currentClass = "";
if (options.entry) {
// Entry point should be in the format "mode:path.to.package.Class"
var currentPackage = options.entry;
var mode = options.entryMode || 'instance';
// Update template with entry points
mainTemplate = mainTemplate.replace(/\{\{entryPoint\}\}/g, AS3Parser.fixClassPath(classes[currentPackage].packageName + '.' + classes[currentPackage].className));
mainTemplate = mainTemplate.replace(/\{\{entryMode\}\}/g, mode);
} else {
mainTemplate = mainTemplate.replace(/\{\{entryPoint\}\}/g, "");
mainTemplate = mainTemplate.replace(/\{\{entryMode\}\}/g, "");
}
//Retrieve converted class code
var groupByPackage = {};
for (i in classes) {
groupByPackage[classes[i].packageName] = groupByPackage[classes[i].packageName] || [];
groupByPackage[classes[i].packageName].push(classes[i]);
}
for (i in groupByPackage) {
classObjects = [];
for (j in groupByPackage[i]) {
packages[AS3Parser.fixClassPath(i + "." + groupByPackage[i][j].className)] = groupByPackage[i][j].toString();
currentClass = classTemplate;
currentClass = currentClass.replace(/\{\{module\}\}/g, AS3Parser.fixClassPath(groupByPackage[i][j].packageName + "." + groupByPackage[i][j].className));
currentClass = currentClass.replace(/\{\{source\}\}/g, AS3Parser.increaseIndent(packages[AS3Parser.fixClassPath(i + "." + groupByPackage[i][j].className)], " "));
classObjects.push(currentClass);
}
packageObjects.push(AS3Parser.increaseIndent(classObjects.join(""), " "));
}
mainTemplate = mainTemplate.replace(/\{\{packages\}\}/g, packageObjects.join(""));
mainTemplate = mainTemplate.replace(/\t/g, " ");
buffer += mainTemplate;
Main.log("Done.");
return {
compiledSource: buffer,
packageSources: packages
};
};
Main.prototype.readDirectory = function(location, pkgBuffer, obj) {
var files = fs.readdirSync(location);
for (var i in files) {
var pkg = pkgBuffer;
if (fs.statSync(location + path.sep + files[i]).isDirectory()) {
var splitPath = location.split(path.sep);
if (pkg != '') {
pkg += '.';
}
this.readDirectory(location + path.sep + files[i], pkg + files[i], obj)
} else if (fs.statSync(location + path.sep + files[i]).isFile() && files[i].lastIndexOf('.as') == files[i].length - 3) {
if (pkg != '') {
pkg += '.';
}
pkg += files[i].substr(0, files[i].length - 3);
var f = fs.readFileSync(location + path.sep + files[i]);
obj[pkg] = new AS3Parser(f.toString(), pkg);
Main.debug("Loaded file: ", location + path.sep + files[i] + " (package: " + pkg + ")");
}
}
};
Main.prototype.buildPackageList = function(location) {
var obj = {};
var topLevel = location;
location = location.replace(/\\/g, '/');
location = location.replace(/[\/]/g, path.sep);
if (fs.existsSync(location) && fs.statSync(location).isDirectory()) {
var splitPath = location.split(path.sep);
this.readDirectory(location, '', obj);
return obj;
} else {
throw new Error("Error could not find directory: " + location);
}
}
module.exports = Main;
};
Program["com.mcleodgaming.as3js.parser.AS3Class"] = function(module, exports) {
var Main, AS3Parser, AS3MemberType, AS3Pattern, AS3Function, AS3Variable;
module.inject = function() {
Main = module.import('com.mcleodgaming.as3js', 'Main');
AS3Parser = module.import('com.mcleodgaming.as3js.parser', 'AS3Parser');
AS3MemberType = module.import('com.mcleodgaming.as3js.enums', 'AS3MemberType');
AS3Pattern = module.import('com.mcleodgaming.as3js.enums', 'AS3Pattern');
AS3Function = module.import('com.mcleodgaming.as3js.types', 'AS3Function');
AS3Variable = module.import('com.mcleodgaming.as3js.types', 'AS3Variable');
};
var AS3Class = function(options) {
this.$init();
options = AS3JS.Utils.getDefaultValue(options, null);
options = options || {};
this.safeRequire = false;
if (typeof options.safeRequire !== 'undefined') {
this.safeRequire = options.safeRequire;
}
if (typeof options.ignoreFlash !== 'undefined') {
this.ignoreFlash = options.ignoreFlash;
}
this.packageName = null;
this.className = null;
this.imports = [];
this.requires = [];
this.importWildcards = [];
this.importExtras = [];
this.interfaces = [];
this.parent = null;
this.parentDefinition = null;
this.members = [];
this.staticMembers = [];
this.getters = [];
this.setters = [];
this.staticGetters = [];
this.staticSetters = [];
this.membersWithAssignments = [];
this.isInterface = false;
this.fieldMap = {};
this.staticFieldMap = {};
this.classMap = {};
this.classMapFiltered = {};
this.packageMap = {};
var $init = new AS3Function();
$init.name = "$init";
$init.value = "{}";
$init.type === AS3MemberType.FUNCTION;
$init.isStatic = false;
this.members.push($init);
this.registerField($init.name, $init);
};
AS3Class.reservedWords = null;
AS3Class.nativeTypes = null;
AS3Class.$cinit = function() {
AS3Class.reservedWords = ["as", "class", "delete", "false", "if", "instanceof", "native", "private", "super", "to", "use", "with", "break", "const", "do", "finally", "implements", "new", "protected", "switch", "true", "var", "case", "continue", "else", "for", "import", "internal", "null", "public", "this", "try", "void", "catch", "default", "extends", "function", "in", "is", "package", "return", "throw", "typeof", "while", "each", "get", "set", "namespace", "include", "dynamic", "final", "natiev", "override", "static", "abstract", "char", "export", "long", "throws", "virtual", "boolean", "debugger", "float", "prototype", "to", "volatile", "byte", "double", "goto", "short", "transient", "cast", "enum", "intrinsic", "synchronized", "type"];
AS3Class.nativeTypes = ["Boolean", "Number", "int", "uint", "String"];
};
AS3Class.prototype.$init = function() {
this.imports = null;
this.requires = null;
this.importWildcards = null;
this.importExtras = null;
this.interfaces = null;
this.parentDefinition = null;
this.members = null;
this.staticMembers = null;
this.getters = null;
this.setters = null;
this.staticGetters = null;
this.staticSetters = null;
this.membersWithAssignments = null;
this.fieldMap = null;
this.staticFieldMap = null;
this.classMap = null;
this.classMapFiltered = null;
this.packageMap = null;
};
AS3Class.prototype.packageName = null;
AS3Class.prototype.className = null;
AS3Class.prototype.imports = null;
AS3Class.prototype.requires = null;
AS3Class.prototype.importWildcards = null;
AS3Class.prototype.importExtras = null;
AS3Class.prototype.interfaces = null;
AS3Class.prototype.parent = null;
AS3Class.prototype.parentDefinition = null;
AS3Class.prototype.members = null;
AS3Class.prototype.staticMembers = null;
AS3Class.prototype.getters = null;
AS3Class.prototype.setters = null;
AS3Class.prototype.staticGetters = null;
AS3Class.prototype.staticSetters = null;
AS3Class.prototype.isInterface = false;
AS3Class.prototype.membersWithAssignments = null;
AS3Class.prototype.fieldMap = null;
AS3Class.prototype.staticFieldMap = null;
AS3Class.prototype.classMap = null;
AS3Class.prototype.classMapFiltered = null;
AS3Class.prototype.packageMap = null;
AS3Class.prototype.safeRequire = false;
AS3Class.prototype.ignoreFlash = false;
AS3Class.prototype.registerImports = function(clsList) {
var i;
for (i in this.imports) {
if (clsList[this.imports[i]]) {
var lastIndex = this.imports[i].lastIndexOf(".");
var shorthand = (lastIndex < 0) ? this.imports[i] : this.imports[i].substr(lastIndex + 1);
this.classMap[shorthand] = clsList[this.imports[i]];
}
}
for (i in this.importExtras) {
if (clsList[this.importExtras[i]]) {
var lastIndex = this.importExtras[i].lastIndexOf(".");
var shorthand = (lastIndex < 0) ? this.importExtras[i] : this.importExtras[i].substr(lastIndex + 1);
this.classMap[shorthand] = clsList[this.importExtras[i]];
}
}
this.packageMap = clsList;
};
AS3Class.prototype.registerField = function(name, value) {
if (value && value.isStatic) {
this.staticFieldMap[name] = this.staticFieldMap[name] || value;
} else {
this.fieldMap[name] = this.fieldMap[name] || value;
}
};
AS3Class.prototype.retrieveField = function(name, isStatic) {
if (isStatic) {
if (this.staticFieldMap[name]) {
return this.staticFieldMap[name];
} else if (this.parentDefinition) {
return this.parentDefinition.retrieveField(name, isStatic);
} else {
return null;
}
} else {
if (this.fieldMap[name]) {
return this.fieldMap[name];
} else if (this.parentDefinition) {
return this.parentDefinition.retrieveField(name, isStatic);
} else {
return null;
}
}
};
AS3Class.prototype.needsImport = function(pkg) {
var i;
var j;
var lastIndex = pkg.lastIndexOf(".");
var shorthand = (lastIndex < 0) ? pkg : pkg.substr(lastIndex + 1);
var matches;
if (this.imports.indexOf(pkg) >= 0) {
return false; //Class was already imported
}
if (shorthand == this.className && pkg == this.packageName) {
return true; //Don't need self
}
if (shorthand == this.parent) {
return true; //Parent class is in another package
}
//Now we must parse through all members one by one, looking at functions and variable types to determine the necessary imports
for (i in this.members) {
//See if the function definition or variable assigment have a need for this package
if (this.members[i] instanceof AS3Function) {
matches = this.members[i].value.match(AS3Pattern.VARIABLE_DECLARATION[1]);
for (j in matches) {
if (matches[j].split(":")[1] == shorthand)
return true;
}
for (j in this.members[i].argList) {
if (typeof this.members[i].argList[j].type == 'string' && this.members[i].argList[j].type == shorthand)
return true;
}
}
if (typeof this.members[i].value == 'string' && this.members[i].value.match(new RegExp("([^a-zA-Z_$.])" + shorthand + "([^0-9a-zA-Z_$])", "g"))) {
return true;
} else if (typeof this.members[i].type == 'string' && this.members[i].type == shorthand) {
return true;
}
}
for (i in this.staticMembers) {
//See if the function definition or variable assigment have a need for this package
if (this.staticMembers[i] instanceof AS3Function) {
matches = this.staticMembers[i].value.match(AS3Pattern.VARIABLE_DECLARATION[1]);
for (j in matches) {
if (matches[j].split(":")[1] == shorthand) {
return true;
}
}
for (j in this.staticMembers[i].argList) {
if (typeof this.staticMembers[i].argList[j].type == 'string' && this.staticMembers[i].argList[j].type == shorthand) {
return true;
}
}
}
if (typeof this.staticMembers[i].value == 'string' && this.staticMembers[i].value.match(new RegExp("([^a-zA-Z_$.])" + shorthand + "([^0-9a-zA-Z_$])", "g"))) {
return true;
} else if (typeof this.staticMembers[i].type == 'string' && this.staticMembers[i].type == shorthand) {
return true;
}
}
for (i in this.getters) {
//See if the function definition or variable assigment have a need for this package
matches = this.getters[i].value.match(AS3Pattern.VARIABLE_DECLARATION[1]);
for (j in matches) {
if (matches[j].split(":")[1] == shorthand) {
return true;
}
}
for (j in this.getters[i].argList) {
if (typeof this.getters[i].argList[j].type == 'string' && this.getters[i].argList[j].type == shorthand) {
return true;
}
}
if (typeof this.getters[i].value == 'string' && this.getters[i].value.match(new RegExp("([^a-zA-Z_$.])" + shorthand + "([^0-9a-zA-Z_$])", "g"))) {
return true;
} else if (typeof this.getters[i].type == 'string' && this.getters[i].type == shorthand) {
return true;
}
}
for (i in this.setters) {
matches = this.setters[i].value.match(AS3Pattern.VARIABLE_DECLARATION[1]);
for (j in matches) {
if (matches[j].split(":")[1] == shorthand) {
return true;
}
}
//See if the function definition or variable assigment have a need for this package
for (j in this.setters[i].argList) {
if (typeof this.setters[i].argList[j].type == 'string' && this.setters[i].argList[j].type == shorthand) {
return true;
}
}
if (typeof this.setters[i].value == 'string' && this.setters[i].value.match(new RegExp("([^a-zA-Z_$.])" + shorthand + "([^0-9a-zA-Z_$])", "g"))) {
return true;
} else if (typeof this.setters[i].type == 'string' && this.setters[i].type == shorthand) {
return true;
}
}
for (i in this.staticGetters) {
matches = this.staticGetters[i].value.match(AS3Pattern.VARIABLE_DECLARATION[1]);
for (j in matches) {
if (matches[j].split(":")[1] == shorthand) {
return true;
}
}
//See if the function definition or variable assigment have a need for this package
for (j in this.staticGetters[i].argList) {
if (typeof this.staticGetters[i].argList[j].type == 'string' && this.staticGetters[i].argList[j].type == shorthand) {
return true;
}
}
if (typeof this.staticGetters[i].value == 'string' && this.staticGetters[i].value.match(new RegExp("([^a-zA-Z_$.])" + shorthand + "([^0-9a-zA-Z_$])", "g"))) {
return true;
} else if (typeof this.staticGetters[i].type == 'string' && this.staticGetters[i].type == shorthand) {
return true;
}
}
for (i in this.staticSetters) {
matches = this.staticSetters[i].value.match(AS3Pattern.VARIABLE_DECLARATION[1]);
for (j in matches) {
if (matches[j].split(":")[1] == shorthand) {
return true;
}
}
for (j in this.staticSetters[i].argList) {
if (typeof this.staticSetters[i].argList[j].type == 'string' && this.staticSetters[i].argList[j].type == shorthand) {
return true;
}
}
//See if the function definition or variable assigment have a need for this package
if (typeof this.staticSetters[i].value == 'string' && this.staticSetters[i].value.match(new RegExp("([^a-zA-Z_$.])" + shorthand + "([^0-9a-zA-Z_$])", "g"))) {
return true;
} else if (typeof this.staticSetters[i].type == 'string' && this.staticSetters[i].type == shorthand) {
return true;
}
}
var classMember;
// Same logic as checkMembersWithAssignments()
// For each member that has an assignment at the top-level scope
for (i = 0; i < this.membersWithAssignments.length; i++) {
classMember = this.membersWithAssignments[i];
// Make a dumb attempt to identify use of the class as assignments here
if (classMember.value && classMember.value.indexOf(shorthand) >= 0 && !(this.parentDefinition && this.parentDefinition.packageName + "." + this.parentDefinition.className === pkg)) {
return true;
}
}
return false;
};
AS3Class.prototype.addImport = function(pkg) {
if (this.imports.indexOf(pkg) < 0) {
this.imports.push(pkg);
}
};
AS3Class.prototype.addExtraImport = function(pkg) {
if (this.importExtras.indexOf(pkg) < 0) {
this.importExtras.push(pkg);
}
};
AS3Class.prototype.findParents = function(classes) {
if (!this.parent) {
return;
}
for (var i in classes) {
//Only gather vars from the parent
if (classes[i] != this && this.parent == classes[i].className) {
this.parentDefinition = classes[i]; //Found our parent
return;
}
}
};
AS3Class.prototype.checkMembersWithAssignments = function() {
var i;
var j;
var classMember;
// If the type of this param is a Class
for (i = 0; i < this.membersWithAssignments.length; i++) {
classMember = this.membersWithAssignments[i];
// Make a dumb attempt to identify use of the class as assignments here
for (j in this.imports) {
if (this.packageMap[this.imports[j]] && classMember.value.indexOf(this.packageMap[this.imports[j]].className) >= 0 && this.parentDefinition !== this.packageMap[this.imports[j]]) {
// If this is a token that matches a class from an import statement, store it in the filtered classMap
this.classMapFiltered[this.packageMap[this.imports[j]].className] = this.packageMap[this.imports[j]];
}
}
}
};
AS3Class.prototype.stringifyFunc = function(fn) {
var buffer = "";
if (fn instanceof AS3Function) {
//Functions need to be handled differently
//Prepend sub-type if it exists
if (fn.subType) {
buffer += fn.subType + '_';
}
//Print out the rest of the name and start the function definition
buffer += fn.name
buffer += " = function(";
//Concat all of the arguments together
tmpArr = [];
for (j = 0; j < fn.argList.length; j++) {
if (!fn.argList[j].isRestParam) {
tmpArr.push(fn.argList[j].name);
}
}
buffer += tmpArr.join(", ") + ") ";
//Function definition is finally added
buffer += fn.value + ";\n";
} else if (fn instanceof AS3Variable) {
//Variables can be added immediately
buffer += fn.name;
buffer += " = " + fn.value + ";\n";
}
return buffer;
};
AS3Class.prototype.process = function(classes) {
var self = this;
var i;
var index;
var currParent = this;
var allMembers = [];
var allFuncs = [];
var allStaticMembers = [];
var allStaticFuncs = [];
while (currParent) {
//Parse members of this parent
for (i in currParent.setters) {
allMembers.push(currParent.setters[i]);
}
for (i in currParent.staticSetters) {
allStaticMembers.push(currParent.staticSetters[i]);
}
for (i in currParent.getters) {
allMembers.push(currParent.getters[i]);
}
for (i in currParent.staticGetters) {
allStaticMembers.push(currParent.staticGetters[i]);
}
for (i in currParent.members) {
allMembers.push(currParent.members[i]);
}
for (i in currParent.staticMembers) {
allStaticMembers.push(currParent.staticMembers[i]);
}
//Go to the next parent
currParent = currParent.parentDefinition;
}
//Add copies of the setters and getters to the "all" arrays (for convenience)
for (i in this.setters) {
if (this.setters[i] instanceof AS3Function) {
allFuncs.push(this.setters[i]);
}
}
for (i in this.staticSetters) {
if (this.staticSetters[i] instanceof AS3Function) {
allStaticFuncs.push(this.staticSetters[i]);
}
}
for (i in this.getters) {
if (this.getters[i] instanceof AS3Function) {
allFuncs.push(this.getters[i]);
}
}
for (i in this.staticGetters) {
if (this.staticGetters[i] instanceof AS3Function) {
allStaticFuncs.push(this.staticGetters[i]);
}
}
for (i in this.members) {
if (this.members[i] instanceof AS3Function) {
allFuncs.push(this.members[i]);
}
if (this.members[i] instanceof AS3Variable) {
// Fix any obvious assignments that rely on implicit static class name (only works for simple statements)
if (this.members[i].value && this.retrieveField(this.members[i].value.replace(/^([a-zA-Z_$][0-9a-zA-Z_$]*)(.*?)$/g, "$1"), true)) {
this.members[i].value = this.className + '.' + this.members[i].value;
}
}
}
for (i in this.staticMembers) {
if (this.staticMembers[i] instanceof AS3Function) {
allStaticFuncs.push(this.staticMembers[i]);
}
if (this.staticMembers[i] instanceof AS3Variable) {
// Fix any obvious assignments that rely on implicit static class name (only works for simple statements)
if (this.staticMembers[i].value && this.retrieveField(this.staticMembers[i].value.replace(/^([a-zA-Z_$][0-9a-zA-Z_$]*)(.*?)$/g, "$1"), true)) {
this.staticMembers[i].value = this.className + '.' + this.staticMembers[i].value;
}
}
}
// Insert $init function for instantiations
for (i in allFuncs) {
Main.debug("Now parsing function: " + this.className + ":" + allFuncs[i].name);
allFuncs[i].value = AS3Parser.parseFunc(this, allFuncs[i].value, allFuncs[i].buildLocalVariableStack(), allFuncs[i].isStatic)[0];
allFuncs[i].value = AS3Parser.checkArguments(allFuncs[i]);
if (allFuncs[i].name === "$init") {
//Inject instantiations here
allFuncs[i].value = AS3Parser.injectInstantiations(this, allFuncs[i]);
}
if (allFuncs[i].name === this.className) {
//Inject $init() into constructor
allFuncs[i].value = AS3Parser.injectInit(this, allFuncs[i]);
}
allFuncs[i].value = AS3Parser.cleanup(allFuncs[i].value);
//Fix supers
allFuncs[i].value = allFuncs[i].value.replace(/super\.(.*?)\(/g, this.parent + '.prototype.$1.call(this, ').replace(/\.call\(this,\s*\)/g, ".call(this)");
allFuncs[i].value = allFuncs[i].value.replace(/super\(/g, this.parent + '.call(this, ').replace(/\.call\(this,\s*\)/g, ".call(this)");
allFuncs[i].value = allFuncs[i].value.replace(new RegExp("this[.]" + this.parent, "g"), this.parent); //Fix extra 'this' on the parent
}
for (i in allStaticFuncs) {
Main.debug("Now parsing static function: " + this.className + ":" + allStaticFuncs[i].name);
allStaticFuncs[i].value = AS3Parser.parseFunc(this, allStaticFuncs[i].value, allStaticFuncs[i].buildLocalVariableStack(), allStaticFuncs[i].isStatic)[0];
allStaticFuncs[i].value = AS3Parser.checkArguments(allStaticFuncs[i]);
allStaticFuncs[i].value = AS3Parser.cleanup(allStaticFuncs[i].value);
}
};
AS3Class.prototype.toString = function() {
//Outputs the class inside a JS function
var i;
var j;
var buffer = "";
if (this.requires.length > 0) {
if (this.safeRequire) {
for (i in this.requires) {
buffer += 'var ' + this.requires[i].substring(1, this.requires[i].length - 1) + ' = (function () { try { return require(' + this.requires[i] + '); } catch(e) { return undefined; }})();\n';
}
} else {
for (i in this.requires) {
buffer += 'var ' + this.requires[i].substring(1, this.requires[i].length - 1) + ' = require(' + this.requires[i] + ');\n';
}
}
buffer += "\n";
}
var tmpArr = null;
//Parent class must be imported if it exists
if (this.parentDefinition) {
buffer += "var " + this.parentDefinition.className + " = module.import('" + this.parentDefinition.packageName + "', '" + this.parentDefinition.className + "');\n";
}
//Create refs for all the other classes
if (this.imports.length > 0) {
tmpArr = [];
for (i in this.imports) {
if (!(this.ignoreFlash && this.imports[i].indexOf('flash.') >= 0) && this.parent != this.imports[i].substr(this.imports[i].lastIndexOf('.') + 1) && this.packageName + '.' + this.className != this.imports[i]) //Ignore flash imports
{
// Must be in the filtered map, otherwise no point in writing
if (!this.packageMap[this.imports[i]]) {
Main.warn("Warning, missing class path: " + this.imports[i] + " (found in " + this.packageName + '.' + this.className + ")");
} else if (this.classMapFiltered[this.packageMap[this.imports[i]].className]) {
tmpArr.push(this.imports[i].substr(this.imports[i].lastIndexOf('.') + 1)); //<-This will return characters after the final '.', or the entire String if no '.'
}
}
}
//Join up separated by commas
if (tmpArr.length > 0) {
buffer += 'var ';
buffer += tmpArr.join(", ") + ";\n";
}
}
//Check for injection function code
var injectedText = "";
for (i in this.imports) {
if (!(this.ignoreFlash && this.imports[i].indexOf('flash.') >= 0) && this.packageName + '.' + this.className != this.imports[i] && !(this.parentDefinition && this.parentDefinition.packageName + '.' + this.parentDefinition.className == this.imports[i])) //Ignore flash imports and parent for injections
{
// Must be in the filtered map, otherwise no point in writing
if (!this.packageMap[this.imports[i]]) {
Main.warn("Warning, missing class path: " + this.imports[i] + " (found in " + this.packageName + '.' + this.className + ")");
} else if (this.classMapFiltered[this.packageMap[this.imports[i]].className]) {
injectedText += "\t" + this.imports[i].substr(this.imports[i].lastIndexOf('.') + 1) + " = module.import('" + this.packageMap[this.imports[i]].packageName + "', '" + this.packageMap[this.imports[i]].className + "');\n";
}
}
}
if (injectedText.length > 0) {
buffer += "module.inject = function () {\n";
buffer += injectedText;
buffer += "};\n";
}
buffer += '\n';
buffer += (this.fieldMap[this.className]) ? "var " + this.stringifyFunc(this.fieldMap[this.className]) : "var " + this.className + " = function " + this.className + "() {};";
buffer += '\n';
buffer += '\n';
if (this.parent) {
//Extend parent if necessary
buffer += this.className + ".prototype = Object.create(" + this.parent + ".prototype);";
}
buffer += '\n\n';
// Deal with static member assigments
if (this.staticMembers.length > 0) {
//Place defaults first
for (i in this.staticMembers) {
if (this.staticMembers[i] instanceof AS3Function) {
buffer += this.className + "." + this.stringifyFunc(this.staticMembers[i]);
} else if (this.staticMembers[i].type === "Number" || this.staticMembers[i].type === "int" || this.staticMembers[i].type === "uint") {
if (isNaN(parseInt(this.staticMembers[i].value))) {
buffer += this.className + "." + this.staticMembers[i].name + ' = 0;\n';
} else {
buffer += this.className + "." + this.stringifyFunc(this.staticMembers[i]);
}
} else if (this.staticMembers[i].type === "Boolean") {
buffer += this.className + "." + this.staticMembers[i].name + ' = false;\n';
} else {
buffer += this.className + "." + this.staticMembers[i].name + ' = null;\n';
}
}
for (i in this.staticGetters) {
buffer += this.className + "." + this.stringifyFunc(this.staticGetters[i]);
}
for (i in this.staticSetters) {
buffer += this.className + "." + this.stringifyFunc(this.staticSetters[i]);
}
buffer += '\n';
buffer += this.className + ".$cinit = function () {\n";
// Now do the assignments for the rest
for (i in this.staticMembers) {
if (!(this.staticMembers[i] instanceof AS3Function)) {
buffer += "\t" + AS3Parser.cleanup(this.className + '.' + this.staticMembers[i].name + ' = ' + this.staticMembers[i].value + ";\n");
}
}
buffer += '\n';
buffer += "};\n";
}
buffer += "\n";
for (i in this.getters) {
buffer += this.className + ".prototype." + this.stringifyFunc(this.getters[i]);
}
for (i in this.setters) {
buffer += this.className + ".prototype." + this.stringifyFunc(this.setters[i]);
}
for (i in this.members) {
if (this.members[i].name === this.className) {
continue;
}
if (this.members[i] instanceof AS3Function || (AS3Class.nativeTypes.indexOf(this.members[i].type) >= 0 && this.members[i].value)) {
buffer += this.className + ".prototype." + this.stringifyFunc(this.members[i]); //Print functions immediately
} else if (this.members[i].type === "Number" || this.members[i].type === "int" || this.members[i].type === "uint") {
if (isNaN(parseInt(this.members[i].value))) {
buffer += this.className + ".prototype." + this.members[i].name + ' = 0;\n';
} else {
buffer += this.className + ".prototype." + this.stringifyFunc(this.members[i]);
}
} else if (this.members[i].type === "Boolean") {
buffer += this.className + ".prototype." + this.members[i].name + ' = false;\n';
} else {
buffer += this.className + ".prototype." + this.members[i].name + ' = null;\n';
}
}
buffer = buffer.substr(0, buffer.length - 2) + "\n"; //Strips the final comma out of the string
buffer += "\n\n";
buffer += "module.exports = " + this.className + ";\n";
//Remaining fixes
buffer = buffer.replace(/(this\.)+/g, "this.");
return buffer;
}
module.exports = AS3Class;
};
Program["com.mcleodgaming.as3js.parser.AS3Parser"] = function(module, exports) {
var path = (function() {
try {
return require("path");
} catch (e) {
return undefined;
}
})();
var fs = (function() {
try {
return require("fs");
} catch (e) {
return undefined;
}
})();
var Main, AS3Class, AS3Token, AS3Encapsulation, AS3MemberType, AS3ParseState, AS3Pattern, AS3Argument, AS3Function, AS3Member, AS3Variable;
module.inject = function() {
Main = module.import('com.mcleodgaming.as3js', 'Main');
AS3Class = module.import('com.mcleodgaming.as3js.parser', 'AS3Class');
AS3Token = module.import('com.mcleodgaming.as3js.parser', 'AS3Token');
AS3Encapsulation = module.import('com.mcleodgaming.as3js.enums', 'AS3Encapsulation');
AS3MemberType = module.import('com.mcleodgaming.as3js.enums', 'AS3MemberType');
AS3ParseState = module.import('com.mcleodgaming.as3js.enums', 'AS3ParseState');
AS3Pattern = module.import('com.mcleodgaming.as3js.enums', 'AS3Pattern');
AS3Argument = module.import('com.mcleodgaming.as3js.types', 'AS3Argument');
AS3Function = module.import('com.mcleodgaming.as3js.types', 'AS3Function');
AS3Member = module.import('com.mcleodgaming.as3js.types', 'AS3Member');
AS3Variable = module.import('com.mcleodgaming.as3js.types', 'AS3Variable');
};
var AS3Parser = function(src, classPath) {
this.$init();
classPath = AS3JS.Utils.getDefaultValue(classPath, null);
//index = 0;
this.stack = [];
this.src = src;
this.classPath = classPath;
this.parserOptions = {};
this.parserOptions.safeRequire = false;
this.parserOptions.ignoreFlash = false;
};
AS3Parser.PREVIOUS_BLOCK = null;
AS3Parser.increaseIndent = function(str, indent) {
return (indent + str).replace(/\n/g, "\n" + indent);
};
AS3Parser.parseArguments = function(str) {
var args = [];
var tmpToken;
var tmpArr = AS3Parser.extractBlock(str, 0, '(', ')');
var tmpExtractArr = null;
var index = tmpArr[1] - 1; //Ending index of parsed block
var tmpStr = tmpArr[0].trim(); //Parsed block
tmpStr = tmpStr.substr(1, tmpStr.length - 2); //Remove outer parentheses
tmpArr = null; //Trash this
tmpArr = tmpStr.split(','); //Split args by commas
//Don't bother if there are no arguments
if (tmpArr.length > 0 && tmpArr[0] != '') {
//Truncate spaces and assign values to arguments as needed
for (var i = 0; i < tmpArr.length; i++) {
tmpStr = tmpArr[i].trim();
args.push(new AS3Argument());
if (tmpStr.indexOf('...') === 0) {
//This is a ...rest argument, stop here
args[args.length - 1].name = tmpStr.substr(3);
args[args.length - 1].isRestParam = true;
Main.debug('----->Parsed a ...rest param: ' + args[args.length - 1].name);
break;
} else {
//Grab the function name
tmpToken = AS3Parser.nextWord(tmpStr, 0, AS3Pattern.VARIABLE[0], AS3Pattern.VARIABLE[1]); //Parse out the function name
args[args.length - 1].name = tmpToken.token; //Set the argument name
Main.debug('----->Sub-Function argument found: ' + tmpToken.token);
//If a colon was next, we'll assume it was typed and grab it
if (tmpToken.index < tmpStr.length && tmpStr.charAt(tmpToken.index) == ':') {
tmpToken = AS3Parser.nextWord(tmpStr, tmpToken.index, AS3Pattern.VARIABLE_TYPE[0], AS3Pattern.VARIABLE_TYPE[1]); //Parse out the argument type
args[args.length - 1].type = tmpToken.token; //Set the argument type
Main.debug('----->Sub-Function argument typed to: ' + tmpToken.token);
}
tmpToken = AS3Parser.nextWord(tmpStr, tmpToken.index, AS3Pattern.ASSIGN_START[0], AS3Pattern.ASSIGN_START[1]);
if (tmpToken.token == "=") {
//Use all characters after self symbol to set value
tmpExtractArr = AS3Parser.extractUpTo(tmpStr, tmpToken.index, /[;\r\n]/g);
//Store value
args[args.length - 1].value = tmpExtractArr[0].trim();
//Store value
Main.debug('----->Sub-Function argument defaulted to: ' + tmpExtractArr[0].trim());
}
}
}
}
return args;
};
AS3Parser.checkForCommentOpen = function(str) {
return (str == "//") ? AS3ParseState.COMMENT_INLINE : (str == "/*") ? AS3ParseState.COMMENT_MULTILINE : null;
};
AS3Parser.checkForCommentClose = function(state, str) {
return (state == AS3ParseState.COMMENT_INLINE && (str.charAt(0) == '\n' || str.charAt(0) == '\r' || str.charAt(0) == '')) ? true : (state == AS3ParseState.COMMENT_MULTILINE && str == "*/") ? true : false;
};
AS3Parser.checkForStringOpen = function(str) {
return (str == '"') ? AS3ParseState.STRING_DOUBLE_QUOTE : (str == "'") ? AS3ParseState.STRING_SINGLE_QUOTE : null;
};
AS3Parser.checkForStringClose = function(state, str) {
return (state == AS3ParseState.STRING_DOUBLE_QUOTE && str == '"') ? true : (state == AS3ParseState.STRING_SINGLE_QUOTE && str == "'") ? true : false;
};
AS3Parser.nextWord = function(src, index, characters, pattern) {
characters = characters || AS3Pattern.IDENTIFIER[0];
pattern = pattern || AS3Pattern.IDENTIFIER[1];
var tokenBuffer = null;
var extraBuffer = ''; //Contains characters that were missed
var escapeToggle = false;
var innerState = null;
for (; index < src.length; index++) {
var c = src.charAt(index);
if (c.match(characters)) {
tokenBuffer = (tokenBuffer) ? tokenBuffer + c : c; //Create new token buffer if needed, otherwise append
} else if (!innerState && AS3Parser.checkForCommentOpen(src.substr(index, 2)) && !tokenBuffer) {
tokenBuffer = null;
Main.debug("Entering comment...");
innerState = AS3Parser.checkForCommentOpen(src.substr(index, 2));
extraBuffer += src.substr(index, 2);
index += 2; //Skip next index
//Loop until we break out of comment
for (; index < src.length; index++) {
if (AS3Parser.checkForCommentClose(innerState, src.substr(index, 2))) {
if (innerState == AS3ParseState.COMMENT_MULTILINE) {
extraBuffer += src.substr(index, 2);
index++; //Skip next token
} else {
extraBuffer += src.charAt(index);
}
innerState = null; //Return to previous state
Main.debug("Exiting comment...");
break;
} else {
extraBuffer += src.charAt(index);
}
}
} else if (!innerState && AS3Parser.checkForStringOpen(src.charAt(index)) && !tokenBuffer) {
tokenBuffer = null;
Main.debug("Entering string...");
innerState = AS3Parser.checkForStringOpen(src.charAt(index));
extraBuffer += src.substr(index, 1);
index++; //Skip to next index
//Loop until we break out of string
for (; index < src.length; index++) {
extraBuffer += src.charAt(index);
if (!escapeToggle && src.charAt(index) == '\\') {
escapeToggle = true;
continue;
}
escapeToggle = false;
if (AS3Parser.checkForStringClose(innerState, src.charAt(index))) {
innerState = null; //Return to previous state
Main.debug("Exiting string...");
break;
}
}
} else if (tokenBuffer && tokenBuffer.match(pattern)) {
return new AS3Token(tokenBuffer, index, extraBuffer); //[Token, Index]
} else {
if (tokenBuffer) {
extraBuffer += tokenBuffer + c;
} else {
extraBuffer += c;
}
tokenBuffer = null;
}
}
return new AS3Token(tokenBuffer || null, index, extraBuffer); //[Token, Index]
};
AS3Parser.extractBlock = function(text, start, opening, closing) {
start = AS3JS.Utils.getDefaultValue(start, 0);
opening = AS3JS.Utils.getDefaultValue(opening, "{");
closing = AS3JS.Utils.getDefaultValue(closing, "}");
var buffer = "";
var i = start;
var count = 0;
var started = false;
var insideString = null;
var insideComment = null;
var escapingChar = false;
while (!(count == 0 && started) && i < text.length) {
if (insideComment) {
//Inside of a comment, wait until we get out
if (insideComment == '//' && (text.charAt(i) == '\n' || text.charAt(i) == '\r')) {
insideComment = null; //End inline comment
Main.debug("Exited comment");
} else if (insideComment == '/*' && text.charAt(i) == '*' && i + 1 < text.length && text.charAt(i + 1) == '/') {
insideComment = null; //End multiline comment
Main.debug("Exited comment");
}
} else if (insideString) {
//Inside of a string, wait until we get out
if (!escapingChar && text.charAt(i) == "\\") {
escapingChar = true; //Start escape sequence
} else if (!escapingChar && text.charAt(i) == insideString) {
insideString = null; //Found closing quote
} else {
escapingChar = false; //Forget escape sequence
}
} else if (text.charAt(i) == opening) {
started = true;
count++; //Found opening
} else if (text.charAt(i) == closing) {
count--; //Found closing
} else if ((text.charAt(i) == '\"' || text.charAt(i) == '\'')) {
insideString = text.charAt(i); //Now inside of a string
} else if (text.charAt(i) == '/' && i + 1 < text.length && text.charAt(i + 1) == '/') {
Main.debug("Entering comment... " + "(//)");
insideComment = '//';
} else if (text.charAt(i) == '/' && i + 1 < text.length && text.charAt(i + 1) == '*') {
Main.debug("Entering comment..." + "(/*)");
insideComment = '/*';
}
if (started) {
buffer += text.charAt(i);
}
i++;
}
if (!started) {
throw new Error("Error, no starting '" + opening + "' found for method body while parsing " + AS3Parser.PREVIOUS_BLOCK);
} else if (count > 0) {
throw new Error("Error, no closing '" + closing + "' found for method body while parsing " + AS3Parser.PREVIOUS_BLOCK);
} else if (count < 0) {
throw new Error("Error, malformed enclosing '" + opening + closing + " body while parsing " + AS3Parser.PREVIOUS_BLOCK);
}
return [buffer, i];
};
AS3Parser.extractUpTo = function(text, start, target) {
var buffer = "";
var i = start;
var insideString = null;
var insideComment = null;
var escapingChar = false;
var pattern = new RegExp(target);
while (i < text.length) {
if (insideComment) {
//Inside of a comment, wait until we get out
if (insideComment == '//' && (text.charAt(i) == '\n' || text.charAt(i) == '\r')) {
insideComment = null; //End inline comment
Main.debug("Exited comment");
} else if (insideComment == '/*' && text.charAt(i) == '*' && i + 1 < text.length && text.charAt(i + 1) == '/') {
insideComment = null; //End multiline comment
Main.debug("Exited comment");
}
} else if (insideString) {
//Inside of a string, wait until we get out
if (!escapingChar && text.charAt(i) == "\\") {
escapingChar = true; //Start escape sequence
} else if (!escapingChar && text.charAt(i) == insideString) {
insideString = null; //Found closing quote
} else {
escapingChar = false; //Forget escape sequence
}
} else if ((text.charAt(i) == '\"' || text.charAt(i) == '\'')) {
insideString = text.charAt(i); //Now inside of a string
} else if (text.charAt(i) == '/' && i + 1 < text.length && text.charAt(i + 1) == '/') {
Main.debug("Entering comment... " + "(//)");
insideComment = '//';
} else if (text.charAt(i) == '/' && i + 1 < text.length && text.charAt(i + 1) == '*') {
Main.debug("Entering comment..." + "(/*)");
insideComment = '/*';
} else if (text.charAt(i).match(pattern)) {
break; //Done
}
buffer += text.charAt(i);
i++;
}
return [buffer, i];
};
AS3Parser.fixClassPath = function(clsPath) {
// Class paths at the root level might accidentally be prepended with a "."
return clsPath.replace(/^\./g, "");
};
AS3Parser.checkArguments = function(fn) {
if (fn.argList.length <= 0) {
return fn.value;
}
var start = fn.value.indexOf('{');
var args = "";
for (var i = 0; i < fn.argList.length; i++) {
//We will inject arguments into the top of the method definition
if (fn.argList[i].isRestParam) {
args += "\n\t\t\tvar " + fn.argList[i].name + " = Array.prototype.slice.call(arguments).splice(" + i + ");";
} else if (fn.argList[i].value) {
args += "\n\t\t\t" + fn.argList[i].name + " = AS3JS.Utils.getDefaultValue(" + fn.argList[i].name + ", " + fn.argList[i].value + ");";
}
}
return fn.value.substr(0, start + 1) + args + fn.value.substr(start + 1);
};
AS3Parser.injectInstantiations = function(cls, fn) {
var start = fn.value.indexOf('{');
var text = "";
for (var i = 0; i < cls.members.length; i++) {
//We will inject instantiated vars into the top of the method definition
if (cls.members[i] instanceof AS3Variable && AS3Class.nativeTypes.indexOf(cls.members[i].type) < 0) {
text += "\n\t\t\tthis." + cls.members[i].name + " = " + cls.members[i].value + ";";
}
}
return fn.value.substr(0, start + 1) + text + fn.value.substr(start + 1);
};
AS3Parser.injectInit = function(cls, fn) {
var start = fn.value.indexOf('{');
var text = "\n\t\t\tthis.$init();";
return fn.value.substr(0, start + 1) + text + fn.value.substr(start + 1);
};
AS3Parser.checkStack = function(stack, name) {
if (!name) {
return null;
}
for (var i = stack.length - 1; i >= 0; i--) {
if (stack[i].name == name) {
return stack[i];
}
}
return null;
};
AS3Parser.lookAhead = function(str, index) {
//Look ahead in the function for assignments
var originalIndex = index;
var startIndex = -1;
var endIndex = -1;
var semicolonIndex = -1;
var token = "";
var extracted = "";
//Not a setter if there is a dot operator immediately after
if (str.charAt(index) == '.') {
return {
token: null,
extracted: '',
startIndex: startIndex,
endIndex: endIndex
};
}
for (; index < str.length; index++) {
if (str.charAt(index).match(/[+-\/=*]/g)) {
//Append to the assignment instruction
token += str.charAt(index);
startIndex = index;
} else if (startIndex < 0 && str.charAt(index).match(/[\t\s]/g)) //Skip these characters
{
continue;
} else {
break; //Exits when token has already been started and no more regexes pass
}
}
//Only allow these patterns
if (!(token == "=" || token == "++" || token == "--" || token == "+=" || token == "-=" || token == "*=" || token == "/=")) {
token = null;
}
if (token) {
//Pick whatever is closer, new line or semicolon
endIndex = str.indexOf('\n', startIndex);
if (endIndex < 0) {
endIndex = str.length - 1;
}
//Windows fix
if (str.charAt(endIndex - 1) == '\r') {
endIndex--;
}
//We want to place closing parens before semicolon if it exists
semicolonIndex = str.indexOf(";", startIndex);
if (semicolonIndex < endIndex) {
endIndex = semicolonIndex;
}
extracted = str.substring(startIndex + token.length, endIndex);
}
return {
token: token,
extracted: extracted,
startIndex: startIndex,
endIndex: endIndex
};
};
AS3Parser.parseFunc = function(cls, fnText, stack, statFlag) {
statFlag = AS3JS.Utils.getDefaultValue(statFlag, false);
var i;
var j;
var index = 0;
var result = '';
var tmpStr = '';
var tmpArgs;
var tmpMember;
var tmpClass;
var tmpField;
var prevToken;
var currToken;
var tmpParse;
var tmpStatic = false;
var tmpPeek;
var objBuffer = ''; //Tracks the current object that is being "pathed" (e.g. "object.field1" or "object.field1[index + 1]", etc)
var justCreatedVar = false; //Keeps track if we just started a var statement (to help test if we're setting a type))
for (index = 0; index < fnText.length; index++) {
objBuffer = '';
prevToken = currToken;
currToken = AS3Parser.nextWord(fnText, index, AS3Pattern.VARIABLE[0], AS3Pattern.VARIABLE[1]);
result += currToken.extra; //<-Puts all other non-identifier characters into the buffer first
tmpMember = AS3Parser.checkStack(stack, currToken.token); //<-Check the stack for a member with this identifier already
index = currToken.index;
if (currToken.token) {
if (currToken.token == 'function') {
var t1 = AS3Parser.nextWord(fnText, index, AS3Pattern.VARIABLE[0], AS3Pattern.VARIABLE[1]);
var t2 = fnText.indexOf('(', index);
//If the parenthesis si less than the last index of the next parsed variable name
result += (t2 < t1.index) ? 'function' : 'function ' + t1.token;
tmpParse = AS3Parser.extractBlock(fnText, index, '(', ')'); //Parse out argument block
index = tmpParse[1]; //Update index
tmpArgs = AS3Parser.parseArguments(tmpParse[0]); //Extract arg types
//Join the args together without types
result += '(' + (function(args) {
var arr = [];
for (var i = 0; i < args.length; i++) {
if (args[i] === '...rest') {
break;
}
arr.push(args[i].name);
}
var str = arr.join(', ');
return str;
})(tmpArgs) + ')';
tmpParse = AS3Parser.extractBlock(fnText, index, '{', '}'); //Extract function block
index = tmpParse[1] - 1; //Update index
tmpParse = AS3Parser.parseFunc(cls, tmpParse[0], stack.concat(tmpArgs), statFlag); //Recurse into function
result += ' ' + tmpParse[0];
} else {
if (currToken.token == 'this') {
//No need to perform any extra checks on the subsequent token
tmpStatic = false;
tmpClass = cls;
objBuffer += currToken.token;
result += currToken.token;
} else {
if (cls.classMap[currToken.token] && cls.parentDefinition !== cls.classMap[currToken.token] && !(justCreatedVar && currToken.extra.match(/:\s*/g))) {
// If this is a token that matches a class from a potential import statement, store it in the filtered classMap
cls.classMapFiltered[currToken.token] = cls.classMap[currToken.token];
}
tmpStatic = (cls.className == currToken.token || cls.retrieveField(currToken.token, true) !== null);
//Find field in class, then make sure we didn't already have a local member defined with this name, and skip next block if static since the definition is the class itself
//Note: tmpMember needs to be checked, if something is in there it means we have a variable with the same name in local scope
if (cls.retrieveField(currToken.token, tmpStatic) && cls.className != currToken.token && !tmpMember && !(prevToken && prevToken.token === "var")) {
tmpMember = cls.retrieveField(currToken.token, tmpStatic); //<-Reconciles the type of the current variable
if (tmpMember && (tmpMember.subType == 'get' || tmpMember.subType == 'set')) {
tmpPeek = AS3Parser.lookAhead(fnText, index);
if (tmpPeek.token) {
//Handle differently if we are assigning a setter
//Prepend the correct term
if (tmpStatic) {
objBuffer += (cls.retrieveField(currToken.token, tmpStatic)) ? cls.className + '.' : currToken.token + '.';
result += (cls.retrieveField(currToken.token, tmpStatic)) ? cls.className + '.' : currToken.token + '.';
} else {
objBuffer += 'this.';
result += 'this.';
}
objBuffer += 'get_' + currToken.token + '()';
result += 'set_' + currToken.token + '(';
index = tmpPeek.endIndex;
if (tmpPeek.token == '++') {
result += objBuffer + ' + 1';
} else if (tmpPeek.token == '--') {
result += objBuffer + ' - 1';
} else {
tmpParse = AS3Parser.parseFunc(cls, tmpPeek.extracted, stack); //Recurse into the assignment to parse vars
if (tmpPeek.token == '=') {
result += tmpParse[0].trim();
} else {
result += objBuffer + ' ' + tmpPeek.token.charAt(0) + ' (' + tmpParse[0] + ')';
}
}
result += ')';
} else {
//Getters are easy
if (tmpStatic) {
objBuffer += (cls.retrieveField(currToken.token, true)) ? cls.className + '.get_' + currToken.token + '()' : 'this.get_' + currToken.token + '()';
result += (cls.retrieveField(currToken.token, true)) ? cls.className + '.get_' + currToken.token + '()' : 'this.get_' + currToken.token + '()';
} else {
objBuffer += 'this.get_' + currToken.token + '()';
result += 'this.get_' + currToken.token + '()';
}
}
} else {
if (tmpStatic) {
objBuffer += (cls.className == currToken.token) ? currToken.token : cls.className + '.' + currToken.token;
result += (cls.className == currToken.token) ? currToken.token : cls.className + '.' + currToken.token;
} else {
objBuffer += (cls.retrieveField(currToken.token, false) && !statFlag && !(prevToken && prevToken.token === 'new' && cls.retrieveField(currToken.token, false).type !== "Class")) ? 'this.' + currToken.token : currToken.token;
result += (cls.retrieveField(currToken.token, false) && !statFlag && !(prevToken && prevToken.token === 'new' && cls.retrieveField(currToken.token, false).type !== "Class")) ? 'this.' + currToken.token : currToken.token;
}
}
} else {
//Likely a local variable, argument, or static reference
if (tmpStatic) {
objBuffer += currToken.token;
result += currToken.token;
} else {
objBuffer += (cls.retrieveField(currToken.token, false) && !tmpMember && !(prevToken && prevToken.token === "var")) ? 'this.' + currToken.token : currToken.token;
result += (cls.retrieveField(currToken.token, false) && !tmpMember && !(prevToken && prevToken.token === "var")) ? 'this.' + currToken.token : currToken.token;
}
}
if (tmpStatic) {
//Just use the class itself, we will reference fields from it. If parser injected the static prefix manually, we'll try to determome the type of var instead
tmpClass = (cls.className == currToken.token) ? cls : (tmpMember) ? cls.classMap[tmpMember.type] || null : null;
} else {
//Use the member's type to determine the class it's mapped to
tmpClass = (tmpMember && tmpMember.type && tmpMember.type != '*') ? cls.classMap[tmpMember.type] : null;
//If no mapping was found, this may be a static reference
if (!tmpClass && cls.classMap[currToken.token]) {
tmpClass = cls.classMap[currToken.token];
tmpStatic = true;
}
}
//If tmpClass is null, it's possible we were trying to retrieve a Vector type. Let's fix this:
if (!tmpClass && tmpMember && tmpMember.type && tmpMember.type.replace(/Vector\.<(.*?)>/g, "$1") != tmpMember.type) {
//Extract Vector type if necessary by testing regex
tmpClass = cls.classMap[tmpMember.type.replace(/Vector\.<(.*?)>/g, "$1")] || null;
}
}
//Note: At this point, tmpMember is no longer used, it was only needed to remember the type of the first token. objBuffer will be building out the token
//If this had a variable declaration before it, we will add it to the local var stack and move on to the next token
if (prevToken && prevToken.token === "var") {
justCreatedVar = true;
if (cls.retrieveField(currToken.token, tmpStatic)) {
//Appends current character index to the result, add dummy var to stack, and move on
result += fnText.charAt(index);
var localVar = new AS3Member();
localVar.name = currToken.token;
stack.push(localVar); //<-Ensures we don't add "this." or anything in front of this variable anymore
continue;
}
} else {
justCreatedVar = false;
}
//We have parsed the current token, and the index sits at the next level down in the object
for (; index < fnText.length; index++) {
//Loop until we stop parsing a variable declaration
if (fnText.charAt(index) == '.') {
var parsingVector = (prevToken && prevToken.token === 'new' && currToken.token === 'Vector');
prevToken = currToken;
if (parsingVector) {
//We need to allow asterix
currToken = AS3Parser.nextWord(fnText, index, AS3Pattern.VARIABLE_TYPE[0], AS3Pattern.VARIABLE_TYPE[1]);
} else {
currToken = AS3Parser.nextWord(fnText, index, AS3Pattern.VARIABLE[0], AS3Pattern.VARIABLE[1]);
}
result += currToken.extra; //<-Puts all other non-identifier characters into the buffer first
index = currToken.index;
if (tmpClass) {
//This means we are coming from a typed variable
tmpField = tmpClass.retrieveField(currToken.token, tmpStatic);
if (tmpField) {
//console.log("parsing: " + tmpField.name + ":" + tmpField.type)
//We found a field that matched this value within the class
if (tmpField instanceof AS3Function) {
if (tmpField.subType == 'get' || tmpField.subType == 'set') {
tmpPeek = AS3Parser.lookAhead(fnText, index);
if (tmpPeek.token) {
//Handle differently if we are assigning a setter
objBuffer += '.get_' + currToken.token + '()';
result += 'set_' + currToken.token + '(';
index = tmpPeek.endIndex;
if (tmpPeek.token == '++') {
result += objBuffer + ' + 1';
} else if (tmpPeek.token == '--') {
result += objBuffer + ' - 1';
} else {
tmpParse = AS3Parser.parseFunc(cls, tmpPeek.extracted, stack); //Recurse into the assignment to parse vars
if (tmpPeek.token == '=') {
result += tmpParse[0].trim();
} else {
result += objBuffer + ' ' + tmpPeek.token.charAt(0) + ' (' + tmpParse[0] + ')';
}
}
result += ')';
} else {
objBuffer += '.get_' + currToken.token + '()';
result += 'get_' + currToken.token + "()";
}
//console.log("set get flag: " + currToken.token);
} else {
objBuffer += '.' + currToken.token;
result += currToken.token;
}
} else {
objBuffer += '.' + currToken.token;
result += currToken.token;
}
} else {
objBuffer += '.' + currToken.token;
result += currToken.token;
//console.log("appened typed: " + currToken.token);
}
//Update the type if this is not a static prop
if (tmpClass && tmpField && tmpField.type && tmpField.type != '*') {
//Extract Vector type if necessary by testing regex
tmpClass = (tmpField.type.replace(/Vector\.<(.*?)>/g, "$1") != tmpField.type) ? tmpClass.classMap[tmpField.type.replace(/Vector\.<(.*?)>/g, "$1")] || null : tmpClass.classMap[tmpField.type] || null;
} else {
tmpClass = null;
}
} else {
//console.log("appened untyped: " + currToken.token);
objBuffer += '.' + currToken.token;
result += currToken.token;
}
} else if (fnText.charAt(index) == '[') {
//We now have to recursively parse the inside of this open bracket
tmpParse = AS3Parser.extractBlock(fnText, index, '[', ']');
index = tmpParse[1];
tmpParse = AS3Parser.parseFunc(cls, tmpParse[0], stack); //Recurse into the portion that was extracted
//console.log("recursed into: " + tmpParse[0]);
objBuffer += tmpParse[0]; //Append this text to the object buffer string so we can remember the variable we have accessed
result += tmpParse[0];
}
tmpStatic = false; //Static can no longer be possible after the second field
if (!fnText.charAt(index).match(/[.\[]/g)) {
objBuffer = ''; //Clear out the current object buffer
index--;
break;
}
index--;
}
}
} else {
index = currToken.index - 1;
}
}
return [result, index];
};
AS3Parser.cleanup = function(text) {
var i;
var type;
var params;
var val;
var matches = text.match(AS3Pattern.VECTOR[0]);
//For each Vector.<>() found in the text
for (i in matches) {
//Strip the type and provided params
type = matches[i].replace(AS3Pattern.VECTOR[0], '$1').trim();
params = matches[i].replace(AS3Pattern.VECTOR[0], '$2').split(',');
//Set the default based on var type
if (type == 'int' || type == 'uint' || type == 'Number') {
val = "0";
} else if (type == 'Boolean') {
val = "false";
} else {
val = "null";
}
//Replace accordingly
if (params.length > 0 && params[0].trim() != '') {
text = text.replace(AS3Pattern.VECTOR[1], "AS3JS.Utils.createArray(" + params[0] + ", " + val + ")");
} else {
text = text.replace(AS3Pattern.VECTOR[1], "[]");
}
}
matches = text.match(AS3Pattern.ARRAY[0]);
//For each Array() found in the text
for (i in matches) {
//Strip the provided params
params = matches[i].replace(AS3Pattern.ARRAY[0], '$1').trim();
//Replace accordingly
if (params.length > 0 && params[0].trim() != '') {
text = text.replace(AS3Pattern.ARRAY[1], "AS3JS.Utils.createArray(" + params[0] + ", null)");
} else {
text = text.replace(AS3Pattern.ARRAY[1], "[]");
}
}
matches = text.match(AS3Pattern.DICTIONARY[0]);
//For each instantiated Dictionary found in the text
for (i in matches) {
// Replace with empty object
text = text.replace(AS3Pattern.DICTIONARY[0], "{}");
}
//Now cleanup variable types
text = text.replace(/([^0-9a-zA-Z_$.])(?:var|const)(\s*[a-zA-Z_$*][0-9a-zA-Z_$.<>]*)\s*:\s*([a-zA-Z_$*][0-9a-zA-Z_$.<>]*)/g, "$1var$2");
return text;
};
AS3Parser.$cinit = function() {
AS3Parser.PREVIOUS_BLOCK = null;
};
AS3Parser.prototype.$init = function() {
this.stack = null;
this.parserOptions = null;
};
AS3Parser.prototype.stack = null;
AS3Parser.prototype.src = null;
AS3Parser.prototype.classPath = null;
AS3Parser.prototype.parserOptions = null;
AS3Parser.prototype.getState = function() {
return (this.stack.length > 0) ? this.stack[this.stack.length - 1] : null;
};
AS3Parser.prototype.parseHelper = function(cls, src) {
var i;
var j;
var c;
var currToken = null;
var tmpToken = null;
var tmpStr = null;
var tmpArr = null;
var currMember = null;
var index;
for (index = 0; index < src.length; index++) {
c = src.charAt(index);
if (this.getState() == AS3ParseState.START) {
//String together letters only until we reach a non-letter
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index - 1; //Update to the new position
if (currToken.token == 'package') {
this.stack.push(AS3ParseState.PACKAGE_NAME);
}
} else if (this.getState() == AS3ParseState.PACKAGE_NAME) {
currToken = AS3Parser.nextWord(src, index, AS3Pattern.OBJECT[0], AS3Pattern.OBJECT[1]); //Package name
tmpToken = AS3Parser.nextWord(src, index, AS3Pattern.CURLY_BRACE[0], AS3Pattern.CURLY_BRACE[1]); //Upcoming curly brace
index = currToken.index - 1;
if (!currToken.token || !tmpToken.token) {
throw new Error("Error parsing package name.");
} else {
if (tmpToken.index < currToken.index) {
cls.packageName = ''; //Curly brace came before next token
index = tmpToken.index;
} else {
cls.packageName = currToken.token; //Just grab the package name
}
Main.debug('Found package: ' + cls.packageName);
cls.importWildcards.push(AS3Parser.fixClassPath(cls.packageName + '.*')); //Add wild card for its own folder
this.stack.push(AS3ParseState.PACKAGE);
Main.debug('Attempting to parse package...');
}
} else if (this.getState() == AS3ParseState.PACKAGE) {
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index - 1;
if (currToken.token == 'class' || currToken.token == 'interface') {
if (currToken.token == 'interface')
cls.isInterface = true;
this.stack.push(AS3ParseState.CLASS_NAME);
Main.debug('Found class keyword...');
} else if (currToken.token == 'import') {
this.stack.push(AS3ParseState.IMPORT_PACKAGE);
Main.debug('Found import keyword...');
} else if (currToken.token == 'require') {
this.stack.push(AS3ParseState.REQUIRE_MODULE);
Main.debug('Found require keyword...');
}
} else if (this.getState() == AS3ParseState.CLASS_NAME) {
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
tmpToken = AS3Parser.nextWord(src, index, AS3Pattern.CURLY_BRACE[0], AS3Pattern.CURLY_BRACE[1]);
index = currToken.index;
if (!currToken.token || !tmpToken.token) {
throw new Error("Error parsing class name.");
} else if (tmpToken.index < currToken.index) {
throw new Error("Error, no class name found before curly brace.");
} else {
//Set the class name
cls.className = currToken.token;
// Update fully qualified class path if needed
this.classPath = this.classPath || AS3Parser.fixClassPath(cls.packageName + '.' + cls.className); //Remove extra "." for top level packages
cls.classMap[cls.className] = cls; //Register self into the import map (used for static detection)
//Now we will check for parent class and any interfaces
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
if (currToken.token == 'extends' && currToken.index < tmpToken.index) {
index = currToken.index;
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index;
//The token following 'extends' must be the parent class
cls.parent = currToken.token;
//Prep the next token
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
Main.debug("Found parent: " + cls.parent);
}
if (currToken.token == 'implements' && currToken.index < tmpToken.index) {
index = currToken.index;
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index;
//The token following 'implements' must be an interface
cls.interfaces.push(currToken.token);
Main.debug("Found interface: " + currToken.token);
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
//While we are at a token before the next curly brace
while (currToken.index < tmpToken.index && currToken.index < src.length) {
//Consider self token another interface being implemented
index = currToken.index;
Main.debug("Found interface: " + currToken.token);
cls.interfaces.push(currToken.token);
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index;
}
}
Main.debug('Parsed class name: ' + cls.className);
//Now parsing inside of the class
this.stack.push(AS3ParseState.CLASS);
Main.debug('Attempting to parse class...');
//Extract out the next method block
AS3Parser.PREVIOUS_BLOCK = cls.className + ":Class";
tmpStr = AS3Parser.extractBlock(src, index)[0];
index += tmpStr.length - 1;
//Recursively call parseHelper again under this new state (Once returned, package will be exited)
this.parseHelper(cls, tmpStr);
}
} else if (this.getState() == AS3ParseState.CLASS) {
currMember = currMember || new AS3Member(); //Declare a new member to work with if it doesn't exist yet
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index - 1;
if (currToken.token == AS3Encapsulation.PUBLIC || currToken.token == AS3Encapsulation.PRIVATE || currToken.token == AS3Encapsulation.PROTECTED) {
currMember.encapsulation = currToken.token;
Main.debug('->Member encapsulation set to ' + currMember.encapsulation);
} else if (currToken.token == 'static') {
currMember.isStatic = true;
Main.debug('-->Static flag set');
} else if (currToken.token == AS3MemberType.VAR || currToken.token == AS3MemberType.CONST) {
Main.debug('--->Member type "variable" set.');
currMember = currMember.createVariable(); //Transform the member into a variable
this.stack.push(AS3ParseState.MEMBER_VARIABLE);
} else if (currToken.token == AS3MemberType.FUNCTION) {
currToken = AS3Parser.nextWord(src, index + 1, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
//Check for getter/setter
if ((currToken.token == 'get' || currToken.token == 'set') && src[index + 1 + currToken.token.length + 1] != '(') {
Main.debug('--->Member sub-type "' + currToken.token + '" set.');
currMember.subType = currToken.token;
index = currToken.index - 1;
}
currMember = currMember.createFunction(); //Transform the member into a function
this.stack.push(AS3ParseState.MEMBER_FUNCTION);
Main.debug('---->Member type "function" set.');
}
} else if (this.getState() == AS3ParseState.MEMBER_VARIABLE) {
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
currMember.name = currToken.token; //Set the member name
Main.debug('---->Variable name declared: ' + currToken.token);
index = currToken.index;
if (src.charAt(index) == ":") {
currToken = AS3Parser.nextWord(src, index, AS3Pattern.VARIABLE_TYPE[0], AS3Pattern.VARIABLE_TYPE[1]);
index = currToken.index;
currMember.type = currToken.token; //Set the value type name
Main.debug('---->Variable type for ' + currMember.name + ' declared as: ' + currToken.token);
}
currToken = AS3Parser.nextWord(src, index, AS3Pattern.ASSIGN_START[0], AS3Pattern.ASSIGN_START[1]);
if (currToken.token == "=") {
//Use all characters after self symbol to set value
index = currToken.index;
tmpArr = AS3Parser.extractUpTo(src, index, /[;\r\n]/g);
//Store value
currMember.value = tmpArr[0].trim();
index = tmpArr[1];
cls.membersWithAssignments.push(currMember);
}
//Store and delete current member and exit
if (currMember.isStatic) {
cls.staticMembers.push(currMember);
} else {
cls.members.push(currMember);
}
cls.registerField(currMember.name, currMember);
currMember = null;
this.stack.pop();
} else if (this.getState() == AS3ParseState.MEMBER_FUNCTION) {
//Parse the arguments
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index;
currMember.name = currToken.token; //Set the member name
Main.debug('****>Function name declared: ' + currToken.token);
AS3Parser.PREVIOUS_BLOCK = currMember.name + ":Function";
tmpArr = AS3Parser.extractBlock(src, index, '(', ')');
index = tmpArr[1] - 1; //Ending index of parsed block
tmpStr = tmpArr[0].trim(); //Parsed block
tmpStr = tmpStr.substr(1, tmpStr.length - 2); //Remove outer parentheses
tmpArr = null; //Trash this
tmpArr = tmpStr.split(','); //Split args by commas
//Don't bother if there are no arguments
if (tmpArr.length > 0 && tmpArr[0] != '') {
//Truncate spaces and assign values to arguments as needed
for (i = 0; i < tmpArr.length; i++) {
tmpStr = tmpArr[i];
//Grab the function name
tmpToken = AS3Parser.nextWord(tmpStr, 0, AS3Pattern.VARIABLE[0], AS3Pattern.VARIABLE[1]); //Parse out the function name
currMember.argList.push(new AS3Argument());
if (tmpStr.indexOf('...') === 0) {
//This is a ...rest argument, stop here
currMember.argList[currMember.argList.length - 1].name = tmpStr.substr(3);
currMember.argList[currMember.argList.length - 1].isRestParam = true;
Main.debug('----->Parsed a ...rest param: ' + currMember.argList[currMember.argList.length - 1].name);
break;
} else {
currMember.argList[currMember.argList.length - 1].name = tmpToken.token; //Set the argument name
Main.debug('----->Function argument found: ' + tmpToken.token);
//If a colon was next, we'll assume it was typed and grab it
if (tmpToken.index < tmpStr.length && tmpStr.charAt(tmpToken.index) == ':') {
tmpToken = AS3Parser.nextWord(tmpStr, tmpToken.index, AS3Pattern.VARIABLE_TYPE[0], AS3Pattern.VARIABLE_TYPE[1]); //Parse out the argument type
currMember.argList[currMember.argList.length - 1].type = tmpToken.token; //Set the argument type
Main.debug('----->Function argument typed to: ' + tmpToken.token);
}
tmpToken = AS3Parser.nextWord(tmpStr, tmpToken.index, AS3Pattern.ASSIGN_START[0], AS3Pattern.ASSIGN_START[1]);
if (tmpToken.token == "=") {
//Use all characters after self symbol to set value
tmpToken = AS3Parser.nextWord(tmpStr, tmpToken.index, AS3Pattern.ASSIGN_UPTO[0], AS3Pattern.ASSIGN_UPTO[1]);
if (!tmpToken) {
throw new Error("Error during variable assignment in arg" + currMember.argList[currMember.argList.length - 1].name);
}
//Store value
currMember.argList[currMember.argList.length - 1].value = tmpToken.token.trim();
Main.debug('----->Function argument defaulted to: ' + tmpToken.token.trim());
}
}
}
}
Main.debug('------>Completed paring args: ', currMember.argList);
//Type the function if needed
if (src.charAt(index + 1) == ":") {
tmpToken = AS3Parser.nextWord(src, index + 1, AS3Pattern.VARIABLE_TYPE[0], AS3Pattern.VARIABLE_TYPE[1]); //Parse out the function type if needed
index = tmpToken.index;
currMember.type = tmpToken.token;
Main.debug('------>Typed the function to: ', currMember.type);
}
if (cls.isInterface) {
//Store and delete current member and exit
currMember.value = '{}';
if (currMember.subType == 'get') {
(currMember.isStatic) ? cls.staticGetters.push(currMember): cls.getters.push(currMember);
} else if (currMember.subType == 'set') {
(currMember.isStatic) ? cls.staticSetters.push(currMember): cls.setters.push(currMember);
} else if (currMember.isStatic) {
cls.staticMembers.push(currMember);
} else {
cls.members.push(currMember);
}
cls.registerField(currMember.name, currMember);
//Done parsing function
currMember = null;
this.stack.pop();
} else {
//Save the function body
AS3Parser.PREVIOUS_BLOCK = currMember.name + ":Function";
tmpArr = AS3Parser.extractBlock(src, index);
index = tmpArr[1];
currMember.value = tmpArr[0].trim();
//Store and delete current member and exit
if (currMember.subType == 'get') {
(currMember.isStatic) ? cls.staticGetters.push(currMember): cls.getters.push(currMember);
} else if (currMember.subType == 'set') {
(currMember.isStatic) ? cls.staticSetters.push(currMember): cls.setters.push(currMember);
} else if (currMember.isStatic) {
cls.staticMembers.push(currMember);
} else {
cls.members.push(currMember);
}
cls.registerField(currMember.name, currMember);
currMember = null;
this.stack.pop();
}
} else if (this.getState() == AS3ParseState.LOCAL_VARIABLE) {
} else if (this.getState() == AS3ParseState.LOCAL_FUNCTION) {
} else if (this.getState() == AS3ParseState.IMPORT_PACKAGE) {
//The current token is a class import
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IMPORT[0], AS3Pattern.IMPORT[1]);
index = currToken.index - 1;
if (!currToken.token) {
throw new Error("Error parsing import.");
} else {
Main.debug("Parsed import name: " + currToken.token);
if (currToken.token.indexOf("*") >= 0) {
cls.importWildcards.push(currToken.token); //To be resolved later
} else {
cls.imports.push(currToken.token); //No need to resolve
}
this.stack.push(AS3ParseState.PACKAGE);
}
} else if (this.getState() == AS3ParseState.REQUIRE_MODULE) {
//The current token is a module requirement
currToken = AS3Parser.nextWord(src, index, AS3Pattern.REQUIRE[0], AS3Pattern.REQUIRE[1]);
index = currToken.index - 1;
if (!currToken.token)
throw new Error("Error parsing require.");
else {
Main.debug("Parsed require name: " + currToken.token);
cls.requires.push(currToken.token.trim());
this.stack.push(AS3ParseState.PACKAGE);
}
}
}
};
AS3Parser.prototype.parse = function(options) {
options = AS3JS.Utils.getDefaultValue(options, null);
options = options || {};
if (typeof options.safeRequire !== 'undefined') {
this.parserOptions.safeRequire = options.safeRequire;
}
if (typeof options.ignoreFlash !== 'undefined') {
this.parserOptions.ignoreFlash = options.ignoreFlash;
}
var classDefinition = new AS3Class(this.parserOptions);
this.stack.splice(0, this.stack.length);
this.stack.push(AS3ParseState.START);
this.parseHelper(classDefinition, this.src);
if (!classDefinition.className) {
throw new Error("Error, no class provided for package: " + this.classPath);
}
return classDefinition;
}
module.exports = AS3Parser;
};
Program["com.mcleodgaming.as3js.parser.AS3Token"] = function(module, exports) {
var AS3Token = function(token, index, extra) {
this.$init();
this.token = token;
this.index = index;
this.extra = extra;
};
AS3Token.prototype.$init = function() {};
AS3Token.prototype.token = null;
AS3Token.prototype.index = 0;
AS3Token.prototype.extra = null
module.exports = AS3Token;
};
Program["com.mcleodgaming.as3js.types.AS3Argument"] = function(module, exports) {
var AS3Variable = module.import('com.mcleodgaming.as3js.types', 'AS3Variable');
var AS3Argument = function() {
this.$init();
};
AS3Argument.prototype = Object.create(AS3Variable.prototype);
AS3Argument.prototype.$init = function() {};
AS3Argument.prototype.isRestParam = false
module.exports = AS3Argument;
};
Program["com.mcleodgaming.as3js.types.AS3Function"] = function(module, exports) {
var AS3Member = module.import('com.mcleodgaming.as3js.types', 'AS3Member');
var AS3Variable;
module.inject = function() {
AS3Variable = module.import('com.mcleodgaming.as3js.types', 'AS3Variable');
};
var AS3Function = function() {
this.$init();
this.argList = [];
};
AS3Function.prototype = Object.create(AS3Member.prototype);
AS3Function.prototype.$init = function() {
this.argList = null;
};
AS3Function.prototype.argList = null;
AS3Function.prototype.hasArgument = function() {
for (var i = 0; i < this.argList.length; i++)
if (this.argList[i].name == this.name)
return true;
return false;
};
AS3Function.prototype.buildLocalVariableStack = function() {
var i;
var text = this.value || '';
var matches = text.match(/(var|,)(.*?)([a-zA-Z_$][0-9a-zA-Z_$]*):([a-zA-Z_$][0-9a-zA-Z_$]*)/g);
var locals = [];
if (this.argList) {
for (i in this.argList) {
locals.push(this.argList[i]);
}
}
for (i in matches) {
var tmpVar = new AS3Variable();
tmpVar.name = matches[i].replace(/(var|,)(.*?)([a-zA-Z_$][0-9a-zA-Z_$]*):([a-zA-Z_$][0-9a-zA-Z_$]*)/g, "$3");
tmpVar.type = matches[i].replace(/(var|,)(.*?)([a-zA-Z_$][0-9a-zA-Z_$]*):([a-zA-Z_$][0-9a-zA-Z_$]*)/g, "$4");
locals.push(tmpVar);
}
return locals;
}
module.exports = AS3Function;
};
Program["com.mcleodgaming.as3js.types.AS3Member"] = function(module, exports) {
var AS3Function, AS3Variable;
module.inject = function() {
AS3Function = module.import('com.mcleodgaming.as3js.types', 'AS3Function');
AS3Variable = module.import('com.mcleodgaming.as3js.types', 'AS3Variable');
};
var AS3Member = function() {
this.$init();
this.name = null;
this.type = '*';
this.subType = null,
this.value = null;
this.encapsulation = "public";
this.isStatic = false;
};
AS3Member.prototype.$init = function() {};
AS3Member.prototype.name = null;
AS3Member.prototype.type = null;
AS3Member.prototype.subType = null;
AS3Member.prototype.value = null;
AS3Member.prototype.encapsulation = null;
AS3Member.prototype.isStatic = false;
AS3Member.prototype.createVariable = function() {
var obj = new AS3Variable();
obj.name = this.name;
obj.type = this.type;
obj.subType = this.subType,
obj.value = this.value;
obj.encapsulation = this.encapsulation;
obj.isStatic = this.isStatic;
return obj;
};
AS3Member.prototype.createFunction = function() {
var obj = new AS3Function();
obj.name = this.name;
obj.type = this.type;
obj.subType = this.subType,
obj.value = this.value;
obj.encapsulation = this.encapsulation;
obj.isStatic = this.isStatic;
return obj;
}
module.exports = AS3Member;
};
Program["com.mcleodgaming.as3js.types.AS3Variable"] = function(module, exports) {
var AS3Member = module.import('com.mcleodgaming.as3js.types', 'AS3Member');
var AS3Variable = function() {
this.$init();
};
AS3Variable.prototype = Object.create(AS3Member.prototype);
AS3Variable.prototype.$init = function() {}
module.exports = AS3Variable;
};
if (typeof module !== 'undefined') {
module.exports = AS3JS.load({
program: Program,
entry: "com.mcleodgaming.as3js.Main",
entryMode: "static"
});
} else if (typeof window !== 'undefined' && typeof AS3JS !== 'undefined') {
window['com.mcleodgaming.as3js.Main'] = AS3JS.load({
program: Program,
entry: "com.mcleodgaming.as3js.Main",
entryMode: "static"
});
}
})();
================================================
FILE: snippets/class-snippet.js
================================================
Program["{{module}}"] = function ( module, exports ) {
{{source}}
};
================================================
FILE: snippets/main-snippet.js
================================================
(function ( ) {
var Program = {};
{{packages}}
if (typeof module !== 'undefined') {
module.exports = AS3JS.load({ program: Program, entry: "{{entryPoint}}", entryMode: "{{entryMode}}" });
} else if (typeof window !== 'undefined' && typeof AS3JS !== 'undefined') {
window['{{entryPoint}}'] = AS3JS.load({ program: Program, entry: "{{entryPoint}}", entryMode: "{{entryMode}}" });
}
})();
================================================
FILE: src/com/mcleodgaming/as3js/Main.as
================================================
package com.mcleodgaming.as3js
{
import com.mcleodgaming.as3js.parser.AS3Class;
import com.mcleodgaming.as3js.parser.AS3Parser;
require "path"
require "fs"
public class Main
{
public static var DEBUG_MODE:Boolean = false;
public static var SILENT:Boolean = false;
public static function debug():void
{
if (Main.SILENT)
{
return;
}
if (Main.DEBUG_MODE)
{
console.log.apply(console, arguments);
}
}
public static function log():void
{
if (Main.SILENT)
{
return;
}
console.log.apply(console, arguments);
}
public static function warn():void
{
if (Main.SILENT)
{
return;
}
console.warn.apply(console, arguments);
}
public function Main()
{
}
public function compile(options:Object = null):Object
{
var packages:Object = { }; //Will contain the final map of package names to source text
var i:*;
var j:*;
var k:*;
var m:*;
var tmp:String;
options = options || {};
var srcPaths:Object = options.srcPaths || {};
var rawPackages:Array = options.rawPackages || [];
var parserOptions:Object = { safeRequire: options.safeRequire, ignoreFlash: options.ignoreFlash};
//Temp classes for holding raw class info
var rawClass:AS3Class;
var rawParser:AS3Parser;
var pkgLists:Object = {};
for (i in srcPaths)
{
pkgLists[srcPaths[i]] = buildPackageList(srcPaths[i]);
}
Main.DEBUG_MODE = options.verbose || Main.DEBUG_MODE;
Main.SILENT = options.silent || Main.SILENT;
var classes:Object = {};
var buffer:String = "";
//First, parse through the file-based classes and get the basic information
for (i in pkgLists)
{
for (j in pkgLists[i])
{
Main.log('Analyzing class path: ' + pkgLists[i][j].classPath);
classes[pkgLists[i][j].classPath] = pkgLists[i][j].parse(parserOptions);
Main.debug(classes[pkgLists[i][j].classPath]);
}
}
// Now parse through any raw string classes
for (i = 0; i < rawPackages.length; i++)
{
Main.log('Analyzing class: ' + i);
rawParser = new AS3Parser(rawPackages[i]);
rawClass = rawParser.parse(parserOptions);
classes[rawParser.classPath] = rawClass;
}
//Resolve all possible package name wildcards
for (i in classes)
{
//For every class
for (j in classes[i].importWildcards)
{
Main.debug('Resolving ' + classes[i].className + '\'s ' + classes[i].importWildcards[j] + ' ...')
//For every wild card in the class
for (k in srcPaths)
{
//For each possible source path (should hopefully just be 1 most of the time -_-)
tmp = srcPaths[k] + path.sep + classes[i].importWildcards[j].replace(/\./g, path.sep).replace(path.sep+'*', '');
tmp = tmp.replace(/\\/g, '/');
tmp = tmp.replace(/[\/]/g, path.sep);
if(fs.existsSync(tmp)) {
Main.debug('Searching path ' + tmp + '...')
//Path exists, read the files in the directory
var files = fs.readdirSync(tmp);
for(m in files) {
//See if this is an ActionScript file
if(fs.statSync(tmp + path.sep + files[m]).isFile() && files[m].lastIndexOf('.as') == files[m].length - 3) {
//See if the class needs the file
if(classes[i].needsImport(classes[i].importWildcards[j].replace(/\*/g, files[m].substr(0, files[m].length - 3)))) {
Main.debug('Auto imported ' + files[m].substr(0, files[m].length - 3));
classes[i].addImport(classes[i].importWildcards[j].replace(/\*/g, files[m].substr(0, files[m].length - 3))); //Pass in package name with wild card replaced
}
}
}
} else {
Main.warn('Warning, could not find directory: ' + tmp);
}
}
// Must do again for classes in case there we
for (k in classes)
{
if(classes[i].needsImport(AS3Parser.fixClassPath(classes[k].packageName + '.' + classes[k].className))) {
Main.debug('Auto imported ' + AS3Parser.fixClassPath(classes[k].packageName + '.' + classes[k].className));
classes[i].addImport(AS3Parser.fixClassPath(classes[k].packageName + '.' + classes[k].className)); //Pass in package name with wild card replaced
}
}
}
}
//Add extra imports before registring them (these will not be imported in the output code, but rather will provide insight for AS3JS to determine variable types)
for (i in classes)
{
for (j in classes)
{
classes[i].addExtraImport(AS3Parser.fixClassPath(classes[j].packageName + '.' + classes[j].className));
}
}
//Resolve import map
for (i in classes)
{
classes[i].registerImports(classes);
}
//Resolve parent imports
for (i in classes)
{
classes[i].findParents(classes);
}
//Walk through the class members that had assignments in the class scope
for (i in classes)
{
classes[i].checkMembersWithAssignments();
}
//Process the function text to comply with JS
for (i in classes)
{
Main.log('Parsing package: ' + AS3Parser.fixClassPath(classes[i].packageName + "." + classes[i].className));
classes[i].process(classes);
}
// Load stringified versions of snippets/main-snippet.js and snippets/class-snippet.js
var mainTemplate:String = "(function(){var Program={};{{packages}}if(typeof module !== 'undefined'){module.exports=AS3JS.load({program:Program,entry:\"{{entryPoint}}\",entryMode:\"{{entryMode}}\"});}else if(typeof window!=='undefined'&&typeof AS3JS!=='undefined'){window['{{entryPoint}}']=AS3JS.load({program:Program,entry:\"{{entryPoint}}\",entryMode:\"{{entryMode}}\"});}})();";
var classTemplate:String = "Program[\"{{module}}\"]=function(module, exports){{{source}}};";
var packageObjects:Array = [];
var classObjects:Array = null;
var currentClass:String = "";
if (options.entry)
{
// Entry point should be in the format "mode:path.to.package.Class"
var currentPackage:String = options.entry;
var mode:String = options.entryMode || 'instance';
// Update template with entry points
mainTemplate = mainTemplate.replace(/\{\{entryPoint\}\}/g, AS3Parser.fixClassPath(classes[currentPackage].packageName + '.' + classes[currentPackage].className));
mainTemplate = mainTemplate.replace(/\{\{entryMode\}\}/g, mode);
} else
{
mainTemplate = mainTemplate.replace(/\{\{entryPoint\}\}/g, "");
mainTemplate = mainTemplate.replace(/\{\{entryMode\}\}/g, "");
}
//Retrieve converted class code
var groupByPackage:Object = { };
for (i in classes)
{
groupByPackage[classes[i].packageName] = groupByPackage[classes[i].packageName] || [];
groupByPackage[classes[i].packageName].push(classes[i]);
}
for (i in groupByPackage)
{
classObjects = [];
for (j in groupByPackage[i])
{
packages[AS3Parser.fixClassPath(i+"."+groupByPackage[i][j].className)] = groupByPackage[i][j].toString();
currentClass = classTemplate;
currentClass = currentClass.replace(/\{\{module\}\}/g, AS3Parser.fixClassPath(groupByPackage[i][j].packageName + "." + groupByPackage[i][j].className));
currentClass = currentClass.replace(/\{\{source\}\}/g, AS3Parser.increaseIndent(packages[AS3Parser.fixClassPath(i+"."+groupByPackage[i][j].className)], " "));
classObjects.push(currentClass);
}
packageObjects.push(AS3Parser.increaseIndent(classObjects.join(""), " "));
}
mainTemplate = mainTemplate.replace(/\{\{packages\}\}/g, packageObjects.join(""));
mainTemplate = mainTemplate.replace(/\t/g, " ");
buffer += mainTemplate;
Main.log("Done.");
return { compiledSource: buffer, packageSources: packages };
}
private function readDirectory(location:String, pkgBuffer:String, obj:Object):void
{
var files:Array = fs.readdirSync(location);
for (var i in files)
{
var pkg:String = pkgBuffer;
if (fs.statSync(location + path.sep + files[i]).isDirectory())
{
var splitPath:Array = location.split(path.sep);
if (pkg != '')
{
pkg += '.';
}
this.readDirectory(location + path.sep + files[i], pkg + files[i], obj)
} else if (fs.statSync(location + path.sep + files[i]).isFile() && files[i].lastIndexOf('.as') == files[i].length - 3)
{
if (pkg != '')
{
pkg += '.';
}
pkg += files[i].substr(0, files[i].length - 3);
var f = fs.readFileSync(location + path.sep + files[i]);
obj[pkg] = new AS3Parser(f.toString(), pkg);
Main.debug("Loaded file: ", location + path.sep + files[i] + " (package: " + pkg + ")");
}
}
}
public function buildPackageList(location:String):void
{
var obj:Object = {};
var topLevel:String = location;
location = location.replace(/\\/g, '/');
location = location.replace(/[\/]/g, path.sep);
if (fs.existsSync(location) && fs.statSync(location).isDirectory())
{
var splitPath = location.split(path.sep);
readDirectory(location, '', obj);
return obj;
} else
{
throw new Error("Error could not find directory: " + location);
}
}
}
}
================================================
FILE: src/com/mcleodgaming/as3js/enums/AS3Encapsulation.as
================================================
package com.mcleodgaming.as3js.enums
{
public class AS3Encapsulation
{
public static const PUBLIC:String = "public";
public static const PRIVATE:String = "private";
public static const PROTECTED:String = "protected";
}
}
================================================
FILE: src/com/mcleodgaming/as3js/enums/AS3MemberType.as
================================================
package com.mcleodgaming.as3js.enums
{
public class AS3MemberType
{
public static const VAR:String = "var";
public static const CONST:String = "const";
public static const FUNCTION:String = "function";
}
}
================================================
FILE: src/com/mcleodgaming/as3js/enums/AS3ParseState.as
================================================
package com.mcleodgaming.as3js.enums
{
public class AS3ParseState
{
public static const START:String = "start";
public static const PACKAGE_NAME:String = "packageName";
public static const PACKAGE:String = "package";
public static const CLASS_NAME:String = "className";
public static const CLASS:String = "class";
public static const CLASS_EXTENDS:String = "classExtends";
public static const CLASS_IMPLEMENTS:String = "classImplements";
public static const COMMENT_INLINE:String = "commentInline";
public static const COMMENT_MULTILINE:String = "commentMultiline";
public static const STRING_SINGLE_QUOTE:String = "stringSingleQuote";
public static const STRING_DOUBLE_QUOTE:String = "stringDoubleQuote";
public static const STRING_REGEX:String = "stringRegex";
public static const MEMBER_VARIABLE:String = "memberVariable";
public static const MEMBER_FUNCTION:String = "memberFunction";
public static const LOCAL_VARIABLE:String = "localVariable";
public static const LOCAL_FUNCTION:String = "localFunction";
public static const IMPORT_PACKAGE:String = "importPackage";
public static const REQUIRE_MODULE:String = "requireModule";
}
}
================================================
FILE: src/com/mcleodgaming/as3js/enums/AS3Pattern.as
================================================
package com.mcleodgaming.as3js.enums
{
public class AS3Pattern
{
public static const IDENTIFIER:Array = [ /\w/g, /\w/g ];
public static const OBJECT:Array = [ /[\w\.]/g, /[\w(\w(\.\w)+)]/g ];
public static const IMPORT:Array = [ /[0-9a-zA-Z_$.*]/g, /[a-zA-Z_$][0-9a-zA-Z_$]([.][a-zA-Z_$][0-9a-zA-Z_$])*\*?/g ];
public static const REQUIRE:Array = [ /./g, /["'](.*?)['"]/g ];
public static const CURLY_BRACE:Array = [ /[\{|\}]/g, /[\{|\}]/g ];
public static const VARIABLE:Array = [ /[0-9a-zA-Z_$]/g, /[a-zA-Z_$][0-9a-zA-Z_$]*/g ];
public static const VARIABLE_TYPE:Array = [ /[a-zA-Z_$<>.*][0-9a-zA-Z_$<>.]*/g, /[a-zA-Z_$<>.*][0-9a-zA-Z_$<>.]*/g ];
public static const VARIABLE_DECLARATION:Array = [ /[0-9a-zA-Z_$:<>.*]/g, /[a-zA-Z_$][0-9a-zA-Z_$]*\s*:\s*([a-zA-Z_$<>\.\*][0-9a-zA-Z_$<>\.]*)/g ];
public static const ASSIGN_START:Array = [ /[=\r\n]/g, /[=\r\n]/g ];
public static const ASSIGN_UPTO:Array = [ new RegExp("[^;\\r\\n]", "g"), /(.*?)/g ];
public static const VECTOR:Array = [ /new[\s\t]+Vector\.<(.*?)>\((.*?)\)/g, /new[\s\t]+Vector\.<(.*?)>\((.*?)\)/ ];
public static const ARRAY:Array = [ /new[\s\t]+Array\((.*?)\)/g, /new[\s\t]+Array\((.*?)\)/ ];
public static const DICTIONARY:Array = [ /new[\s\t]+Dictionary\((.*?)\)/g ];
public static const REST_ARG:Array = [ /\.\.\.[a-zA-Z_$][0-9a-zA-Z_$]*/g, /\.\.\.[a-zA-Z_$][0-9a-zA-Z_$]*/g];
}
}
================================================
FILE: src/com/mcleodgaming/as3js/parser/AS3Class.as
================================================
package com.mcleodgaming.as3js.parser
{
import com.mcleodgaming.as3js.Main;
import com.mcleodgaming.as3js.enums.*;
import com.mcleodgaming.as3js.types.*;
public class AS3Class
{
public static var reservedWords:Array = ["as", "class", "delete", "false", "if", "instanceof", "native", "private", "super", "to", "use", "with", "break", "const", "do", "finally", "implements", "new", "protected", "switch", "true", "var", "case", "continue", "else", "for", "import", "internal", "null", "public", "this", "try", "void", "catch", "default", "extends", "function", "in", "is", "package", "return", "throw", "typeof", "while", "each", "get", "set", "namespace", "include", "dynamic", "final", "natiev", "override", "static", "abstract", "char", "export", "long", "throws", "virtual", "boolean", "debugger", "float", "prototype", "to", "volatile", "byte", "double", "goto", "short", "transient", "cast", "enum", "intrinsic", "synchronized", "type"];
public static var nativeTypes:Array = ["Boolean", "Number", "int", "uint", "String" ];
public var packageName:String;
public var className:String;
public var imports:Vector.;
public var requires:Vector.;
public var importWildcards:Vector.;
public var importExtras:Vector.;
public var interfaces:Vector.;
public var parent:String;
public var parentDefinition:AS3Class;
public var members:Vector.;
public var staticMembers:Vector.;
public var getters:Vector.;
public var setters:Vector.;
public var staticGetters:Vector.;
public var staticSetters:Vector.;
public var isInterface:Boolean;
public var membersWithAssignments:Vector.; //List of class members that have assignments in the class-level scope
public var fieldMap:Object; //Maps instance field names to instance members
public var staticFieldMap:Object; //Maps static field names to static members
public var classMap:Object; //Maps class shorthand name to class
public var classMapFiltered:Object; //Same as classMap but only references classes that are actually used (i.e. used in another way besides just a "type")
public var packageMap:Object; //Maps full package path to class
// Options
public var safeRequire:Boolean; //Try catch around parsed require statements
public var ignoreFlash:Boolean; //Ignore FL imports
public function AS3Class(options:Object = null)
{
options = options || { };
safeRequire = false;
if (typeof options.safeRequire !== 'undefined')
{
safeRequire = options.safeRequire;
}
if (typeof options.ignoreFlash !== 'undefined')
{
ignoreFlash = options.ignoreFlash;
}
packageName = null;
className = null;
imports = new Vector.();
requires = new Vector.();
importWildcards = new Vector.();
importExtras = new Vector.();
interfaces = new Vector.();
parent = null;
parentDefinition = null;
members = new Vector.();
staticMembers = new Vector.();
getters = new Vector.();
setters = new Vector.();
staticGetters = new Vector.();
staticSetters = new Vector.();
membersWithAssignments = new Vector.();
isInterface = false;
fieldMap = {};
staticFieldMap = {};
classMap = { };
classMapFiltered = { };
packageMap = { };
var $init:AS3Function = new AS3Function();
$init.name = "$init";
$init.value = "{}";
$init.type === AS3MemberType.FUNCTION;
$init.isStatic = false;
members.push($init);
registerField($init.name, $init);
}
public function registerImports(clsList:Object):void
{
var i;
for (i in imports)
{
if (clsList[imports[i]])
{
var lastIndex:int = imports[i].lastIndexOf(".");
var shorthand:String = (lastIndex < 0) ? imports[i] : imports[i].substr(lastIndex + 1);
classMap[shorthand] = clsList[imports[i]];
}
}
for (i in importExtras)
{
if (clsList[importExtras[i]])
{
var lastIndex:int = importExtras[i].lastIndexOf(".");
var shorthand:String = (lastIndex < 0) ? importExtras[i] : importExtras[i].substr(lastIndex + 1);
classMap[shorthand] = clsList[importExtras[i]];
}
}
packageMap = clsList;
}
public function registerField(name:String, value:AS3Member):void
{
if (value && value.isStatic)
{
staticFieldMap[name] = staticFieldMap[name] || value;
} else
{
fieldMap[name] = fieldMap[name] || value;
}
}
public function retrieveField(name:String, isStatic:Boolean):AS3Member
{
if (isStatic)
{
if (staticFieldMap[name])
{
return staticFieldMap[name];
} else if (parentDefinition)
{
return parentDefinition.retrieveField(name, isStatic);
} else
{
return null;
}
} else
{
if (fieldMap[name])
{
return fieldMap[name];
} else if (parentDefinition)
{
return parentDefinition.retrieveField(name, isStatic);
} else
{
return null;
}
}
}
public function needsImport(pkg:String):Boolean
{
var i:*;
var j:*;
var lastIndex:int = pkg.lastIndexOf(".");
var shorthand:String = (lastIndex < 0) ? pkg : pkg.substr(lastIndex + 1);
var matches:Vector.;
if (imports.indexOf(pkg) >= 0)
{
return false; //Class was already imported
}
if (shorthand == className && pkg == packageName)
{
return true; //Don't need self
}
if (shorthand == parent)
{
return true; //Parent class is in another package
}
//Now we must parse through all members one by one, looking at functions and variable types to determine the necessary imports
for (i in members)
{
//See if the function definition or variable assigment have a need for this package
if (members[i] instanceof AS3Function)
{
matches = members[i].value.match(AS3Pattern.VARIABLE_DECLARATION[1]);
for (j in matches)
{
if(matches[j].split(":")[1] == shorthand)
return true;
}
for (j in members[i].argList)
{
if(typeof members[i].argList[j].type == 'string' && members[i].argList[j].type == shorthand)
return true;
}
}
if (typeof members[i].value == 'string' && members[i].value.match(new RegExp("([^a-zA-Z_$.])" + shorthand + "([^0-9a-zA-Z_$])", "g")))
{
return true;
} else if (typeof members[i].type == 'string' && members[i].type == shorthand)
{
return true;
}
}
for (i in staticMembers)
{
//See if the function definition or variable assigment have a need for this package
if (staticMembers[i] instanceof AS3Function)
{
matches = staticMembers[i].value.match(AS3Pattern.VARIABLE_DECLARATION[1]);
for (j in matches)
{
if (matches[j].split(":")[1] == shorthand)
{
return true;
}
}
for (j in staticMembers[i].argList)
{
if (typeof staticMembers[i].argList[j].type == 'string' && staticMembers[i].argList[j].type == shorthand)
{
return true;
}
}
}
if (typeof staticMembers[i].value == 'string' && staticMembers[i].value.match(new RegExp("([^a-zA-Z_$.])" + shorthand + "([^0-9a-zA-Z_$])", "g")))
{
return true;
} else if (typeof staticMembers[i].type == 'string' && staticMembers[i].type == shorthand)
{
return true;
}
}
for (i in getters)
{
//See if the function definition or variable assigment have a need for this package
matches = getters[i].value.match(AS3Pattern.VARIABLE_DECLARATION[1]);
for (j in matches)
{
if (matches[j].split(":")[1] == shorthand)
{
return true;
}
}
for (j in getters[i].argList)
{
if (typeof getters[i].argList[j].type == 'string' && getters[i].argList[j].type == shorthand)
{
return true;
}
}
if (typeof getters[i].value == 'string' && getters[i].value.match(new RegExp("([^a-zA-Z_$.])" + shorthand + "([^0-9a-zA-Z_$])", "g")))
{
return true;
} else if (typeof getters[i].type == 'string' && getters[i].type == shorthand)
{
return true;
}
}
for (i in setters)
{
matches = setters[i].value.match(AS3Pattern.VARIABLE_DECLARATION[1]);
for (j in matches)
{
if (matches[j].split(":")[1] == shorthand)
{
return true;
}
}
//See if the function definition or variable assigment have a need for this package
for (j in setters[i].argList)
{
if (typeof setters[i].argList[j].type == 'string' && setters[i].argList[j].type == shorthand)
{
return true;
}
}
if (typeof setters[i].value == 'string' && setters[i].value.match(new RegExp("([^a-zA-Z_$.])" + shorthand + "([^0-9a-zA-Z_$])", "g")))
{
return true;
} else if (typeof setters[i].type == 'string' && setters[i].type == shorthand)
{
return true;
}
}
for (i in staticGetters)
{
matches = staticGetters[i].value.match(AS3Pattern.VARIABLE_DECLARATION[1]);
for (j in matches)
{
if (matches[j].split(":")[1] == shorthand)
{
return true;
}
}
//See if the function definition or variable assigment have a need for this package
for (j in staticGetters[i].argList)
{
if (typeof staticGetters[i].argList[j].type == 'string' && staticGetters[i].argList[j].type == shorthand)
{
return true;
}
}
if (typeof staticGetters[i].value == 'string' && staticGetters[i].value.match(new RegExp("([^a-zA-Z_$.])" + shorthand + "([^0-9a-zA-Z_$])", "g")))
{
return true;
} else if (typeof staticGetters[i].type == 'string' && staticGetters[i].type == shorthand)
{
return true;
}
}
for (i in staticSetters)
{
matches = staticSetters[i].value.match(AS3Pattern.VARIABLE_DECLARATION[1]);
for (j in matches)
{
if (matches[j].split(":")[1] == shorthand)
{
return true;
}
}
for (j in staticSetters[i].argList)
{
if (typeof staticSetters[i].argList[j].type == 'string' && staticSetters[i].argList[j].type == shorthand)
{
return true;
}
}
//See if the function definition or variable assigment have a need for this package
if (typeof staticSetters[i].value == 'string' && staticSetters[i].value.match(new RegExp("([^a-zA-Z_$.])" + shorthand + "([^0-9a-zA-Z_$])", "g")))
{
return true;
} else if (typeof staticSetters[i].type == 'string' && staticSetters[i].type == shorthand)
{
return true;
}
}
var classMember:AS3Member;
// Same logic as checkMembersWithAssignments()
// For each member that has an assignment at the top-level scope
for (i = 0; i < membersWithAssignments.length; i++)
{
classMember = membersWithAssignments[i];
// Make a dumb attempt to identify use of the class as assignments here
if (classMember.value && classMember.value.indexOf(shorthand) >= 0 && !(parentDefinition && parentDefinition.packageName + "." + parentDefinition.className === pkg))
{
return true;
}
}
return false;
}
public function addImport(pkg:String):void
{
if (imports.indexOf(pkg) < 0)
{
imports.push(pkg);
}
}
public function addExtraImport(pkg:String):void
{
if (importExtras.indexOf(pkg) < 0)
{
importExtras.push(pkg);
}
}
public function findParents(classes:Vector.):void
{
if (!parent)
{
return;
}
for (var i in classes)
{
//Only gather vars from the parent
if (classes[i] != this && parent == classes[i].className)
{
parentDefinition = classes[i]; //Found our parent
return;
}
}
}
public function checkMembersWithAssignments():void
{
var i:int;
var j:*;
var classMember:AS3Member;
// If the type of this param is a Class
for (i = 0; i < membersWithAssignments.length; i++)
{
classMember = membersWithAssignments[i];
// Make a dumb attempt to identify use of the class as assignments here
for (j in imports)
{
if (packageMap[imports[j]] && classMember.value.indexOf(packageMap[imports[j]].className) >= 0 && parentDefinition !== packageMap[imports[j]])
{
// If this is a token that matches a class from an import statement, store it in the filtered classMap
classMapFiltered[packageMap[imports[j]].className] = packageMap[imports[j]];
}
}
}
}
public function stringifyFunc(fn:AS3Member):String
{
var buffer:String = "";
if (fn instanceof AS3Function)
{
//Functions need to be handled differently
//Prepend sub-type if it exists
if (fn.subType)
{
buffer += fn.subType + '_';
}
//Print out the rest of the name and start the function definition
buffer += fn.name
buffer += " = function(";
//Concat all of the arguments together
tmpArr = [];
for (j = 0; j < fn.argList.length; j++)
{
if (!fn.argList[j].isRestParam)
{
tmpArr.push(fn.argList[j].name);
}
}
buffer += tmpArr.join(", ") + ") ";
//Function definition is finally added
buffer += fn.value + ";\n";
} else if (fn instanceof AS3Variable)
{
//Variables can be added immediately
buffer += fn.name;
buffer += " = " + fn.value + ";\n";
}
return buffer;
}
public function process(classes:Vector.):void
{
var self:AS3Class = this;
var i:int;
var index:int;
var currParent:AS3Class = this;
var allMembers:Vector. = new Vector.();
var allFuncs:Vector. = new Vector.();
var allStaticMembers:Vector. = new Vector.();
var allStaticFuncs:Vector. = new Vector.();
while (currParent)
{
//Parse members of this parent
for (i in currParent.setters)
{
allMembers.push(currParent.setters[i]);
}
for (i in currParent.staticSetters)
{
allStaticMembers.push(currParent.staticSetters[i]);
}
for (i in currParent.getters)
{
allMembers.push(currParent.getters[i]);
}
for (i in currParent.staticGetters)
{
allStaticMembers.push(currParent.staticGetters[i]);
}
for (i in currParent.members)
{
allMembers.push(currParent.members[i]);
}
for (i in currParent.staticMembers)
{
allStaticMembers.push(currParent.staticMembers[i]);
}
//Go to the next parent
currParent = currParent.parentDefinition;
}
//Add copies of the setters and getters to the "all" arrays (for convenience)
for (i in setters)
{
if (setters[i] instanceof AS3Function)
{
allFuncs.push(setters[i]);
}
}
for (i in staticSetters)
{
if (staticSetters[i] instanceof AS3Function)
{
allStaticFuncs.push(staticSetters[i]);
}
}
for (i in getters)
{
if (getters[i] instanceof AS3Function)
{
allFuncs.push(getters[i]);
}
}
for (i in staticGetters)
{
if (staticGetters[i] instanceof AS3Function)
{
allStaticFuncs.push(staticGetters[i]);
}
}
for (i in members)
{
if (members[i] instanceof AS3Function)
{
allFuncs.push(members[i]);
}
if (members[i] instanceof AS3Variable)
{
// Fix any obvious assignments that rely on implicit static class name (only works for simple statements)
if (members[i].value && retrieveField(members[i].value.replace(/^([a-zA-Z_$][0-9a-zA-Z_$]*)(.*?)$/g, "$1"), true))
{
members[i].value = className + '.' + members[i].value;
}
}
}
for (i in staticMembers)
{
if (staticMembers[i] instanceof AS3Function)
{
allStaticFuncs.push(staticMembers[i]);
}
if (staticMembers[i] instanceof AS3Variable)
{
// Fix any obvious assignments that rely on implicit static class name (only works for simple statements)
if (staticMembers[i].value && retrieveField(staticMembers[i].value.replace(/^([a-zA-Z_$][0-9a-zA-Z_$]*)(.*?)$/g, "$1"), true))
{
staticMembers[i].value = className + '.' + staticMembers[i].value;
}
}
}
// Insert $init function for instantiations
for (i in allFuncs)
{
Main.debug("Now parsing function: " + className + ":" + allFuncs[i].name);
allFuncs[i].value = AS3Parser.parseFunc(this, allFuncs[i].value, allFuncs[i].buildLocalVariableStack(), allFuncs[i].isStatic)[0];
allFuncs[i].value = AS3Parser.checkArguments(allFuncs[i]);
if (allFuncs[i].name === "$init")
{
//Inject instantiations here
allFuncs[i].value = AS3Parser.injectInstantiations(this, allFuncs[i]);
}
if (allFuncs[i].name === className)
{
//Inject $init() into constructor
allFuncs[i].value = AS3Parser.injectInit(this, allFuncs[i]);
}
allFuncs[i].value = AS3Parser.cleanup(allFuncs[i].value);
//Fix supers
allFuncs[i].value = allFuncs[i].value.replace(/super\.(.*?)\(/g, parent + '.prototype.$1.call(this, ').replace(/\.call\(this,\s*\)/g, ".call(this)");
allFuncs[i].value = allFuncs[i].value.replace(/super\(/g, parent + '.call(this, ').replace(/\.call\(this,\s*\)/g, ".call(this)");
allFuncs[i].value = allFuncs[i].value.replace(new RegExp("this[.]" + parent, "g"), parent); //Fix extra 'this' on the parent
}
for (i in allStaticFuncs)
{
Main.debug("Now parsing static function: " + className + ":" + allStaticFuncs[i].name);
allStaticFuncs[i].value = AS3Parser.parseFunc(this, allStaticFuncs[i].value, allStaticFuncs[i].buildLocalVariableStack(), allStaticFuncs[i].isStatic)[0];
allStaticFuncs[i].value = AS3Parser.checkArguments(allStaticFuncs[i]);
allStaticFuncs[i].value = AS3Parser.cleanup(allStaticFuncs[i].value);
}
}
public function toString():String
{
//Outputs the class inside a JS function
var i:*;
var j:*;
var buffer:String = "";
if (requires.length > 0)
{
if (safeRequire)
{
for (i in requires)
{
buffer += 'var ' + requires[i].substring(1, requires[i].length-1) + ' = (function () { try { return require(' + requires[i] + '); } catch(e) { return undefined; }})();\n';
}
} else
{
for (i in requires)
{
buffer += 'var ' + requires[i].substring(1, requires[i].length-1) + ' = require(' + requires[i] + ');\n';
}
}
buffer += "\n";
}
var tmpArr:Array = null;
//Parent class must be imported if it exists
if (parentDefinition)
{
buffer += "var " + parentDefinition.className + " = module.import('" + parentDefinition.packageName + "', '" + parentDefinition.className + "');\n";
}
//Create refs for all the other classes
if (imports.length > 0)
{
tmpArr = [];
for (i in imports)
{
if (!(ignoreFlash && imports[i].indexOf('flash.') >= 0) && parent != imports[i].substr(imports[i].lastIndexOf('.') + 1) && packageName + '.' + className != imports[i]) //Ignore flash imports
{
// Must be in the filtered map, otherwise no point in writing
if (!packageMap[imports[i]])
{
Main.warn("Warning, missing class path: " + imports[i] + " (found in " + packageName + '.' + className + ")");
} else if (classMapFiltered[packageMap[imports[i]].className])
{
tmpArr.push(imports[i].substr(imports[i].lastIndexOf('.') + 1)); //<-This will return characters after the final '.', or the entire String if no '.'
}
}
}
//Join up separated by commas
if (tmpArr.length > 0)
{
buffer += 'var ';
buffer += tmpArr.join(", ") + ";\n";
}
}
//Check for injection function code
var injectedText = "";
for (i in imports)
{
if (!(ignoreFlash && imports[i].indexOf('flash.') >= 0) && packageName + '.' + className != imports[i] && !(parentDefinition && parentDefinition.packageName + '.' + parentDefinition.className == imports[i])) //Ignore flash imports and parent for injections
{
// Must be in the filtered map, otherwise no point in writing
if (!packageMap[imports[i]])
{
Main.warn("Warning, missing class path: " + imports[i] + " (found in " + packageName + '.' + className + ")");
} else if (classMapFiltered[packageMap[imports[i]].className])
{
injectedText += "\t" + imports[i].substr(imports[i].lastIndexOf('.') + 1) + " = module.import('" + packageMap[imports[i]].packageName + "', '" + packageMap[imports[i]].className + "');\n";
}
}
}
if (injectedText.length > 0)
{
buffer += "module.inject = function () {\n";
buffer += injectedText;
buffer += "};\n";
}
buffer += '\n';
buffer += (fieldMap[className]) ? "var " + stringifyFunc(fieldMap[className]) : "var " + className + " = function " + className + "() {};";
buffer += '\n';
buffer += '\n';
if (parent)
{
//Extend parent if necessary
buffer += className + ".prototype = Object.create(" + parent + ".prototype);";
}
buffer += '\n\n';
// Deal with static member assigments
if (staticMembers.length > 0)
{
//Place defaults first
for (i in staticMembers)
{
if (staticMembers[i] instanceof AS3Function)
{
buffer += className + "." + stringifyFunc(staticMembers[i]);
} else if (staticMembers[i].type === "Number" || staticMembers[i].type === "int" || staticMembers[i].type === "uint")
{
if (isNaN(parseInt(staticMembers[i].value)))
{
buffer += className + "." + staticMembers[i].name + ' = 0;\n';
} else
{
buffer += className + "." + stringifyFunc(staticMembers[i]);
}
} else if (staticMembers[i].type === "Boolean")
{
buffer += className + "." + staticMembers[i].name + ' = false;\n';
} else
{
buffer += className + "." + staticMembers[i].name + ' = null;\n';
}
}
for (i in staticGetters)
{
buffer += className + "." + stringifyFunc(staticGetters[i]);
}
for (i in staticSetters)
{
buffer += className + "." + stringifyFunc(staticSetters[i]);
}
buffer += '\n';
buffer += className + ".$cinit = function () {\n";
// Now do the assignments for the rest
for (i in staticMembers)
{
if (!(staticMembers[i] instanceof AS3Function))
{
buffer += "\t" + AS3Parser.cleanup( className + '.' + staticMembers[i].name + ' = ' + staticMembers[i].value + ";\n");
}
}
buffer += '\n';
buffer += "};\n";
}
buffer += "\n";
for (i in getters)
{
buffer += className + ".prototype." + stringifyFunc(getters[i]);
}
for (i in setters)
{
buffer += className + ".prototype." + stringifyFunc(setters[i]);
}
for (i in members)
{
if (members[i].name === className)
{
continue;
}
if (members[i] instanceof AS3Function || (AS3Class.nativeTypes.indexOf(members[i].type) >= 0 && members[i].value))
{
buffer += className + ".prototype." + stringifyFunc(members[i]); //Print functions immediately
} else if (members[i].type === "Number" || members[i].type === "int" || members[i].type === "uint")
{
if (isNaN(parseInt(members[i].value)))
{
buffer += className + ".prototype." + members[i].name + ' = 0;\n';
} else
{
buffer += className + ".prototype." + stringifyFunc(members[i]);
}
} else if (members[i].type === "Boolean")
{
buffer += className + ".prototype." + members[i].name + ' = false;\n';
} else
{
buffer += className + ".prototype." + members[i].name + ' = null;\n';
}
}
buffer = buffer.substr(0, buffer.length - 2) + "\n"; //Strips the final comma out of the string
buffer += "\n\n";
buffer += "module.exports = " + className + ";\n";
//Remaining fixes
buffer = buffer.replace(/(this\.)+/g, "this.");
return buffer;
}
}
}
================================================
FILE: src/com/mcleodgaming/as3js/parser/AS3Parser.as
================================================
package com.mcleodgaming.as3js.parser
{
import com.mcleodgaming.as3js.Main;
import com.mcleodgaming.as3js.enums.*;
import com.mcleodgaming.as3js.types.*;
require "path"
require "fs"
public class AS3Parser
{
/**
* Keeps track of previous function name to assist extractBlock() debugging
* TODO: This could definitely be implemented better
*/
public static var PREVIOUS_BLOCK:String;
//public var index:int;
public var stack:Array;
public var src:String;
public var classPath:String;
public var parserOptions:Object;
public function AS3Parser(src:String, classPath:String = null):void
{
//index = 0;
stack = [];
this.src = src;
this.classPath = classPath;
parserOptions = { };
parserOptions.safeRequire = false;
parserOptions.ignoreFlash = false;
}
public static function increaseIndent(str:String, indent:String):String
{
return (indent + str).replace(/\n/g, "\n" + indent);
}
public static function parseArguments(str:String):Array
{
var args:Vector. = new Vector.();
var tmpToken:AS3Token;
var tmpArr:Array = AS3Parser.extractBlock(str, 0, '(', ')');
var tmpExtractArr:Array = null;
var index:int = tmpArr[1] - 1; //Ending index of parsed block
var tmpStr:String = tmpArr[0].trim(); //Parsed block
tmpStr = tmpStr.substr(1, tmpStr.length - 2); //Remove outer parentheses
tmpArr = null; //Trash this
tmpArr = tmpStr.split(','); //Split args by commas
//Don't bother if there are no arguments
if (tmpArr.length > 0 && tmpArr[0] != '')
{
//Truncate spaces and assign values to arguments as needed
for (var i = 0; i < tmpArr.length; i++)
{
tmpStr = tmpArr[i].trim();
args.push(new AS3Argument());
if (tmpStr.indexOf('...') === 0)
{
//This is a ...rest argument, stop here
args[args.length-1].name = tmpStr.substr(3);
args[args.length-1].isRestParam = true;
Main.debug('----->Parsed a ...rest param: ' + args[args.length-1].name);
break;
} else
{
//Grab the function name
tmpToken = AS3Parser.nextWord(tmpStr, 0, AS3Pattern.VARIABLE[0], AS3Pattern.VARIABLE[1]); //Parse out the function name
args[args.length-1].name = tmpToken.token; //Set the argument name
Main.debug('----->Sub-Function argument found: ' + tmpToken.token);
//If a colon was next, we'll assume it was typed and grab it
if (tmpToken.index < tmpStr.length && tmpStr.charAt(tmpToken.index) == ':')
{
tmpToken = AS3Parser.nextWord(tmpStr, tmpToken.index, AS3Pattern.VARIABLE_TYPE[0], AS3Pattern.VARIABLE_TYPE[1]); //Parse out the argument type
args[args.length-1].type = tmpToken.token; //Set the argument type
Main.debug('----->Sub-Function argument typed to: ' + tmpToken.token);
}
tmpToken = AS3Parser.nextWord(tmpStr, tmpToken.index, AS3Pattern.ASSIGN_START[0], AS3Pattern.ASSIGN_START[1]);
if (tmpToken.token == "=")
{
//Use all characters after self symbol to set value
tmpExtractArr = AS3Parser.extractUpTo(tmpStr, tmpToken.index, /[;\r\n]/g);
//Store value
args[args.length-1].value = tmpExtractArr[0].trim();
//Store value
Main.debug('----->Sub-Function argument defaulted to: ' + tmpExtractArr[0].trim());
}
}
}
}
return args;
}
public static function checkForCommentOpen(str:String):String
{
return (str == "//") ? AS3ParseState.COMMENT_INLINE : (str == "/*") ? AS3ParseState.COMMENT_MULTILINE : null;
}
public static function checkForCommentClose(state, str):Boolean
{
return (state == AS3ParseState.COMMENT_INLINE && (str.charAt(0) == '\n' || str.charAt(0) == '\r' || str.charAt(0) == '')) ? true : (state == AS3ParseState.COMMENT_MULTILINE && str == "*/") ? true : false;
}
public static function checkForStringOpen(str:String):String
{
return (str == '"') ? AS3ParseState.STRING_DOUBLE_QUOTE : (str == "'") ? AS3ParseState.STRING_SINGLE_QUOTE : null;
}
public static function checkForStringClose(state, str):Boolean
{
return (state == AS3ParseState.STRING_DOUBLE_QUOTE && str == '"') ? true : (state == AS3ParseState.STRING_SINGLE_QUOTE && str == "'") ? true : false;
}
public static function nextWord(src:String, index:int, characters:String, pattern:String):AS3Token
{
characters = characters || AS3Pattern.IDENTIFIER[0];
pattern = pattern || AS3Pattern.IDENTIFIER[1];
var tokenBuffer:String = null;
var extraBuffer:String = ''; //Contains characters that were missed
var escapeToggle:Boolean = false;
var innerState:String = null;
for (; index < src.length; index++)
{
var c = src.charAt(index);
if (c.match(characters))
{
tokenBuffer = (tokenBuffer) ? tokenBuffer + c : c; //Create new token buffer if needed, otherwise append
} else if (!innerState && AS3Parser.checkForCommentOpen(src.substr(index, 2)) && !tokenBuffer)
{
tokenBuffer = null;
Main.debug("Entering comment...");
innerState = AS3Parser.checkForCommentOpen(src.substr(index, 2));
extraBuffer += src.substr(index, 2);
index += 2; //Skip next index
//Loop until we break out of comment
for (; index < src.length; index++)
{
if (AS3Parser.checkForCommentClose(innerState, src.substr(index, 2)))
{
if (innerState == AS3ParseState.COMMENT_MULTILINE)
{
extraBuffer += src.substr(index, 2);
index++; //Skip next token
} else
{
extraBuffer += src.charAt(index);
}
innerState = null; //Return to previous state
Main.debug("Exiting comment...");
break;
} else
{
extraBuffer += src.charAt(index);
}
}
} else if (!innerState && AS3Parser.checkForStringOpen(src.charAt(index)) && !tokenBuffer)
{
tokenBuffer = null;
Main.debug("Entering string...");
innerState = AS3Parser.checkForStringOpen(src.charAt(index));
extraBuffer += src.substr(index, 1);
index++; //Skip to next index
//Loop until we break out of string
for (; index < src.length; index++)
{
extraBuffer += src.charAt(index);
if (!escapeToggle && src.charAt(index) == '\\')
{
escapeToggle = true;
continue;
}
escapeToggle = false;
if (AS3Parser.checkForStringClose(innerState, src.charAt(index)))
{
innerState = null; //Return to previous state
Main.debug("Exiting string...");
break;
}
}
} else if (tokenBuffer && tokenBuffer.match(pattern))
{
return new AS3Token(tokenBuffer, index, extraBuffer); //[Token, Index]
} else
{
if (tokenBuffer)
{
extraBuffer += tokenBuffer + c;
} else
{
extraBuffer += c;
}
tokenBuffer = null;
}
}
return new AS3Token(tokenBuffer || null, index, extraBuffer); //[Token, Index]
}
public static function extractBlock(text:String, start:int = 0, opening:String = "{", closing:String = "}"):Array
{
var buffer:String = "";
var i:int = start;
var count:int = 0;
var started:Boolean = false;
var insideString:String = null;
var insideComment:String = null;
var escapingChar:Boolean = false;
while (!(count == 0 && started) && i < text.length)
{
if (insideComment)
{
//Inside of a comment, wait until we get out
if (insideComment == '//' && (text.charAt(i) == '\n' || text.charAt(i) == '\r'))
{
insideComment = null; //End inline comment
Main.debug("Exited comment");
} else if (insideComment == '/*' && text.charAt(i) == '*' && i + 1 < text.length && text.charAt(i + 1) == '/')
{
insideComment = null; //End multiline comment
Main.debug("Exited comment");
}
} else if (insideString)
{
//Inside of a string, wait until we get out
if (!escapingChar && text.charAt(i) == "\\")
{
escapingChar = true; //Start escape sequence
} else if (!escapingChar && text.charAt(i) == insideString)
{
insideString = null; //Found closing quote
} else
{
escapingChar = false; //Forget escape sequence
}
} else if (text.charAt(i) == opening)
{
started = true;
count++; //Found opening
} else if (text.charAt(i) == closing)
{
count--; //Found closing
} else if ((text.charAt(i) == '\"' || text.charAt(i) == '\''))
{
insideString = text.charAt(i); //Now inside of a string
} else if (text.charAt(i) == '/' && i + 1 < text.length && text.charAt(i + 1) == '/')
{
Main.debug("Entering comment... " + "(//)");
insideComment = '//';
} else if (text.charAt(i) == '/' && i + 1 < text.length && text.charAt(i + 1) == '*')
{
Main.debug("Entering comment..." + "(/*)");
insideComment = '/*';
}
if (started)
{
buffer += text.charAt(i);
}
i++;
}
if (!started)
{
throw new Error("Error, no starting '" + opening + "' found for method body while parsing " + AS3Parser.PREVIOUS_BLOCK);
} else if (count > 0)
{
throw new Error("Error, no closing '" + closing + "' found for method body while parsing " + AS3Parser.PREVIOUS_BLOCK);
} else if (count < 0)
{
throw new Error("Error, malformed enclosing '" + opening + closing + " body while parsing " + AS3Parser.PREVIOUS_BLOCK);
}
return [buffer, i];
}
public static function extractUpTo(text:String, start:int, target:String):Array
{
var buffer:String = "";
var i:int = start;
var insideString:String = null;
var insideComment:String = null;
var escapingChar:Boolean = false;
var pattern:String = new RegExp(target);
while (i < text.length)
{
if (insideComment)
{
//Inside of a comment, wait until we get out
if (insideComment == '//' && (text.charAt(i) == '\n' || text.charAt(i) == '\r'))
{
insideComment = null; //End inline comment
Main.debug("Exited comment");
} else if (insideComment == '/*' && text.charAt(i) == '*' && i + 1 < text.length && text.charAt(i + 1) == '/')
{
insideComment = null; //End multiline comment
Main.debug("Exited comment");
}
} else if (insideString)
{
//Inside of a string, wait until we get out
if (!escapingChar && text.charAt(i) == "\\")
{
escapingChar = true; //Start escape sequence
} else if (!escapingChar && text.charAt(i) == insideString)
{
insideString = null; //Found closing quote
} else
{
escapingChar = false; //Forget escape sequence
}
} else if ((text.charAt(i) == '\"' || text.charAt(i) == '\''))
{
insideString = text.charAt(i); //Now inside of a string
} else if (text.charAt(i) == '/' && i + 1 < text.length && text.charAt(i + 1) == '/')
{
Main.debug("Entering comment... " + "(//)");
insideComment = '//';
} else if (text.charAt(i) == '/' && i + 1 < text.length && text.charAt(i + 1) == '*')
{
Main.debug("Entering comment..." + "(/*)");
insideComment = '/*';
} else if (text.charAt(i).match(pattern))
{
break; //Done
}
buffer += text.charAt(i);
i++;
}
return [buffer, i];
}
public static function fixClassPath(clsPath:String):String
{
// Class paths at the root level might accidentally be prepended with a "."
return clsPath.replace(/^\./g, "");
}
public function getState():String
{
return (this.stack.length > 0) ? this.stack[this.stack.length - 1] : null;
}
private function parseHelper(cls:AS3Class, src:String):void
{
var i:*;
var j:*;
var c:String;
var currToken:AS3Token = null;
var tmpToken:AS3Token = null;
var tmpStr:String = null;
var tmpArr:Array = null;
var currMember:AS3Member = null;
var index:int;
for (index = 0; index < src.length; index++)
{
c = src.charAt(index);
if (getState() == AS3ParseState.START)
{
//String together letters only until we reach a non-letter
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index - 1; //Update to the new position
if (currToken.token == 'package')
{
stack.push(AS3ParseState.PACKAGE_NAME);
}
} else if (getState() == AS3ParseState.PACKAGE_NAME)
{
currToken = AS3Parser.nextWord(src, index, AS3Pattern.OBJECT[0], AS3Pattern.OBJECT[1]); //Package name
tmpToken = AS3Parser.nextWord(src, index, AS3Pattern.CURLY_BRACE[0], AS3Pattern.CURLY_BRACE[1]); //Upcoming curly brace
index = currToken.index - 1;
if (!currToken.token || !tmpToken.token)
{
throw new Error("Error parsing package name.");
} else
{
if (tmpToken.index < currToken.index)
{
cls.packageName = ''; //Curly brace came before next token
index = tmpToken.index;
} else
{
cls.packageName = currToken.token; //Just grab the package name
}
Main.debug('Found package: ' + cls.packageName);
cls.importWildcards.push(AS3Parser.fixClassPath(cls.packageName + '.*')); //Add wild card for its own folder
stack.push(AS3ParseState.PACKAGE);
Main.debug('Attempting to parse package...');
}
} else if (getState() == AS3ParseState.PACKAGE)
{
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index - 1;
if (currToken.token == 'class' || currToken.token == 'interface')
{
if(currToken.token == 'interface')
cls.isInterface = true;
stack.push(AS3ParseState.CLASS_NAME);
Main.debug('Found class keyword...');
} else if (currToken.token == 'import')
{
stack.push(AS3ParseState.IMPORT_PACKAGE);
Main.debug('Found import keyword...');
} else if (currToken.token == 'require')
{
stack.push(AS3ParseState.REQUIRE_MODULE);
Main.debug('Found require keyword...');
}
} else if (getState() == AS3ParseState.CLASS_NAME)
{
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
tmpToken = AS3Parser.nextWord(src, index, AS3Pattern.CURLY_BRACE[0], AS3Pattern.CURLY_BRACE[1]);
index = currToken.index;
if (!currToken.token || !tmpToken.token)
{
throw new Error("Error parsing class name.");
} else if (tmpToken.index < currToken.index)
{
throw new Error("Error, no class name found before curly brace.");
} else
{
//Set the class name
cls.className = currToken.token;
// Update fully qualified class path if needed
classPath = classPath || AS3Parser.fixClassPath(cls.packageName + '.' + cls.className); //Remove extra "." for top level packages
cls.classMap[cls.className] = cls; //Register self into the import map (used for static detection)
//Now we will check for parent class and any interfaces
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
if (currToken.token == 'extends' && currToken.index < tmpToken.index)
{
index = currToken.index;
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index;
//The token following 'extends' must be the parent class
cls.parent = currToken.token;
//Prep the next token
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
Main.debug("Found parent: " + cls.parent);
}
if (currToken.token == 'implements' && currToken.index < tmpToken.index)
{
index = currToken.index;
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index;
//The token following 'implements' must be an interface
cls.interfaces.push(currToken.token);
Main.debug("Found interface: " + currToken.token);
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
//While we are at a token before the next curly brace
while (currToken.index < tmpToken.index && currToken.index < src.length)
{
//Consider self token another interface being implemented
index = currToken.index;
Main.debug("Found interface: " + currToken.token);
cls.interfaces.push(currToken.token);
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index;
}
}
Main.debug('Parsed class name: ' + cls.className);
//Now parsing inside of the class
stack.push(AS3ParseState.CLASS);
Main.debug('Attempting to parse class...');
//Extract out the next method block
AS3Parser.PREVIOUS_BLOCK = cls.className + ":Class";
tmpStr = AS3Parser.extractBlock(src, index)[0];
index += tmpStr.length - 1;
//Recursively call parseHelper again under this new state (Once returned, package will be exited)
parseHelper(cls, tmpStr);
}
} else if (getState() == AS3ParseState.CLASS)
{
currMember = currMember || new AS3Member(); //Declare a new member to work with if it doesn't exist yet
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index - 1;
if (currToken.token == AS3Encapsulation.PUBLIC || currToken.token == AS3Encapsulation.PRIVATE || currToken.token == AS3Encapsulation.PROTECTED)
{
currMember.encapsulation = currToken.token;
Main.debug('->Member encapsulation set to ' + currMember.encapsulation);
} else if (currToken.token == 'static')
{
currMember.isStatic = true;
Main.debug('-->Static flag set');
} else if (currToken.token == AS3MemberType.VAR || currToken.token == AS3MemberType.CONST)
{
Main.debug('--->Member type "variable" set.');
currMember = currMember.createVariable(); //Transform the member into a variable
stack.push(AS3ParseState.MEMBER_VARIABLE);
} else if (currToken.token == AS3MemberType.FUNCTION)
{
currToken = AS3Parser.nextWord(src, index + 1, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
//Check for getter/setter
if ((currToken.token == 'get' || currToken.token == 'set') && src[index + 1 + currToken.token.length + 1] != '(')
{
Main.debug('--->Member sub-type "' + currToken.token + '" set.');
currMember.subType = currToken.token;
index = currToken.index - 1;
}
currMember = currMember.createFunction(); //Transform the member into a function
stack.push(AS3ParseState.MEMBER_FUNCTION);
Main.debug('---->Member type "function" set.');
}
} else if (getState() == AS3ParseState.MEMBER_VARIABLE)
{
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
currMember.name = currToken.token; //Set the member name
Main.debug('---->Variable name declared: ' + currToken.token);
index = currToken.index;
if (src.charAt(index) == ":")
{
currToken = AS3Parser.nextWord(src, index, AS3Pattern.VARIABLE_TYPE[0], AS3Pattern.VARIABLE_TYPE[1]);
index = currToken.index;
currMember.type = currToken.token;//Set the value type name
Main.debug('---->Variable type for ' + currMember.name + ' declared as: ' + currToken.token);
}
currToken = AS3Parser.nextWord(src, index, AS3Pattern.ASSIGN_START[0], AS3Pattern.ASSIGN_START[1]);
if (currToken.token == "=")
{
//Use all characters after self symbol to set value
index = currToken.index;
tmpArr = AS3Parser.extractUpTo(src, index, /[;\r\n]/g);
//Store value
currMember.value = tmpArr[0].trim();
index = tmpArr[1];
cls.membersWithAssignments.push(currMember);
}
//Store and delete current member and exit
if (currMember.isStatic)
{
cls.staticMembers.push(currMember);
} else
{
cls.members.push(currMember);
}
cls.registerField(currMember.name, currMember);
currMember = null;
stack.pop();
} else if (getState() == AS3ParseState.MEMBER_FUNCTION)
{
//Parse the arguments
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IDENTIFIER[0], AS3Pattern.IDENTIFIER[1]);
index = currToken.index;
currMember.name = currToken.token; //Set the member name
Main.debug('****>Function name declared: ' + currToken.token);
AS3Parser.PREVIOUS_BLOCK = currMember.name + ":Function";
tmpArr = AS3Parser.extractBlock(src, index, '(', ')');
index = tmpArr[1] - 1; //Ending index of parsed block
tmpStr = tmpArr[0].trim(); //Parsed block
tmpStr = tmpStr.substr(1, tmpStr.length - 2); //Remove outer parentheses
tmpArr = null; //Trash this
tmpArr = tmpStr.split(','); //Split args by commas
//Don't bother if there are no arguments
if (tmpArr.length > 0 && tmpArr[0] != '')
{
//Truncate spaces and assign values to arguments as needed
for (i = 0; i < tmpArr.length; i++)
{
tmpStr = tmpArr[i];
//Grab the function name
tmpToken = AS3Parser.nextWord(tmpStr, 0, AS3Pattern.VARIABLE[0], AS3Pattern.VARIABLE[1]); //Parse out the function name
currMember.argList.push(new AS3Argument());
if (tmpStr.indexOf('...') === 0)
{
//This is a ...rest argument, stop here
currMember.argList[currMember.argList.length-1].name = tmpStr.substr(3);
currMember.argList[currMember.argList.length-1].isRestParam = true;
Main.debug('----->Parsed a ...rest param: ' + currMember.argList[currMember.argList.length-1].name);
break;
} else
{
currMember.argList[currMember.argList.length-1].name = tmpToken.token; //Set the argument name
Main.debug('----->Function argument found: ' + tmpToken.token);
//If a colon was next, we'll assume it was typed and grab it
if (tmpToken.index < tmpStr.length && tmpStr.charAt(tmpToken.index) == ':')
{
tmpToken = AS3Parser.nextWord(tmpStr, tmpToken.index, AS3Pattern.VARIABLE_TYPE[0], AS3Pattern.VARIABLE_TYPE[1]); //Parse out the argument type
currMember.argList[currMember.argList.length-1].type = tmpToken.token; //Set the argument type
Main.debug('----->Function argument typed to: ' + tmpToken.token);
}
tmpToken = AS3Parser.nextWord(tmpStr, tmpToken.index, AS3Pattern.ASSIGN_START[0], AS3Pattern.ASSIGN_START[1]);
if (tmpToken.token == "=")
{
//Use all characters after self symbol to set value
tmpToken = AS3Parser.nextWord(tmpStr, tmpToken.index, AS3Pattern.ASSIGN_UPTO[0], AS3Pattern.ASSIGN_UPTO[1]);
if (!tmpToken)
{
throw new Error("Error during variable assignment in arg" + currMember.argList[currMember.argList.length - 1].name);
}
//Store value
currMember.argList[currMember.argList.length-1].value = tmpToken.token.trim();
Main.debug('----->Function argument defaulted to: ' + tmpToken.token.trim());
}
}
}
}
Main.debug('------>Completed paring args: ', currMember.argList);
//Type the function if needed
if (src.charAt(index + 1) == ":")
{
tmpToken = AS3Parser.nextWord(src, index + 1, AS3Pattern.VARIABLE_TYPE[0], AS3Pattern.VARIABLE_TYPE[1]); //Parse out the function type if needed
index = tmpToken.index;
currMember.type = tmpToken.token;
Main.debug('------>Typed the function to: ', currMember.type);
}
if (cls.isInterface)
{
//Store and delete current member and exit
currMember.value = '{}';
if (currMember.subType == 'get')
{
(currMember.isStatic) ? cls.staticGetters.push(currMember) : cls.getters.push(currMember);
} else if (currMember.subType == 'set')
{
(currMember.isStatic) ? cls.staticSetters.push(currMember) : cls.setters.push(currMember);
} else if (currMember.isStatic)
{
cls.staticMembers.push(currMember);
} else
{
cls.members.push(currMember);
}
cls.registerField(currMember.name, currMember);
//Done parsing function
currMember = null;
stack.pop();
} else
{
//Save the function body
AS3Parser.PREVIOUS_BLOCK = currMember.name + ":Function";
tmpArr = AS3Parser.extractBlock(src, index);
index = tmpArr[1];
currMember.value = tmpArr[0].trim();
//Store and delete current member and exit
if (currMember.subType == 'get')
{
(currMember.isStatic) ? cls.staticGetters.push(currMember) : cls.getters.push(currMember);
} else if (currMember.subType == 'set')
{
(currMember.isStatic) ? cls.staticSetters.push(currMember) : cls.setters.push(currMember);
} else if (currMember.isStatic)
{
cls.staticMembers.push(currMember);
} else
{
cls.members.push(currMember);
}
cls.registerField(currMember.name, currMember);
currMember = null;
stack.pop();
}
} else if (getState() == AS3ParseState.LOCAL_VARIABLE)
{
} else if (getState() == AS3ParseState.LOCAL_FUNCTION)
{
} else if (getState() == AS3ParseState.IMPORT_PACKAGE)
{
//The current token is a class import
currToken = AS3Parser.nextWord(src, index, AS3Pattern.IMPORT[0], AS3Pattern.IMPORT[1]);
index = currToken.index - 1;
if (!currToken.token)
{
throw new Error("Error parsing import.");
} else
{
Main.debug("Parsed import name: " + currToken.token);
if (currToken.token.indexOf("*") >= 0)
{
cls.importWildcards.push(currToken.token); //To be resolved later
}
else
{
cls.imports.push(currToken.token); //No need to resolve
}
stack.push(AS3ParseState.PACKAGE);
}
} else if (getState() == AS3ParseState.REQUIRE_MODULE)
{
//The current token is a module requirement
currToken = AS3Parser.nextWord(src, index, AS3Pattern.REQUIRE[0], AS3Pattern.REQUIRE[1]);
index = currToken.index - 1;
if(!currToken.token)
throw new Error("Error parsing require.");
else {
Main.debug("Parsed require name: " + currToken.token);
cls.requires.push(currToken.token.trim());
stack.push(AS3ParseState.PACKAGE);
}
}
}
}
public function parse(options:Object = null):AS3Class
{
options = options || { };
if (typeof options.safeRequire !== 'undefined')
{
parserOptions.safeRequire = options.safeRequire;
}
if (typeof options.ignoreFlash !== 'undefined')
{
parserOptions.ignoreFlash = options.ignoreFlash;
}
var classDefinition:AS3Class = new AS3Class(parserOptions);
stack.splice(0, stack.length);
stack.push(AS3ParseState.START);
parseHelper(classDefinition, src);
if (!classDefinition.className)
{
throw new Error("Error, no class provided for package: " + classPath);
}
return classDefinition;
}
public static function checkArguments(fn:AS3Function):String
{
if (fn.argList.length <= 0)
{
return fn.value;
}
var start:int = fn.value.indexOf('{');
var args:String = "";
for (var i:int = 0; i < fn.argList.length; i++)
{
//We will inject arguments into the top of the method definition
if (fn.argList[i].isRestParam)
{
args += "\n\t\t\tvar " + fn.argList[i].name + " = Array.prototype.slice.call(arguments).splice(" + i + ");";
} else if (fn.argList[i].value)
{
args += "\n\t\t\t" + fn.argList[i].name + " = AS3JS.Utils.getDefaultValue(" + fn.argList[i].name + ", " + fn.argList[i].value + ");";
}
}
return fn.value.substr(0, start + 1) + args + fn.value.substr(start + 1);
}
public static function injectInstantiations(cls:AS3Class, fn:AS3Function):String
{
var start:int = fn.value.indexOf('{');
var text:String = "";
for (var i = 0; i < cls.members.length; i++)
{
//We will inject instantiated vars into the top of the method definition
if (cls.members[i] instanceof AS3Variable && AS3Class.nativeTypes.indexOf(cls.members[i].type) < 0)
{
text += "\n\t\t\tthis." + cls.members[i].name + " = " + cls.members[i].value + ";";
}
}
return fn.value.substr(0, start + 1) + text + fn.value.substr(start + 1);
}
public static function injectInit(cls:AS3Class, fn:AS3Function):String
{
var start:int = fn.value.indexOf('{');
var text:String = "\n\t\t\tthis.$init();";
return fn.value.substr(0, start + 1) + text + fn.value.substr(start + 1);
}
public static function checkStack(stack:Array, name:String):void
{
if (!name)
{
return null;
}
for (var i = stack.length - 1; i >= 0; i--)
{
if (stack[i].name == name)
{
return stack[i];
}
}
return null;
}
public static function lookAhead(str:String, index:int):Object
{
//Look ahead in the function for assignments
var originalIndex:int = index;
var startIndex:int = -1;
var endIndex:int = -1;
var semicolonIndex:int = -1;
var token:String = "";
var extracted:String = "";
//Not a setter if there is a dot operator immediately after
if (str.charAt(index) == '.')
{
return { token: null, extracted: '', startIndex: startIndex, endIndex: endIndex };
}
for (; index < str.length; index++)
{
if(str.charAt(index).match(/[+-\/=*]/g))
{
//Append to the assignment instruction
token += str.charAt(index);
startIndex = index;
} else if (startIndex < 0 && str.charAt(index).match(/[\t\s]/g)) //Skip these characters
{
continue;
} else
{
break; //Exits when token has already been started and no more regexes pass
}
}
//Only allow these patterns
if (!(token == "=" || token == "++" || token == "--" || token == "+=" || token == "-=" || token == "*=" || token == "/="))
{
token = null;
}
if (token)
{
//Pick whatever is closer, new line or semicolon
endIndex = str.indexOf('\n', startIndex);
if (endIndex < 0)
{
endIndex = str.length - 1;
}
//Windows fix
if (str.charAt(endIndex - 1) == '\r')
{
endIndex--;
}
//We want to place closing parens before semicolon if it exists
semicolonIndex = str.indexOf(";", startIndex);
if (semicolonIndex < endIndex)
{
endIndex = semicolonIndex;
}
extracted = str.substring(startIndex + token.length, endIndex);
}
return { token: token, extracted: extracted, startIndex: startIndex, endIndex: endIndex };
}
public static function parseFunc(cls:AS3Class, fnText:String, stack:Array, statFlag:Boolean = false):Array
{
var i:int;
var j:int;
var index:int = 0;
var result:String = '';
var tmpStr:String = '';
var tmpArgs:Vector.;
var tmpMember:AS3Member;
var tmpClass:AS3Class;
var tmpField:AS3Member;
var prevToken:AS3Token;
var currToken:AS3Token;
var tmpParse:String;
var tmpStatic:Boolean = false;
var tmpPeek:String;
var objBuffer = ''; //Tracks the current object that is being "pathed" (e.g. "object.field1" or "object.field1[index + 1]", etc)
var justCreatedVar:Boolean = false; //Keeps track if we just started a var statement (to help test if we're setting a type))
for (index = 0; index < fnText.length; index++)
{
objBuffer = '';
prevToken = currToken;
currToken = AS3Parser.nextWord(fnText, index, AS3Pattern.VARIABLE[0], AS3Pattern.VARIABLE[1]);
result += currToken.extra; //<-Puts all other non-identifier characters into the buffer first
tmpMember = AS3Parser.checkStack(stack, currToken.token); //<-Check the stack for a member with this identifier already
index = currToken.index;
if (currToken.token)
{
if (currToken.token == 'function')
{
var t1 = AS3Parser.nextWord(fnText, index, AS3Pattern.VARIABLE[0], AS3Pattern.VARIABLE[1]);
var t2 = fnText.indexOf('(', index);
//If the parenthesis si less than the last index of the next parsed variable name
result += (t2 < t1.index) ? 'function' : 'function ' + t1.token;
tmpParse = AS3Parser.extractBlock(fnText, index, '(', ')'); //Parse out argument block
index = tmpParse[1]; //Update index
tmpArgs = AS3Parser.parseArguments(tmpParse[0]); //Extract arg types
//Join the args together without types
result += '(' + (function(args) {
var arr = [];
for (var i = 0; i < args.length; i++)
{
if (args[i] === '...rest')
{
break;
}
arr.push(args[i].name);
}
var str = arr.join(', ');
return str;
})(tmpArgs) + ')';
tmpParse = AS3Parser.extractBlock(fnText, index, '{', '}'); //Extract function block
index = tmpParse[1] - 1; //Update index
tmpParse = AS3Parser.parseFunc(cls, tmpParse[0], stack.concat(tmpArgs), statFlag); //Recurse into function
result += ' ' + tmpParse[0];
} else
{
if (currToken.token == 'this')
{
//No need to perform any extra checks on the subsequent token
tmpStatic = false;
tmpClass = cls;
objBuffer += currToken.token;
result += currToken.token;
} else
{
if (cls.classMap[currToken.token] && cls.parentDefinition !== cls.classMap[currToken.token] && !(justCreatedVar && currToken.extra.match(/:\s*/g)))
{
// If this is a token that matches a class from a potential import statement, store it in the filtered classMap
cls.classMapFiltered[currToken.token] = cls.classMap[currToken.token];
}
tmpStatic = (cls.className == currToken.token || cls.retrieveField(currToken.token, true) !== null);
//Find field in class, then make sure we didn't already have a local member defined with this name, and skip next block if static since the definition is the class itself
//Note: tmpMember needs to be checked, if something is in there it means we have a variable with the same name in local scope
if (cls.retrieveField(currToken.token, tmpStatic) && cls.className != currToken.token && !tmpMember && !(prevToken && prevToken.token === "var"))
{
tmpMember = cls.retrieveField(currToken.token, tmpStatic); //<-Reconciles the type of the current variable
if (tmpMember && (tmpMember.subType == 'get' || tmpMember.subType == 'set'))
{
tmpPeek = AS3Parser.lookAhead(fnText, index);
if (tmpPeek.token)
{
//Handle differently if we are assigning a setter
//Prepend the correct term
if (tmpStatic)
{
objBuffer += (cls.retrieveField(currToken.token, tmpStatic)) ? cls.className + '.' : currToken.token + '.';
result += (cls.retrieveField(currToken.token, tmpStatic)) ? cls.className + '.' : currToken.token + '.';
} else
{
objBuffer += 'this.';
result += 'this.';
}
objBuffer += 'get_' + currToken.token + '()';
result += 'set_' + currToken.token + '(';
index = tmpPeek.endIndex;
if (tmpPeek.token == '++')
{
result += objBuffer + ' + 1';
} else if (tmpPeek.token == '--')
{
result += objBuffer + ' - 1';
} else
{
tmpParse = AS3Parser.parseFunc(cls, tmpPeek.extracted, stack); //Recurse into the assignment to parse vars
if (tmpPeek.token == '=')
{
result += tmpParse[0].trim();
} else
{
result += objBuffer + ' ' + tmpPeek.token.charAt(0) + ' (' + tmpParse[0] + ')';
}
}
result += ')';
} else
{
//Getters are easy
if (tmpStatic)
{
objBuffer += (cls.retrieveField(currToken.token, true)) ? cls.className + '.get_' + currToken.token + '()' : 'this.get_' + currToken.token + '()';
result += (cls.retrieveField(currToken.token, true)) ? cls.className + '.get_' + currToken.token + '()' : 'this.get_' + currToken.token + '()';
} else
{
objBuffer += 'this.get_' + currToken.token + '()';
result += 'this.get_' + currToken.token + '()';
}
}
} else
{
if (tmpStatic)
{
objBuffer += (cls.className == currToken.token) ? currToken.token : cls.className + '.' + currToken.token;
result += (cls.className == currToken.token) ? currToken.token : cls.className + '.' + currToken.token;
} else
{
objBuffer += (cls.retrieveField(currToken.token, false) && !statFlag && !(prevToken && prevToken.token === 'new' && cls.retrieveField(currToken.token, false).type !== "Class")) ? 'this.' + currToken.token : currToken.token;
result += (cls.retrieveField(currToken.token, false) && !statFlag && !(prevToken && prevToken.token === 'new' && cls.retrieveField(currToken.token, false).type !== "Class")) ? 'this.' + currToken.token : currToken.token;
}
}
} else
{
//Likely a local variable, argument, or static reference
if (tmpStatic)
{
objBuffer += currToken.token;
result += currToken.token;
} else
{
objBuffer += (cls.retrieveField(currToken.token, false) && !tmpMember && !(prevToken && prevToken.token === "var")) ? 'this.' + currToken.token : currToken.token;
result += (cls.retrieveField(currToken.token, false) && !tmpMember && !(prevToken && prevToken.token === "var")) ? 'this.' + currToken.token : currToken.token;
}
}
if (tmpStatic)
{
//Just use the class itself, we will reference fields from it. If parser injected the static prefix manually, we'll try to determome the type of var instead
tmpClass = (cls.className == currToken.token) ? cls : (tmpMember) ? cls.classMap[tmpMember.type] || null : null;
} else
{
//Use the member's type to determine the class it's mapped to
tmpClass = (tmpMember && tmpMember.type && tmpMember.type != '*') ? cls.classMap[tmpMember.type] : null;
//If no mapping was found, this may be a static reference
if (!tmpClass && cls.classMap[currToken.token])
{
tmpClass = cls.classMap[currToken.token];
tmpStatic = true;
}
}
//If tmpClass is null, it's possible we were trying to retrieve a Vector type. Let's fix this:
if (!tmpClass && tmpMember && tmpMember.type && tmpMember.type.replace(/Vector\.<(.*?)>/g, "$1") != tmpMember.type)
{
//Extract Vector type if necessary by testing regex
tmpClass = cls.classMap[tmpMember.type.replace(/Vector\.<(.*?)>/g, "$1")] || null;
}
}
//Note: At this point, tmpMember is no longer used, it was only needed to remember the type of the first token. objBuffer will be building out the token
//If this had a variable declaration before it, we will add it to the local var stack and move on to the next token
if (prevToken && prevToken.token === "var")
{
justCreatedVar = true;
if (cls.retrieveField(currToken.token, tmpStatic))
{
//Appends current character index to the result, add dummy var to stack, and move on
result += fnText.charAt(index);
var localVar:AS3Member = new AS3Member();
localVar.name = currToken.token;
stack.push(localVar); //<-Ensures we don't add "this." or anything in front of this variable anymore
continue;
}
} else
{
justCreatedVar = false;
}
//We have parsed the current token, and the index sits at the next level down in the object
for (; index < fnText.length; index++)
{
//Loop until we stop parsing a variable declaration
if (fnText.charAt(index) == '.')
{
var parsingVector = (prevToken && prevToken.token === 'new' && currToken.token === 'Vector');
prevToken = currToken;
if (parsingVector)
{
//We need to allow asterix
currToken = AS3Parser.nextWord(fnText, index, AS3Pattern.VARIABLE_TYPE[0], AS3Pattern.VARIABLE_TYPE[1]);
} else
{
currToken = AS3Parser.nextWord(fnText, index, AS3Pattern.VARIABLE[0], AS3Pattern.VARIABLE[1]);
}
result += currToken.extra; //<-Puts all other non-identifier characters into the buffer first
index = currToken.index;
if (tmpClass)
{
//This means we are coming from a typed variable
tmpField = tmpClass.retrieveField(currToken.token, tmpStatic);
if (tmpField)
{
//console.log("parsing: " + tmpField.name + ":" + tmpField.type)
//We found a field that matched this value within the class
if (tmpField instanceof AS3Function)
{
if (tmpField.subType == 'get' || tmpField.subType == 'set')
{
tmpPeek = AS3Parser.lookAhead(fnText, index);
if (tmpPeek.token)
{
//Handle differently if we are assigning a setter
objBuffer += '.get_' + currToken.token + '()';
result += 'set_' + currToken.token + '(';
index = tmpPeek.endIndex;
if (tmpPeek.token == '++')
{
result += objBuffer + ' + 1';
} else if (tmpPeek.token == '--')
{
result += objBuffer + ' - 1';
} else
{
tmpParse = AS3Parser.parseFunc(cls, tmpPeek.extracted, stack); //Recurse into the assignment to parse vars
if (tmpPeek.token == '=')
{
result += tmpParse[0].trim();
} else
{
result += objBuffer + ' ' + tmpPeek.token.charAt(0) + ' (' + tmpParse[0] + ')';
}
}
result += ')';
} else
{
objBuffer += '.get_' + currToken.token + '()';
result += 'get_' + currToken.token + "()";
}
//console.log("set get flag: " + currToken.token);
} else
{
objBuffer += '.' + currToken.token;
result += currToken.token;
}
} else
{
objBuffer += '.' + currToken.token;
result += currToken.token;
}
} else
{
objBuffer += '.' + currToken.token;
result += currToken.token;
//console.log("appened typed: " + currToken.token);
}
//Update the type if this is not a static prop
if (tmpClass && tmpField && tmpField.type && tmpField.type != '*')
{
//Extract Vector type if necessary by testing regex
tmpClass = (tmpField.type.replace(/Vector\.<(.*?)>/g, "$1") != tmpField.type) ? tmpClass.classMap[tmpField.type.replace(/Vector\.<(.*?)>/g, "$1")] || null : tmpClass.classMap[tmpField.type] || null;
} else
{
tmpClass = null;
}
} else
{
//console.log("appened untyped: " + currToken.token);
objBuffer += '.' + currToken.token;
result += currToken.token;
}
} else if (fnText.charAt(index) == '[')
{
//We now have to recursively parse the inside of this open bracket
tmpParse = AS3Parser.extractBlock(fnText, index, '[', ']');
index = tmpParse[1];
tmpParse = AS3Parser.parseFunc(cls, tmpParse[0], stack); //Recurse into the portion that was extracted
//console.log("recursed into: " + tmpParse[0]);
objBuffer += tmpParse[0]; //Append this text to the object buffer string so we can remember the variable we have accessed
result += tmpParse[0];
}
tmpStatic = false; //Static can no longer be possible after the second field
if (!fnText.charAt(index).match(/[.\[]/g))
{
objBuffer = ''; //Clear out the current object buffer
index--;
break;
}
index--;
}
}
} else
{
index = currToken.index - 1;
}
}
return [result, index];
}
public static function cleanup(text:String):String
{
var i:int;
var type:String;
var params:Array;
var val:String;
var matches:Array = text.match(AS3Pattern.VECTOR[0]);
//For each Vector.<>() found in the text
for (i in matches)
{
//Strip the type and provided params
type = matches[i].replace(AS3Pattern.VECTOR[0], '$1').trim();
params = matches[i].replace(AS3Pattern.VECTOR[0], '$2').split(',');
//Set the default based on var type
if (type == 'int' || type == 'uint' || type == 'Number')
{
val = "0";
} else if (type == 'Boolean')
{
val = "false";
} else
{
val = "null";
}
//Replace accordingly
if (params.length > 0 && params[0].trim() != '')
{
text = text.replace(AS3Pattern.VECTOR[1], "AS3JS.Utils.createArray(" + params[0] + ", " + val + ")");
} else
{
text = text.replace(AS3Pattern.VECTOR[1], "[]");
}
}
matches = text.match(AS3Pattern.ARRAY[0]);
//For each Array() found in the text
for (i in matches)
{
//Strip the provided params
params = matches[i].replace(AS3Pattern.ARRAY[0], '$1').trim();
//Replace accordingly
if (params.length > 0 && params[0].trim() != '')
{
text = text.replace(AS3Pattern.ARRAY[1], "AS3JS.Utils.createArray(" + params[0] + ", null)");
} else
{
text = text.replace(AS3Pattern.ARRAY[1], "[]");
}
}
matches = text.match(AS3Pattern.DICTIONARY[0]);
//For each instantiated Dictionary found in the text
for (i in matches)
{
// Replace with empty object
text = text.replace(AS3Pattern.DICTIONARY[0], "{}");
}
//Now cleanup variable types
text = text.replace(/([^0-9a-zA-Z_$.])(?:var|const)(\s*[a-zA-Z_$*][0-9a-zA-Z_$.<>]*)\s*:\s*([a-zA-Z_$*][0-9a-zA-Z_$.<>]*)/g, "$1var$2");
return text;
}
}
}
================================================
FILE: src/com/mcleodgaming/as3js/parser/AS3Token.as
================================================
package com.mcleodgaming.as3js.parser
{
public class AS3Token
{
public var token:String;
public var index:int;
public var extra:String;
public function AS3Token(token:String, index:int, extra:String)
{
this.token = token;
this.index = index;
this.extra = extra;
}
}
}
================================================
FILE: src/com/mcleodgaming/as3js/types/AS3Argument.as
================================================
package com.mcleodgaming.as3js.types
{
public class AS3Argument extends AS3Variable
{
public var isRestParam:Boolean = false;
public function AS3Argument()
{
}
}
}
================================================
FILE: src/com/mcleodgaming/as3js/types/AS3Function.as
================================================
package com.mcleodgaming.as3js.types
{
public class AS3Function extends AS3Member
{
public var argList:Array;
public function AS3Function()
{
argList = [];
}
public function hasArgument():Boolean
{
for(var i = 0; i < argList.length; i++)
if(argList[i].name == name)
return true;
return false;
}
public function buildLocalVariableStack():Array
{
var i;
var text = value || '';
var matches = text.match(/(var|,)(.*?)([a-zA-Z_$][0-9a-zA-Z_$]*):([a-zA-Z_$][0-9a-zA-Z_$]*)/g);
var locals = [];
if(argList) {
for(i in argList) {
locals.push(argList[i]);
}
}
for(i in matches) {
var tmpVar = new AS3Variable();
tmpVar.name = matches[i].replace(/(var|,)(.*?)([a-zA-Z_$][0-9a-zA-Z_$]*):([a-zA-Z_$][0-9a-zA-Z_$]*)/g, "$3");
tmpVar.type = matches[i].replace(/(var|,)(.*?)([a-zA-Z_$][0-9a-zA-Z_$]*):([a-zA-Z_$][0-9a-zA-Z_$]*)/g, "$4");
locals.push(tmpVar);
}
return locals;
}
}
}
================================================
FILE: src/com/mcleodgaming/as3js/types/AS3Member.as
================================================
package com.mcleodgaming.as3js.types
{
public class AS3Member
{
public var name:String;
public var type:String;
public var subType:String;
public var value:String;
public var encapsulation:String;
public var isStatic:Boolean;
public function AS3Member()
{
name = null;
type = '*';
subType = null,
value = null;
encapsulation = "public";
isStatic = false;
}
public function createVariable():AS3Variable
{
var obj = new AS3Variable();
obj.name = name;
obj.type = type;
obj.subType = subType,
obj.value = value;
obj.encapsulation = encapsulation;
obj.isStatic = isStatic;
return obj;
}
public function createFunction():AS3Function
{
var obj = new AS3Function();
obj.name = name;
obj.type = type;
obj.subType = subType,
obj.value = value;
obj.encapsulation = encapsulation;
obj.isStatic = isStatic;
return obj;
}
}
}
================================================
FILE: src/com/mcleodgaming/as3js/types/AS3Variable.as
================================================
package com.mcleodgaming.as3js.types
{
/**
* ...
* @author Greg McLeod
*/
public class AS3Variable extends AS3Member
{
public function AS3Variable()
{
}
}
}