| Index: src/js/json.js
 | 
| diff --git a/src/js/json.js b/src/js/json.js
 | 
| index a7b692000870b1722be6d914383559675eed23f1..eee56afae05c20d8e88e20e23f8e59234520f6e9 100644
 | 
| --- a/src/js/json.js
 | 
| +++ b/src/js/json.js
 | 
| @@ -15,7 +15,26 @@
 | 
|  var GlobalJSON = global.JSON;
 | 
|  var GlobalSet = global.Set;
 | 
|  var InternalArray = utils.InternalArray;
 | 
| +var MakeTypeError;
 | 
| +var MaxSimple;
 | 
| +var MinSimple;
 | 
| +var ObjectHasOwnProperty;
 | 
| +var Stack;
 | 
| +var StackHas;
 | 
| +var StackPop;
 | 
| +var StackPush;
 | 
|  var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol");
 | 
| +
 | 
| +utils.Import(function(from) {
 | 
| +  MakeTypeError = from.MakeTypeError;
 | 
| +  MaxSimple = from.MaxSimple;
 | 
| +  MinSimple = from.MinSimple;
 | 
| +  ObjectHasOwnProperty = from.ObjectHasOwnProperty;
 | 
| +  Stack = from.Stack;
 | 
| +  StackHas = from.StackHas;
 | 
| +  StackPop = from.StackPop;
 | 
| +  StackPush = from.StackPush;
 | 
| +});
 | 
|  
 | 
|  // -------------------------------------------------------------------
 | 
|  
 | 
| @@ -65,6 +84,137 @@
 | 
|    }
 | 
|  }
 | 
|  
 | 
| +
 | 
| +function SerializeArray(value, replacer, stack, indent, gap) {
 | 
| +  if (StackHas(stack, value)) throw MakeTypeError(kCircularStructure);
 | 
| +  StackPush(stack, value);
 | 
| +  var stepback = indent;
 | 
| +  indent += gap;
 | 
| +  var partial = new InternalArray();
 | 
| +  var len = TO_LENGTH(value.length);
 | 
| +  for (var i = 0; i < len; i++) {
 | 
| +    var strP = JSONSerialize(%_NumberToString(i), value, replacer, stack,
 | 
| +                             indent, gap);
 | 
| +    if (IS_UNDEFINED(strP)) {
 | 
| +      strP = "null";
 | 
| +    }
 | 
| +    partial.push(strP);
 | 
| +  }
 | 
| +  var final;
 | 
| +  if (gap == "") {
 | 
| +    final = "[" + partial.join(",") + "]";
 | 
| +  } else if (partial.length > 0) {
 | 
| +    var separator = ",\n" + indent;
 | 
| +    final = "[\n" + indent + partial.join(separator) + "\n" +
 | 
| +        stepback + "]";
 | 
| +  } else {
 | 
| +    final = "[]";
 | 
| +  }
 | 
| +  StackPop(stack);
 | 
| +  return final;
 | 
| +}
 | 
| +
 | 
| +
 | 
| +function SerializeObject(value, replacer, stack, indent, gap) {
 | 
| +  if (StackHas(stack, value)) throw MakeTypeError(kCircularStructure);
 | 
| +  StackPush(stack, value);
 | 
| +  var stepback = indent;
 | 
| +  indent += gap;
 | 
| +  var partial = new InternalArray();
 | 
| +  var keys = %object_keys(value);
 | 
| +  for (var i = 0; i < keys.length; i++) {
 | 
| +    var p = keys[i];
 | 
| +    var strP = JSONSerialize(p, value, replacer, stack, indent, gap);
 | 
| +    if (!IS_UNDEFINED(strP)) {
 | 
| +      var member = %QuoteJSONString(p) + ":";
 | 
| +      if (gap != "") member += " ";
 | 
| +      member += strP;
 | 
| +      partial.push(member);
 | 
| +    }
 | 
| +  }
 | 
| +  var final;
 | 
| +  if (gap == "") {
 | 
| +    final = "{" + partial.join(",") + "}";
 | 
| +  } else if (partial.length > 0) {
 | 
| +    var separator = ",\n" + indent;
 | 
| +    final = "{\n" + indent + partial.join(separator) + "\n" +
 | 
| +        stepback + "}";
 | 
| +  } else {
 | 
| +    final = "{}";
 | 
| +  }
 | 
| +  StackPop(stack);
 | 
| +  return final;
 | 
| +}
 | 
