function serialize(interpreter) {
  // Shallow-copy all properties of interest onto a root object.
  var properties = [
    'OBJECT',
    'OBJECT_PROTO',
    'FUNCTION',
    'FUNCTION_PROTO',
    'ARRAY',
    'ARRAY_PROTO',
    'REGEXP',
    'REGEXP_PROTO',
    'BOOLEAN',
    'DATE',
    'NUMBER',
    'STRING',
    'ERROR',
    'EVAL_ERROR',
    'RANGE_ERROR',
    'REFERENCE_ERROR',
    'SYNTAX_ERROR',
    'TYPE_ERROR',
    'URI_ERROR',
    'globalScope',
    'globalObject',
    'stateStack',
    'tasks',
  ];
  var root = Object.create(null);
  for (var i = 0; i < properties.length; i++) {
    root[properties[i]] = interpreter[properties[i]];
  }

  recordAcornConstructors_(interpreter);
  // Find all objects.
  objectList = [];
  objectMap && objectMap.clear();
  objectHunt_(root);
  // Serialize every object.
  var json = [];
  for (var i = 0; i < objectList.length; i++) {
    var jsonObj = Object.create(null);
    json.push(jsonObj);
    var obj = objectList[i];
    // Uncomment this line for a debugging label.
    //jsonObj['#'] = i;
    switch (Object.getPrototypeOf(obj)) {
      case null:
        jsonObj['type'] = 'Map';
        break;
      case Object.prototype:
        if (obj === Interpreter.SCOPE_REFERENCE) {
          jsonObj['type'] = 'ScopeReference';
          continue; // No need to index properties.
        }
        jsonObj['type'] = 'Object';
        break;
      case Function.prototype:
        jsonObj['type'] = 'Function';
        jsonObj['id'] = obj.id;
        if (obj.id === undefined) {
          throw Error('Native function has no ID: ' + obj);
        }
        continue; // No need to index properties.
      case Array.prototype:
        // Currently we assume that Arrays are not sparse.
        jsonObj['type'] = 'Array';
        if (obj.length) {
          jsonObj['data'] = obj.map(encodeValue_);
        }
        continue; // No need to index properties.
      case Date.prototype:
        jsonObj['type'] = 'Date';
        jsonObj['data'] = obj.toJSON();
        continue; // No need to index properties.
      case RegExp.prototype:
        jsonObj['type'] = 'RegExp';
        jsonObj['source'] = obj.source;
        jsonObj['flags'] = obj.flags;
        continue; // No need to index properties.
      case Interpreter.Object.prototype:
        jsonObj['type'] = 'PseudoObject';
        break;
      case Interpreter.Scope.prototype:
        jsonObj['type'] = 'Scope';
        break;
      case Interpreter.State.prototype:
        jsonObj['type'] = 'State';
        break;
      case Interpreter.Task.prototype:
        jsonObj['type'] = 'Task';
        break;
      case NODE_CONSTRUCTOR.prototype:
        jsonObj['type'] = 'Node';
        break;
      default:
        throw TypeError('Unknown type: ' + obj);
    }
    var props = Object.create(null);
    var nonConfigurable = [];
    var nonEnumerable = [];
    var nonWritable = [];
    var getter = [];
    var setter = [];
    var names = Object.getOwnPropertyNames(obj);
    for (var j = 0; j < names.length; j++) {
      var name = names[j];
      if (jsonObj['type'] === 'Node' && name === 'loc') {
        jsonObj['loc'] = encodeLoc_(obj.loc);
      } else {
        var descriptor = Object.getOwnPropertyDescriptor(obj, name);
        props[name] = encodeValue_(descriptor.value);
        if (!descriptor.configurable) {
          nonConfigurable.push(name);
        }
        if (!descriptor.enumerable) {
          nonEnumerable.push(name);
        }
        if (!descriptor.writable) {
          nonWritable.push(name);
        }
        if (descriptor.get) {
          getter.push(name);
        }
        if (descriptor.set) {
          setter.push(name);
        }
      }
    }
    if (names.length) {
      jsonObj['props'] = props;
    }
    if (nonConfigurable.length) {
      jsonObj['nonConfigurable'] = nonConfigurable;
    }
    if (nonEnumerable.length) {
      jsonObj['nonEnumerable'] = nonEnumerable;
    }
    if (nonWritable.length) {
      jsonObj['nonWritable'] = nonWritable;
    }
    if (getter.length) {
      jsonObj['getter'] = getter;
    }
    if (setter.length) {
      jsonObj['setter'] = setter;
    }
  }
  // Garbage collect.
  objectList = [];
  objectMap && objectMap.clear();
  return json;
}
Generate JSON that completely describes an interpreter's state.
@param {!Interpreter} interpreter JS-Interpreter instance to serialize.
@returns {string} Serialized JSON.