Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

For every non-trivial language feature, we should introduce a dedicated child page.

Notation:

  • $0, $1, ... are auxiliary variables generated by the compiler, chosen to avoid name-clashes with any other identifiers in scope.
  • exp1, exp2, ... (italics) are arbitrary complex ActionScript expressions

Compilation Units

An ActionScript application consists of several compilation units, where one is selected as the application's entry point. A compilation unit is defined in one ActionScript source file, containing a primary declaration and optionally static code. Compilation units are organized in packages.

...

For our implementation, we have to take into account that in a browser, code loading cannot be done synchronously "on demand". Thus we use AMD and map compilation units to AMD modules. The AMD loader resolves all static dependencies and loads the code of all compilation units that are potentially needed at runtime. We have to take care of initializing those compilation units only when they are actually used. This determines the value of a compilation unit the corresponding AMD module returns: Instead of the primary declaration itself, the return value is a compilation unit object from which you can retrieve the primary declaration. The trick is that the compilation unit self-initializes when the primary declaration is first requested!.

We could implement the primary declaration request by a method of the compilation unit object. But this would involve a function call for each retrieval of the primary declaration, even if the compilation has already been initialized. A trick for an efficient implementation is to use a get property that, upon its first invocation, initializes the compilation unit and replaces itself by a simple field with a direct reference to the primary declaration. For the sake of brevity and readability (we want the reader to mainly ignore this helper property), let's call that property _ (underscore).

...

This is not only a performance / memory consumption problem, as in AS3, the method keeps is its identity. This is important for the typical use case that bound methods are registered as event listener callbacks. When trying to remove an event listener, identity has to be ensured, or the function will not be found and the event listener is not removed.

...

"this" is always in scope

TODO

Statements

TODO

for each

TODO

Operators

TODO

&&=

TODO

||=

In JavaScript, you always have to write this.member explicitly.
In ActionScript, this is automatically in scope in every non-static method.
This could be simulated by enclosing the method body in a with(this) block, but this solution is discouraged because with is considered evil, not supported in strict mode, prevents optimizations, and the scope sequence of parameters would be wrong.

Complement "this."

Instead, the compiler has to take care to complement "this." before non-static class members. Note that class members may be hidden by local variables and parameters, so "this." must not be complemented in such cases.

ActionScript:

Code Block

public class MyClass {
  public var id:String;
  public function MyClass(id:String) {
    this.id = id;
  }
  public function toString():String {
    return id;
  }
}

JavaScript:

Code Block

function MyClass(id) {
  this.id = id;
}
Object.defineProperties(MyClass.prototype, {
  id: { value: null, writable: true },
  toString: {
    value: function() {
      return this.id;
    }
  }
});

Note that the second occurrence of id in the constructor body is not prefixed by "this.", as it refers to the parameter.

"this" inside functions

An occurrence of this inside a function (in contrast to a method) does not refer to the "outer" this defined by the class context:

ActionScript:

Code Block

public class MyClass {
  public var prefix:String;
  public function MyClass(prefix:String) {
    this.prefix = prefix;
  }
  public function prefixAll(values:Array):Array {
    return values.map(function(s:String):String {
      return this.prefix + s; // "this" is not defined here as intended!
    });
  }
}

This code does not work as intended, because this inside functions is bound dynamically.
You can solve this problem by either using the second optional parameter thisObject of Array#map(), or by simply removing the this. qualifier from prefix. Let's do the latter:

Code Block

  public function prefixAll(values:Array):Array {
    return values.map(function(s:String):String {
      return prefix + s;
    });
  }

This works, because the anonymous function inside prefixAll is also in lexical scope of the outer this. Unfortunately, this fact is hidden at runtime, because the local this of the anonymous function hides the outer this. Thus, to be able to access the outer this in the generated JS code, we have to alias it, say to this$:

JavaScript:

Code Block

function MyClass(prefix) {
  this.prefix = prefix;
}
Object.defineProperties(MyClass.prototype, {
  "prefix": { value: null, writable: true },
  "prefixAll": {
    value: function(values) {
      var this$ = this;
      return values.map(function(s) {
        return this$.prefix + s;
      });
    }
  }
});

