- Extended lazy loading to general objects, not just functions.
authorchristian.plesner.hansen@gmail.com <christian.plesner.hansen@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 24 Apr 2009 08:13:09 +0000 (08:13 +0000)
committerchristian.plesner.hansen@gmail.com <christian.plesner.hansen@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 24 Apr 2009 08:13:09 +0000 (08:13 +0000)
- Added lazily loaded JSON object.

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1784 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

23 files changed:
src/SConscript [changed mode: 0755->0644]
src/arm/ic-arm.cc
src/bootstrapper.cc
src/contexts.h
src/date-delay.js
src/handles.cc
src/handles.h
src/heap.cc
src/ia32/ic-ia32.cc
src/json-delay.js [new file with mode: 0644]
src/macros.py
src/messages.js
src/objects-debug.cc
src/objects-inl.h
src/objects.cc
src/objects.h
src/property.h
src/string.js
src/v8natives.js
test/mjsunit/json.js [new file with mode: 0644]
test/mjsunit/mjsunit.js
tools/visual_studio/js2c.cmd
tools/visual_studio/v8.vcproj

old mode 100755 (executable)
new mode 100644 (file)
index 0d11e69..b0a2933
@@ -123,6 +123,7 @@ debug-delay.js
 mirror-delay.js
 date-delay.js
 regexp-delay.js
