diff --git a/Makefile b/Makefile index 10e95c8..a20d84b 100644 --- a/Makefile +++ b/Makefile @@ -85,6 +85,7 @@ test: candor.a cand $(TESTS) @test/test-gc @./cand test/functional/basics.can @./cand test/functional/arrays.can + @./cand test/functional/objects.can @./cand test/functional/binary.can @./cand test/functional/while.can diff --git a/TODO b/TODO index 2f22c61..213a16d 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,3 @@ -* non-strings as object's keys * Optimize `while` benchmark (for non-optimized code) * On-stack replacement and profile-based optimizations * Incremental GC diff --git a/include/candor.h b/include/candor.h index 99da0ee..5efde78 100644 --- a/include/candor.h +++ b/include/candor.h @@ -169,9 +169,9 @@ class Object : public Value { public: static Object* New(); - void Set(String* key, Value* value); + void Set(Value* key, Value* value); void Set(const char* key, Value* value); - Value* Get(String* key); + Value* Get(Value* key); Value* Get(const char* key); Array* Keys(); diff --git a/src/api.cc b/src/api.cc index 5a4cc05..0d11e07 100644 --- a/src/api.cc +++ b/src/api.cc @@ -340,7 +340,7 @@ Object* Object::New() { } -void Object::Set(String* key, Value* value) { +void Object::Set(Value* key, Value* value) { char** slot = HObject::LookupProperty(Isolate::GetCurrent()->heap, addr(), key->addr(), @@ -349,7 +349,7 @@ void Object::Set(String* key, Value* value) { } -Value* Object::Get(String* key) { +Value* Object::Get(Value* key) { return Value::New(*HObject::LookupProperty(Isolate::GetCurrent()->heap, addr(), key->addr(), diff --git a/src/parser.cc b/src/parser.cc index d7ad2a7..993cc0a 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -485,7 +485,7 @@ AstNode* Parser::ParseObjectLiteral() { key = new AstNode(AstNode::kProperty, Peek()); Skip(); } else if (Peek()->is(kNumber)) { - key = new AstNode(AstNode::kProperty, Peek()); + key = new AstNode(AstNode::kNumber, Peek()); Skip(); } else { SetError("Expected string or number as object literal's key"); diff --git a/src/runtime.cc b/src/runtime.cc index cc755aa..1d79d1b 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -59,6 +59,46 @@ void RuntimeCollectGarbage(Heap* heap, char* stack_top) { } +off_t RuntimeGetHash(Heap* heap, char* value) { + Heap::HeapTag tag = HValue::GetTag(value); + + switch (tag) { + case Heap::kTagString: + return HString::Hash(value); + case Heap::kTagFunction: + case Heap::kTagObject: + case Heap::kTagArray: + case Heap::kTagCData: + return reinterpret_cast(value); + case Heap::kTagNil: + return 0; + case Heap::kTagBoolean: + if (HBoolean::Value(value)) { + return 1; + } else { + return 0; + } + case Heap::kTagNumber: + { + int64_t intval; + + if (HValue::IsUnboxed(value)) { + intval = HNumber::IntegralValue(value); + } else { + intval = HNumber::DoubleValue(value); + } + + // And create new string + return ComputeHash(intval); + } + default: + UNEXPECTED + } + + return 0; +} + + off_t RuntimeLookupProperty(Heap* heap, char* obj, char* key, @@ -72,13 +112,13 @@ off_t RuntimeLookupProperty(Heap* heap, bool is_array = HValue::GetTag(obj) == Heap::kTagArray; - char* strkey; + char* keyptr; int64_t numkey; uint32_t hash; if (is_array) { numkey = HNumber::IntegralValue(RuntimeToNumber(heap, key)); - strkey = reinterpret_cast(HNumber::Tag(numkey)); + keyptr = reinterpret_cast(HNumber::Tag(numkey)); hash = ComputeHash(numkey); // Update array's length on insertion (if increased) @@ -86,8 +126,8 @@ off_t RuntimeLookupProperty(Heap* heap, HArray::SetLength(obj, numkey + 1); } } else { - strkey = RuntimeToString(heap, key); - hash = HString::Hash(strkey); + keyptr = key; + hash = RuntimeGetHash(heap, key); } // Dive into space and walk it in circular manner @@ -100,9 +140,9 @@ off_t RuntimeLookupProperty(Heap* heap, key_slot = *reinterpret_cast(space + index); if (key_slot == HNil::New()) break; if (is_array) { - if (key_slot == strkey) break; + if (key_slot == keyptr) break; } else { - if (RuntimeStringCompare(key_slot, strkey) == 0) break; + if (RuntimeStrictCompare(key_slot, key) == 0) break; } index += 8; @@ -114,11 +154,11 @@ off_t RuntimeLookupProperty(Heap* heap, assert(insert); RuntimeGrowObject(heap, obj); - return RuntimeLookupProperty(heap, obj, strkey, insert); + return RuntimeLookupProperty(heap, obj, keyptr, insert); } if (insert) { - *reinterpret_cast(space + index) = strkey; + *reinterpret_cast(space + index) = keyptr; } return HMap::space_offset + index + (mask + 8); @@ -277,6 +317,34 @@ char* RuntimeToBoolean(Heap* heap, char* value) { } +size_t RuntimeStrictCompare(char* lhs, char* rhs) { + Heap::HeapTag tag = HValue::GetTag(lhs); + Heap::HeapTag rtag = HValue::GetTag(rhs); + + // We can only compare objects with equal type + if (rtag != tag) return -1; + + switch (tag) { + case Heap::kTagString: + return RuntimeStringCompare(lhs, rhs); + case Heap::kTagFunction: + case Heap::kTagObject: + case Heap::kTagArray: + case Heap::kTagCData: + case Heap::kTagNil: + return lhs == rhs ? 0 : -1; + case Heap::kTagBoolean: + return HBoolean::Value(lhs) == HBoolean::Value(rhs) ? 0 : -1; + case Heap::kTagNumber: + return HNumber::DoubleValue(lhs) == HNumber::DoubleValue(rhs) ? 0 : -1; + default: + UNEXPECTED + } + + return 0; +} + + size_t RuntimeStringCompare(char* lhs, char* rhs) { uint32_t lhs_length = HString::Length(lhs); uint32_t rhs_length = HString::Length(rhs); diff --git a/src/runtime.h b/src/runtime.h index 14f3c3c..3ee82ba 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -19,6 +19,9 @@ char* RuntimeAllocate(Heap* heap, uint32_t bytes); typedef void (*RuntimeCollectGarbageCallback)(Heap* heap, char* stack_top); void RuntimeCollectGarbage(Heap* heap, char* stack_top); +typedef off_t (*RuntimeGetHashCallback)(Heap* heap, char* value); +off_t RuntimeGetHash(Heap* heap, char* value); + // Performs lookup into a hashmap // if insert=1 - inserts key into map space typedef off_t (*RuntimeLookupPropertyCallback)(Heap* heap, @@ -38,7 +41,8 @@ char* RuntimeToString(Heap* heap, char* value); char* RuntimeToNumber(Heap* heap, char* value); char* RuntimeToBoolean(Heap* heap, char* value); -typedef size_t (*RuntimeStringCompareCallback)(char* lhs, char* rhs); +typedef size_t (*RuntimeCompareCallback)(char* lhs, char* rhs); +size_t RuntimeStrictCompare(char* lhs, char* rhs); size_t RuntimeStringCompare(char* lhs, char* rhs); char* RuntimeConcatenateStrings(Heap* heap, char* lhs, char* rhs); diff --git a/test/functional/objects.can b/test/functional/objects.can new file mode 100644 index 0000000..382aa90 --- /dev/null +++ b/test/functional/objects.can @@ -0,0 +1,15 @@ +print = global.print +assert = global.assert + +print('-- can: objects --') + +a = { i_am_key: true } + +obj = {} +obj[a] = 1; + +assert(obj[a] == 1, "non-string and non-number key") + +keys = keysof obj +assert(sizeof keys == 1, "keysof should work") +assert(keys[0] === a, "key should be the object") diff --git a/test/functional/while.can b/test/functional/while.can new file mode 100644 index 0000000..d6d5f6c --- /dev/null +++ b/test/functional/while.can @@ -0,0 +1,22 @@ +print = global.print +assert = global.assert + +print('-- can: while --') + +i = 100 +j = 0 +while (i--) { + if (i % 2) continue + j++ +} + +assert(j == 50, "continue") + +i = 100 +j = 0 +while (i--) { + if (i < 50) break + j++ +} + +assert(j == 50, "break") diff --git a/test/test-functional.cc b/test/test-functional.cc index c515ed4..fb03566 100644 --- a/test/test-functional.cc +++ b/test/test-functional.cc @@ -213,13 +213,14 @@ TEST_START("functional test") }) // Numeric keys - FUN_TEST("a = { 1: 2, 2: 3}\nreturn a[1] + a[2] + a['1'] + a['2']", { - assert(result->As()->Value() == 10); + FUN_TEST("a = { 1: 2, 2: 3, '1': 2, '2': 3}\n" + "return a[1] + a[2] + a['1'] + a['2'] + a[1.0] + a[2.0]", { + assert(result->As()->Value() == 15); }); FUN_TEST("a = { 1.1: 2, 2.2: 3}\n" - "return a[1.1] + a[2.2] + a['1.1'] + a['2.2']", { - assert(result->As()->Value() == 10); + "return a[1.1] + a[2.2]", { + assert(result->As()->Value() == 5); }); // Arrays diff --git a/test/test-parser.cc b/test/test-parser.cc index 1500145..af2079c 100644 --- a/test/test-parser.cc +++ b/test/test-parser.cc @@ -114,7 +114,7 @@ TEST_START("parser test") "[kProperty x]:[1] [kProperty y]:[2]]]") PARSER_TEST("a = { 1 : 1, 2 : 2 }", "[kAssign [a] [kObjectLiteral " - "[kProperty 1]:[1] [kProperty 2]:[2]]]") + "[1]:[1] [2]:[2]]]") PARSER_TEST("key() {\nreturn 'key'\n}\na = { key: 2 }\nreturn a.key", "[kFunction [key] @[] [return [kString key]]] " "[kAssign [a] [kObjectLiteral [kProperty key]:[2]]] "