diff --git a/doc/site/core/map.markdown b/doc/site/core/map.markdown index 189f58b2..a70e3f49 100644 --- a/doc/site/core/map.markdown +++ b/doc/site/core/map.markdown @@ -5,7 +5,11 @@ An associative collection that maps keys to values. More details [here](../maps. ### **clear** -Removes all entries +Removes all entries from the map. + +### **containsKey**(key) + +Returns `true` if the map contains `key` or `false` otherwise. ### **count** diff --git a/src/wren_core.c b/src/wren_core.c index fe06e07b..a6bca4a2 100644 --- a/src/wren_core.c +++ b/src/wren_core.c @@ -230,6 +230,21 @@ static int validateIndexValue(WrenVM* vm, Value* args, int count, double value, return -1; } +// Validates that [key] is a valid object for use as a map key. Returns true if +// it is. If not, reports an error and returns false. +static bool validateKey(WrenVM* vm, Value* args, int index) +{ + Value arg = args[index]; + if (IS_BOOL(arg) || IS_CLASS(arg) || IS_NULL(arg) || + IS_NUM(arg) || IS_RANGE(arg) || IS_STRING(arg)) + { + return true; + } + + args[0] = OBJ_VAL(wrenNewString(vm, "Key must be a value type.", 25)); + return false; +} + // Validates that the argument at [argIndex] is an integer within `[0, count)`. // Also allows negative indices which map backwards from the end. Returns the // valid positive index value. If invalid, reports an error and returns -1. @@ -719,13 +734,19 @@ DEF_NATIVE(map_instantiate) DEF_NATIVE(map_subscript) { - // TODO: Validate key type. - RETURN_VAL(wrenMapGet(AS_MAP(args[0]), args[1])); + if (!validateKey(vm, args, 1)) return PRIM_ERROR; + + Value value; + if (wrenMapGet(AS_MAP(args[0]), args[1], &value)) RETURN_VAL(value); + + // Not found. + RETURN_NULL; } DEF_NATIVE(map_subscriptSetter) { - // TODO: Validate key type. + if (!validateKey(vm, args, 1)) return PRIM_ERROR; + wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]); RETURN_VAL(args[2]); } @@ -740,6 +761,14 @@ DEF_NATIVE(map_clear) RETURN_NULL; } +DEF_NATIVE(map_containsKey) +{ + if (!validateKey(vm, args, 1)) return PRIM_ERROR; + + Value dummy; + RETURN_BOOL(wrenMapGet(AS_MAP(args[0]), args[1], &dummy)); +} + DEF_NATIVE(map_count) { RETURN_NUM(AS_MAP(args[0])->count); @@ -1468,6 +1497,7 @@ void wrenInitializeCore(WrenVM* vm) NATIVE(vm->mapClass, "[ ]", map_subscript); NATIVE(vm->mapClass, "[ ]=", map_subscriptSetter); NATIVE(vm->mapClass, "clear", map_clear); + NATIVE(vm->mapClass, "containsKey ", map_containsKey); NATIVE(vm->mapClass, "count", map_count); NATIVE(vm->mapClass, "iterate_ ", map_iterate); NATIVE(vm->mapClass, "keyIteratorValue_ ", map_keyIteratorValue); diff --git a/src/wren_value.c b/src/wren_value.c index 060d3d9a..ea3bd883 100644 --- a/src/wren_value.c +++ b/src/wren_value.c @@ -489,7 +489,7 @@ static void ensureMapCapacity(WrenVM* vm, ObjMap* map) map->capacity = newCapacity; } -Value wrenMapGet(ObjMap* map, Value key) +bool wrenMapGet(ObjMap* map, Value key, Value* value) { // Figure out where to insert it in the table. Use open addressing and // basic linear probing. @@ -502,10 +502,14 @@ Value wrenMapGet(ObjMap* map, Value key) MapEntry* entry = &map->entries[index]; // If we found an empty slot, the key is not in the table. - if (IS_UNDEFINED(entry->key)) return NULL_VAL; + if (IS_UNDEFINED(entry->key)) return false; // If the key matches, we found it. - if (wrenValuesEqual(entry->key, key)) return entry->value; + if (wrenValuesEqual(entry->key, key)) + { + *value = entry->value; + return true; + } // Try the next slot. index = (index + 1) % map->capacity; diff --git a/src/wren_value.h b/src/wren_value.h index 0745206b..137e65c6 100644 --- a/src/wren_value.h +++ b/src/wren_value.h @@ -638,13 +638,16 @@ Value wrenListRemoveAt(WrenVM* vm, ObjList* list, int index); // Creates a new empty map. ObjMap* wrenNewMap(WrenVM* vm); -// Returns the value in [map] associated with [key] or `NULL_VALUE` if it wasn't -// found. -Value wrenMapGet(ObjMap* map, Value key); +// Looks up [key] in [map]. If found, stores its value in [value] and returns +// `true`. Otherwise, returns `false`. +bool wrenMapGet(ObjMap* map, Value key, Value* value); // Associates [key] with [value] in [map]. void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value); +// Returns `true` if [map] contains [key]. +bool wrenMapContainsKey(WrenVM* vm, ObjMap* map, Value key); + // Creates a new range from [from] to [to]. Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive); diff --git a/test/map/contains_key.wren b/test/map/contains_key.wren new file mode 100644 index 00000000..9dc97c23 --- /dev/null +++ b/test/map/contains_key.wren @@ -0,0 +1,11 @@ +var map = { + "one": 1, + "two": 2, + "three": 3 +} + +IO.print(map.containsKey("one")) // expect: true +IO.print(map.containsKey("two")) // expect: true +IO.print(map.containsKey("three")) // expect: true +IO.print(map.containsKey("four")) // expect: false +IO.print(map.containsKey("five")) // expect: false diff --git a/test/map/contains_key_not_value.wren b/test/map/contains_key_not_value.wren new file mode 100644 index 00000000..0477be74 --- /dev/null +++ b/test/map/contains_key_not_value.wren @@ -0,0 +1 @@ +var result = {}.containsKey([]) // expect runtime error: Key must be a value type. diff --git a/test/map/subscript_key_not_value.wren b/test/map/subscript_key_not_value.wren new file mode 100644 index 00000000..8e813e01 --- /dev/null +++ b/test/map/subscript_key_not_value.wren @@ -0,0 +1 @@ +var result = {}[[]] // expect runtime error: Key must be a value type. diff --git a/test/map/subscript_setter_key_not_value.wren b/test/map/subscript_setter_key_not_value.wren new file mode 100644 index 00000000..c8ec79b1 --- /dev/null +++ b/test/map/subscript_setter_key_not_value.wren @@ -0,0 +1 @@ +var result = {}[[]] = "value" // expect runtime error: Key must be a value type.