1
0
forked from Mirror/wren

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.
This commit is contained in:
Bob Nystrom
2014-01-20 08:45:15 -08:00
parent d40d04f0c5
commit 97bf314f4b
15 changed files with 291 additions and 88 deletions

View File

@ -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) }
}

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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;
}

View File

@ -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;

View File

@ -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.

31
test/range/from.wren Normal file
View File

@ -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.

8
test/range/iterate.wren Normal file
View File

@ -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.

View File

@ -0,0 +1 @@
(1..3).iterate("") // expect runtime error: Iterator must be a number.

View File

@ -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

25
test/range/max.wren Normal file
View File

@ -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

26
test/range/min.wren Normal file
View File

@ -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

25
test/range/to.wren Normal file
View File

@ -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

6
test/range/type.wren Normal file
View File

@ -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