@awayfl/avm2
Version:
Virtual machine for executing AS3 code
131 lines (130 loc) • 6.63 kB
JavaScript
import { release } from '@awayfl/swf-loader';
import { assert } from '@awayjs/graphics';
import { IndentingWriter } from '@awayfl/swf-loader';
import { RuntimeTraits } from './RuntimeTraits';
import { RuntimeTraitInfo } from './RuntimeTraitInfo';
import { createMethodForTrait } from './createMethodForTrait';
/**
* The Traits class represents the collection of compile-time traits associated with a type.
* It's not used for runtime name resolution on instances; instead, the combined traits for
* a type and all its super types is resolved and translated to an instance of RuntimeTraits.
*/
var Traits = /** @class */ (function () {
function Traits(traits, global) {
if (global === void 0) { global = false; }
this.traits = traits;
var multinames = global ? (this._multinames = Traits._globalMultinames) : (this._multinames = {});
for (var i = 0; i < this.traits.length; i++) {
var trait = this.traits[i];
var mn = trait.multiname;
multinames[mn.namespaces[0].uri + '.' + mn.name] = trait;
}
}
Traits.prototype.attachHolder = function (holder) {
for (var i = 0; i < this.traits.length; i++) {
release || assert(!this.traits[i].holder);
this.traits[i].holder = holder;
}
};
Traits.prototype.trace = function (writer) {
if (writer === void 0) { writer = new IndentingWriter(); }
this.traits.forEach(function (x) { return writer.writeLn(x.toString()); });
};
Traits.getGlobalTrait = function (mn) {
var nm = mn.name;
var t;
for (var _i = 0, _a = mn.namespaces; _i < _a.length; _i++) {
var ns = _a[_i];
if ((t = Traits._globalMultinames[ns.uri + '.' + nm]))
return t;
}
return null;
};
Traits.prototype.getTrait = function (mn) {
var nm = mn.name;
var t;
for (var _i = 0, _a = mn.namespaces; _i < _a.length; _i++) {
var ns = _a[_i];
if ((t = this._multinames[ns.uri + '.' + nm]) && t.holder.traits === this)
return t;
}
return null;
};
/**
* Turns a list of compile-time traits into runtime traits with resolved bindings.
*
* Runtime traits are stored in 2-dimensional maps. The outer dimension is keyed on the
* trait's local name. The inner dimension is a map of mangled namespace names to traits.
*
* Lookups are thus O(n) in the number of namespaces present in the query, instead of O(n+m)
* in the number of traits (n) on the type times the number of namespaces present in the
* query (m).
*
* Negative result note: an implementation with ECMAScript Maps with Namespace objects as
* keys was tried and found to be much slower than the Object-based one implemented here.
* Mostly, the difference was in how well accesses are optimized in JS engines, with Maps
* being new-ish and less well-optimized.
*
* Additionally, all protected traits get added to a map with their unqualified name as key.
* That map is created with the super type's map on its prototype chain. If a type overrides
* a protected trait, it gets set as that type's value for the unqualified name. Additionally,
* its name is canonicalized to use the namespace used in the initially introducing type.
* During name lookup, we first check for a hit in that map and (after verifying that the mn
* has a correct protected name in its namespaces set) return the most recent trait. That way,
* all lookups always get the most recent trait, even if they originate from a super class.
*/
Traits.prototype.resolveRuntimeTraits = function (superTraits, protectedNs, scope, forceNativeMethods) {
if (forceNativeMethods === void 0) { forceNativeMethods = false; }
// Resolve traits so that indexOf works out.
var protectedNsMappings = Object.create(superTraits ? superTraits.protectedNsMappings : null);
var result = new RuntimeTraits(superTraits, protectedNs, protectedNsMappings);
// Add all of the child traits, replacing or extending parent traits where necessary.
for (var i = 0; i < this.traits.length; i++) {
var trait = this.traits[i];
var mn = trait.multiname;
var runtimeTrait = new RuntimeTraitInfo(mn, trait.kind, trait.abc);
if (mn.namespaces[0].type === 1 /* NamespaceType.Protected */) {
// Names for protected traits get canonicalized to the name of the type that initially
// introduces the trait.
if (result.protectedNsMappings[mn.name]) {
runtimeTrait.multiname = result.protectedNsMappings[mn.name].multiname;
}
result.protectedNsMappings[mn.name] = runtimeTrait;
}
var currentTrait = result.addTrait(runtimeTrait);
switch (trait.kind) {
case 1 /* TRAIT.Method */:
runtimeTrait.value = createMethodForTrait(trait, scope, forceNativeMethods);
break;
case 2 /* TRAIT.Getter */:
runtimeTrait.get = createMethodForTrait(trait, scope, forceNativeMethods);
if (currentTrait && currentTrait.set) {
runtimeTrait.set = currentTrait.set;
runtimeTrait.kind = 7 /* TRAIT.GetterSetter */;
}
break;
case 3 /* TRAIT.Setter */:
runtimeTrait.set = createMethodForTrait(trait, scope, forceNativeMethods);
if (currentTrait && currentTrait.get) {
runtimeTrait.get = currentTrait.get;
runtimeTrait.kind = 7 /* TRAIT.GetterSetter */;
}
break;
case 0 /* TRAIT.Slot */:
case 6 /* TRAIT.Const */:
case 4 /* TRAIT.Class */:
// Only non-const slots need to be writable. Everything else is fixed.
runtimeTrait.writable = true;
runtimeTrait.slot = trait.slot;
runtimeTrait.value = trait.getDefaultValue();
runtimeTrait.typeName = trait.typeName;
// TODO: Throw error for const without default.
result.addSlotTrait(runtimeTrait);
}
}
return result;
};
Traits._globalMultinames = {};
return Traits;
}());
export { Traits };