<commit_before>var traceScope = options.register(new Option("traceScope", "ts", false, "trace scope execution"));
var traceToplevel = options.register(new Option("traceToplevel", "ttl", false, "trace top level execution"));
var traceClasses = options.register(new Option("traceClasses", "tc", false, "trace class creation"));
var traceExecution = options.register(new Option("traceExecution", "tx", false, "trace script execution"));

const ALWAYS_INTERPRET = 0x1;
const HEURISTIC_JIT = 0x2;

const jsGlobal = (function() { return this || (1, eval)('this'); })();

function defineReadOnlyProperty(obj, name, value) {
  Object.defineProperty(obj, name, { value: value, writable: false, configurable: false, enumerable: false });
}

function defineGetterAndSetter(obj, name, getter, setter) {
  Object.defineProperty(obj, name, { get: getter, set: setter });
}

/**
 * Override the [] operator by wrapping it in accessor (get/set) functions. This is necessary because in AS3,
 * the [] operator has different semantics depending on whether the receiver is an Array or Vector. For the
 * latter we need to coerce values whenever they are stored.
 */

const GET_ACCESSOR = "$get";
const SET_ACCESSOR = "$set";

defineReadOnlyProperty(Object.prototype, GET_ACCESSOR, function (i) {
  return this[i];
});

defineReadOnlyProperty(Object.prototype, SET_ACCESSOR, function (i, v) {
  this[i] = v;
});

/**
 * Gets the next name index of an object. Index |zero| is actually not an index,
 * but rather an indicator that no such index exists.
 */
defineReadOnlyProperty(Object.prototype, "nextNameIndex", function (index) {
  if (index < Object.keys(this).length) {
    return index + 1;
  }
  return 0;
});

/**
 * Gets the nextName after the specified |index|, which you would expect to be index + 1, but
 * it's actually index - 1;
 */
defineReadOnlyProperty(Object.prototype, "nextName", function (index) {
  var keys = Object.keys(this);
  assert (index > 0 && index < keys.length + 1);
  return keys[index - 1];
});

var builtinClasses = (function () {
  var builtins = {};
  // TODO: Figure out why adding Function here blows the stack.
  [Object, Number, String, Array, Boolean].forEach(function (cls) {
    builtins[cls.name] = cls;
  });
  return builtins;
})();

function toDouble(x) {
  return Number(x);
}

function toBoolean(x) {
  return Boolean(x);
}

function toUint(x) {
  var obj = x | 0;
  return obj < 0 ? (obj + 4294967296) : obj;
}

function toInt(x) {
  return parseInt(x);
}

function toString(x) {
  return new String(x);
}

/**
 * Similar to |toString| but returns |null| for |null| or |undefined| instead
 * of "null" or "undefined".
 */
function coerceString(x) {
  if (!x) {
    return null;
  }
  return new String(x);
}

function typeOf(x) {
  if (x === null) {
    return typeof x;
  }
  var type = typeof x;
  if (type === "object") {
    return typeof (x.valueOf());
  }
  return type;
}

function deleteProperty(obj, multiname) {
  var resolved = resolveMultiname(obj, multiname, false);
  if (resolved) {
    return delete obj[resolved.getQualifiedName()];
  }
  return false;
}

function applyType(factory, types) {
  if (factory === toplevel.Vector) {
    assert (types.length === 1);
    return Vector(types[0]);
  }
  notImplemented();
  return undefined;
}

function Vector(type) {
  function vector() {
    this.push.apply(this, arguments);
  }
  vector.prototype = Object.create(Array.prototype);
  return vector;
}

Vector.coerce = function(x) {
  return x;
};

Array.coerce = function(x) {
  return x;
};

function nextName(obj, index) {
  return obj.nextName(index);
}

/**
 * Determine if the given object has any more properties after the specified |index| in the given |obj|
 * and if so, return the next index or |zero| otherwise. If the |obj| has no more properties then continue
 * the search in |obj.__proto__|. This function returns an updated index and object to be used during
 * iteration.
 *
 * the |for (x in obj) { ... }| statement is compiled into the following pseudo bytecode:
 *
 * index = 0;
 * while (true) {
 *   (obj, index) = hasNext2(obj, index);
 *   if (index) { #1
 *     x = nextName(obj, index); #2
 *   } else {
 *     break;
 *   }
 * }
 *
 * #1 If we return zero, the iteration stops.
 * #2 The spec says we need to get the nextName at index + 1, but it's actually index - 1, this caused
 * me two hours of my life that I will probably never get back.
 *
 * TODO: We can't match the iteration order semantics of Action Script, hopefully programmers don't rely on it.
 */
