From 66f0b57bf3ef344fc885277e06887249b26291bb Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Thu, 30 Jan 2014 09:12:44 -0800 Subject: [PATCH] Allow ranges in list subscript operator. --- Makefile | 2 +- src/wren_core.c | 108 ++++++++++++++---- test/list/subscript_not_num.wren | 2 - test/list/subscript_range.wren | 31 +++++ test/list/subscript_range_from_not_int.wren | 2 + test/list/subscript_range_from_too_large.wren | 2 + test/list/subscript_range_from_too_small.wren | 2 + ...ubscript_range_to_exclusive_too_large.wren | 2 + ...ubscript_range_to_exclusive_too_small.wren | 2 + test/list/subscript_range_to_not_int.wren | 2 + test/list/subscript_range_to_too_large.wren | 2 + test/list/subscript_range_to_too_small.wren | 2 + test/list/subscript_wrong_type.wren | 2 + 13 files changed, 133 insertions(+), 28 deletions(-) delete mode 100644 test/list/subscript_not_num.wren create mode 100644 test/list/subscript_range.wren create mode 100644 test/list/subscript_range_from_not_int.wren create mode 100644 test/list/subscript_range_from_too_large.wren create mode 100644 test/list/subscript_range_from_too_small.wren create mode 100644 test/list/subscript_range_to_exclusive_too_large.wren create mode 100644 test/list/subscript_range_to_exclusive_too_small.wren create mode 100644 test/list/subscript_range_to_not_int.wren create mode 100644 test/list/subscript_range_to_too_large.wren create mode 100644 test/list/subscript_range_to_too_small.wren create mode 100644 test/list/subscript_wrong_type.wren diff --git a/Makefile b/Makefile index 965e9a62..61af05f5 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ prep: mkdir -p build/debug build/release # Run the tests against the debug build of Wren. -test: wrend +test: debug @./script/test.py # Generate the Wren site. diff --git a/src/wren_core.c b/src/wren_core.c index cb18900d..cef0670a 100644 --- a/src/wren_core.c +++ b/src/wren_core.c @@ -68,15 +68,25 @@ const char* coreLibSource = " }\n" "}\n"; + // Validates that the given argument in [args] is a Num. Returns true if it is. // If not, reports an error and returns false. static bool validateNum(WrenVM* vm, Value* args, int index, const char* argName) { if (IS_NUM(args[index])) return true; - char message[100]; - snprintf(message, 100, "%s must be a number.", argName); - args[0] = wrenNewString(vm, message, strlen(message)); + args[0] = OBJ_VAL(wrenStringConcat(vm, argName, " must be a number.")); + return false; +} + +// Validates that [value] is an integer. Returns true if it is. If not, reports +// an error and returns false. +static bool validateIntValue(WrenVM* vm, Value* args, double value, + const char* argName) +{ + if (trunc(value) == value) return true; + + args[0] = OBJ_VAL(wrenStringConcat(vm, argName, " must be an integer.")); return false; } @@ -87,24 +97,18 @@ static bool validateInt(WrenVM* vm, Value* args, int index, const char* argName) // Make sure it's a number first. if (!validateNum(vm, args, index, argName)) return false; - double value = AS_NUM(args[index]); - if (trunc(value) == value) return true; - - char message[100]; - snprintf(message, 100, "%s must be an integer.", argName); - args[0] = wrenNewString(vm, message, strlen(message)); - return false; + return validateIntValue(vm, args, AS_NUM(args[index]), argName); } -// Validates that [index] is an integer within `[0, count)`. Also allows +// Validates that [value] 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. -static int validateIndex(WrenVM* vm, Value* args, int count, int argIndex, - const char* argName) +static int validateIndexValue(WrenVM* vm, Value* args, int count, double value, + const char* argName) { - if (!validateInt(vm, args, argIndex, argName)) return -1; + if (!validateIntValue(vm, args, value, argName)) return -1; - int index = (int)AS_NUM(args[argIndex]); + int index = (int)value; // Negative indices count from the end. if (index < 0) index = count + index; @@ -112,13 +116,21 @@ static int validateIndex(WrenVM* vm, Value* args, int count, int argIndex, // Check bounds. if (index >= 0 && index < count) return index; - char message[100]; - snprintf(message, 100, "%s out of bounds.", argName); - args[0] = wrenNewString(vm, message, strlen(message)); - + args[0] = OBJ_VAL(wrenStringConcat(vm, argName, " out of bounds.")); return -1; } +// 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. +static int validateIndex(WrenVM* vm, Value* args, int count, int argIndex, + const char* argName) +{ + if (!validateNum(vm, args, argIndex, argName)) return -1; + + return validateIndexValue(vm, args, count, AS_NUM(args[argIndex]), argName); +} + // Validates that the given argument in [args] is a String. Returns true if it // is. If not, reports an error and returns false. static bool validateString(WrenVM* vm, Value* args, int index, @@ -126,9 +138,7 @@ static bool validateString(WrenVM* vm, Value* args, int index, { if (IS_STRING(args[index])) return true; - char message[100]; - snprintf(message, 100, "%s must be a string.", argName); - args[0] = wrenNewString(vm, message, strlen(message)); + args[0] = OBJ_VAL(wrenStringConcat(vm, argName, " must be a string.")); return false; } @@ -329,10 +339,58 @@ DEF_NATIVE(list_removeAt) DEF_NATIVE(list_subscript) { ObjList* list = AS_LIST(args[0]); - int index = validateIndex(vm, args, list->count, 1, "Subscript"); - if (index == -1) return PRIM_ERROR; - RETURN_VAL(list->elements[index]); + if (IS_NUM(args[1])) + { + int index = validateIndex(vm, args, list->count, 1, "Subscript"); + if (index == -1) return PRIM_ERROR; + + RETURN_VAL(list->elements[index]); + } + + if (!IS_RANGE(args[1])) + { + RETURN_ERROR("Subscript must be a number or a range."); + } + + ObjRange* range = AS_RANGE(args[1]); + + int from = validateIndexValue(vm, args, list->count, range->from, + "Range start"); + if (from == -1) return PRIM_ERROR; + + int to; + int count; + + if (range->isInclusive) + { + to = validateIndexValue(vm, args, list->count, range->to, "Range end"); + if (to == -1) return PRIM_ERROR; + + count = abs(from - to) + 1; + } + else + { + if (!validateIntValue(vm, args, range->to, "Range end")) return PRIM_ERROR; + + // Bounds check it manually here since the excusive range can hang over + // the edge. + to = (int)range->to; + if (to < 0) to = list->count + to; + + if (to < -1 || to > list->count) RETURN_ERROR("Range end out of bounds."); + + count = abs(from - to); + } + + int step = from < to ? 1 : -1; + ObjList* result = wrenNewList(vm, count); + for (int i = 0; i < count; i++) + { + result->elements[i] = list->elements[from + (i * step)]; + } + + RETURN_OBJ(result); } DEF_NATIVE(list_subscriptSetter) diff --git a/test/list/subscript_not_num.wren b/test/list/subscript_not_num.wren deleted file mode 100644 index 3784239c..00000000 --- a/test/list/subscript_not_num.wren +++ /dev/null @@ -1,2 +0,0 @@ -var a = [1, 2, 3] -a["2"] // expect runtime error: Subscript must be a number. diff --git a/test/list/subscript_range.wren b/test/list/subscript_range.wren new file mode 100644 index 00000000..6c7d1f2f --- /dev/null +++ b/test/list/subscript_range.wren @@ -0,0 +1,31 @@ +// Returns lists. +var list = ["a", "b", "c", "d", "e"] +IO.print(list[0..0]) // expect: [a] +IO.print(list[1...1]) // expect: [] +IO.print(list[1..2]) // expect: [b, c] +IO.print(list[1...2]) // expect: [b] +IO.print(list[2..4]) // expect: [c, d, e] +IO.print(list[2...5]) // expect: [c, d, e] + +// A backwards range reverses. +IO.print(list[3..1]) // expect: [d, c, b] +IO.print(list[3...1]) // expect: [d, c] +IO.print(list[3...3]) // expect: [] + +// Negative ranges index from the end. +IO.print(list[-5..-2]) // expect: [a, b, c, d] +IO.print(list[-5...-2]) // expect: [a, b, c] +IO.print(list[-3..-5]) // expect: [c, b, a] +IO.print(list[-3...-6]) // expect: [c, b, a] + +// Half-negative ranges are treated like the negative value is fixed before +// walking the range. +IO.print(list[-5..3]) // expect: [a, b, c, d] +IO.print(list[-3...5]) // expect: [c, d, e] +IO.print(list[-2..1]) // expect: [d, c, b] +IO.print(list[-2...0]) // expect: [d, c, b] + +IO.print(list[1..-2]) // expect: [b, c, d] +IO.print(list[2...-1]) // expect: [c, d] +IO.print(list[4..-5]) // expect: [e, d, c, b, a] +IO.print(list[3...-6]) // expect: [d, c, b, a] diff --git a/test/list/subscript_range_from_not_int.wren b/test/list/subscript_range_from_not_int.wren new file mode 100644 index 00000000..28527a5d --- /dev/null +++ b/test/list/subscript_range_from_not_int.wren @@ -0,0 +1,2 @@ +var a = [1, 2, 3] +a[1.5..2] // expect runtime error: Range start must be an integer. diff --git a/test/list/subscript_range_from_too_large.wren b/test/list/subscript_range_from_too_large.wren new file mode 100644 index 00000000..6b9859f3 --- /dev/null +++ b/test/list/subscript_range_from_too_large.wren @@ -0,0 +1,2 @@ +var a = [1, 2, 3] +a[3..2] // expect runtime error: Range start out of bounds. diff --git a/test/list/subscript_range_from_too_small.wren b/test/list/subscript_range_from_too_small.wren new file mode 100644 index 00000000..72186ef6 --- /dev/null +++ b/test/list/subscript_range_from_too_small.wren @@ -0,0 +1,2 @@ +var a = [1, 2, 3] +a[-4..2] // expect runtime error: Range start out of bounds. diff --git a/test/list/subscript_range_to_exclusive_too_large.wren b/test/list/subscript_range_to_exclusive_too_large.wren new file mode 100644 index 00000000..12e98bc9 --- /dev/null +++ b/test/list/subscript_range_to_exclusive_too_large.wren @@ -0,0 +1,2 @@ +var a = [1, 2, 3] +a[1...4] // expect runtime error: Range end out of bounds. diff --git a/test/list/subscript_range_to_exclusive_too_small.wren b/test/list/subscript_range_to_exclusive_too_small.wren new file mode 100644 index 00000000..a85d3dea --- /dev/null +++ b/test/list/subscript_range_to_exclusive_too_small.wren @@ -0,0 +1,2 @@ +var a = [1, 2, 3] +a[0...-5] // expect runtime error: Range end out of bounds. diff --git a/test/list/subscript_range_to_not_int.wren b/test/list/subscript_range_to_not_int.wren new file mode 100644 index 00000000..2fecba38 --- /dev/null +++ b/test/list/subscript_range_to_not_int.wren @@ -0,0 +1,2 @@ +var a = [1, 2, 3] +a[1..2.5] // expect runtime error: Range end must be an integer. diff --git a/test/list/subscript_range_to_too_large.wren b/test/list/subscript_range_to_too_large.wren new file mode 100644 index 00000000..364b50e2 --- /dev/null +++ b/test/list/subscript_range_to_too_large.wren @@ -0,0 +1,2 @@ +var a = [1, 2, 3] +a[1..3] // expect runtime error: Range end out of bounds. diff --git a/test/list/subscript_range_to_too_small.wren b/test/list/subscript_range_to_too_small.wren new file mode 100644 index 00000000..75550fe4 --- /dev/null +++ b/test/list/subscript_range_to_too_small.wren @@ -0,0 +1,2 @@ +var a = [1, 2, 3] +a[0..-4] // expect runtime error: Range end out of bounds. diff --git a/test/list/subscript_wrong_type.wren b/test/list/subscript_wrong_type.wren new file mode 100644 index 00000000..4f1d9272 --- /dev/null +++ b/test/list/subscript_wrong_type.wren @@ -0,0 +1,2 @@ +var a = [1, 2, 3] +a["2"] // expect runtime error: Subscript must be a number or a range.