+json-delay.js
 '''.split()
 
 
index ad6eb2c..e418556 100644 (file)
@@ -127,27 +127,20 @@ static void GenerateDictionaryLoad(MacroAssembler* masm,
 }
 
 
-// Helper function used to check that a value is either not a function
-// or is loaded if it is a function.
-static void GenerateCheckNonFunctionOrLoaded(MacroAssembler* masm,
-                                             Label* miss,
-                                             Register value,
-                                             Register scratch) {
+// Helper function used to check that a value is either not an object
+// or is loaded if it is an object.
+static void GenerateCheckNonObjectOrLoaded(MacroAssembler* masm,
+                                           Label* miss,
+                                           Register value,
+                                           Register scratch) {
   Label done;
   // Check if the value is a Smi.
   __ tst(value, Operand(kSmiTagMask));
   __ b(eq, &done);
-  // Check if the value is a function.
-  __ ldr(scratch, FieldMemOperand(value, HeapObject::kMapOffset));
-  __ ldrb(scratch, FieldMemOperand(scratch, Map::kInstanceTypeOffset));
-  __ cmp(scratch, Operand(JS_FUNCTION_TYPE));
-  __ b(ne, &done);
-  // Check if the function has been loaded.
-  __ ldr(scratch,
-         FieldMemOperand(value, JSFunction::kSharedFunctionInfoOffset));
-  __ ldr(scratch,
-         FieldMemOperand(scratch, SharedFunctionInfo::kLazyLoadDataOffset));
-  __ cmp(scratch, Operand(Factory::undefined_value()));
+  // Check if the object has been loaded.
+  __ ldr(scratch, FieldMemOperand(value, JSObject::kMapOffset));
+  __ ldrb(scratch, FieldMemOperand(scratch, Map::kBitField2Offset));
+  __ tst(scratch, Operand(1 << Map::kNeedsLoading));
   __ b(ne, miss);
   __ bind(&done);
 }
@@ -284,9 +277,9 @@ static void GenerateNormalHelper(MacroAssembler* masm,
   __ b(ne, miss);
 
   // Check that the function has been loaded.
-  __ ldr(r0, FieldMemOperand(r1, JSFunction::kSharedFunctionInfoOffset));
-  __ ldr(r0, FieldMemOperand(r0, SharedFunctionInfo::kLazyLoadDataOffset));
-  __ cmp(r0, Operand(Factory::undefined_value()));
+  __ ldr(r0, FieldMemOperand(r1, JSObject::kMapOffset));
+  __ ldrb(r0, FieldMemOperand(r0, Map::kBitField2Offset));
+  __ tst(r0, Operand(1 << Map::kNeedsLoading));
   __ b(ne, miss);
 
   // Patch the receiver with the global proxy if necessary.
@@ -470,7 +463,7 @@ void LoadIC::GenerateNormal(MacroAssembler* masm) {
 
   __ bind(&probe);
   GenerateDictionaryLoad(masm, &miss, r1, r0);
-  GenerateCheckNonFunctionOrLoaded(masm, &miss, r0, r1);
+  GenerateCheckNonObjectOrLoaded(masm, &miss, r0, r1);
   __ Ret();
 
   // Global object access: Check access rights.
index eebec74..31d8c63 100644 (file)
@@ -741,6 +741,19 @@ void Genesis::CreateRoots(v8::Handle<v8::ObjectTemplate> global_template,
     global_context()->set_regexp_function(*regexp_fun);
   }
 
+  {  // -- J S O N
+    Handle<String> name = Factory::NewStringFromAscii(CStrVector("JSON"));
+    Handle<JSFunction> cons = Factory::NewFunction(
+        name,
+        Factory::the_hole_value());
+    cons->SetInstancePrototype(global_context()->initial_object_prototype());
+    cons->SetInstanceClassName(*name);
+    Handle<JSObject> json_object = Factory::NewJSObject(cons, TENURED);
+    ASSERT(json_object->IsJSObject());
+    SetProperty(global, name, json_object, DONT_ENUM);
+    global_context()->set_json_object(*json_object);
+  }
+
   {  // --- arguments_boilerplate_
     // Make sure we can recognize argument objects at runtime.
     // This is done by introducing an anonymous function with
@@ -1068,6 +1081,10 @@ bool Genesis::InstallNatives() {
               Natives::GetIndex("regexp"),
               Top::global_context(),
               Handle<Context>(Top::context()->runtime_context()));
+    SetupLazy(Handle<JSObject>(global_context()->json_object()),
+              Natives::GetIndex("json"),
+              Top::global_context(),
+              Handle<Context>(Top::context()->runtime_context()));
 
   } else if (strlen(FLAG_natives_file) != 0) {
     // Otherwise install natives from natives file if file exists and
index 3c25bd3..ed4b1cf 100644 (file)
@@ -64,6 +64,7 @@ enum ContextLookupFlags {
   V(OBJECT_FUNCTION_INDEX, JSFunction, object_function) \
   V(ARRAY_FUNCTION_INDEX, JSFunction, array_function) \
   V(DATE_FUNCTION_INDEX, JSFunction, date_function) \
+  V(JSON_OBJECT_INDEX, JSObject, json_object) \
   V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \
   V(INITIAL_OBJECT_PROTOTYPE_INDEX, JSObject, initial_object_prototype) \
   V(CREATE_DATE_FUN_INDEX, JSFunction,  create_date_fun) \
@@ -186,6 +187,7 @@ class Context: public FixedArray {
     OBJECT_FUNCTION_INDEX,
     ARRAY_FUNCTION_INDEX,
     DATE_FUNCTION_INDEX,
+    JSON_OBJECT_INDEX,
     REGEXP_FUNCTION_INDEX,
     CREATE_DATE_FUN_INDEX,
     TO_NUMBER_FUN_INDEX,
index 2421e5b..dbb9c2c 100644 (file)
@@ -985,6 +985,25 @@ function DateToGMTString() {
 }
 
 
+function PadInt(n) {
+  // Format integers to have at least two digits.
+  return n < 10 ? '0' + n : n;
+}
+
+
+function DateToISOString() {
+  return this.getUTCFullYear() + '-' + PadInt(this.getUTCMonth() + 1) +
+      '-' + PadInt(this.getUTCDate()) + 'T' + PadInt(this.getUTCHours()) +
+      ':' + PadInt(this.getUTCMinutes()) + ':' + PadInt(this.getUTCSeconds()) +
+      'Z';
+}
+
+
+function DateToJSON(key) {
+  return CheckJSONPrimitive(this.toISOString());
+}
+
+
 // -------------------------------------------------------------------
 
 function SetupDate() {
@@ -1044,7 +1063,9 @@ function SetupDate() {
     "toGMTString", DateToGMTString,
     "toUTCString", DateToUTCString,
     "getYear", DateGetYear,
-    "setYear", DateSetYear
+    "setYear", DateSetYear,
+    "toISOString", DateToISOString,
+    "toJSON", DateToJSON
   ));
 }
 
index 99161ce..6cd5006 100644 (file)
@@ -627,9 +627,9 @@ OptimizedObjectForAddingMultipleProperties::
 }
 
 
-void LoadLazy(Handle<JSFunction> fun, bool* pending_exception) {
+void LoadLazy(Handle<JSObject> obj, bool* pending_exception) {
   HandleScope scope;
-  Handle<FixedArray> info(FixedArray::cast(fun->shared()->lazy_load_data()));
+  Handle<FixedArray> info(FixedArray::cast(obj->map()->constructor()));
   int index = Smi::cast(info->get(0))->value();
   ASSERT(index >= 0);
   Handle<Context> compile_context(Context::cast(info->get(1)));
@@ -674,27 +674,39 @@ void LoadLazy(Handle<JSFunction> fun, bool* pending_exception) {
 
   // Reset the lazy load data before running the script to make sure
   // not to get recursive lazy loading.
-  fun->shared()->set_lazy_load_data(Heap::undefined_value());
+  obj->map()->set_needs_loading(false);
+  obj->map()->set_constructor(info->get(3));
 
   // Run the script.
   Handle<JSFunction> script_fun(
       Factory::NewFunctionFromBoilerplate(boilerplate, function_context));
   Execution::Call(script_fun, receiver, 0, NULL, pending_exception);
 
-  // If lazy loading failed, restore the unloaded state of fun.
-  if (*pending_exception) fun->shared()->set_lazy_load_data(*info);
+  // If lazy loading failed, restore the unloaded state of obj.
+  if (*pending_exception) {
+    obj->map()->set_needs_loading(true);
+    obj->map()->set_constructor(*info);
+  }
 }
 
 
-void SetupLazy(Handle<JSFunction> fun,
+void SetupLazy(Handle<JSObject> obj,
                int index,
                Handle<Context> compile_context,
                Handle<Context> function_context) {
-  Handle<FixedArray> arr = Factory::NewFixedArray(3);
+  Handle<FixedArray> arr = Factory::NewFixedArray(4);
   arr->set(0, Smi::FromInt(index));
   arr->set(1, *compile_context);  // Compile in this context
   arr->set(2, *function_context);  // Set function context to this
-  fun->shared()->set_lazy_load_data(*arr);
+  arr->set(3, obj->map()->constructor());  // Remember the constructor
+  Handle<Map> old_map(obj->map());
+  Handle<Map> new_map = Factory::CopyMap(old_map);
+  obj->set_map(*new_map);
+  new_map->set_needs_loading(true);
+  // Store the lazy loading info in the constructor field.  We'll
+  // reestablish the constructor from the fixed array after loading.
+  new_map->set_constructor(*arr);
+  ASSERT(!obj->IsLoaded());
 }
 
 } }  // namespace v8::internal
index 9cc1db4..652d6c7 100644 (file)
@@ -301,11 +301,11 @@ bool CompileLazy(Handle<JSFunction> function, ClearExceptionFlag flag);
 bool CompileLazyInLoop(Handle<JSFunction> function, ClearExceptionFlag flag);
 
 // These deal with lazily loaded properties.
-void SetupLazy(Handle<JSFunction> fun,
+void SetupLazy(Handle<JSObject> obj,
                int index,
                Handle<Context> compile_context,
                Handle<Context> function_context);
-void LoadLazy(Handle<JSFunction> fun, bool* pending_exception);
+void LoadLazy(Handle<JSObject> obj, bool* pending_exception);
 
 class NoHandleAllocation BASE_EMBEDDED {
  public:
index a57884c..0775a5d 100644 (file)
@@ -939,6 +939,7 @@ Object* Heap::AllocateMap(InstanceType instance_type, int instance_size) {
   map->set_code_cache(empty_fixed_array());
   map->set_unused_property_fields(0);
   map->set_bit_field(0);
+  map->set_bit_field2(0);
   return map;
 }
 
@@ -1409,7 +1410,6 @@ Object* Heap::AllocateSharedFunctionInfo(Object* name) {
   share->set_formal_parameter_count(0);
   share->set_instance_class_name(Object_symbol());
   share->set_function_data(undefined_value());
-  share->set_lazy_load_data(undefined_value());
   share->set_script(undefined_value());
   share->set_start_position_and_type(0);
   share->set_debug_info(undefined_value());
index 664303f..559ac24 100644 (file)
@@ -123,23 +123,19 @@ static void GenerateDictionaryLoad(MacroAssembler* masm, Label* miss_label,
 }
 
 
-// Helper function used to check that a value is either not a function
-// or is loaded if it is a function.
-static void GenerateCheckNonFunctionOrLoaded(MacroAssembler* masm, Label* miss,
-                                             Register value, Register scratch) {
+// Helper function used to check that a value is either not an object
+// or is loaded if it is an object.
+static void GenerateCheckNonObjectOrLoaded(MacroAssembler* masm, Label* miss,
+                                           Register value, Register scratch) {
   Label done;
   // Check if the value is a Smi.
   __ test(value, Immediate(kSmiTagMask));
   __ j(zero, &done, not_taken);
-  // Check if the value is a function.
-  __ CmpObjectType(value, JS_FUNCTION_TYPE, scratch);
-  __ j(not_equal, &done, taken);
-  // Check if the function has been loaded.
-  __ mov(scratch, FieldOperand(value, JSFunction::kSharedFunctionInfoOffset));
-  __ mov(scratch,
-         FieldOperand(scratch, SharedFunctionInfo::kLazyLoadDataOffset));
-  __ cmp(scratch, Factory::undefined_value());
-  __ j(not_equal, miss, not_taken);
+  // Check if the object has been loaded.
+  __ mov(scratch, FieldOperand(value, JSFunction::kMapOffset));
+  __ mov(scratch, FieldOperand(scratch, Map::kBitField2Offset));
+  __ test(scratch, Immediate(1 << Map::kNeedsLoading));
+  __ j(not_zero, miss, not_taken);
   __ bind(&done);
 }
 
@@ -268,7 +264,7 @@ void KeyedLoadIC::GenerateGeneric(MacroAssembler* masm) {
   __ j(not_zero, &slow, not_taken);
   // Probe the dictionary leaving result in ecx.
   GenerateDictionaryLoad(masm, &slow, ebx, ecx, edx, eax);
-  GenerateCheckNonFunctionOrLoaded(masm, &slow, ecx, edx);
+  GenerateCheckNonObjectOrLoaded(masm, &slow, ecx, edx);
   __ mov(eax, Operand(ecx));
   __ IncrementCounter(&Counters::keyed_load_generic_symbol, 1);
   __ ret(0);
@@ -493,10 +489,10 @@ static void GenerateNormalHelper(MacroAssembler* masm,
   __ j(not_equal, miss, not_taken);
 
   // Check that the function has been loaded.
-  __ mov(edx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
-  __ mov(edx, FieldOperand(edx, SharedFunctionInfo::kLazyLoadDataOffset));
-  __ cmp(edx, Factory::undefined_value());
-  __ j(not_equal, miss, not_taken);
+  __ mov(edx, FieldOperand(edi, JSFunction::kMapOffset));
+  __ mov(edx, FieldOperand(edx, Map::kBitField2Offset));
+  __ test(edx, Immediate(1 << Map::kNeedsLoading));
+  __ j(not_zero, miss, not_taken);
 
   // Patch the receiver with the global proxy if necessary.
   if (is_global_object) {
@@ -683,7 +679,7 @@ void LoadIC::GenerateNormal(MacroAssembler* masm) {
   // Search the dictionary placing the result in eax.
   __ bind(&probe);
   GenerateDictionaryLoad(masm, &miss, edx, eax, ebx, ecx);
-  GenerateCheckNonFunctionOrLoaded(masm, &miss, eax, edx);
+  GenerateCheckNonObjectOrLoaded(masm, &miss, eax, edx);
   __ ret(0);
 
   // Global object access: Check access rights.
diff --git a/src/json-delay.js b/src/json-delay.js
new file mode 100644 (file)
index 0000000..3640d7c
--- /dev/null
@@ -0,0 +1,279 @@
+// Copyright 2009 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+var $JSON = global.JSON;
+
+function IsValidJSON(s) {
+  // All empty whitespace is not valid.
+  if (/^\s*$/.test(s))
+    return false;
+  // This is taken from http://www.json.org/json2.js which is released to the
+  // public domain.
+
+  var backslashesRe = /\\["\\\/bfnrtu]/g;
+  var simpleValuesRe =
+      /"[^"\\\n\r\x00-\x1f\x7f-\x9f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
+  var openBracketsRe = /(?:^|:|,)(?:[\s]*\[)+/g;
+  var remainderRe = /^[\],:{}\s]*$/;
+
+  return remainderRe.test(s.replace(backslashesRe, '@').
+      replace(simpleValuesRe, ']').
+      replace(openBracketsRe, ''));  
+}
+
+function ParseJSONUnfiltered(text) {
+  var s = $String(text);
+  if (IsValidJSON(s)) {
+    try {
+      return global.eval('(' + s + ')');
+    } catch (e) {
+      // ignore exceptions
+    }
+  }
+  throw MakeSyntaxError('invalid_json', [s]);
+}
+
+function Revive(holder, name, reviver) {
+  var val = holder[name];
+  if (IS_OBJECT(val)) {
+    if (IS_ARRAY(val)) {
+      var length = val.length;
+      for (var i = 0; i < length; i++) {
+        var newElement = Revive(val, $String(i), reviver);
+        val[i] = newElement;
+      }
+    } else {
+      for (var p in val) {
+        if (ObjectHasOwnProperty.call(val, p)) {
+          var newElement = Revive(val, p, reviver);
+          if (IS_UNDEFINED(newElement)) {
+            delete val[p];
+          } else {
+            val[p] = newElement;
+          }
+        }
+      }
+    }
+  }
+  return reviver.call(holder, name, val);
+}
+
+function JSONParse(text, reviver) {
+  var unfiltered = ParseJSONUnfiltered(text);
+  if (IS_FUNCTION(reviver)) {
+    return Revive({'': unfiltered}, '', reviver);
+  } else {
+    return unfiltered;
+  }
+}
+
+var characterQuoteCache = {
+  '\"': '\\"',
+  '\\': '\\\\',
+  '/': '\\/',
+  '\b': '\\b',
+  '\f': '\\f',
+  '\n': '\\n',
+  '\r': '\\r',
+  '\t': '\\t',
+  '\x0B': '\\u000b'
+};
+
+function QuoteSingleJSONCharacter(c) {
+  if (c in characterQuoteCache)
+    return characterQuoteCache[c];
+  var charCode = c.charCodeAt(0);
+  var result;
+  if (charCode < 16) result = '\\u000';
+  else if (charCode < 256) result = '\\u00';
+  else if (charCode < 4096) result = '\\u0';
+  else result = '\\u';
+  result += charCode.toString(16);
+  characterQuoteCache[c] = result;
+  return result;
+}
+
+function QuoteJSONString(str) {
+  var quotable = /[\\\"\x00-\x1f\x80-\uffff]/g;
+  return '"' + str.replace(quotable, QuoteSingleJSONCharacter) + '"';
+}
+
+function StackContains(stack, val) {
+  var length = stack.length;
+  for (var i = 0; i < length; i++) {
+    if (stack[i] === val)
+      return true;
+  }
+  return false;
+}
+
+function SerializeArray(value, replacer, stack, indent, gap) {
+  if (StackContains(stack, value))
+    throw MakeTypeError('circular_structure', []);
+  stack.push(value);
+  var stepback = indent;
+  indent += gap;
+  var partial = [];
+  var len = value.length;
+  for (var i = 0; i < len; i++) {
+    var strP = JSONSerialize($String(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 = "[]";
+  }
+  stack.pop();
+  return final;
+}
+
+function SerializeObject(value, replacer, stack, indent, gap) {
+  if (StackContains(stack, value))
+    throw MakeTypeError('circular_structure', []);
+  stack.push(value);
+  var stepback = indent;
+  indent += gap;
+  var partial = [];
+  if (IS_ARRAY(replacer)) {
+    var length = replacer.length;
+    for (var i = 0; i < length; i++) {
+      if (ObjectHasOwnProperty.call(replacer, i)) {
+        var p = replacer[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);
+        }
+      }
+    }
+  } else {
+    for (var p in value) {
+      if (ObjectHasOwnProperty.call(value, p)) {
+        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 = "{}";
+  }
+  stack.pop();
+  return final;
+}
+
+function JSONSerialize(key, holder, replacer, stack, indent, gap) {
+  var value = holder[key];
+  if (IS_OBJECT(value) && value) {
+    var toJSON = value.toJSON;
+    if (IS_FUNCTION(toJSON))
+      value = toJSON.call(value, key);
+  }
+  if (IS_FUNCTION(replacer))
+    value = replacer.call(holder, key, value);
+  // Unwrap value if necessary
+  if (IS_OBJECT(value)) {
+    if (IS_NUMBER_WRAPPER(value)) {
+      value = $Number(value);
+    } else if (IS_STRING_WRAPPER(value)) {
+      value = $String(value);
+    }
+  }
+  switch (typeof value) {
+    case "string":
+      return QuoteJSONString(value);
+    case "object":
+      if (!value) {
+        return "null";
+      } else if (IS_ARRAY(value)) {
+        return SerializeArray(value, replacer, stack, indent, gap);
+      } else {
+        return SerializeObject(value, replacer, stack, indent, gap);
+      }
+    case "number":
+      return $isFinite(value) ? $String(value) : "null";
+    case "boolean":
+      return value ? "true" : "false";
+  }
+}
+
+function JSONStringify(value, replacer, space) {
+  var stack = [];
+  var indent = "";
+  if (IS_OBJECT(space)) {
+    // Unwrap 'space' if it is wrapped
+    if (IS_NUMBER_WRAPPER(space)) {
+      space = $Number(space);
+    } else if (IS_STRING_WRAPPER(space)) {
+      space = $String(space);
+    }
+  }
+  var gap;
+  if (IS_NUMBER(space)) {
+    space = $Math.min(space, 100);
+    gap = "";
+    for (var i = 0; i < space; i++)
+      gap += " ";
+  } else if (IS_STRING(space)) {
+    gap = space;
+  } else {
+    gap = "";
+  }
+  return JSONSerialize('', {'': value}, replacer, stack, indent, gap);
+}
+
+function SetupJSON() {
+  InstallFunctions($JSON, DONT_ENUM, $Array(
+    "parse", JSONParse,
+    "stringify", JSONStringify
+  ));
+}
+
+SetupJSON();
index d78ecd9..ebfd816 100644 (file)
@@ -84,6 +84,8 @@ macro IS_BOOLEAN(arg)           = (typeof(arg) === 'boolean');
 macro IS_REGEXP(arg)            = %HasRegExpClass(arg);
 macro IS_ARRAY(arg)             = %HasArrayClass(arg);
 macro IS_DATE(arg)              = %HasDateClass(arg);
+macro IS_NUMBER_WRAPPER(arg)    = %HasNumberClass(arg);
+macro IS_STRING_WRAPPER(arg)    = %HasStringClass(arg);
 macro IS_ERROR(arg)             = (%ClassOf(arg) === 'Error');
 macro IS_SCRIPT(arg)            = (%ClassOf(arg) === 'Script');
 macro FLOOR(arg)                = %Math_floor(arg);
index fa6fb1f..df8a2d1 100644 (file)
@@ -114,6 +114,9 @@ const kMessages = {
   illegal_return:               "Illegal return statement",
   error_loading_debugger:       "Error loading debugger %0",
   no_input_to_regexp:           "No input to %0",
+  result_not_primitive:         "Result of %0 must be a primitive, was %1",
+  invalid_json:                 "String '%0' is not valid JSON",
+  circular_structure:           "Converting circular structure to JSON"
 };
 
 
index 635ef0f..e172014 100644 (file)
@@ -558,8 +558,6 @@ void SharedFunctionInfo::SharedFunctionInfoPrint() {
   code()->ShortPrint();
   PrintF("\n - source code = ");
   GetSourceCode()->ShortPrint();
-  PrintF("\n - lazy load: %s",
-         lazy_load_data() == Heap::undefined_value() ? "no" : "yes");
   // Script files are often large, hard to read.
   // PrintF("\n - script =");
   // script()->Print();
@@ -579,7 +577,6 @@ void SharedFunctionInfo::SharedFunctionInfoVerify() {
   VerifyObjectField(kCodeOffset);
   VerifyObjectField(kInstanceClassNameOffset);
   VerifyObjectField(kExternalReferenceDataOffset);
-  VerifyObjectField(kLazyLoadDataOffset);
   VerifyObjectField(kScriptOffset);
   VerifyObjectField(kDebugInfoOffset);
 }
index 73b9c84..ff64d65 100644 (file)
@@ -1811,6 +1811,16 @@ void Map::set_bit_field(byte value) {
 }
 
 
+byte Map::bit_field2() {
+  return READ_BYTE_FIELD(this, kBitField2Offset);
+}
+
+
+void Map::set_bit_field2(byte value) {
+  WRITE_BYTE_FIELD(this, kBitField2Offset, value);
+}
+
+
 void Map::set_non_instance_prototype(bool value) {
   if (value) {
     set_bit_field(bit_field() | (1 << kHasNonInstancePrototype));
@@ -2075,7 +2085,6 @@ ACCESSORS(SharedFunctionInfo, instance_class_name, Object,
           kInstanceClassNameOffset)
 ACCESSORS(SharedFunctionInfo, function_data, Object,
           kExternalReferenceDataOffset)
-ACCESSORS(SharedFunctionInfo, lazy_load_data, Object, kLazyLoadDataOffset)
 ACCESSORS(SharedFunctionInfo, script, Object, kScriptOffset)
 ACCESSORS(SharedFunctionInfo, debug_info, Object, kDebugInfoOffset)
 ACCESSORS(SharedFunctionInfo, inferred_name, String, kInferredNameOffset)
@@ -2141,8 +2150,8 @@ bool JSFunction::IsBoilerplate() {
 }
 
 
-bool JSFunction::IsLoaded() {
-  return shared()->lazy_load_data() == Heap::undefined_value();
+bool JSObject::IsLoaded() {
+  return !map()->needs_loading();
 }
 
 
index 31c5bab..9471ecd 100644 (file)
@@ -358,7 +358,7 @@ Object* JSObject::GetLazyProperty(Object* receiver,
   Handle<Object> receiver_handle(receiver);
   Handle<String> name_handle(name);
   bool pending_exception;
-  LoadLazy(Handle<JSFunction>(JSFunction::cast(result->GetValue())),
+  LoadLazy(Handle<JSObject>(JSObject::cast(result->GetLazyValue())),
            &pending_exception);
   if (pending_exception) return Failure::Exception();
   return this_handle->GetPropertyWithReceiver(*receiver_handle,
@@ -377,7 +377,7 @@ Object* JSObject::SetLazyProperty(LookupResult* result,
   Handle<String> name_handle(name);
   Handle<Object> value_handle(value);
   bool pending_exception;
-  LoadLazy(Handle<JSFunction>(JSFunction::cast(result->GetValue())),
+  LoadLazy(Handle<JSObject>(JSObject::cast(result->GetLazyValue())),
            &pending_exception);
   if (pending_exception) return Failure::Exception();
   return this_handle->SetProperty(*name_handle, *value_handle, attributes);
@@ -389,7 +389,7 @@ Object* JSObject::DeleteLazyProperty(LookupResult* result, String* name) {
   Handle<JSObject> this_handle(this);
   Handle<String> name_handle(name);
   bool pending_exception;
-  LoadLazy(Handle<JSFunction>(JSFunction::cast(result->GetValue())),
+  LoadLazy(Handle<JSObject>(JSObject::cast(result->GetLazyValue())),
            &pending_exception);
   if (pending_exception) return Failure::Exception();
   return this_handle->DeleteProperty(*name_handle);
@@ -2715,6 +2715,7 @@ Object* Map::Copy() {
   Map::cast(result)->set_inobject_properties(inobject_properties());
   Map::cast(result)->set_unused_property_fields(unused_property_fields());
   Map::cast(result)->set_bit_field(bit_field());
+  Map::cast(result)->set_bit_field2(bit_field2());
   Map::cast(result)->ClearCodeCache();
   return result;
 }
index db3c449..d17aeea 100644 (file)
@@ -1242,6 +1242,9 @@ class JSObject: public HeapObject {
                           String* name,
                           PropertyAttributes* attributes);
 
+  // Tells whether this object needs to be loaded.
+  inline bool IsLoaded();
+
   bool HasProperty(String* name) {
     return GetPropertyAttribute(name) != ABSENT;
   }
@@ -2397,6 +2400,10 @@ class Map: public HeapObject {
   inline byte bit_field();
   inline void set_bit_field(byte value);
 
+  // Bit field 2.
+  inline byte bit_field2();
+  inline void set_bit_field2(byte value);
+
   // Tells whether the object in the prototype property will be used
   // for instances created from this function.  If the prototype
   // property is set to a value that is not a JSObject, the prototype
@@ -2447,6 +2454,20 @@ class Map: public HeapObject {
     return ((1 << kIsUndetectable) & bit_field()) != 0;
   }
 
+  inline void set_needs_loading(bool value) {
+    if (value) {
+      set_bit_field2(bit_field2() | (1 << kNeedsLoading));
+    } else {
+      set_bit_field2(bit_field2() & ~(1 << kNeedsLoading));
+    }
+  }
+
+  // Does this object or function require a lazily loaded script to be
+  // run before being used?
+  inline bool needs_loading() {
+    return ((1 << kNeedsLoading) & bit_field2()) != 0;
+  }
+
   // Tells whether the instance has a call-as-function handler.
   inline void set_has_instance_call_handler() {
     set_bit_field(bit_field() | (1 << kHasInstanceCallHandler));
@@ -2550,7 +2571,7 @@ class Map: public HeapObject {
   static const int kInstanceTypeOffset = kInstanceAttributesOffset + 0;
   static const int kUnusedPropertyFieldsOffset = kInstanceAttributesOffset + 1;
   static const int kBitFieldOffset = kInstanceAttributesOffset + 2;
-  // The  byte at position 3 is not in use at the moment.
+  static const int kBitField2Offset = kInstanceAttributesOffset + 3;
 
   // Bit positions for bit field.
   static const int kUnused = 0;  // To be used for marking recently used maps.
@@ -2561,6 +2582,10 @@ class Map: public HeapObject {
   static const int kIsUndetectable = 5;
   static const int kHasInstanceCallHandler = 6;
   static const int kIsAccessCheckNeeded = 7;
+
+  // Bit positions for but field 2
+  static const int kNeedsLoading = 0;
+
  private:
   DISALLOW_IMPLICIT_CONSTRUCTORS(Map);
 };
@@ -2677,10 +2702,6 @@ class SharedFunctionInfo: public HeapObject {
   // on objects.
   DECL_ACCESSORS(function_data, Object)
 
-  // [lazy load data]: If the function has lazy loading, this field
-  // contains contexts and other data needed to load it.
-  DECL_ACCESSORS(lazy_load_data, Object)
-
   // [script info]: Script from which the function originates.
   DECL_ACCESSORS(script, Object)
 
@@ -2754,9 +2775,7 @@ class SharedFunctionInfo: public HeapObject {
       kExpectedNofPropertiesOffset + kIntSize;
   static const int kExternalReferenceDataOffset =
       kInstanceClassNameOffset + kPointerSize;
-  static const int kLazyLoadDataOffset =
-      kExternalReferenceDataOffset + kPointerSize;
-  static const int kScriptOffset = kLazyLoadDataOffset + kPointerSize;
+  static const int kScriptOffset = kExternalReferenceDataOffset + kPointerSize;
   static const int kStartPositionAndTypeOffset = kScriptOffset + kPointerSize;
   static const int kEndPositionOffset = kStartPositionAndTypeOffset + kIntSize;
   static const int kFunctionTokenPositionOffset = kEndPositionOffset + kIntSize;
@@ -2809,9 +2828,6 @@ class JSFunction: public JSObject {
   // function.
   inline bool IsBoilerplate();
 
-  // Tells whether this function needs to be loaded.
-  inline bool IsLoaded();
-
   // [literals]: Fixed array holding the materialized literals.
   //
   // If the function contains object, regexp or array literals, the
index 65d4a0d..60a9b54 100644 (file)
@@ -245,14 +245,25 @@ class LookupResult BASE_EMBEDDED {
   // Tells whether the value needs to be loaded.
   bool IsLoaded() {
     if (lookup_type_ == DESCRIPTOR_TYPE || lookup_type_ == DICTIONARY_TYPE) {
-      Object* value = GetValue();
-      if (value->IsJSFunction()) {
-        return JSFunction::cast(value)->IsLoaded();
-      }
+      Object* target = GetLazyValue();
+      return !target->IsJSObject() || JSObject::cast(target)->IsLoaded();
     }
     return true;
   }
 
+  Object* GetLazyValue() {
+    switch (type()) {
+      case FIELD:
+        return holder()->FastPropertyAt(GetFieldIndex());
+      case NORMAL:
+        return holder()->property_dictionary()->ValueAt(GetDictionaryEntry());
+      case CONSTANT_FUNCTION:
+        return GetConstantFunction();
+      default:
+        return Smi::FromInt(0);
+    }
+  }
+
   Map* GetTransitionMap() {
     ASSERT(lookup_type_ == DESCRIPTOR_TYPE);
     ASSERT(type() == MAP_TRANSITION);
index 3ccad9a..c7a838e 100644 (file)
@@ -812,6 +812,11 @@ ReplaceResultBuilder.prototype.generate = function() {
 }
 
 
+function StringToJSON(key) {
+  return CheckJSONPrimitive(this.valueOf());
+}
+
+
 // -------------------------------------------------------------------
 
 function SetupString() {
@@ -858,7 +863,8 @@ function SetupString() {
     "small", StringSmall,
     "strike", StringStrike,
     "sub", StringSub,
-    "sup", StringSup
+    "sup", StringSup,
+    "toJSON", StringToJSON
   ));
 }
 
index 9772e2f..9ed6558 100644 (file)
@@ -312,13 +312,19 @@ function BooleanValueOf() {
 }
 
 
+function BooleanToJSON(key) {
+  return CheckJSONPrimitive(this.valueOf());
+}
+
+
 // ----------------------------------------------------------------------------
 
 
 function SetupBoolean() {
   InstallFunctions($Boolean.prototype, DONT_ENUM, $Array(
     "toString", BooleanToString,
-    "valueOf", BooleanValueOf
+    "valueOf", BooleanValueOf,
+    "toJSON", BooleanToJSON
   ));
 }
 
@@ -418,6 +424,18 @@ function NumberToPrecision(precision) {
 }
 
 
+function CheckJSONPrimitive(val) {
+  if (!IsPrimitive(val))
+    throw MakeTypeError('result_not_primitive', ['toJSON', val]);
+  return val;
+}
+
+
+function NumberToJSON(key) {
+  return CheckJSONPrimitive(this.valueOf());
+}
+
+
 // ----------------------------------------------------------------------------
 
 function SetupNumber() {
@@ -455,7 +473,8 @@ function SetupNumber() {
     "valueOf", NumberValueOf,
     "toFixed", NumberToFixed,
     "toExponential", NumberToExponential,
-    "toPrecision", NumberToPrecision
+    "toPrecision", NumberToPrecision,
+    "toJSON", NumberToJSON
   ));
 }
 
diff --git a/test/mjsunit/json.js b/test/mjsunit/json.js
new file mode 100644 (file)
index 0000000..ce7d078
--- /dev/null
@@ -0,0 +1,195 @@
+// Copyright 2009 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+function GenericToJSONChecks(Constructor, value, alternative) {
+  var n1 = new Constructor(value);
+  n1.valueOf = function () { return alternative; };
+  assertEquals(alternative, n1.toJSON());
+  var n2 = new Constructor(value);
+  n2.valueOf = null;
+  assertThrows(function () { n2.toJSON(); }, TypeError);
+  var n3 = new Constructor(value);
+  n3.valueOf = function () { return {}; };
+  assertThrows(function () { n3.toJSON(); }, TypeError, 'result_not_primitive');
+  var n4 = new Constructor(value);
+  n4.valueOf = function () {
+    assertEquals(0, arguments.length);
+    assertEquals(this, n4);
+    return null;
+  };
+  assertEquals(null, n4.toJSON());
+}
+
+// Number toJSON
+assertEquals(3, (3).toJSON());
+assertEquals(3, (3).toJSON(true));
+assertEquals(4, (new Number(4)).toJSON());
+GenericToJSONChecks(Number, 5, 6);
+
+// Boolean toJSON
+assertEquals(true, (true).toJSON());
+assertEquals(true, (true).toJSON(false));
+assertEquals(false, (false).toJSON());
+assertEquals(true, (new Boolean(true)).toJSON());
+GenericToJSONChecks(Boolean, true, false);
+GenericToJSONChecks(Boolean, false, true);
+
+// String toJSON
+assertEquals("flot", "flot".toJSON());
+assertEquals("flot", "flot".toJSON(3));
+assertEquals("tolf", (new String("tolf")).toJSON());
+GenericToJSONChecks(String, "x", "y");
+
+// Date toJSON
+assertEquals("1970-01-01T00:00:00Z", new Date(0).toJSON());
+assertEquals("1979-01-11T08:00:00Z", new Date("1979-01-11 08:00 GMT").toJSON());
+assertEquals("2005-05-05T05:05:05Z", new Date("2005-05-05 05:05:05 GMT").toJSON());
+var n1 = new Date(10000);
+n1.toISOString = function () { return "foo"; };
+assertEquals("foo", n1.toJSON());
+var n2 = new Date(10001);
+n2.toISOString = null;
+assertThrows(function () { n2.toJSON(); }, TypeError);
+var n3 = new Date(10002);
+n3.toISOString = function () { return {}; };
+assertThrows(function () { n3.toJSON(); }, TypeError, "result_not_primitive");
+var n4 = new Date(10003);
+n4.toISOString = function () {
+  assertEquals(0, arguments.length);
+  assertEquals(this, n4);
+  return null;
+};
+assertEquals(null, n4.toJSON());
+
+assertEquals(Object.prototype, JSON.__proto__);
+assertEquals("[object JSON]", Object.prototype.toString.call(JSON));
+
+// DontEnum
+for (var p in this)
+  assertFalse(p == "JSON");
+
+// Parse
+
+assertEquals({}, JSON.parse("{}"));
+assertEquals(null, JSON.parse("null"));
+assertEquals(true, JSON.parse("true"));
+assertEquals(false, JSON.parse("false"));
+assertEquals("foo", JSON.parse('"foo"'));
+assertEquals("f\no", JSON.parse('"f\\no"'));
+assertEquals(1.1, JSON.parse("1.1"));
+assertEquals(1, JSON.parse("1.0"));
+assertEquals(0.0000000003, JSON.parse("3e-10"));
+assertEquals([], JSON.parse("[]"));
+assertEquals([1], JSON.parse("[1]"));
+assertEquals([1, "2", true, null], JSON.parse('[1, "2", true, null]'));
+
+function GetFilter(name) {
+  function Filter(key, value) {
+    return (key == name) ? undefined : value;
+  }
+  return Filter;
+}
+
+var pointJson = '{"x": 1, "y": 2}';
+assertEquals({'x': 1, 'y': 2}, JSON.parse(pointJson));
+assertEquals({'x': 1}, JSON.parse(pointJson, GetFilter('y')));
+assertEquals({'y': 2}, JSON.parse(pointJson, GetFilter('x')));
+assertEquals([1, 2, 3], JSON.parse("[1, 2, 3]"));
+assertEquals([1, undefined, 3], JSON.parse("[1, 2, 3]", GetFilter(1)));
+assertEquals([1, 2, undefined], JSON.parse("[1, 2, 3]", GetFilter(2)));
+
+function DoubleNumbers(key, value) {
+  return (typeof value == 'number') ? 2 * value : value;
+}
+
+var deepObject = '{"a": {"b": 1, "c": 2}, "d": {"e": {"f": 3}}}';
+assertEquals({"a": {"b": 1, "c": 2}, "d": {"e": {"f": 3}}},
+             JSON.parse(deepObject));
+assertEquals({"a": {"b": 2, "c": 4}, "d": {"e": {"f": 6}}},
+             JSON.parse(deepObject, DoubleNumbers));
+
+function TestInvalid(str) {
+  assertThrows(function () { JSON.parse(str); }, SyntaxError);
+}
+
+TestInvalid('"abc\x00def"');
+TestInvalid('"abc\x10def"');
+TestInvalid('"abc\x1fdef"');
+
+TestInvalid("[1, 2");
+TestInvalid('{"x": 3');
+
+// Stringify
+
+assertEquals("true", JSON.stringify(true));
+assertEquals("false", JSON.stringify(false));
+assertEquals("null", JSON.stringify(null));
+assertEquals("false", JSON.stringify({toJSON: function () { return false; }}));
+assertEquals("4", JSON.stringify(4));
+assertEquals('"foo"', JSON.stringify("foo"));
+assertEquals("null", JSON.stringify(Infinity));
+assertEquals("null", JSON.stringify(-Infinity));
+assertEquals("null", JSON.stringify(NaN));
+assertEquals("4", JSON.stringify(new Number(4)));
+assertEquals('"bar"', JSON.stringify(new String("bar")));
+
+assertEquals('"foo\\u0000bar"', JSON.stringify("foo\0bar"));
+assertEquals('"f\\"o\'o\\\\b\\ba\\fr\\nb\\ra\\tz"',
+             JSON.stringify("f\"o\'o\\b\ba\fr\nb\ra\tz"));
+
+assertEquals("[1,2,3]", JSON.stringify([1, 2, 3]));
+assertEquals("[\n 1,\n 2,\n 3\n]", JSON.stringify([1, 2, 3], null, 1));
+assertEquals("[\n  1,\n  2,\n  3\n]", JSON.stringify([1, 2, 3], null, 2));
+assertEquals("[\n  1,\n  2,\n  3\n]",
+             JSON.stringify([1, 2, 3], null, new Number(2)));
+assertEquals("[\n^1,\n^2,\n^3\n]", JSON.stringify([1, 2, 3], null, "^"));
+assertEquals("[\n^1,\n^2,\n^3\n]",
+             JSON.stringify([1, 2, 3], null, new String("^")));
+assertEquals("[\n 1,\n 2,\n [\n  3,\n  [\n   4\n  ],\n  5\n ],\n 6,\n 7\n]",
+             JSON.stringify([1, 2, [3, [4], 5], 6, 7], null, 1));
+assertEquals("[]", JSON.stringify([], null, 1));
+assertEquals("[1,2,[3,[4],5],6,7]",
+             JSON.stringify([1, 2, [3, [4], 5], 6, 7], null));
+assertEquals("[2,4,[6,[8],10],12,14]",
+             JSON.stringify([1, 2, [3, [4], 5], 6, 7], DoubleNumbers));
+
+var circular = [1, 2, 3];
+circular[2] = circular;
+assertThrows(function () { JSON.stringify(circular); }, TypeError);
+
+var singleton = [];
+var multiOccurrence = [singleton, singleton, singleton];
+assertEquals("[[],[],[]]", JSON.stringify(multiOccurrence));
+
+assertEquals('{"x":5,"y":6}', JSON.stringify({x:5,y:6}));
+assertEquals('{"x":5}', JSON.stringify({x:5,y:6}, ['x']));
+assertEquals('{\n "a": "b",\n "c": "d"\n}',
+             JSON.stringify({a:"b",c:"d"}, null, 1));
+assertEquals('{"y":6,"x":5}', JSON.stringify({x:5,y:6}, ['y', 'x']));
+
+assertEquals(undefined, JSON.stringify(undefined));
+assertEquals(undefined, JSON.stringify(function () { }));
index 320e8d1..2c52a31 100644 (file)
@@ -51,6 +51,25 @@ function fail(expected, found, name_opt) {
 }
 
 
+function deepObjectEquals(a, b) {
+  var aProps = [];
+  for (var key in a)
+    aProps.push(key);
+  var bProps = [];
+  for (var key in b)
+    bProps.push(key);
+  aProps.sort();
+  bProps.sort();
+  if (!deepEquals(aProps, bProps))
+    return false;
+  for (var i = 0; i < aProps.length; i++) {
+    if (!deepEquals(a[aProps[i]], b[aProps[i]]))
+      return false;
+  }
+  return true;
+}
+
+
 function deepEquals(a, b) {
   if (a == b) return true;
   if (typeof a == "number" && typeof b == "number" && isNaN(a) && isNaN(b)) {
@@ -73,8 +92,9 @@ function deepEquals(a, b) {
       }
     }
     return true;
+  } else {
+    return deepObjectEquals(a, b);
   }
-  return false;
 }
 
 
@@ -130,12 +150,20 @@ function assertNotNull(value, name_opt) {
 }
 
 
-function assertThrows(code) {
+function assertThrows(code, type_opt, cause_opt) {
   var threwException = true;
   try {
-    eval(code);
+    if (typeof code == 'function') {
+      code();
+    } else {
+      eval(code);
+    }
     threwException = false;
   } catch (e) {
+    if (typeof type_opt == 'function')
+      assertInstanceof(e, type_opt);
+    if (arguments.length >= 3)
+      assertEquals(e.type, cause_opt);
     // Do nothing.
   }
   if (!threwException) assertTrue(false, "did not throw exception");
index b6a46a2..df5293b 100644 (file)
@@ -3,4 +3,4 @@ set SOURCE_DIR=%1
 set TARGET_DIR=%2
 set PYTHON="..\..\..\third_party\python_24\python.exe"
 if not exist %PYTHON% set PYTHON=python.exe
-%PYTHON% ..\js2c.py %TARGET_DIR%\natives.cc %TARGET_DIR%\natives-empty.cc CORE %SOURCE_DIR%\macros.py %SOURCE_DIR%\runtime.js %SOURCE_DIR%\v8natives.js %SOURCE_DIR%\array.js %SOURCE_DIR%\string.js %SOURCE_DIR%\uri.js %SOURCE_DIR%\math.js %SOURCE_DIR%\messages.js %SOURCE_DIR%\apinatives.js %SOURCE_DIR%\debug-delay.js %SOURCE_DIR%\mirror-delay.js %SOURCE_DIR%\date-delay.js %SOURCE_DIR%\regexp-delay.js
+%PYTHON% ..\js2c.py %TARGET_DIR%\natives.cc %TARGET_DIR%\natives-empty.cc CORE %SOURCE_DIR%\macros.py %SOURCE_DIR%\runtime.js %SOURCE_DIR%\v8natives.js %SOURCE_DIR%\array.js %SOURCE_DIR%\string.js %SOURCE_DIR%\uri.js %SOURCE_DIR%\math.js %SOURCE_DIR%\messages.js %SOURCE_DIR%\apinatives.js %SOURCE_DIR%\debug-delay.js %SOURCE_DIR%\mirror-delay.js %SOURCE_DIR%\date-delay.js %SOURCE_DIR%\regexp-delay.js %SOURCE_DIR%\json-delay.js
index 212fd07..c2f336e 100644 (file)
                                >
                        </File>
                        <File
+                               RelativePath="..\..\src\json-delay.js"
+                               >
+                       </File>
+                       <File
                                RelativePath="..\..\src\runtime.js"
                                >
                        </File>
                                                Name="VCCustomBuildTool"
                                                Description="Processing js files..."
                                                CommandLine=".\js2c.cmd ..\..\src &quot;$(IntDir)\DerivedSources&quot;"
-                                               AdditionalDependencies="..\..\src\macros.py;..\..\src\runtime.js;..\..\src\v8natives.js;..\..\src\array.js;..\..\src\string.js;..\..\src\uri.js;..\..\src\math.js;..\..\src\messages.js;..\..\src\apinatives.js;..\..\src\debug-delay.js;..\..\src\mirror-delay.js;..\..\src\date-delay.js;..\..\src\regexp-delay.js"
+                                               AdditionalDependencies="..\..\src\macros.py;..\..\src\runtime.js;..\..\src\v8natives.js;..\..\src\array.js;..\..\src\string.js;..\..\src\uri.js;..\..\src\math.js;..\..\src\messages.js;..\..\src\apinatives.js;..\..\src\debug-delay.js;..\..\src\mirror-delay.js;..\..\src\date-delay.js;..\..\src\regexp-delay.js;..\..\src\json-delay.js"
                                                Outputs="$(IntDir)\DerivedSources\natives.cc;$(IntDir)\DerivedSources\natives-empty.cc"
                                        />
                                </FileConfiguration>
                                                Name="VCCustomBuildTool"
                                                Description="Processing js files..."
                                                CommandLine=".\js2c.cmd ..\..\src &quot;$(IntDir)\DerivedSources&quot;"
-                                               AdditionalDependencies="..\..\src\macros.py;..\..\src\runtime.js;..\..\src\v8natives.js;..\..\src\array.js;..\..\src\string.js;..\..\src\uri.js;..\..\src\math.js;..\..\src\messages.js;..\..\src\apinatives.js;..\..\src\debug-delay.js;..\..\src\mirror-delay.js;..\..\src\date-delay.js;..\..\src\regexp-delay.js"
+                                               AdditionalDependencies="..\..\src\macros.py;..\..\src\runtime.js;..\..\src\v8natives.js;..\..\src\array.js;..\..\src\string.js;..\..\src\uri.js;..\..\src\math.js;..\..\src\messages.js;..\..\src\apinatives.js;..\..\src\debug-delay.js;..\..\src\mirror-delay.js;..\..\src\date-delay.js;..\..\src\regexp-delay.js;..\..\src\json-delay.js"
                                                Outputs="$(IntDir)\DerivedSources\natives.cc;$(IntDir)\DerivedSources\natives-empty.cc"
                                        />
                                </FileConfiguration>