This aliasing should be done at most once right at the start of the method only if it contains at least one function that needs an outer this reference.

Statements

Almost all statements can be mapped one-to-one from ActionScript to JavaScript.
There are only two exceptions: for each and try... catch.

for each

Unlike the for... in loop, the for each... in loop has been introduced to JavaScript later, so to make sure the generated code runs in JavaScript 1.5 browsers, we have to simulate it.

Notation:

  • $0, $1 are auxiliary variables generated by the compiler, chosen to avoid name-clashes with any other identifiers in scope.
  • <lhs>, <rhs> are arbitrary complex ActionScript expressions
  • <block> is an ActionScript code block (the loop body)

Then,

Code Block

for each (<lhs> in <rhs>) {
  <block>
}

becomes

Code Block

var $1;
for (var $0 in ($1 = <rhs>)) {
  <lhs> = $1[$0];
  <block>
}

Note that if <block> is not enclosed by curly braces, these have to added, as we add a second statement to the loop body.

try... catch... finally

While JavaScript supports try... catch... finally statements, it (naturally) does not support multiple catch clauses, using different error types, like so:

ActionScript:

Code Block

try {
  ...
} catch (e1:ArgumentError) {
  // handle argument errors
} catch (e2:TypeError) {
  // handle type errors
}

Note that a catch clause implicitly declares its variable, scoped to the catch body only. The variables could have the same name and would not clash.
The AS3 semantics is that if the error is an ArgumentError, the first catch body is executed, if the error is a TypeError, the second catch body is executed, and on any other error, the error is not caught at all.

Thus, the corresponding JavaScript code needs to check the runtime type of the error variable:

JavaScript:

Code Block

try {
  ...
} catch (e) {
  if (is(e, ArgumentError)) {
    var e1 = e;
    // handle argument errors
  } else if (is(e, TypeError)) {
    var e2 = e;
    // handle type errors
  } else {
    throw e;
  }
}

Note that in case no type check matches, the error has to be re-thrown. This code can be omitted if the original code contains a catch clause with an untyped or * typed error variable.

Operators

Besides as and is, which are discussed in the Interfaces section, only two operators are known to be missing in JavaScript, namely short-circuit-and-assignment (&&=) and short-circuit-or-assignment (||=).

For both operators, the trick is that both the left-hand-side and right-hand-side expression are not evaluated too often. To achieve this, we introduce auxiliary variables like for for each. The code transformation examples are for &&= only, as ||= works analogously.

Simple Pattern

ActionScript:

Code Block

<identifier> &&= <rhs>

JavaScript:

Code Block

<identifier> = <identifier> && <rhs>

Complex Pattern

If the left-hand-side can be split into two expressions, where the first evaluates to some object and the second to some property, do so and rewrite:

ActionScript:

Code Block

<exp1>[<exp2>] &&= <rhs>

JavaScript:

Code Block

var $0, $1;
($0 = <exp1>)[$1 = <exp2>] = $0[$1] && <rhs>

Note that foo().y is the same as foo()['y'] and thus also matches this pattern.

Examples

Simple lhs

ActionScript:

Code Block

x &&= foo()

JavaScript:

Code Block

x = x && foo()

Complex lhs

Complex object

ActionScript:

Code Block

foo().y &&= bar()

JavaScript:

Code Block

var $0, $1;
($0  = foo())[$1 = 'y'] = $0[$1] && bar();

which of course could be simplified to

Code Block

var $0;
($0 = foo()).y = $0.y && bar()
Complex property

ActionScript:

Code Block

x[foo()] &&= bar()

JavaScript:

Code Block

var $0, $1;
($0 = x)[$1 = foo()] = $0[$1] && bar()

which of course could be simplified to

Code Block

var $1;
x[$1 = foo()] = x[$1] && bar()
Complex object and property

ActionScript:

Code Block

baz()[foo()] &&= bar()

JavaScript:

Code Block

var $0, $1;
($0 = baz())[$1 = foo()] = $0[$1] && bar()

which cannot be simplified.TODO