diff --git a/src/primitives.c b/src/primitives.c index 17303761..04b501b4 100644 --- a/src/primitives.c +++ b/src/primitives.c @@ -64,52 +64,97 @@ DEF_FIBER_PRIMITIVE(fn_call6) { callFunction(fiber, AS_FN(args[0]), 7); } DEF_FIBER_PRIMITIVE(fn_call7) { callFunction(fiber, AS_FN(args[0]), 8); } DEF_FIBER_PRIMITIVE(fn_call8) { callFunction(fiber, AS_FN(args[0]), 9); } +// Grows [list] if needed to ensure it can hold [count] elements. +static void ensureListCapacity(WrenVM* vm, ObjList* list, int count) +{ + if (list->capacity >= count) return; + + int capacity = list->capacity * LIST_GROW_FACTOR; + if (capacity < LIST_MIN_CAPACITY) capacity = LIST_MIN_CAPACITY; + + list->capacity *= 2; + list->elements = wrenReallocate(vm, list->elements, + list->capacity * sizeof(Value), capacity * sizeof(Value)); + // TODO(bob): Handle allocation failure. + list->capacity = capacity; +} + +// Validates that [index] is an integer within `[0, count)`. Also allows +// negative indices which map backwards from the end. Returns the valid positive +// index value, or -1 if the index wasn't valid (not a number, not an int, out +// of bounds). +static int validateIndex(Value index, int count) +{ + if (!IS_NUM(index)) return -1; + + double indexNum = AS_NUM(index); + int intIndex = (int)indexNum; + // Make sure the index is an integer. + if (indexNum != intIndex) return -1; + + // Negative indices count from the end. + if (indexNum < 0) indexNum = count + indexNum; + + // Check bounds. + if (indexNum < 0 || indexNum >= count) return -1; + + return indexNum; +} + DEF_PRIMITIVE(list_add) { ObjList* list = AS_LIST(args[0]); - // TODO(bob): Move this into value or other module? - // Grow the list if needed. - if (list->capacity < list->count + 1) - { - int capacity = list->capacity * LIST_GROW_FACTOR; - if (capacity < LIST_MIN_CAPACITY) capacity = LIST_MIN_CAPACITY; - - list->capacity *= 2; - list->elements = wrenReallocate(vm, list->elements, - list->capacity * sizeof(Value), capacity * sizeof(Value)); - // TODO(bob): Handle allocation failure. - list->capacity = capacity; - } - + ensureListCapacity(vm, list, list->count + 1); list->elements[list->count++] = args[1]; return args[1]; } +DEF_PRIMITIVE(list_clear) +{ + ObjList* list = AS_LIST(args[0]); + wrenReallocate(vm, list->elements, list->capacity * sizeof(Value), 0); + list->capacity = 0; + list->count = 0; + return NULL_VAL; +} + DEF_PRIMITIVE(list_count) { ObjList* list = AS_LIST(args[0]); return NUM_VAL(list->count); } -DEF_PRIMITIVE(list_subscript) +DEF_PRIMITIVE(list_insert) { - // TODO(bob): Instead of returning null here, all of these failure cases - // should signal an error explicitly somehow. - if (!IS_NUM(args[1])) return NULL_VAL; - - double indexNum = AS_NUM(args[1]); - int index = (int)indexNum; - // Make sure the index is an integer. - if (indexNum != index) return NULL_VAL; - ObjList* list = AS_LIST(args[0]); - // Negative indices count from the end. - if (index < 0) index = list->count + index; + int index = validateIndex(args[2], list->count + 1); + // TODO(bob): Instead of returning null here, should signal an error + // explicitly somehow. + if (index == -1) return NULL_VAL; - // Check bounds. - if (index < 0 || index >= list->count) return NULL_VAL; + ensureListCapacity(vm, list, list->count + 1); + + // Shift items down. + for (int i = list->count; i > index; i--) + { + list->elements[i] = list->elements[i - 1]; + } + + list->elements[index] = args[1]; + list->count++; + return args[1]; +} + +DEF_PRIMITIVE(list_subscript) +{ + ObjList* list = AS_LIST(args[0]); + + int index = validateIndex(args[1], list->count); + // TODO(bob): Instead of returning null here, should signal an error + // explicitly somehow. + if (index == -1) return NULL_VAL; return list->elements[index]; } @@ -351,7 +396,9 @@ void wrenLoadCore(WrenVM* vm) vm->listClass = defineClass(vm, "List", vm->objectClass); PRIMITIVE(vm->listClass, "add ", list_add); + PRIMITIVE(vm->listClass, "clear", list_clear); PRIMITIVE(vm->listClass, "count", list_count); + PRIMITIVE(vm->listClass, "insert ", list_insert); PRIMITIVE(vm->listClass, "[ ]", list_subscript); vm->nullClass = defineClass(vm, "Null", vm->objectClass); diff --git a/test/list/clear.wren b/test/list/clear.wren new file mode 100644 index 00000000..12af2d45 --- /dev/null +++ b/test/list/clear.wren @@ -0,0 +1,7 @@ +var a = [1, 2, 3] +a.clear +io.write(a) // expect: [] +io.write(a.count) // expect: 0 + +// Returns null. +io.write([1, 2].clear) // expect: null diff --git a/test/list/insert.wren b/test/list/insert.wren new file mode 100644 index 00000000..e7303cbb --- /dev/null +++ b/test/list/insert.wren @@ -0,0 +1,44 @@ +// TODO(bob): What happens if the index isn't an integer? +// TODO(bob): Out of bounds. + +// Add to empty list. +var a = [] +a.insert(1, 0) +io.write(a) // expect: [1] + +// Normal indices. +var b = [1, 2, 3] +b.insert(4, 0) +io.write(b) // expect: [4, 1, 2, 3] + +var c = [1, 2, 3] +c.insert(4, 1) +io.write(c) // expect: [1, 4, 2, 3] + +var d = [1, 2, 3] +d.insert(4, 2) +io.write(d) // expect: [1, 2, 4, 3] + +var e = [1, 2, 3] +e.insert(4, 3) +io.write(e) // expect: [1, 2, 3, 4] + +// Negative indices. +var f = [1, 2, 3] +f.insert(4, -4) +io.write(f) // expect: [4, 1, 2, 3] + +var g = [1, 2, 3] +g.insert(4, -3) +io.write(g) // expect: [1, 4, 2, 3] + +var h = [1, 2, 3] +h.insert(4, -2) +io.write(h) // expect: [1, 2, 4, 3] + +var i = [1, 2, 3] +i.insert(4, -1) +io.write(i) // expect: [1, 2, 3, 4] + +// Returns.inserted value. +io.write([1, 2].insert(3, 0)) // expect: 3