forked from Mirror/wren
Better argument validation for some list methods.
This commit is contained in:
@ -1,8 +1,6 @@
|
||||
#ifndef wren_common_h
|
||||
#define wren_common_h
|
||||
|
||||
// TODO: Would be good to use "const" on pointer params when possible.
|
||||
|
||||
// This header contains macros and defines used across the entire Wren
|
||||
// implementation. In particular, it contains "configuration" defines that
|
||||
// control how Wren works. Some of these are only used while hacking on
|
||||
|
||||
147
src/wren_core.c
147
src/wren_core.c
@ -87,6 +87,80 @@ const char* coreLibSource =
|
||||
" ... other { return new Range(this, other - 1) }\n"
|
||||
"}\n";
|
||||
|
||||
// TODO: Remove this and migrate everything to use validateIndex().
|
||||
// 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 validateIndexOld(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;
|
||||
}
|
||||
|
||||
// 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));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validates that the given argument in [args] is an integer. Returns true if
|
||||
// it is. If not, reports an error and returns false.
|
||||
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;
|
||||
}
|
||||
|
||||
// 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. If invalid, reports an error and returns -1.
|
||||
static int validateIndex(WrenVM* vm, Value* args, int count, int argIndex,
|
||||
const char* argName)
|
||||
{
|
||||
if (!validateInt(vm, args, argIndex, argName)) return -1;
|
||||
|
||||
int index = (int)AS_NUM(args[argIndex]);
|
||||
|
||||
// Negative indices count from the end.
|
||||
if (index < 0) index = count + index;
|
||||
|
||||
// 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));
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEF_NATIVE(bool_not)
|
||||
{
|
||||
RETURN_BOOL(!AS_BOOL(args[0]));
|
||||
@ -106,28 +180,6 @@ DEF_NATIVE(bool_toString)
|
||||
|
||||
DEF_NATIVE(fn_call) { return PRIM_CALL; }
|
||||
|
||||
// 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_NATIVE(list_add)
|
||||
{
|
||||
ObjList* list = AS_LIST(args[0]);
|
||||
@ -156,10 +208,8 @@ DEF_NATIVE(list_insert)
|
||||
ObjList* list = AS_LIST(args[0]);
|
||||
|
||||
// count + 1 here so you can "insert" at the very end.
|
||||
int index = validateIndex(args[2], list->count + 1);
|
||||
// TODO: Instead of returning null here, should signal an error explicitly
|
||||
// somehow.
|
||||
if (index == -1) RETURN_NULL;
|
||||
int index = validateIndex(vm, args, list->count + 1, 2, "Index");
|
||||
if (index == -1) return PRIM_ERROR;
|
||||
|
||||
wrenListInsert(vm, list, args[1], index);
|
||||
RETURN_VAL(args[1]);
|
||||
@ -170,12 +220,13 @@ DEF_NATIVE(list_iterate)
|
||||
// If we're starting the iteration, return the first index.
|
||||
if (IS_NULL(args[1])) RETURN_NUM(0);
|
||||
|
||||
ObjList* list = AS_LIST(args[0]);
|
||||
double index = AS_NUM(args[1]);
|
||||
// TODO: Handle arg not a number or not an integer.
|
||||
if (!validateInt(vm, args, 1, "Iterator")) return PRIM_ERROR;
|
||||
|
||||
// Stop if we're out of elements.
|
||||
if (index >= list->count - 1) RETURN_FALSE;
|
||||
ObjList* list = AS_LIST(args[0]);
|
||||
int index = (int)AS_NUM(args[1]);
|
||||
|
||||
// Stop if we're out of bounds.
|
||||
if (index < 0 || index >= list->count - 1) RETURN_FALSE;
|
||||
|
||||
// Otherwise, move to the next index.
|
||||
RETURN_NUM(index + 1);
|
||||
@ -184,15 +235,16 @@ DEF_NATIVE(list_iterate)
|
||||
DEF_NATIVE(list_iteratorValue)
|
||||
{
|
||||
ObjList* list = AS_LIST(args[0]);
|
||||
double index = AS_NUM(args[1]);
|
||||
// TODO: Handle index out of bounds or not integer.
|
||||
RETURN_VAL(list->elements[(int)index]);
|
||||
int index = validateIndex(vm, args, list->count, 1, "Iterator");
|
||||
if (index == -1) return PRIM_ERROR;
|
||||
|
||||
RETURN_VAL(list->elements[index]);
|
||||
}
|
||||
|
||||
DEF_NATIVE(list_removeAt)
|
||||
{
|
||||
ObjList* list = AS_LIST(args[0]);
|
||||
int index = validateIndex(args[1], list->count);
|
||||
int index = validateIndexOld(args[1], list->count);
|
||||
// TODO: Instead of returning null here, should signal an error explicitly
|
||||
// somehow.
|
||||
if (index == -1) RETURN_NULL;
|
||||
@ -204,7 +256,7 @@ DEF_NATIVE(list_subscript)
|
||||
{
|
||||
ObjList* list = AS_LIST(args[0]);
|
||||
|
||||
int index = validateIndex(args[1], list->count);
|
||||
int index = validateIndexOld(args[1], list->count);
|
||||
// TODO: Instead of returning null here, should signal an error explicitly
|
||||
// somehow.
|
||||
if (index == -1) RETURN_NULL;
|
||||
@ -216,7 +268,7 @@ DEF_NATIVE(list_subscriptSetter)
|
||||
{
|
||||
ObjList* list = AS_LIST(args[0]);
|
||||
|
||||
int index = validateIndex(args[1], list->count);
|
||||
int index = validateIndexOld(args[1], list->count);
|
||||
// TODO: Instead of returning null here, should signal an error explicitly
|
||||
// somehow.
|
||||
if (index == -1) RETURN_NULL;
|
||||
@ -249,45 +301,34 @@ DEF_NATIVE(num_negate)
|
||||
RETURN_NUM(-AS_NUM(args[0]));
|
||||
}
|
||||
|
||||
// Validates that the second argument in [args] is a Num. Returns true if it is.
|
||||
// If not, sets a type error and returns false.
|
||||
static bool checkNumType(WrenVM* vm, Value* args)
|
||||
{
|
||||
if (IS_NUM(args[1])) return true;
|
||||
|
||||
const char* msg = "TypeError: Right operand must be Num.";
|
||||
args[0] = wrenNewString(vm, msg, strlen(msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
DEF_NATIVE(num_minus)
|
||||
{
|
||||
if (!checkNumType(vm, args)) return PRIM_ERROR;
|
||||
if (!validateNum(vm, args, 1, "Right operand")) return PRIM_ERROR;
|
||||
RETURN_NUM(AS_NUM(args[0]) - AS_NUM(args[1]));
|
||||
}
|
||||
|
||||
DEF_NATIVE(num_plus)
|
||||
{
|
||||
if (!checkNumType(vm, args)) return PRIM_ERROR;
|
||||
if (!validateNum(vm, args, 1, "Right operand")) return PRIM_ERROR;
|
||||
// TODO: Handle coercion to string if RHS is a string.
|
||||
RETURN_NUM(AS_NUM(args[0]) + AS_NUM(args[1]));
|
||||
}
|
||||
|
||||
DEF_NATIVE(num_multiply)
|
||||
{
|
||||
if (!checkNumType(vm, args)) return PRIM_ERROR;
|
||||
if (!validateNum(vm, args, 1, "Right operand")) return PRIM_ERROR;
|
||||
RETURN_NUM(AS_NUM(args[0]) * AS_NUM(args[1]));
|
||||
}
|
||||
|
||||
DEF_NATIVE(num_divide)
|
||||
{
|
||||
if (!checkNumType(vm, args)) return PRIM_ERROR;
|
||||
if (!validateNum(vm, args, 1, "Right operand")) return PRIM_ERROR;
|
||||
RETURN_NUM(AS_NUM(args[0]) / AS_NUM(args[1]));
|
||||
}
|
||||
|
||||
DEF_NATIVE(num_mod)
|
||||
{
|
||||
if (!checkNumType(vm, args)) return PRIM_ERROR;
|
||||
if (!validateNum(vm, args, 1, "Right operand")) return PRIM_ERROR;
|
||||
if (!IS_NUM(args[1])) RETURN_NULL;
|
||||
RETURN_NUM(fmod(AS_NUM(args[0]), AS_NUM(args[1])));
|
||||
}
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
// TODO: What happens if the index isn't an integer?
|
||||
// TODO: Out of bounds.
|
||||
|
||||
// Add to empty list.
|
||||
var a = []
|
||||
a.insert(1, 0)
|
||||
|
||||
2
test/list/insert_index_not_int.wren
Normal file
2
test/list/insert_index_not_int.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a.insert("value", 1.5) // expect runtime error: Index must be an integer.
|
||||
2
test/list/insert_index_not_num.wren
Normal file
2
test/list/insert_index_not_num.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a.insert("value", "2") // expect runtime error: Index must be a number.
|
||||
2
test/list/insert_index_too_large.wren
Normal file
2
test/list/insert_index_too_large.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a.insert("value", 4) // expect runtime error: Index out of bounds.
|
||||
2
test/list/insert_index_too_small.wren
Normal file
2
test/list/insert_index_too_small.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a.insert("value", -5) // expect runtime error: Index out of bounds.
|
||||
7
test/list/iterate.wren
Normal file
7
test/list/iterate.wren
Normal file
@ -0,0 +1,7 @@
|
||||
var a = ["one", "two", "three", "four"]
|
||||
IO.write(a.iterate(null)) // expect: 0
|
||||
IO.write(a.iterate(0)) // expect: 1
|
||||
IO.write(a.iterate(1)) // expect: 2
|
||||
IO.write(a.iterate(2)) // expect: 3
|
||||
IO.write(a.iterate(3)) // expect: false
|
||||
IO.write(a.iterate(-1)) // expect: false
|
||||
2
test/list/iterate_iterator_not_int.wren
Normal file
2
test/list/iterate_iterator_not_int.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a.iterate(1.5) // expect runtime error: Iterator must be an integer.
|
||||
2
test/list/iterate_iterator_not_num.wren
Normal file
2
test/list/iterate_iterator_not_num.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a.iterate("2") // expect runtime error: Iterator must be a number.
|
||||
5
test/list/iterator_value.wren
Normal file
5
test/list/iterator_value.wren
Normal file
@ -0,0 +1,5 @@
|
||||
var a = ["one", "two", "three", "four"]
|
||||
IO.write(a.iteratorValue(0)) // expect: one
|
||||
IO.write(a.iteratorValue(1)) // expect: two
|
||||
IO.write(a.iteratorValue(2)) // expect: three
|
||||
IO.write(a.iteratorValue(3)) // expect: four
|
||||
2
test/list/iterator_value_iterator_not_int.wren
Normal file
2
test/list/iterator_value_iterator_not_int.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a.iteratorValue(1.5) // expect runtime error: Iterator must be an integer.
|
||||
2
test/list/iterator_value_iterator_not_num.wren
Normal file
2
test/list/iterator_value_iterator_not_num.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a.iteratorValue("2") // expect runtime error: Iterator must be a number.
|
||||
2
test/list/iterator_value_iterator_too_large.wren
Normal file
2
test/list/iterator_value_iterator_too_large.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a.iteratorValue(4) // expect runtime error: Iterator out of bounds.
|
||||
2
test/list/iterator_value_iterator_too_small.wren
Normal file
2
test/list/iterator_value_iterator_too_small.wren
Normal file
@ -0,0 +1,2 @@
|
||||
var a = [1, 2, 3]
|
||||
a.iteratorValue(-5) // expect runtime error: Iterator out of bounds.
|
||||
1
test/number/divide_operand_not_num.wren
Normal file
1
test/number/divide_operand_not_num.wren
Normal file
@ -0,0 +1 @@
|
||||
1 / false // expect runtime error: Right operand must be a number.
|
||||
@ -1 +0,0 @@
|
||||
1 / false // expect runtime error: TypeError: Right operand must be Num.
|
||||
1
test/number/minus_operand_not_num.wren
Normal file
1
test/number/minus_operand_not_num.wren
Normal file
@ -0,0 +1 @@
|
||||
1 - false // expect runtime error: Right operand must be a number.
|
||||
@ -1 +0,0 @@
|
||||
1 - false // expect runtime error: TypeError: Right operand must be Num.
|
||||
1
test/number/mod_operand_not_num.wren
Normal file
1
test/number/mod_operand_not_num.wren
Normal file
@ -0,0 +1 @@
|
||||
1 % false // expect runtime error: Right operand must be a number.
|
||||
@ -1 +0,0 @@
|
||||
1 % false // expect runtime error: TypeError: Right operand must be Num.
|
||||
1
test/number/multiply_operand_not_num.wren
Normal file
1
test/number/multiply_operand_not_num.wren
Normal file
@ -0,0 +1 @@
|
||||
1 * false // expect runtime error: Right operand must be a number.
|
||||
@ -1 +0,0 @@
|
||||
1 * false // expect runtime error: TypeError: Right operand must be Num.
|
||||
3
test/number/plus_operand_not_num.wren
Normal file
3
test/number/plus_operand_not_num.wren
Normal file
@ -0,0 +1,3 @@
|
||||
1 + false // expect runtime error: Right operand must be a number.
|
||||
|
||||
// TODO: What about a string on the RHS?
|
||||
@ -1,3 +0,0 @@
|
||||
1 + false // expect runtime error: TypeError: Right operand must be Num.
|
||||
|
||||
// TODO: What about a string on the RHS?
|
||||
Reference in New Issue
Block a user