UNPKG

@awayfl/avm2

Version:

Virtual machine for executing AS3 code

131 lines (130 loc) 6.63 kB
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 };