function hasNext2(obj, index) {
  assert (obj);
  assert (index >= 0);

  var nextNameIndex = obj.nextNameIndex(index);
  if (!nextNameIndex) {
    obj = obj.__proto__;
    index = obj ? obj.nextNameIndex(0) : 0;
  } else {
    index = nextNameIndex;
  }
  return {index: index, object: obj};
}

/**
 * Scopes are used to emulate the scope stack as a linked list of scopes, rather than a stack. Each
 * scope holds a reference to a scope [object] (which may exist on multiple scope chains, thus preventing
 * us from chaining the scope objects together directly).
 *
 * Scope Operations:
 *
 *  push scope: scope = new Scope(scope, object)
 *  pop scope: scope = scope.parent
 *  get global scope: scope.global
 *  get scope object: scope.object
 *
 * Method closures have a [savedScope] property which is bound when the closure is created. Since we use a
 * linked list of scopes rather than a scope stack, we don't need to clone the scope stack, we can bind
 * the closure to the current scope.
 *
 * The "scope stack" for a method always starts off as empty and methods push and pop scopes on their scope
 * stack explicitly. If a property is not found on the current scope stack, it is then looked up
 * in the [savedScope]. To emulate this we actually wrap every generated function in a closure, such as
 *
 *  function fnClosure(scope) {
 *    return function fn() {
 *      ... scope;
 *    };
 *  }
 *
 * When functions are created, we bind the function to the current scope, using fnClosure.bind(null, this)();
 */
var Scope = (function () {
  function scope(parent, object) {
    this.parent = parent;
    this.object = object;
    this.global = parent ? parent.global : this;
  }

  scope.prototype.findProperty = function findProperty(multiname, strict) {
    if (traceScope.value) {
      print("Scope Find Property: " + multiname);
    }
    assert (this.object);
    for (var i = 0, j = multiname.namespaces.length; i < j; i++) {
      if (multiname.getQName(i).getQualifiedName() in this.object) {
        return this.object;
      }
    }
    if (this.parent) {
      return this.parent.findProperty(multiname, strict);
    }
    var obj;
    if ((obj = toplevel.findProperty(multiname, strict, true))) {
      return obj;
    }
    if (strict) {
      unexpected("Cannot find property " + multiname);
    }
    return this.global.object;
  };

  /**
   * Returns the first multiname that binds to a property in the scope chain.
   */
  scope.prototype.resolveMultiname = function(multiname) {
    assert (!multiname.isQName());
    var resolved = resolveMultiname(this.object, multiname);
    if (resolved) {
      return resolved;
    }
    if (this.parent) {
      return this.parent.resolveMultiname(multiname);
    }
    // FIXME: This doesn't work since it also returns what global it's found
    // in. When is this used?
    return toplevel.resolveMultiname(multiname);
  };

  scope.prototype.trace = function () {
    var current = this;
    while (current) {
      print(current.object + (current.object ? " - " + current.object.debugName : ""));
      current = current.parent;
    }
  };

  return scope;
})();

/**
 * Resolve the [multiname] to a QName in the specified [obj], this is a linear search that uses [hasOwnProperty]
 * with the qualified name.
 */
function resolveMultiname(obj, multiname, checkPrototype) {
  assert (!multiname.isQName(), "We shouldn't resolve an already resolved name: " + multiname);
  obj = Object(obj);
  for (var i = 0, count = multiname.namespaces.length; i < count; i++) {
    var name = multiname.getQName(i);
    if (checkPrototype) {
      if (name.getQualifiedName() in obj) {
        return name;
      }
    } else {
      if (obj.hasOwnProperty(name.getQualifiedName())) {
        return name;
      }
    }
  }
  return null;
}

function getProperty(obj, multiname) {
  if (multiname.isQName()) {
    if (typeof multiname.name === "number") {
      return obj[GET_ACCESSOR](multiname.name);
    } else {
      return obj[multiname.getQualifiedName()];
    }
  } else {
    var resolved = resolveMultiname(obj, multiname, true);
    if (resolved) {
      print("resolved " + resolved);
      print(obj[resolved.getQualifiedName()]);
      return obj[resolved.getQualifiedName()];
    }
  }
  return undefined;
}

function setProperty(obj, multiname, value) {
  if (multiname.isQName()) {
    if (typeof multiname.name === "number") {
      obj[SET_ACCESSOR](multiname.name, value);
    } else {
      obj[multiname.getQualifiedName()] = value;
    }
  } else {
    var resolved = resolveMultiname(Object.getPrototypeOf(obj), multiname, true);
    if (resolved) {
      obj[resolved.getQualifiedName()] = value;
    } else {
      // If we can't resolve the multiname, we're probably adding a dynamic
      // property, so just go ahead and use its name directly.
      // TODO: Remove assertion and loop when we're certain it will never fail.
      var publicNSIndex;
      for (var i = 0, j = multiname.namespaces.length; i < j; i++) {
        if (multiname.namespaces[i].isPublic()) {
          publicNSIndex = i;
          break;
        }
      }
      assert(multiname.getQName(publicNSIndex).getQualifiedName() === "public$" + multiname.name);
      obj["public$" + multiname.name] = value;
    }
  }
}

