Re-apply r5165 (Added support for ES5's propertyname production)
authorsgjesse@chromium.org <sgjesse@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 6 Aug 2010 08:03:44 +0000 (08:03 +0000)
committersgjesse@chromium.org <sgjesse@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 6 Aug 2010 08:03:44 +0000 (08:03 +0000)
TBR=lrn@chromium.org
Review URL: http://codereview.chromium.org/3073031

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

src/parser.cc
src/runtime.cc
src/token.cc
src/token.h
test/mjsunit/object-literal.js
test/sputnik/sputnik.status

index dd5f9bd..bf1a348 100644 (file)
@@ -265,6 +265,7 @@ class Parser {
   Literal* GetLiteralNumber(double value);
 
   Handle<String> ParseIdentifier(bool* ok);
+  Handle<String> ParseIdentifierName(bool* ok);
   Handle<String> ParseIdentifierOrGetOrSet(bool* is_get,
                                            bool* is_set,
                                            bool* ok);
@@ -3121,7 +3122,7 @@ Expression* Parser::ParseLeftHandSideExpression(bool* ok) {
       case Token::PERIOD: {
         Consume(Token::PERIOD);
         int pos = scanner().location().beg_pos;
-        Handle<String> name = ParseIdentifier(CHECK_OK);
+        Handle<String> name = ParseIdentifierName(CHECK_OK);
         result = factory()->NewProperty(result, NEW(Literal(name)), pos);
         break;
       }
@@ -3207,7 +3208,7 @@ Expression* Parser::ParseMemberWithNewPrefixesExpression(PositionStack* stack,
       case Token::PERIOD: {
         Consume(Token::PERIOD);
         int pos = scanner().location().beg_pos;
-        Handle<String> name = ParseIdentifier(CHECK_OK);
+        Handle<String> name = ParseIdentifierName(CHECK_OK);
         result = factory()->NewProperty(result, NEW(Literal(name)), pos);
         break;
       }
@@ -3586,8 +3587,8 @@ void Parser::BuildObjectLiteralConstantProperties(
 Expression* Parser::ParseObjectLiteral(bool* ok) {
   // ObjectLiteral ::
   //   '{' (
-  //       ((Identifier | String | Number) ':' AssignmentExpression)
-  //     | (('get' | 'set') FunctionLiteral)
+  //       ((IdentifierName | String | Number) ':' AssignmentExpression)
+  //     | (('get' | 'set') (IdentifierName | String | Number) FunctionLiteral)
   //    )*[','] '}'
 
   ZoneListWrapper<ObjectLiteral::Property> properties =
@@ -3597,7 +3598,8 @@ Expression* Parser::ParseObjectLiteral(bool* ok) {
   Expect(Token::LBRACE, CHECK_OK);
   while (peek() != Token::RBRACE) {
     Literal* key = NULL;
-    switch (peek()) {
+    Token::Value next = peek();
+    switch (next) {
       case Token::IDENTIFIER: {
         // Store identifier keys as literal symbols to avoid
         // resolving them when compiling code for the object
@@ -3608,15 +3610,26 @@ Expression* Parser::ParseObjectLiteral(bool* ok) {
             ParseIdentifierOrGetOrSet(&is_getter, &is_setter, CHECK_OK);
         if (is_getter || is_setter) {
           // Special handling of getter and setter syntax.
-          if (peek() == Token::IDENTIFIER) {
-            Handle<String> name = ParseIdentifier(CHECK_OK);
+          Handle<String> name;
+          next = peek();
+          if (next == Token::IDENTIFIER ||
+              next == Token::STRING ||
+              next == Token::NUMBER ||
+              Token::IsKeyword(next)) {
+            Consume(next);
+            Handle<String> name =
+                factory()->LookupSymbol(scanner_.literal_string(),
+                                        scanner_.literal_length());
             FunctionLiteral* value =
-                ParseFunctionLiteral(name, RelocInfo::kNoPosition,
-                                     DECLARATION, CHECK_OK);
+                ParseFunctionLiteral(name,
+                                     RelocInfo::kNoPosition,
+                                     DECLARATION,
+                                     CHECK_OK);
             ObjectLiteral::Property* property =
                 NEW(ObjectLiteral::Property(is_getter, value));
-            if (IsBoilerplateProperty(property))
+            if (IsBoilerplateProperty(property)) {
               number_of_boilerplate_properties++;
+            }
             properties.Add(property);
             if (peek() != Token::RBRACE) Expect(Token::COMMA, CHECK_OK);
             continue;  // restart the while
@@ -3625,14 +3638,20 @@ Expression* Parser::ParseObjectLiteral(bool* ok) {
         key = NEW(Literal(id));
         break;
       }
-
+#define CASE_KEYWORD(name, ignore1, ignore2) \
+      case Token::name:
+      TOKEN_LIST(IGNORE_TOKEN, CASE_KEYWORD, IGNORE_TOKEN)
+#undef CASE_KEYWORD
+      // FALLTHROUGH - keyword tokens fall through to the same code as strings.
       case Token::STRING: {
-        Consume(Token::STRING);
+        Consume(next);
         Handle<String> string =
             factory()->LookupSymbol(scanner_.literal_string(),
                                     scanner_.literal_length());
         uint32_t index;
-        if (!string.is_null() && string->AsArrayIndex(&index)) {
+        if (next == Token::STRING &&
+            !string.is_null() &&
+            string->AsArrayIndex(&index)) {
           key = NewNumberLiteral(index);
         } else {
           key = NEW(Literal(string));
@@ -4008,6 +4027,19 @@ Handle<String> Parser::ParseIdentifier(bool* ok) {
                                  scanner_.literal_length());
 }
 
+
+Handle<String> Parser::ParseIdentifierName(bool* ok) {
+  Token::Value next = Next();
+  if (next != Token::IDENTIFIER && !Token::IsKeyword(next)) {
+    ReportUnexpectedToken(next);
+    *ok = false;
+    return Handle<String>();
+  }
+  return factory()->LookupSymbol(scanner_.literal_string(),
+                                 scanner_.literal_length());
+}
+
+
 // This function reads an identifier and determines whether or not it
 // is 'get' or 'set'.  The reason for not using ParseIdentifier and
 // checking on the output is that this involves heap allocation which
index d460e44..32c5dff 100644 (file)
@@ -305,14 +305,13 @@ static Handle<Object> CreateObjectLiteralBoilerplate(
       }
       Handle<Object> result;
       uint32_t element_index = 0;
-      if (key->IsSymbol()) {
-        // If key is a symbol it is not an array element.
-        Handle<String> name(String::cast(*key));
-        ASSERT(!name->AsArrayIndex(&element_index));
-        result = SetProperty(boilerplate, name, value, NONE);
-      } else if (key->ToArrayIndex(&element_index)) {
+      if (key->ToArrayIndex(&element_index)) {
         // Array index (uint32).
         result = SetElement(boilerplate, element_index, value);
+      } else if (key->IsSymbol()) {
+        // The key is not an array index.
+        Handle<String> name(String::cast(*key));
+        result = SetProperty(boilerplate, name, value, NONE);
       } else {
         // Non-uint32 number.
         ASSERT(key->IsNumber());
index 8cee99b..21fa9ee 100644 (file)
@@ -53,4 +53,12 @@ int8_t Token::precedence_[NUM_TOKENS] = {
 #undef T
 
 
+#define KT(a, b, c) 'T',
+#define KK(a, b, c) 'K',
+const char Token::token_type[] = {
+  TOKEN_LIST(KT, KK, IGNORE_TOKEN)
+};
+#undef KT
+#undef KK
+
 } }  // namespace v8::internal
index 2a228d6..0d8960b 100644 (file)
@@ -220,6 +220,10 @@ class Token {
   }
 
   // Predicates
+  static bool IsKeyword(Value tok) {
+    return token_type[tok] == 'K';
+  }
+
   static bool IsAssignmentOp(Value tok) {
     return INIT_VAR <= tok && tok <= ASSIGN_MOD;
   }
@@ -263,6 +267,7 @@ class Token {
   static const char* name_[NUM_TOKENS];
   static const char* string_[NUM_TOKENS];
   static int8_t precedence_[NUM_TOKENS];
+  static const char token_type[NUM_TOKENS];
 };
 
 } }  // namespace v8::internal
index cc6f59d..3a6c009 100644 (file)
@@ -103,3 +103,110 @@ a = makeRegexpInObject();
 b = makeRegexpInObject();
 assertTrue(a.a.b === b.a.b);
 assertFalse(a.a.c === b.a.c);
+
+
+// Test keywords valid as property names in initializers and dot-access.
+var keywords = [
+  "break",
+  "case",
+  "catch",
+  "const",
+  "continue",
+  "debugger",
+  "default",
+  "delete",
+  "do",
+  "else",
+  "false",
+  "finally",
+  "for",
+  "function",
+  "if",
+  "in",
+  "instanceof",
+  "native",
+  "new",
+  "null",
+  "return",
+  "switch",
+  "this",
+  "throw",
+  "true",
+  "try",
+  "typeof",
+  "var",
+  "void",
+  "while",
+  "with",
+];
+
+function testKeywordProperty(keyword) {
+  try {
+    // Sanity check that what we get is a keyword.
+    eval("var " + keyword + " = 42;");
+    assertUnreachable("Not a keyword: " + keyword);
+  } catch (e) { }
+  
+  // Simple property, read and write.
+  var x = eval("({" + keyword + ": 42})");
+  assertEquals(42, x[keyword]);
+  assertEquals(42, eval("x." + keyword));
+  eval("x." + keyword + " = 37");
+  assertEquals(37, x[keyword]);
+  assertEquals(37, eval("x." + keyword));
+  
+  // Getter/setter property, read and write.
+  var y = eval("({value : 42, get " + keyword + "(){return this.value}," +
+               " set " + keyword + "(v) { this.value = v; }})");
+  assertEquals(42, y[keyword]);
+  assertEquals(42, eval("y." + keyword));
+  eval("y." + keyword + " = 37");
+  assertEquals(37, y[keyword]);
+  assertEquals(37, eval("y." + keyword));
+  
+  // Quoted keyword works is read back by unquoted as well.
+  var z = eval("({\"" + keyword + "\": 42})");
+  assertEquals(42, z[keyword]);
+  assertEquals(42, eval("z." + keyword));
+  
+  // Function property, called.
+  var was_called;
+  function test_call() { this.was_called = true; was_called = true; }
+  var w = eval("({" + keyword + ": test_call, was_called: false})");
+  eval("w." + keyword + "();");
+  assertTrue(was_called);
+  assertTrue(w.was_called);
+
+  // Function property, constructed.
+  function construct() { this.constructed = true; }
+  var v = eval("({" + keyword + ": construct})");
+  var vo = eval("new v." + keyword + "()");
+  assertTrue(vo instanceof construct);
+  assertTrue(vo.constructed);
+}
+
+for (var i = 0; i < keywords.length; i++) {
+  testKeywordProperty(keywords[i]);
+}
+
+// Test getter and setter properties with string/number literal names.
+
+var obj = {get 42() { return 42; },
+           get 3.14() { return "PI"; },
+           get "PI"() { return 3.14; },
+           readback: 0,
+           set 37(v) { this.readback = v; },
+           set 1.44(v) { this.readback = v; },
+           set "Poo"(v) { this.readback = v; }}
+
+assertEquals(42, obj[42]);
+assertEquals("PI", obj[3.14]);
+assertEquals(3.14, obj["PI"]);
+obj[37] = "t1";
+assertEquals("t1", obj.readback);
+obj[1.44] = "t2";
+assertEquals("t2", obj.readback);
+obj["Poo"] = "t3";
+assertEquals("t3", obj.readback);
+
+
index 4c6fd1e..13108c0 100644 (file)
@@ -158,11 +158,6 @@ S15.5.4.11_D1.1_T1: PASS || FAIL_OK
 S15.5.4.11_D1.1_T3: PASS || FAIL_OK
 S12.6.4_D1: PASS || FAIL_OK
 
-# We deliberately don't throw type errors when iterating through the
-# undefined object
-S9.9_A1: FAIL_OK
-S9.9_A2: FAIL_OK
-
 # We allow function declarations within statements
 S12.5_A9_T1: FAIL_OK
 S12.5_A9_T2: FAIL_OK
@@ -184,6 +179,21 @@ S15.3.4.2_A1_T1: FAIL_OK
 S8.5_A2.2: PASS, FAIL if $system == linux, FAIL if $system == macos
 S8.5_A2.1: PASS, FAIL if $system == linux, FAIL if $system == macos
 
+##################### ES3 TESTS #########################
+# These tests check for ES3 semantics, and differ from ES5.
+# When we follow ES5 semantics, it's ok to fail the test.
+
+# Allow keywords as names of properties in object initialisers and 
+# in dot-notation property access. 
+S11.1.5_A4.1: FAIL_OK
+S11.1.5_A4.2: FAIL_OK
+
+# Don't throw type errors when iterating through the undefined object.
+S9.9_A1: FAIL_OK
+S9.9_A2: FAIL_OK
+
+
+
 ##################### SKIPPED TESTS #####################
 
 # These tests take a looong time to run in debug mode.