From 97bf314f4b1099cc875db54736966ebf02533a3a Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Mon, 20 Jan 2014 08:45:15 -0800 Subject: [PATCH] Make Range a native object type. Still need to implement better semantics, but this is an important first step. It's much faster now too, which is good. --- corelib.wren | 25 ----- src/wren_core.c | 155 ++++++++++++++++++++--------- src/wren_value.c | 18 ++++ src/wren_value.h | 24 ++++- src/wren_vm.c | 8 ++ src/wren_vm.h | 2 + test/number/range.wren | 14 --- test/range/from.wren | 31 ++++++ test/range/iterate.wren | 8 ++ test/range/iterate_wrong_type.wren | 1 + test/range/iterator_value.wren | 11 ++ test/range/max.wren | 25 +++++ test/range/min.wren | 26 +++++ test/range/to.wren | 25 +++++ test/range/type.wren | 6 ++ 15 files changed, 291 insertions(+), 88 deletions(-) delete mode 100644 test/number/range.wren create mode 100644 test/range/from.wren create mode 100644 test/range/iterate.wren create mode 100644 test/range/iterate_wrong_type.wren create mode 100644 test/range/iterator_value.wren create mode 100644 test/range/max.wren create mode 100644 test/range/min.wren create mode 100644 test/range/to.wren create mode 100644 test/range/type.wren diff --git a/corelib.wren b/corelib.wren index 0fd4aca8..3cde9258 100644 --- a/corelib.wren +++ b/corelib.wren @@ -27,28 +27,3 @@ class List { return result } } - -class Range { - new(min, max) { - _min = min - _max = max - } - - min { return _min } - max { return _max } - - iterate(previous) { - if (previous == null) return _min - if (previous == _max) return false - return previous + 1 - } - - iteratorValue(iterator) { - return iterator - } -} - -class Num { - .. other { return new Range(this, other) } - ... other { return new Range(this, other - 1) } -} diff --git a/src/wren_core.c b/src/wren_core.c index 4b0d9bd4..173f8ba3 100644 --- a/src/wren_core.c +++ b/src/wren_core.c @@ -68,31 +68,6 @@ const char* coreLibSource = " result = result + \"]\"\n" " return result\n" " }\n" -"}\n" -"\n" -"class Range {\n" -" new(min, max) {\n" -" _min = min\n" -" _max = max\n" -" }\n" -"\n" -" min { return _min }\n" -" max { return _max }\n" -"\n" -" iterate(previous) {\n" -" if (previous == null) return _min\n" -" if (previous == _max) return false\n" -" return previous + 1\n" -" }\n" -"\n" -" iteratorValue(iterator) {\n" -" return iterator\n" -" }\n" -"}\n" -"\n" -"class Num {\n" -" .. other { return new Range(this, other) }\n" -" ... other { return new Range(this, other - 1) }\n" "}\n"; // Validates that the given argument in [args] is a Num. Returns true if it is. @@ -514,6 +489,26 @@ DEF_NATIVE(num_bitwiseNot) RETURN_NUM(~value); } +DEF_NATIVE(num_dotDot) +{ + // TODO: Check arg type. + double from = AS_NUM(args[0]); + double to = AS_NUM(args[1]); + + RETURN_VAL(wrenNewRange(vm, from, to)); +} + +DEF_NATIVE(num_dotDotDot) +{ + // TODO: Check arg type. + double from = AS_NUM(args[0]); + + // TODO: Is this how we want half-open ranges to work? + double to = AS_NUM(args[1]) - 1; + + RETURN_VAL(wrenNewRange(vm, from, to)); +} + DEF_NATIVE(object_eqeq) { RETURN_BOOL(wrenValuesEqual(args[0], args[1])); @@ -541,6 +536,60 @@ DEF_NATIVE(object_type) RETURN_VAL(OBJ_VAL(wrenGetClass(vm, args[0]))); } +DEF_NATIVE(range_from) +{ + ObjRange* range = AS_RANGE(args[0]); + RETURN_NUM(range->from); +} + +DEF_NATIVE(range_to) +{ + ObjRange* range = AS_RANGE(args[0]); + RETURN_NUM(range->to); +} + +DEF_NATIVE(range_min) +{ + ObjRange* range = AS_RANGE(args[0]); + RETURN_NUM(fmin(range->from, range->to)); +} + +DEF_NATIVE(range_max) +{ + ObjRange* range = AS_RANGE(args[0]); + RETURN_NUM(fmax(range->from, range->to)); +} + +DEF_NATIVE(range_iterate) +{ + ObjRange* range = AS_RANGE(args[0]); + + // Start the iteration. + if (IS_NULL(args[1])) RETURN_NUM(range->from); + + if (!validateNum(vm, args, 1, "Iterator")) return PRIM_ERROR; + + double iterator = AS_NUM(args[1]); + + if (iterator >= range->to) RETURN_FALSE; + + RETURN_NUM(AS_NUM(args[1]) + 1); +} + +DEF_NATIVE(range_iteratorValue) +{ + // Assume the iterator is a number so that is the value of the range. + RETURN_VAL(args[1]); +} + +DEF_NATIVE(range_toString) +{ + char buffer[51]; + ObjRange* range = AS_RANGE(args[0]); + sprintf(buffer, "%.14g..%.14g", range->from, range->to); + RETURN_VAL(wrenNewString(vm, buffer, strlen(buffer))); +} + DEF_NATIVE(string_contains) { if (!validateString(vm, args, 1, "Argument")) return PRIM_ERROR; @@ -710,8 +759,7 @@ void wrenInitializeCore(WrenVM* vm) NATIVE(vm->fiberClass, "run", fiber_run); NATIVE(vm->fiberClass, "run ", fiber_run1); // TODO: Primitives for switching to a fiber without setting the caller. - // (I.e. symmetric coroutines.) Also a getter to tell if a fiber is complete - // or not. + // (I.e. symmetric coroutines.) vm->fnClass = defineClass(vm, "Function"); NATIVE(vm->fnClass, "call", fn_call); @@ -735,6 +783,38 @@ void wrenInitializeCore(WrenVM* vm) vm->nullClass = defineClass(vm, "Null"); NATIVE(vm->nullClass, "toString", null_toString); + vm->numClass = defineClass(vm, "Num"); + NATIVE(vm->numClass, "abs", num_abs); + NATIVE(vm->numClass, "ceil", num_ceil); + NATIVE(vm->numClass, "cos", num_cos); + NATIVE(vm->numClass, "floor", num_floor); + NATIVE(vm->numClass, "isNan", num_isNan); + NATIVE(vm->numClass, "sin", num_sin); + NATIVE(vm->numClass, "sqrt", num_sqrt); + NATIVE(vm->numClass, "toString", num_toString) + NATIVE(vm->numClass, "-", num_negate); + NATIVE(vm->numClass, "- ", num_minus); + NATIVE(vm->numClass, "+ ", num_plus); + NATIVE(vm->numClass, "* ", num_multiply); + NATIVE(vm->numClass, "/ ", num_divide); + NATIVE(vm->numClass, "% ", num_mod); + NATIVE(vm->numClass, "< ", num_lt); + NATIVE(vm->numClass, "> ", num_gt); + NATIVE(vm->numClass, "<= ", num_lte); + NATIVE(vm->numClass, ">= ", num_gte); + NATIVE(vm->numClass, "~", num_bitwiseNot); + NATIVE(vm->numClass, ".. ", num_dotDot); + NATIVE(vm->numClass, "... ", num_dotDotDot); + + vm->rangeClass = defineClass(vm, "Range"); + NATIVE(vm->rangeClass, "from", range_from); + NATIVE(vm->rangeClass, "to", range_to); + NATIVE(vm->rangeClass, "min", range_min); + NATIVE(vm->rangeClass, "max", range_max); + NATIVE(vm->rangeClass, "iterate ", range_iterate); + NATIVE(vm->rangeClass, "iteratorValue ", range_iteratorValue); + NATIVE(vm->rangeClass, "toString", range_toString); + vm->stringClass = defineClass(vm, "String"); NATIVE(vm->stringClass, "contains ", string_contains); NATIVE(vm->stringClass, "count", string_count); @@ -760,27 +840,6 @@ void wrenInitializeCore(WrenVM* vm) NATIVE(vm->listClass, "[ ]", list_subscript); NATIVE(vm->listClass, "[ ]=", list_subscriptSetter); - vm->numClass = AS_CLASS(findGlobal(vm, "Num")); - NATIVE(vm->numClass, "abs", num_abs); - NATIVE(vm->numClass, "ceil", num_ceil); - NATIVE(vm->numClass, "cos", num_cos); - NATIVE(vm->numClass, "floor", num_floor); - NATIVE(vm->numClass, "isNan", num_isNan); - NATIVE(vm->numClass, "sin", num_sin); - NATIVE(vm->numClass, "sqrt", num_sqrt); - NATIVE(vm->numClass, "toString", num_toString) - NATIVE(vm->numClass, "-", num_negate); - NATIVE(vm->numClass, "- ", num_minus); - NATIVE(vm->numClass, "+ ", num_plus); - NATIVE(vm->numClass, "* ", num_multiply); - NATIVE(vm->numClass, "/ ", num_divide); - NATIVE(vm->numClass, "% ", num_mod); - NATIVE(vm->numClass, "< ", num_lt); - NATIVE(vm->numClass, "> ", num_gt); - NATIVE(vm->numClass, "<= ", num_lte); - NATIVE(vm->numClass, ">= ", num_gte); - NATIVE(vm->numClass, "~", num_bitwiseNot); - // These are defined just so that 0 and -0 are equal, which is specified by // IEEE 754 even though they have different bit representations. NATIVE(vm->numClass, "== ", num_eqeq); diff --git a/src/wren_value.c b/src/wren_value.c index b73551a2..7c7ef545 100644 --- a/src/wren_value.c +++ b/src/wren_value.c @@ -301,6 +301,16 @@ Value wrenListRemoveAt(WrenVM* vm, ObjList* list, int index) return removed; } +Value wrenNewRange(WrenVM* vm, double from, double to) +{ + ObjRange* range = allocate(vm, sizeof(ObjRange) + 16); + initObj(vm, &range->obj, OBJ_RANGE); + range->from = from; + range->to = to; + + return OBJ_VAL(range); +} + Value wrenNewString(WrenVM* vm, const char* text, size_t length) { // Allocate before the string object in case this triggers a GC which would @@ -368,6 +378,7 @@ void wrenFreeObj(WrenVM* vm, Obj* obj) case OBJ_CLOSURE: case OBJ_INSTANCE: + case OBJ_RANGE: case OBJ_UPVALUE: break; } @@ -389,6 +400,7 @@ static ObjClass* getObjectClass(WrenVM* vm, Obj* obj) case OBJ_FN: return vm->fnClass; case OBJ_INSTANCE: return ((ObjInstance*)obj)->classObj; case OBJ_LIST: return vm->listClass; + case OBJ_RANGE: return vm->rangeClass; case OBJ_STRING: return vm->stringClass; case OBJ_UPVALUE: ASSERT(0, "Upvalues should not be used as first-class objects."); @@ -460,6 +472,7 @@ static void printObject(Obj* obj) case OBJ_FN: printf("[fn %p]", obj); break; case OBJ_INSTANCE: printf("[instance %p]", obj); break; case OBJ_LIST: printList((ObjList*)obj); break; + case OBJ_RANGE: printf("[fn %p]", obj); break; case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break; case OBJ_UPVALUE: printf("[upvalue %p]", obj); break; } @@ -530,6 +543,11 @@ bool wrenIsInstance(Value value) return IS_OBJ(value) && AS_OBJ(value)->type == OBJ_INSTANCE; } +bool wrenIsRange(Value value) +{ + return IS_OBJ(value) && AS_OBJ(value)->type == OBJ_RANGE; +} + bool wrenIsString(Value value) { return IS_OBJ(value) && AS_OBJ(value)->type == OBJ_STRING; diff --git a/src/wren_value.h b/src/wren_value.h index d1141845..a9341010 100644 --- a/src/wren_value.h +++ b/src/wren_value.h @@ -54,6 +54,7 @@ typedef enum { OBJ_FN, OBJ_INSTANCE, OBJ_LIST, + OBJ_RANGE, OBJ_STRING, OBJ_UPVALUE } ObjType; @@ -66,7 +67,7 @@ typedef enum typedef struct sObj { - unsigned int type : 3; // ObjType. + unsigned int type : 4; // ObjType. unsigned int flags : 1; // ObjFlags. // The next object in the linked list of all currently allocated objects. @@ -307,6 +308,17 @@ typedef struct Value* elements; } ObjList; +typedef struct +{ + Obj obj; + + // The beginning of the range. + double from; + + // The end of the range. May be greater or less than [from]. + double to; +} ObjRange; + // Value -> ObjClass*. #define AS_CLASS(value) ((ObjClass*)AS_OBJ(value)) @@ -329,6 +341,9 @@ typedef struct // Value -> double. #define AS_NUM(v) ((v).num) +// Value -> ObjRange*. +#define AS_RANGE(v) ((ObjRange*)AS_OBJ(v)) + // Value -> ObjString*. #define AS_STRING(v) ((ObjString*)AS_OBJ(v)) @@ -350,6 +365,9 @@ typedef struct // Returns true if [value] is an instance. #define IS_INSTANCE(value) (wrenIsInstance(value)) +// Returns true if [value] is a range object. +#define IS_RANGE(value) (wrenIsRange(value)) + // Returns true if [value] is a string object. #define IS_STRING(value) (wrenIsString(value)) @@ -541,6 +559,9 @@ void wrenListInsert(WrenVM* vm, ObjList* list, Value value, int index); // Removes and returns the item at [index] from [list]. Value wrenListRemoveAt(WrenVM* vm, ObjList* list, int index); +// Creates a new range from [from] to [to]. +Value wrenNewRange(WrenVM* vm, double from, double to); + // Creates a new string object and copies [text] into it. Value wrenNewString(WrenVM* vm, const char* text, size_t length); @@ -567,6 +588,7 @@ bool wrenIsClosure(Value value); bool wrenIsFiber(Value value); bool wrenIsFn(Value value); bool wrenIsInstance(Value value); +bool wrenIsRange(Value value); bool wrenIsString(Value value); inline Value wrenObjectToValue(Obj* obj) diff --git a/src/wren_vm.c b/src/wren_vm.c index c667acb1..b5a9add1 100644 --- a/src/wren_vm.c +++ b/src/wren_vm.c @@ -259,6 +259,13 @@ static void markClosure(WrenVM* vm, ObjClosure* closure) vm->bytesAllocated += sizeof(Upvalue*) * closure->fn->numUpvalues; } +static void markRange(WrenVM* vm, ObjRange* range) +{ + // Don't recurse if already marked. Avoids getting stuck in a loop on cycles. + if (range->obj.flags & FLAG_MARKED) return; + range->obj.flags |= FLAG_MARKED; +} + static void markString(WrenVM* vm, ObjString* string) { // Don't recurse if already marked. Avoids getting stuck in a loop on cycles. @@ -291,6 +298,7 @@ void wrenMarkObj(WrenVM* vm, Obj* obj) case OBJ_FN: markFn( vm, (ObjFn*) obj); break; case OBJ_INSTANCE: markInstance(vm, (ObjInstance*)obj); break; case OBJ_LIST: markList( vm, (ObjList*) obj); break; + case OBJ_RANGE: markRange( vm, (ObjRange*) obj); break; case OBJ_STRING: markString( vm, (ObjString*) obj); break; case OBJ_UPVALUE: markUpvalue( vm, (Upvalue*) obj); break; } diff --git a/src/wren_vm.h b/src/wren_vm.h index 5452078e..07da30d5 100644 --- a/src/wren_vm.h +++ b/src/wren_vm.h @@ -191,6 +191,7 @@ struct WrenVM { SymbolTable methods; + // TODO: Use an array for some of these. ObjClass* boolClass; ObjClass* classClass; ObjClass* fiberClass; @@ -199,6 +200,7 @@ struct WrenVM ObjClass* nullClass; ObjClass* numClass; ObjClass* objectClass; + ObjClass* rangeClass; ObjClass* stringClass; SymbolTable globalSymbols; diff --git a/test/number/range.wren b/test/number/range.wren deleted file mode 100644 index 3bd27082..00000000 --- a/test/number/range.wren +++ /dev/null @@ -1,14 +0,0 @@ -var inclusive = 2..5 -IO.print(inclusive is Range) // expect: true -IO.print(inclusive.min) // expect: 2 -IO.print(inclusive.max) // expect: 5 - -var exclusive = 2...5 -IO.print(exclusive is Range) // expect: true -IO.print(exclusive.min) // expect: 2 -IO.print(exclusive.max) // expect: 4 - -// TODO: Non-number RHS. -// TODO: Non-integer RHS. -// TODO: Range iteration. -// TODO: Empty or negative ranges. diff --git a/test/range/from.wren b/test/range/from.wren new file mode 100644 index 00000000..71df3157 --- /dev/null +++ b/test/range/from.wren @@ -0,0 +1,31 @@ +// Ordered range. +IO.print((2..5).from) // expect: 2 +IO.print((3..3).from) // expect: 3 +IO.print((0..3).from) // expect: 0 +IO.print((-5..3).from) // expect: -5 +IO.print((-5..-2).from) // expect: -5 + +// Backwards range. +IO.print((5..2).from) // expect: 5 +IO.print((3..0).from) // expect: 3 +IO.print((3..-5).from) // expect: 3 +IO.print((-2..-5).from) // expect: -2 + +// Exclusive ordered range. +IO.print((2...5).from) // expect: 2 +IO.print((3...3).from) // expect: 3 +IO.print((0...3).from) // expect: 0 +IO.print((-5...3).from) // expect: -5 +IO.print((-5...-2).from) // expect: -5 + +// Exclusive backwards range. +IO.print((5...2).from) // expect: 5 +IO.print((3...0).from) // expect: 3 +IO.print((3...-5).from) // expect: 3 +IO.print((-2...-5).from) // expect: -2 + +// TODO: Test toString. +// TODO: Non-number RHS. +// TODO: Non-integer RHS. +// TODO: Range iteration. +// TODO: Empty or negative ranges. diff --git a/test/range/iterate.wren b/test/range/iterate.wren new file mode 100644 index 00000000..ab4dd82a --- /dev/null +++ b/test/range/iterate.wren @@ -0,0 +1,8 @@ +var range = 1..3 +IO.print(range.iterate(null)) // expect: 1 +IO.print(range.iterate(1)) // expect: 2 +IO.print(range.iterate(2)) // expect: 3 +IO.print(range.iterate(3)) // expect: false +IO.print(range.iterate(4)) // expect: false + +// TODO: Negative and empty ranges. diff --git a/test/range/iterate_wrong_type.wren b/test/range/iterate_wrong_type.wren new file mode 100644 index 00000000..2b4779fe --- /dev/null +++ b/test/range/iterate_wrong_type.wren @@ -0,0 +1 @@ +(1..3).iterate("") // expect runtime error: Iterator must be a number. diff --git a/test/range/iterator_value.wren b/test/range/iterator_value.wren new file mode 100644 index 00000000..5274517e --- /dev/null +++ b/test/range/iterator_value.wren @@ -0,0 +1,11 @@ +var range = 1..3 +IO.print(range.iteratorValue(1)) // expect: 1 +IO.print(range.iteratorValue(2)) // expect: 2 +IO.print(range.iteratorValue(3)) // expect: 3 + +// Doesn't bother to bounds check. +IO.print(range.iteratorValue(-2)) // expect: -2 +IO.print(range.iteratorValue(5)) // expect: 5 + +// Or type check. +IO.print(range.iteratorValue("s")) // expect: s diff --git a/test/range/max.wren b/test/range/max.wren new file mode 100644 index 00000000..1cc27493 --- /dev/null +++ b/test/range/max.wren @@ -0,0 +1,25 @@ +// Ordered range. +IO.print((2..5).max) // expect: 5 +IO.print((3..3).max) // expect: 3 +IO.print((0..3).max) // expect: 3 +IO.print((-5..3).max) // expect: 3 +IO.print((-5..-2).max) // expect: -2 + +// Backwards range. +IO.print((5..2).max) // expect: 5 +IO.print((3..0).max) // expect: 3 +IO.print((3..-5).max) // expect: 3 +IO.print((-2..-5).max) // expect: -2 + +// Exclusive ordered range. +IO.print((2...5).max) // expect: 4 +IO.print((3...3).max) // expect: 3 +IO.print((0...3).max) // expect: 2 +IO.print((-5...3).max) // expect: 2 +IO.print((-5...-2).max) // expect: -3 + +// Exclusive backwards range. +IO.print((5...2).max) // expect: 5 +IO.print((3...0).max) // expect: 3 +IO.print((3...-5).max) // expect: 3 +IO.print((-2...-5).max) // expect: -2 diff --git a/test/range/min.wren b/test/range/min.wren new file mode 100644 index 00000000..baaba399 --- /dev/null +++ b/test/range/min.wren @@ -0,0 +1,26 @@ +// Ordered range. +IO.print((2..5).min) // expect: 2 +IO.print((3..3).min) // expect: 3 +IO.print((0..3).min) // expect: 0 +IO.print((-5..3).min) // expect: -5 +IO.print((-5..-2).min) // expect: -5 + +// Backwards range. +IO.print((5..2).min) // expect: 2 +IO.print((3..0).min) // expect: 0 +IO.print((3..-5).min) // expect: -5 +IO.print((-2..-5).min) // expect: -5 + +// Exclusive ordered range. +IO.print((2...5).min) // expect: 2 +IO.print((3...3).min) // expect: 2 +IO.print((0...3).min) // expect: 0 +IO.print((-5...3).min) // expect: -5 +IO.print((-5...-2).min) // expect: -5 + +// Exclusive backwards range. +// TODO: Is this what we want? "..." always means "subtract 1"? +IO.print((5...2).min) // expect: 1 +IO.print((3...0).min) // expect: -1 +IO.print((3...-5).min) // expect: -6 +IO.print((-2...-5).min) // expect: -6 diff --git a/test/range/to.wren b/test/range/to.wren new file mode 100644 index 00000000..cec8836a --- /dev/null +++ b/test/range/to.wren @@ -0,0 +1,25 @@ +// Ordered range. +IO.print((2..5).to) // expect: 5 +IO.print((3..3).to) // expect: 3 +IO.print((0..3).to) // expect: 3 +IO.print((-5..3).to) // expect: 3 +IO.print((-5..-2).to) // expect: -2 + +// Backwards range. +IO.print((5..2).to) // expect: 2 +IO.print((3..0).to) // expect: 0 +IO.print((3..-5).to) // expect: -5 +IO.print((-2..-5).to) // expect: -5 + +// Exclusive ordered range. +IO.print((2...5).to) // expect: 4 +IO.print((3...3).to) // expect: 2 +IO.print((0...3).to) // expect: 2 +IO.print((-5...3).to) // expect: 2 +IO.print((-5...-2).to) // expect: -3 + +// Exclusive backwards range. +IO.print((5...2).to) // expect: 1 +IO.print((3...0).to) // expect: -1 +IO.print((3...-5).to) // expect: -6 +IO.print((-2...-5).to) // expect: -6 diff --git a/test/range/type.wren b/test/range/type.wren new file mode 100644 index 00000000..c7fe8873 --- /dev/null +++ b/test/range/type.wren @@ -0,0 +1,6 @@ +var range = 2..5 + +IO.print(range is Range) // expect: true +IO.print(range is Object) // expect: true +IO.print(range is String) // expect: false +IO.print(range.type == Range) // expect: true