/**
 * Global object for a script.
 */
var Global = (function () {
  function Global(runtime, script) {
    script.global = this;
    runtime.applyTraits(this, script.traits, undefined, new Scope(null, this));
    script.loaded = true;
  }
  return Global;
})();

/**
 * Toplevel that keeps track of all parsed ABCs and caches stuff.
 */
const toplevel = (function () {

  function Toplevel() {
    /* All ABCs that have been parsed. */
    this.abcs = [];

    /* Classes that have been loaded. */
    this.loadedClasses = [];
    // TODO: Caching
  }

  Toplevel.prototype = {
    getTypeByName: function getTypeByName(multiname, strict, execute) {
      var resolved = this.resolveMultiname(multiname, execute);
      if (resolved) {
        return resolved.object[resolved.name.getQualifiedName()];
      }
      if (strict) {
        return unexpected("Cannot find type " + multiname);
      } else {
        return null;
      }
    },

    findProperty: function findProperty(multiname, strict, execute) {
      if (traceToplevel.value) {
        print("Toplevel Find Property: " + multiname);
      }
      var resolved = this.resolveMultiname(multiname, execute);
      if (resolved) {
        return resolved.object;
      }
      if (strict) {
        return unexpected("Cannot find property " + multiname);
      } else {
        return null;
      }
      return null;
    },

    resolveMultiname: function _resolveMultiname(multiname, execute) {
      function ensureScriptIsExecuted(abc, script) {
        if (!script.executed && !script.executing) {
          executeScript(abc, script);
        }
      }
      var abcs = this.abcs;
      for (var i = 0, j = abcs.length; i < j; i++) {
        var abc = abcs[i];
        var scripts = abc.scripts;
        for (var k = 0, l = scripts.length; k < l; k++) {
          var script = scripts[k];
          var global = script.global;
          if (multiname.isQName()) {
            if (script.global.hasOwnProperty(multiname.getQualifiedName())) {
              if (traceToplevel.value) {
                print("Toplevel Resolved Multiname: " + multiname + " in " + abc + ", script: " + k);
                print("Script is executed ? " + script.executed + ", should we: " + execute + " is it in progress: " + script.executing);
                print("Value is: " + script.global[multiname.getQualifiedName()]);
              }
              if (execute) {
                ensureScriptIsExecuted(abc, script);
              }
              return { object: global, name: multiname };
            }
          } else {
            var resolved = resolveMultiname(global, multiname, false);
            if (resolved) {
              if (execute) {
                ensureScriptIsExecuted(abc, script);
              }
              return { object: global, name: resolved };
            }
          }
        }
      }
      return null;
    }
  };


  var toplevel = new Toplevel();
  /* FIXME: Only in place until we can run playerglobals. */
  // builtins.__proto__ = Global.prototype;
  // toplevel.abcs.push({ scripts: [{ global: builtins,
  //                                 executed: true }] });
  return toplevel;

})();

/**
 * Execution context for a script.
 */
