mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-11 22:28:45 +01:00
Allow ranges in list subscript operator.
This commit is contained in:
2
Makefile
2
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.
|
||||
|
||||
108
src/wren_core.c
108
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)
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
var a = [1, 2, 3]
|
||||
a["2"] // expect runtime error: Subscript must be a number.
|
||||
31
test/list/subscript_range.wren
Normal file
31
test/list/subscript_range.wren
Normal file
@ -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]
|
||||
2
test/list/subscript_range_from_not_int.wren
Normal file
2
test/list/subscript_range_from_not_int.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a[1.5..2] // expect runtime error: Range start must be an integer.
|
||||
2
test/list/subscript_range_from_too_large.wren
Normal file
2
test/list/subscript_range_from_too_large.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a[3..2] // expect runtime error: Range start out of bounds.
|
||||
2
test/list/subscript_range_from_too_small.wren
Normal file
2
test/list/subscript_range_from_too_small.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a[-4..2] // expect runtime error: Range start out of bounds.
|
||||
2
test/list/subscript_range_to_exclusive_too_large.wren
Normal file
2
test/list/subscript_range_to_exclusive_too_large.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a[1...4] // expect runtime error: Range end out of bounds.
|
||||
2
test/list/subscript_range_to_exclusive_too_small.wren
Normal file
2
test/list/subscript_range_to_exclusive_too_small.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a[0...-5] // expect runtime error: Range end out of bounds.
|
||||
2
test/list/subscript_range_to_not_int.wren
Normal file
2
test/list/subscript_range_to_not_int.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a[1..2.5] // expect runtime error: Range end must be an integer.
|
||||
2
test/list/subscript_range_to_too_large.wren
Normal file
2
test/list/subscript_range_to_too_large.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a[1..3] // expect runtime error: Range end out of bounds.
|
||||
2
test/list/subscript_range_to_too_small.wren
Normal file
2
test/list/subscript_range_to_too_small.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a[0..-4] // expect runtime error: Range end out of bounds.
|
||||
2
test/list/subscript_wrong_type.wren
Normal file
2
test/list/subscript_wrong_type.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a["2"] // expect runtime error: Subscript must be a number or a range.
|
||||
Reference in New Issue
Block a user