| +
 | 
| +
 | 
| +function JSONSerialize(key, holder, replacer, stack, indent, gap) {
 | 
| +  var value = holder[key];
 | 
| +  if (IS_RECEIVER(value)) {
 | 
| +    var toJSON = value.toJSON;
 | 
| +    if (IS_CALLABLE(toJSON)) {
 | 
| +      value = %_Call(toJSON, value, key);
 | 
| +    }
 | 
| +  }
 | 
| +  if (IS_CALLABLE(replacer)) {
 | 
| +    value = %_Call(replacer, holder, key, value);
 | 
| +  }
 | 
| +  if (IS_STRING(value)) {
 | 
| +    return %QuoteJSONString(value);
 | 
| +  } else if (IS_NUMBER(value)) {
 | 
| +    return JSON_NUMBER_TO_STRING(value);
 | 
| +  } else if (IS_BOOLEAN(value)) {
 | 
| +    return value ? "true" : "false";
 | 
| +  } else if (IS_NULL(value)) {
 | 
| +    return "null";
 | 
| +  } else if (IS_RECEIVER(value) && !IS_CALLABLE(value)) {
 | 
| +    // Non-callable object. If it's a primitive wrapper, it must be unwrapped.
 | 
| +    if (%is_arraylike(value)) {
 | 
| +      return SerializeArray(value, replacer, stack, indent, gap);
 | 
| +    } else if (IS_NUMBER_WRAPPER(value)) {
 | 
| +      value = TO_NUMBER(value);
 | 
| +      return JSON_NUMBER_TO_STRING(value);
 | 
| +    } else if (IS_STRING_WRAPPER(value)) {
 | 
| +      return %QuoteJSONString(TO_STRING(value));
 | 
| +    } else if (IS_BOOLEAN_WRAPPER(value)) {
 | 
| +      return %_ValueOf(value) ? "true" : "false";
 | 
| +    } else {
 | 
| +      return SerializeObject(value, replacer, stack, indent, gap);
 | 
| +    }
 | 
| +  }
 | 
| +  // Undefined or a callable object.
 | 
| +  return UNDEFINED;
 | 
| +}
 | 
| +
 | 
| +
 | 
| +function JSONStringify(value, replacer, space) {
 | 
| +  if (arguments.length === 1) return %BasicJSONStringify(value, UNDEFINED, "");
 | 
| +  if (!IS_CALLABLE(replacer)) {
 | 
| +    return %BasicJSONStringify(value, replacer, space);
 | 
| +  }
 | 
| +  if (IS_OBJECT(space)) {
 | 
| +    // Unwrap 'space' if it is wrapped
 | 
| +    if (IS_NUMBER_WRAPPER(space)) {
 | 
| +      space = TO_NUMBER(space);
 | 
| +    } else if (IS_STRING_WRAPPER(space)) {
 | 
| +      space = TO_STRING(space);
 | 
| +    }
 | 
| +  }
 | 
| +  var gap;
 | 
| +  if (IS_NUMBER(space)) {
 | 
| +    space = MaxSimple(0, MinSimple(TO_INTEGER(space), 10));
 | 
| +    gap = %_SubString("          ", 0, space);
 | 
| +  } else if (IS_STRING(space)) {
 | 
| +    if (space.length > 10) {
 | 
| +      gap = %_SubString(space, 0, 10);
 | 
| +    } else {
 | 
| +      gap = space;
 | 
| +    }
 | 
| +  } else {
 | 
| +    gap = "";
 | 
| +  }
 | 
| +  return JSONSerialize('', {'': value}, replacer, new Stack(), "", gap);
 | 
| +}
 | 
| +
 | 
|  // -------------------------------------------------------------------
 | 
|  
 | 
|  %AddNamedProperty(GlobalJSON, toStringTagSymbol, "JSON", READ_ONLY | DONT_ENUM);
 | 
| @@ -72,6 +222,7 @@
 | 
|  // Set up non-enumerable properties of the JSON object.
 | 
|  utils.InstallFunctions(GlobalJSON, DONT_ENUM, [
 | 
|    "parse", JSONParse,
 | 
| +  "stringify", JSONStringify
 | 
|  ]);
 | 
|  
 | 
|  // -------------------------------------------------------------------
 | 
| 
 |