var Runtime = (function () {
  var functionCount = 0;

  function VerifyError(m) {
    this.m = m;
  }

  VerifyError.prototype = {
    toString: function () {
      return this.m;
    }
  };


  function runtime(abc, mode) {
    this.abc = abc;
    this.mode = mode;
    this.compiler = new Compiler(abc);
    this.interpreter = new Interpreter(abc);

    /**
     * All runtime exceptions are boxed in this object to tag them as having
     * originated from within the VM.
     */
    this.exception = { value: undefined };
  }

  runtime.prototype.createActivation = function (method) {
    return Object.create(method.activationPrototype);
  };

  runtime.prototype.createFunction = function (method, scope) {
    if (method.isNative()) {
      return natives[method.name.getQualifiedName()] ||
        function() {
          print("Calling undefined native method: " + method.name.getQualifiedName());
        };
    }

    function closeOverScope(fn, scope) {
      return function () {
        Array.prototype.unshift.call(arguments, scope);
        var global = (this === jsGlobal ? scope.global.object : this);
        return fn.apply(global, arguments);
      };
    }

    function interpretedMethod(interpreter, method, scope) {
      return function () {
        var global = (this === jsGlobal ? scope.global.object : this);
        return interpreter.interpretMethod(global, method, scope, arguments);
      };
    }

    const mode = this.mode;

    /**
     * We use not having an analysis to mean "not initialized".
     */
    if (!method.analysis) {
      method.analysis = new Analysis(method, { massage: true });
      method.activationPrototype = this.applyTraits({}, method.traits);
    }

    if (mode === ALWAYS_INTERPRET) {
      return interpretedMethod(this.interpreter, method, scope);
    }

    if (method.compiledMethod) {
      return closeOverScope(method.compiledMethod, scope);
    }

    if (!method.analysis.restructureControlFlow()) {
      return interpretedMethod(this.interpreter, method, scope);
    }

    var result = this.compiler.compileMethod(method, scope);

    var parameters = method.parameters.map(function (p) {
      return p.name;
    });

    parameters.unshift(SAVED_SCOPE_NAME);

    function flatten(array, indent) {
      var str = "";
      array.forEach(function (x) {
        if (x instanceof Indent) {
          str += flatten(x.statements, indent + "  ");
        } else if (x instanceof Array) {
          str += flatten(x, indent);
        } else {
          str += indent + x + "\n";
        }
      });
      return str;
    }

    var body = flatten(result.statements, "");
    if (traceLevel.value > 4) {
      print('\033[93m' + body + '\033[0m');
    }
    method.compiledMethod = new Function(parameters, body);

    /* Hook to set breakpoints in compiled code. */
    if (functionCount == 13) {
      body = "stop();" + body;
    }

    if (traceLevel.value > 0) {
      /* Unfortunately inner functions are not pretty-printed by the JS engine, so here we recompile the
       * inner function by itself just for pretty printing purposes.
       */
      eval ("function fnSource" + functionCount + " (" + parameters.join(", ") + ") { " + body + " }");
      print('\033[92m' + eval("fnSource" + functionCount) + '\033[0m');
    }

    functionCount++;
    return closeOverScope(method.compiledMethod, scope);
  };

  /**
   * ActionScript Classes are modeled as constructor functions (class objects) which hold additional properties:
   *
   * [scope]: a scope object holding the current class object
   *
   * [baseClass]: a reference to the base class object
   *
   * [instanceTraits]: an accumulated set of traits that are to be applied to instances of this class
   *
   * [prototype]: the prototype object of this constructor function  is populated with the set of instance traits,
   *   when instances are of this class are created, their __proto__ is set to this object thus inheriting this
   *   default set of properties.
   *
   * [construct]: a reference to the class object itself, this is used when invoking the constructor with an already
   *   constructed object (i.e. constructsuper)
   *
   * additionally, the class object also has a set of class traits applied to it which are visible via scope lookups.
   */
  runtime.prototype.createClass = function createClass(classInfo, baseClass, scope) {
    scope = new Scope(scope, null);
    var className = classInfo.instance.name.name;
    if (traceExecution.value) {
      print("Creating class " + className  + (builtinClasses[className] ? " replaced with builtin " + builtinClasses[className].name : ""));
    }

    var cls = builtinClasses[className] || this.createFunction(classInfo.instance.init, scope);
    cls.debugName = "[class " + className + "]";
    scope.object = cls;

    var instanceTraits = classInfo.instance.traits;

    cls.scope = scope;
    cls.classInfo = classInfo;
    cls.baseClass = baseClass;
    cls.instanceTraits = instanceTraits;
    defineGetterAndSetter(cls, "public$prototype",
                          function () {
                            return this.prototype;
                          },
                          function (p) {
                            this.prototype = p;
                          }
                         );

    cls.prototype = baseClass ? Object.create(baseClass.prototype) : {};
    cls.prototype.debugName = "[class " + className + "].prototype";

    var baseTraits = baseClass ? baseClass.instanceTraits : new Traits([], true);
    this.applyTraits(cls.prototype, instanceTraits, baseTraits, scope);
    this.applyTraits(cls, classInfo.traits, null, scope);

    if (traceClasses.value) {
      toplevel.loadedClasses.push(cls);
      var writer = new IndentingWriter();
      writer.enter(cls.debugName + " {");
      writer.enter("instance");
      writer.writeArray(Object.keys(cls.prototype));
      writer.leave("");
      writer.enter("static");
      writer.writeArray(Object.keys(cls));
      writer.leave("");
      writer.leave("}");
    }

    /* Call the static constructor. */
    this.createFunction(classInfo.init, scope).call(cls);

    return cls;
  };

  /* Extend builtin Objects so they behave as classes. */
  Object.instanceTraits = new Traits([]);<commit_msg>Added option to trace property access, added uint/int built-ins.<commit_after>var traceScope = options.register(new Option("traceScope", "ts", false, "trace scope execution"));
var traceToplevel = options.register(new Option("traceToplevel", "ttl", false, "trace top level execution"));
var traceClasses = options.register(new Option("traceClasses", "tc", false, "trace class creation"));
var traceExecution = options.register(new Option("traceExecution", "tx", false, "trace script execution"));
var tracePropertyAccess = options.register(new Option("tracePropertyAccess", "tpa", false, "trace property access"));

const ALWAYS_INTERPRET = 0x1;
const HEURISTIC_JIT = 0x2;

const jsGlobal = (function() { return this || (1, eval)('this'); })();

function defineReadOnlyProperty(obj, name, value) {
  Object.defineProperty(obj, name, { value: value, writable: false, configurable: false, enumerable: false });
}

function defineGetterAndSetter(obj, name, getter, setter) {
  Object.defineProperty(obj, name, { get: getter, set: setter });
}

/**
 * Override the [] operator by wrapping it in accessor (get/set) functions. This is necessary because in AS3,
 * the [] operator has different semantics depending on whether the receiver is an Array or Vector. For the
 * latter we need to coerce values whenever they are stored.
 */

const GET_ACCESSOR = "$get";
const SET_ACCESSOR = "$set";

defineReadOnlyProperty(Object.prototype, GET_ACCESSOR, function (i) {
  return this[i];
});

defineReadOnlyProperty(Object.prototype, SET_ACCESSOR, function (i, v) {
  this[i] = v;
});

/**
 * Gets the next name index of an object. Index |zero| is actually not an index,
 * but rather an indicator that no such index exists.
 */
defineReadOnlyProperty(Object.prototype, "nextNameIndex", function (index) {
  if (index < Object.keys(this).length) {
    return index + 1;
  }
  return 0;
});

/**
 * Gets the nextName after the specified |index|, which you would expect to be index + 1, but
 * it's actually index - 1;
 */
defineReadOnlyProperty(Object.prototype, "nextName", function (index) {
  var keys = Object.keys(this);
  assert (index > 0 && index < keys.length + 1);
  return keys[index - 1];
});

var builtinClasses = (function () {
  var builtins = {};
  // TODO: Figure out why adding Function here blows the stack.
  [Object, Number, String, Array, Boolean].forEach(function (cls) {
    builtins[cls.name] = cls;
  });


  builtins.int = function int(x) {
    return Number(x) | 0;
  };

  builtins.uint = function uint(x) {
    return Number(x) >>> 0;
  };

  return builtins;
})();

function toDouble(x) {
  return Number(x);
}

function toBoolean(x) {
  return Boolean(x);
}

function toUint(x) {
  var obj = x | 0;
  return obj < 0 ? (obj + 4294967296) : obj;
}

function toInt(x) {
  return parseInt(x);
}

function toString(x) {
  return new String(x);
}

/**
 * Similar to |toString| but returns |null| for |null| or |undefined| instead
 * of "null" or "undefined".
 */
function coerceString(x) {
  if (!x) {
    return null;
  }
  return new String(x);
}

function typeOf(x) {
  if (x === null) {
    return typeof x;
  }
  var type = typeof x;
  if (type === "object") {
    return typeof (x.valueOf());
  }
  return type;
}

function deleteProperty(obj, multiname) {
  var resolved = resolveMultiname(obj, multiname, false);
  if (resolved) {
    return delete obj[resolved.getQualifiedName()];
  }
  return false;
}

function applyType(factory, types) {
  if (factory === toplevel.Vector) {
    assert (types.length === 1);
    return Vector(types[0]);
  }
  notImplemented();
  return undefined;
}

function Vector(type) {
  function vector() {
    this.push.apply(this, arguments);
  }
  vector.prototype = Object.create(Array.prototype);
  return vector;
}

Vector.coerce = function(x) {
  return x;
};

Array.coerce = function(x) {
  return x;
};

function nextName(obj, index) {
  return obj.nextName(index);
}

/**
 * Determine if the given object has any more properties after the specified |index| in the given |obj|
 * and if so, return the next index or |zero| otherwise. If the |obj| has no more properties then continue
 * the search in |obj.__proto__|. This function returns an updated index and object to be used during
 * iteration.
 *
 * the |for (x in obj) { ... }| statement is compiled into the following pseudo bytecode:
 *
 * index = 0;
 * while (true) {
 *   (obj, index) = hasNext2(obj, index);
 *   if (index) { #1
 *     x = nextName(obj, index); #2
 *   } else {
 *     break;
 *   }
 * }
 *
 * #1 If we return zero, the iteration stops.
 * #2 The spec says we need to get the nextName at index + 1, but it's actually index - 1, this caused
 * me two hours of my life that I will probably never get back.
 *
 * TODO: We can't match the iteration order semantics of Action Script, hopefully programmers don't rely on it.
 */
function hasNext2(obj, index) {
  assert (obj);
  assert (index >= 0);

  var nextNameIndex = obj.nextNameIndex(index);
  if (!nextNameIndex) {
    obj = obj.__proto__;
    index = obj ? obj.nextNameIndex(0) : 0;
  } else {
    index = nextNameIndex;
  }
  return {index: index, object: obj};
}

/**
 * Scopes are used to emulate the scope stack as a linked list of scopes, rather than a stack. Each
 * scope holds a reference to a scope [object] (which may exist on multiple scope chains, thus preventing
 * us from chaining the scope objects together directly).
 *
 * Scope Operations:
 *
 *  push scope: scope = new Scope(scope, object)
 *  pop scope: scope = scope.parent
 *  get global scope: scope.global
 *  get scope object: scope.object
 *
 * Method closures have a [savedScope] property which is bound when the closure is created. Since we use a
 * linked list of scopes rather than a scope stack, we don't need to clone the scope stack, we can bind
 * the closure to the current scope.
 *
 * The "scope stack" for a method always starts off as empty and methods push and pop scopes on their scope
 * stack explicitly. If a property is not found on the current scope stack, it is then looked up
 * in the [savedScope]. To emulate this we actually wrap every generated function in a closure, such as
 *
 *  function fnClosure(scope) {
 *    return function fn() {
 *      ... scope;
 *    };
 *  }
 *
 * When functions are created, we bind the function to the current scope, using fnClosure.bind(null, this)();
 */
var Scope = (function () {
  function scope(parent, object) {
    this.parent = parent;
    this.object = object;
    this.global = parent ? parent.global : this;
  }

  scope.prototype.findProperty = function findProperty(multiname, strict) {
    if (traceScope.value || tracePropertyAccess.value) {
      print("scopeFindProperty: " + multiname);
    }
    assert (this.object);
    for (var i = 0, j = multiname.namespaces.length; i < j; i++) {
      if (multiname.getQName(i).getQualifiedName() in this.object) {
        return this.object;
      }
    }
    if (this.parent) {
      return this.parent.findProperty(multiname, strict);
    }
    var obj;
    if ((obj = toplevel.findProperty(multiname, strict, true))) {
      return obj;
    }
    if (strict) {
      unexpected("Cannot find property " + multiname);
    }
    return this.global.object;
  };

  /**
   * Returns the first multiname that binds to a property in the scope chain.
   */
  scope.prototype.resolveMultiname = function(multiname) {
    assert (!multiname.isQName());
    var resolved = resolveMultiname(this.object, multiname);
    if (resolved) {
      return resolved;
    }
    if (this.parent) {
      return this.parent.resolveMultiname(multiname);
    }
    // FIXME: This doesn't work since it also returns what global it's found
    // in. When is this used?
    return toplevel.resolveMultiname(multiname);
  };

  scope.prototype.trace = function () {
    var current = this;
    while (current) {
      print(current.object + (current.object ? " - " + current.object.debugName : ""));
      current = current.parent;
    }
  };

  return scope;
})();

/**
 * Resolve the [multiname] to a QName in the specified [obj], this is a linear search that uses [hasOwnProperty]
 * with the qualified name.
 */
function resolveMultiname(obj, multiname, checkPrototype) {
  assert (!multiname.isQName(), "We shouldn't resolve an already resolved name: " + multiname);
  obj = Object(obj);
  for (var i = 0, count = multiname.namespaces.length; i < count; i++) {
    var name = multiname.getQName(i);
    if (checkPrototype) {
      if (name.getQualifiedName() in obj) {
        return name;
      }
    } else {
      if (obj.hasOwnProperty(name.getQualifiedName())) {
        return name;
      }
    }
  }
  return null;
}

function getProperty(obj, multiname) {
  if (multiname.isQName()) {
    if (typeof multiname.name === "number") {
      return obj[GET_ACCESSOR](multiname.name);
    } else {
      if (tracePropertyAccess.value) {
        print("getProperty: multiname: " + multiname + " value: " + obj[multiname.getQualifiedName()]);
      }
      return obj[multiname.getQualifiedName()];
    }
  } else {
    var resolved = resolveMultiname(obj, multiname, true);
    if (resolved) {
      if (tracePropertyAccess.value) {
        print("getProperty: resolved multiname: " + resolved + " value: " + obj[resolved.getQualifiedName()]);
      }
      return obj[resolved.getQualifiedName()];
    }
  }
  return undefined;
}

function setProperty(obj, multiname, value) {
  if (multiname.isQName()) {
    if (typeof multiname.name === "number") {
      obj[SET_ACCESSOR](multiname.name, value);
    } else {
      if (tracePropertyAccess.value) {
        print("setProperty: multiname: " + multiname + " value: " + obj[multiname.getQualifiedName()]);
      }
      obj[multiname.getQualifiedName()] = value;
    }
  } else {
    var resolved = resolveMultiname(Object.getPrototypeOf(obj), multiname, true);
    if (resolved) {
      if (tracePropertyAccess.value) {
        print("setProperty: resolved multiname: " + resolved + " value: " + value);
      }
      obj[resolved.getQualifiedName()] = value;
    } else {
      // If we can't resolve the multiname, we're probably adding a dynamic
      // property, so just go ahead and use its name directly.
      // TODO: Remove assertion and loop when we're certain it will never fail.
      var publicNSIndex;
      for (var i = 0, j = multiname.namespaces.length; i < j; i++) {
        if (multiname.namespaces[i].isPublic()) {
          publicNSIndex = i;
          break;
        }
      }
      if (tracePropertyAccess.value) {
        print("setProperty: adding public: " + multiname + " value: " + value);
      }
      assert(multiname.getQName(publicNSIndex).getQualifiedName() === "public$" + multiname.name);
      obj["public$" + multiname.name] = value;
    }
  }
}

/**
 * Global object for a script.
 */
var Global = (function () {
  function Global(runtime, script) {
    script.global = this;
    runtime.applyTraits(this, script.traits, undefined, new Scope(null, this));
    script.loaded = true;
  }
  return Global;
})();

/**
 * Toplevel that keeps track of all parsed ABCs and caches stuff.
 */
const toplevel = (function () {

  function Toplevel() {
    /* All ABCs that have been parsed. */
    this.abcs = [];

    /* Classes that have been loaded. */
    this.loadedClasses = [];
    // TODO: Caching
  }

  Toplevel.prototype = {
    getTypeByName: function getTypeByName(multiname, strict, execute) {
      var resolved = this.resolveMultiname(multiname, execute);
      if (resolved) {
        return resolved.object[resolved.name.getQualifiedName()];
      }
      if (strict) {
        return unexpected("Cannot find type " + multiname);
      } else {
        return null;
      }
    },

    findProperty: function findProperty(multiname, strict, execute) {
      if (traceToplevel.value) {
        print("Toplevel Find Property: " + multiname);
      }
      var resolved = this.resolveMultiname(multiname, execute);
      if (resolved) {
        return resolved.object;
      }
      if (strict) {
        return unexpected("Cannot find property " + multiname);
      } else {
        return null;
      }
      return null;
    },

    resolveMultiname: function _resolveMultiname(multiname, execute) {
      function ensureScriptIsExecuted(abc, script) {
        if (!script.executed && !script.executing) {
          executeScript(abc, script);
        }
      }
      var abcs = this.abcs;
      for (var i = 0, j = abcs.length; i < j; i++) {
        var abc = abcs[i];
        var scripts = abc.scripts;
        for (var k = 0, l = scripts.length; k < l; k++) {
          var script = scripts[k];
          var global = script.global;
          if (multiname.isQName()) {
            if (script.global.hasOwnProperty(multiname.getQualifiedName())) {
              if (traceToplevel.value) {
                print("Toplevel Resolved Multiname: " + multiname + " in " + abc + ", script: " + k);
                print("Script is executed ? " + script.executed + ", should we: " + execute + " is it in progress: " + script.executing);
                print("Value is: " + script.global[multiname.getQualifiedName()]);
              }
              if (execute) {
                ensureScriptIsExecuted(abc, script);
              }
              return { object: global, name: multiname };
            }
          } else {
            var resolved = resolveMultiname(global, multiname, false);
            if (resolved) {
              if (execute) {
                ensureScriptIsExecuted(abc, script);
              }
              return { object: global, name: resolved };
            }
          }
        }
      }
      return null;
    }
  };


  var toplevel = new Toplevel();
  /* FIXME: Only in place until we can run playerglobals. */
  // builtins.__proto__ = Global.prototype;
  // toplevel.abcs.push({ scripts: [{ global: builtins,
  //                                 executed: true }] });
  return toplevel;

})();

/**
 * Execution context for a script.
 */
var Runtime = (function () {
  var functionCount = 0;

  function VerifyError(m) {
    this.m = m;
  }

  VerifyError.prototype = {
    toString: function () {
      return this.m;
    }
  };


  function runtime(abc, mode) {
    this.abc = abc;
    this.mode = mode;
    this.compiler = new Compiler(abc);
    this.interpreter = new Interpreter(abc);

    /**
     * All runtime exceptions are boxed in this object to tag them as having
     * originated from within the VM.
     */
    this.exception = { value: undefined };
  }

  runtime.prototype.createActivation = function (method) {
    return Object.create(method.activationPrototype);
  };

  runtime.prototype.createFunction = function (method, scope) {
    if (method.isNative()) {
      return natives[method.name.getQualifiedName()] ||
        function() {
          print("Calling undefined native method: " + method.name.getQualifiedName());
        };
    }

    function closeOverScope(fn, scope) {
      return function () {
        Array.prototype.unshift.call(arguments, scope);
        var global = (this === jsGlobal ? scope.global.object : this);
        return fn.apply(global, arguments);
      };
    }

    function interpretedMethod(interpreter, method, scope) {
      return function () {
        var global = (this === jsGlobal ? scope.global.object : this);
        return interpreter.interpretMethod(global, method, scope, arguments);
      };
    }

    const mode = this.mode;

    /**
     * We use not having an analysis to mean "not initialized".
     */
    if (!method.analysis) {
      method.analysis = new Analysis(method, { massage: true });
      method.activationPrototype = this.applyTraits({}, method.traits);
    }

    if (mode === ALWAYS_INTERPRET) {
      return interpretedMethod(this.interpreter, method, scope);
    }

    if (method.compiledMethod) {
      return closeOverScope(method.compiledMethod, scope);
    }

    if (!method.analysis.restructureControlFlow()) {
      return interpretedMethod(this.interpreter, method, scope);
    }

    var result = this.compiler.compileMethod(method, scope);

    var parameters = method.parameters.map(function (p) {
      return p.name;
    });

    parameters.unshift(SAVED_SCOPE_NAME);

    function flatten(array, indent) {
      var str = "";
      array.forEach(function (x) {
        if (x instanceof Indent) {
          str += flatten(x.statements, indent + "  ");
        } else if (x instanceof Array) {
          str += flatten(x, indent);
        } else {
          str += indent + x + "\n";
        }
      });
      return str;
    }

    var body = flatten(result.statements, "");
    if (traceLevel.value > 4) {
      print('\033[93m' + body + '\033[0m');
    }
    method.compiledMethod = new Function(parameters, body);

    /* Hook to set breakpoints in compiled code. */
    if (functionCount == 13) {
      body = "stop();" + body;
    }

    if (traceLevel.value > 0) {
      /* Unfortunately inner functions are not pretty-printed by the JS engine, so here we recompile the
       * inner function by itself just for pretty printing purposes.
       */
      eval ("function fnSource" + functionCount + " (" + parameters.join(", ") + ") { " + body + " }");
      print('\033[92m' + eval("fnSource" + functionCount) + '\033[0m');
    }

    functionCount++;
    return closeOverScope(method.compiledMethod, scope);
  };

  /**
   * ActionScript Classes are modeled as constructor functions (class objects) which hold additional properties:
   *
   * [scope]: a scope object holding the current class object
   *
   * [baseClass]: a reference to the base class object
   *
   * [instanceTraits]: an accumulated set of traits that are to be applied to instances of this class
   *
   * [prototype]: the prototype object of this constructor function  is populated with the set of instance traits,
   *   when instances are of this class are created, their __proto__ is set to this object thus inheriting this
   *   default set of properties.
   *
   * [construct]: a reference to the class object itself, this is used when invoking the constructor with an already
   *   constructed object (i.e. constructsuper)
   *
   * additionally, the class object also has a set of class traits applied to it which are visible via scope lookups.
   */
  runtime.prototype.createClass = function createClass(classInfo, baseClass, scope) {
    scope = new Scope(scope, null);
    var className = classInfo.instance.name.name;
    if (traceExecution.value) {
      print("Creating class " + className  + (builtinClasses[className] ? " replaced with builtin " + builtinClasses[className].name : ""));
    }

    var cls = builtinClasses[className] || this.createFunction(classInfo.instance.init, scope);
    cls.debugName = "[class " + className + "]";
    scope.object = cls;

    var instanceTraits = classInfo.instance.traits;

    cls.scope = scope;
    cls.classInfo = classInfo;
    cls.baseClass = baseClass;
    cls.instanceTraits = instanceTraits;

    /**
     * Each AS3 class needs its own explicit public prototype.
     */
    defineGetterAndSetter(cls, "public$prototype",
      function () {
        return this.prototype;
      },
      function (p) {
        this.prototype = p;
      }
    );

    cls.prototype = baseClass ? Object.create(baseClass.prototype) : {};
    cls.prototype.debugName = "[class " + className + "].prototype";

    var baseTraits = baseClass ? baseClass.instanceTraits : new Traits([], true);
    this.applyTraits(cls.prototype, instanceTraits, baseTraits, scope);
    this.applyTraits(cls, classInfo.traits, null, scope);

    if (traceClasses.value) {
      toplevel.loadedClasses.push(cls);
      var writer = new IndentingWriter();
      writer.enter(cls.debugName + " {");
      writer.enter("instance");
      writer.writeArray(Object.keys(cls.prototype));
      writer.leave("");
      writer.enter("static");
      writer.writeArray(Object.keys(cls));
      writer.leave("");
      writer.leave("}");
    }

    /* Call the static constructor. */
    this.createFunction(classInfo.init, scope).call(cls);

    return cls;
  };

  /* Extend builtin Objects so they behave as classes. */
  Object.instanceTraits = new Traits([]);
{'fraction_non_alphanumeric': 0.12357793305249351, 'fraction_numerical': 0.0034558434023661175, 'mean_word_length': 2.8122896432349265, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 14, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1506069', 'n_tokens_mistral': 11952, 'n_tokens_neox': 11686, 'n_words': 3537}