From e2a2d1507b013831047da767cb2cd84d4b2e9bb0 Mon Sep 17 00:00:00 2001 From: Michel Hermier Date: Wed, 28 Apr 2021 12:53:23 +0200 Subject: [PATCH 1/8] Add `wrenGrayValues`. --- src/vm/wren_value.c | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index c49a3b6be..4c448c594 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -1002,14 +1002,19 @@ void wrenGrayValue(WrenVM* vm, Value value) wrenGrayObj(vm, AS_OBJ(value)); } -void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer) +void wrenGrayValues(WrenVM* vm, Value* values, size_t size) { - for (int i = 0; i < buffer->count; i++) + for (size_t i = 0; i < size; i++) { - wrenGrayValue(vm, buffer->data[i]); + wrenGrayValue(vm, values[i]); } } +void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer) +{ + wrenGrayValues(vm, buffer->data, buffer->count); +} + static void blackenClass(WrenVM* vm, ObjClass* classObj) { // The metaclass. @@ -1061,10 +1066,7 @@ static void blackenFiber(WrenVM* vm, ObjFiber* fiber) } // Stack variables. - for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++) - { - wrenGrayValue(vm, *slot); - } + wrenGrayValues(vm, fiber->stack, fiber->stackTop - fiber->stack); // Open upvalues. ObjUpvalue* upvalue = fiber->openUpvalues; @@ -1113,10 +1115,7 @@ static void blackenInstance(WrenVM* vm, ObjInstance* instance) wrenGrayObj(vm, (Obj*)instance->obj.classObj); // Mark the fields. - for (int i = 0; i < instance->obj.classObj->numFields; i++) - { - wrenGrayValue(vm, instance->fields[i]); - } + wrenGrayValues(vm, instance->fields, instance->obj.classObj->numFields); // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjInstance); @@ -1153,10 +1152,7 @@ static void blackenMap(WrenVM* vm, ObjMap* map) static void blackenModule(WrenVM* vm, ObjModule* module) { // Top-level variables. - for (int i = 0; i < module->variables.count; i++) - { - wrenGrayValue(vm, module->variables.data[i]); - } + wrenGrayBuffer(vm, &module->variables); wrenBlackenSymbolTable(vm, &module->variableNames); From 1d1947e49997a4710b4bae038987c2953049619b Mon Sep 17 00:00:00 2001 From: Michel Hermier Date: Wed, 28 Apr 2021 12:30:45 +0200 Subject: [PATCH 2/8] Add `ObjMemorySegment`. --- src/vm/wren_value.c | 53 +++++++++++++++++++++++++++++++++++++++++++++ src/vm/wren_value.h | 42 +++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index 4c448c594..e62d52fe3 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -43,6 +43,48 @@ static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj) vm->first = obj; } +ObjMemorySegment* wrenNewMemorySegment(WrenVM* vm, ObjType type, + ObjClass* classObj, + const Value* data, size_t count, + const void* foreign_data, size_t foreign_count) +{ + const size_t size = wrenMemorySegmentSizeOf(count, foreign_count); + + ObjMemorySegment* ms = (ObjMemorySegment*)wrenReallocate(vm, NULL, 0, size); + initObj(vm, &ms->obj, type, classObj); + + ms->foreign_count = foreign_count; + ms->count = count; + + if (data != NULL) + { + // Copy the data. + for (size_t i = 0; i < count; i++) + { + ms->data[i] = data[i]; + } + } + else + { + // Initialize fields to null. + for (size_t i = 0; i < count; i++) + { + ms->data[i] = NULL_VAL; + } + } + + if (foreign_data != NULL) + { + memcpy(wrenMemorySegmentData(ms), foreign_data, foreign_count); + } + else + { + memset(wrenMemorySegmentData(ms), 0, foreign_count); + } + + return ms; +} + ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name) { ObjClass* classObj = ALLOCATE(vm, ObjClass); @@ -1015,6 +1057,17 @@ void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer) wrenGrayValues(vm, buffer->data, buffer->count); } +static void blackenMemorySegment(WrenVM* vm, ObjMemorySegment* ms) +{ + wrenGrayObj(vm, (Obj*)ms->obj.classObj); + + // Mark the fields. + wrenGrayValues(vm, ms->data, ms->count); + + // Keep track of how much memory is still in use. + vm->bytesAllocated += wrenMemorySegmentSizeOf(ms->count, ms->foreign_count); +} + static void blackenClass(WrenVM* vm, ObjClass* classObj) { // The metaclass. diff --git a/src/vm/wren_value.h b/src/vm/wren_value.h index 2ca0cfdcd..3a79a50a9 100644 --- a/src/vm/wren_value.h +++ b/src/vm/wren_value.h @@ -54,6 +54,8 @@ #define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance* #define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList* #define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap* +#define AS_MEMORYSEGMENT(value) /* ObjMemorySegment* */ \ + (ObjMemorySegment*)(AS_OBJ(value)) #define AS_MODULE(value) ((ObjModule*)AS_OBJ(value)) // ObjModule* #define AS_NUM(value) (wrenValueToNum(value)) // double #define AS_RANGE(v) ((ObjRange*)AS_OBJ(v)) // ObjRange* @@ -148,6 +150,20 @@ typedef struct DECLARE_BUFFER(Value, Value); +// FIXME: Do we need foreign_count or memorypage_count ? +typedef struct +{ + Obj obj; + + size_t foreign_count; + + size_t count; + + Value data[FLEXIBLE_ARRAY]; + +// uint8_t foreign_data[FLEXIBLE_ARRAY]; +} ObjMemorySegment; + // A heap-allocated string object. struct sObjString { @@ -617,6 +633,32 @@ typedef struct #endif +static inline size_t wrenMemorySegmentSizeOf(size_t count, size_t foreign_count) +{ + return sizeof(ObjMemorySegment) + sizeof(Value) * count + foreign_count; +} + +// Creates a new memory segment of the given [classObj]. +ObjMemorySegment* wrenNewMemorySegment(WrenVM* vm, ObjType type, + ObjClass* classObj, + const Value* data, size_t count, + const void* foreign_data, size_t foreign_count); + +static inline Value* wrenMemorySegmentAt(ObjMemorySegment* ms, size_t index) +{ + ASSERT(ms != NULL, "Unexpected NULL memory segment."); + ASSERT(index < ms->count, "Out of bounds field."); + + return &ms->data[index]; +} + +static inline void* wrenMemorySegmentData(ObjMemorySegment* ms) +{ + ASSERT(ms != NULL, "Unexpected NULL memory segment."); + + return &ms->data[ms->count]; +} + // Creates a new "raw" class. It has no metaclass or superclass whatsoever. // This is only used for bootstrapping the initial Object and Class classes, // which are a little special. From 622ad59c023eed08b1cc03e67dd62ff2811767de Mon Sep 17 00:00:00 2001 From: Michel Hermier Date: Wed, 28 Apr 2021 13:35:06 +0200 Subject: [PATCH 3/8] Reimplement `ObjForeign` using `ObjMemorySegment`. --- src/vm/wren_value.c | 22 ++++------------------ src/vm/wren_value.h | 11 +++-------- src/vm/wren_vm.c | 10 +++++----- src/vm/wren_vm.h | 2 +- 4 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index e62d52fe3..f54a3ba50 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -273,14 +273,9 @@ void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed) } } -ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size) +ObjMemorySegment* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size) { - ObjForeign* object = ALLOCATE_FLEX(vm, ObjForeign, uint8_t, size); - initObj(vm, &object->obj, OBJ_FOREIGN, classObj); - - // Zero out the bytes. - memset(object->data, 0, size); - return object; + return wrenNewMemorySegment(vm, OBJ_FOREIGN, classObj, NULL, 0, NULL, size); } ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots) @@ -1154,15 +1149,6 @@ static void blackenFn(WrenVM* vm, ObjFn* fn) // TODO: What about the function name? } -static void blackenForeign(WrenVM* vm, ObjForeign* foreign) -{ - // TODO: Keep track of how much memory the foreign object uses. We can store - // this in each foreign object, but it will balloon the size. We may not want - // that much overhead. One option would be to let the foreign class register - // a C function that returns a size for the object. That way the VM doesn't - // always have to explicitly store it. -} - static void blackenInstance(WrenVM* vm, ObjInstance* instance) { wrenGrayObj(vm, (Obj*)instance->obj.classObj); @@ -1251,7 +1237,7 @@ static void blackenObject(WrenVM* vm, Obj* obj) case OBJ_CLOSURE: blackenClosure( vm, (ObjClosure*) obj); break; case OBJ_FIBER: blackenFiber( vm, (ObjFiber*) obj); break; case OBJ_FN: blackenFn( vm, (ObjFn*) obj); break; - case OBJ_FOREIGN: blackenForeign( vm, (ObjForeign*) obj); break; + case OBJ_FOREIGN: blackenMemorySegment(vm, (ObjMemorySegment*)obj); break; case OBJ_INSTANCE: blackenInstance(vm, (ObjInstance*)obj); break; case OBJ_LIST: blackenList( vm, (ObjList*) obj); break; case OBJ_MAP: blackenMap( vm, (ObjMap*) obj); break; @@ -1306,7 +1292,7 @@ void wrenFreeObj(WrenVM* vm, Obj* obj) } case OBJ_FOREIGN: - wrenFinalizeForeign(vm, (ObjForeign*)obj); + wrenFinalizeForeign(vm, (ObjMemorySegment*)obj); break; case OBJ_LIST: diff --git a/src/vm/wren_value.h b/src/vm/wren_value.h index 3a79a50a9..f59f502fe 100644 --- a/src/vm/wren_value.h +++ b/src/vm/wren_value.h @@ -50,7 +50,6 @@ #define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) // ObjClosure* #define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v)) // ObjFiber* #define AS_FN(value) ((ObjFn*)AS_OBJ(value)) // ObjFn* -#define AS_FOREIGN(v) ((ObjForeign*)AS_OBJ(v)) // ObjForeign* #define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance* #define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList* #define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap* @@ -80,6 +79,8 @@ #define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance #define IS_LIST(value) (wrenIsObjType(value, OBJ_LIST)) // ObjList #define IS_MAP(value) (wrenIsObjType(value, OBJ_MAP)) // ObjMap +#define IS_MEMORYSEGMENT(value) /* ObjMemorySegment */ \ + (IS_FOREIGN(value)) #define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange #define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString @@ -431,12 +432,6 @@ struct sObjClass Value attributes; }; -typedef struct -{ - Obj obj; - uint8_t data[FLEXIBLE_ARRAY]; -} ObjForeign; - typedef struct { Obj obj; @@ -704,7 +699,7 @@ static inline bool wrenHasError(const ObjFiber* fiber) return !IS_NULL(fiber->error); } -ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size); +ObjMemorySegment* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size); // Creates a new empty function. Before being used, it must have code, // constants, etc. added to it. diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index 254d0b037..ede1201ca 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -677,7 +677,7 @@ static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack) vm->apiStack = NULL; } -void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign) +void wrenFinalizeForeign(WrenVM* vm, ObjMemorySegment* foreign) { // TODO: Don't look up every time. int symbol = wrenSymbolTableFind(&vm->methodNames, "", 10); @@ -696,7 +696,7 @@ void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign) ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign."); WrenFinalizerFn finalizer = (WrenFinalizerFn)method->as.foreign; - finalizer(foreign->data); + finalizer(wrenMemorySegmentData(foreign)); } // Let the host resolve an imported module name if it wants to. @@ -1705,7 +1705,7 @@ void* wrenGetSlotForeign(WrenVM* vm, int slot) ASSERT(IS_FOREIGN(vm->apiStack[slot]), "Slot must hold a foreign instance."); - return AS_FOREIGN(vm->apiStack[slot])->data; + return wrenMemorySegmentData(AS_MEMORYSEGMENT(vm->apiStack[slot])); } const char* wrenGetSlotString(WrenVM* vm, int slot) @@ -1754,10 +1754,10 @@ void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size) ObjClass* classObj = AS_CLASS(vm->apiStack[classSlot]); ASSERT(classObj->numFields == -1, "Class must be a foreign class."); - ObjForeign* foreign = wrenNewForeign(vm, classObj, size); + ObjMemorySegment* foreign = wrenNewForeign(vm, classObj, size); vm->apiStack[slot] = OBJ_VAL(foreign); - return (void*)foreign->data; + return (void*)wrenMemorySegmentData(foreign); } void wrenSetSlotNewList(WrenVM* vm, int slot) diff --git a/src/vm/wren_vm.h b/src/vm/wren_vm.h index 7ab74c9ee..bb6f6af71 100644 --- a/src/vm/wren_vm.h +++ b/src/vm/wren_vm.h @@ -132,7 +132,7 @@ struct WrenVM void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize); // Invoke the finalizer for the foreign object referenced by [foreign]. -void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign); +void wrenFinalizeForeign(WrenVM* vm, ObjMemorySegment* foreign); // Creates a new [WrenHandle] for [value]. WrenHandle* wrenMakeHandle(WrenVM* vm, Value value); From 315d61dca64bea7b8000f3d2528a025136ad5dcc Mon Sep 17 00:00:00 2001 From: Michel Hermier Date: Wed, 28 Apr 2021 12:34:35 +0200 Subject: [PATCH 4/8] Reimplement `ObjInstance` using `ObjMemorySegment`. --- src/vm/wren_value.c | 29 ++++------------------------- src/vm/wren_value.h | 9 +-------- src/vm/wren_vm.c | 28 ++++++++++++---------------- 3 files changed, 17 insertions(+), 49 deletions(-) diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index f54a3ba50..4ebcdbdd7 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -307,17 +307,8 @@ void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length) Value wrenNewInstance(WrenVM* vm, ObjClass* classObj) { - ObjInstance* instance = ALLOCATE_FLEX(vm, ObjInstance, - Value, classObj->numFields); - initObj(vm, &instance->obj, OBJ_INSTANCE, classObj); - - // Initialize fields to null. - for (int i = 0; i < classObj->numFields; i++) - { - instance->fields[i] = NULL_VAL; - } - - return OBJ_VAL(instance); + return OBJ_VAL(wrenNewMemorySegment(vm, OBJ_INSTANCE, classObj, + NULL, classObj->numFields, NULL, 0)); } ObjList* wrenNewList(WrenVM* vm, uint32_t numElements) @@ -1149,18 +1140,6 @@ static void blackenFn(WrenVM* vm, ObjFn* fn) // TODO: What about the function name? } -static void blackenInstance(WrenVM* vm, ObjInstance* instance) -{ - wrenGrayObj(vm, (Obj*)instance->obj.classObj); - - // Mark the fields. - wrenGrayValues(vm, instance->fields, instance->obj.classObj->numFields); - - // Keep track of how much memory is still in use. - vm->bytesAllocated += sizeof(ObjInstance); - vm->bytesAllocated += sizeof(Value) * instance->obj.classObj->numFields; -} - static void blackenList(WrenVM* vm, ObjList* list) { // Mark the elements. @@ -1237,8 +1216,8 @@ static void blackenObject(WrenVM* vm, Obj* obj) case OBJ_CLOSURE: blackenClosure( vm, (ObjClosure*) obj); break; case OBJ_FIBER: blackenFiber( vm, (ObjFiber*) obj); break; case OBJ_FN: blackenFn( vm, (ObjFn*) obj); break; - case OBJ_FOREIGN: blackenMemorySegment(vm, (ObjMemorySegment*)obj); break; - case OBJ_INSTANCE: blackenInstance(vm, (ObjInstance*)obj); break; + case OBJ_FOREIGN: + case OBJ_INSTANCE: blackenMemorySegment(vm, (ObjMemorySegment*)obj); break; case OBJ_LIST: blackenList( vm, (ObjList*) obj); break; case OBJ_MAP: blackenMap( vm, (ObjMap*) obj); break; case OBJ_MODULE: blackenModule( vm, (ObjModule*) obj); break; diff --git a/src/vm/wren_value.h b/src/vm/wren_value.h index f59f502fe..ae58668f3 100644 --- a/src/vm/wren_value.h +++ b/src/vm/wren_value.h @@ -50,7 +50,6 @@ #define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) // ObjClosure* #define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v)) // ObjFiber* #define AS_FN(value) ((ObjFn*)AS_OBJ(value)) // ObjFn* -#define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance* #define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList* #define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap* #define AS_MEMORYSEGMENT(value) /* ObjMemorySegment* */ \ @@ -80,7 +79,7 @@ #define IS_LIST(value) (wrenIsObjType(value, OBJ_LIST)) // ObjList #define IS_MAP(value) (wrenIsObjType(value, OBJ_MAP)) // ObjMap #define IS_MEMORYSEGMENT(value) /* ObjMemorySegment */ \ - (IS_FOREIGN(value)) + (IS_FOREIGN(value) || IS_INSTANCE(value)) #define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange #define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString @@ -432,12 +431,6 @@ struct sObjClass Value attributes; }; -typedef struct -{ - Obj obj; - Value fields[FLEXIBLE_ARRAY]; -} ObjInstance; - typedef struct { Obj obj; diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index ede1201ca..faf1f1b1f 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -942,10 +942,9 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) { uint8_t field = READ_BYTE(); Value receiver = stackStart[0]; - ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); - ObjInstance* instance = AS_INSTANCE(receiver); - ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); - PUSH(instance->fields[field]); + ASSERT(IS_MEMORYSEGMENT(receiver), "Receiver should be a memory segment."); + ObjMemorySegment* ms = AS_MEMORYSEGMENT(receiver); + PUSH(*wrenMemorySegmentAt(ms, field)); DISPATCH(); } @@ -1117,10 +1116,9 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) { uint8_t field = READ_BYTE(); Value receiver = stackStart[0]; - ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); - ObjInstance* instance = AS_INSTANCE(receiver); - ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); - instance->fields[field] = PEEK(); + ASSERT(IS_MEMORYSEGMENT(receiver), "Receiver should be a memory segment."); + ObjMemorySegment* ms = AS_MEMORYSEGMENT(receiver); + *wrenMemorySegmentAt(ms, field) = PEEK(); DISPATCH(); } @@ -1128,10 +1126,9 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) { uint8_t field = READ_BYTE(); Value receiver = POP(); - ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); - ObjInstance* instance = AS_INSTANCE(receiver); - ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); - PUSH(instance->fields[field]); + ASSERT(IS_MEMORYSEGMENT(receiver), "Receiver should be a memory segment."); + ObjMemorySegment* ms = AS_MEMORYSEGMENT(receiver); + PUSH(*wrenMemorySegmentAt(ms, field)); DISPATCH(); } @@ -1139,10 +1136,9 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) { uint8_t field = READ_BYTE(); Value receiver = POP(); - ASSERT(IS_INSTANCE(receiver), "Receiver should be instance."); - ObjInstance* instance = AS_INSTANCE(receiver); - ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field."); - instance->fields[field] = PEEK(); + ASSERT(IS_MEMORYSEGMENT(receiver), "Receiver should be a memory segment."); + ObjMemorySegment* ms = AS_MEMORYSEGMENT(receiver); + *wrenMemorySegmentAt(ms, field) = PEEK(); DISPATCH(); } From 36ab0eb286db04a243f3a8fb295cfcf9ffe8149e Mon Sep 17 00:00:00 2001 From: Michel Hermier Date: Thu, 9 Mar 2023 10:50:06 +0100 Subject: [PATCH 5/8] Add `Tuple` --- src/vm/wren_core.c | 153 ++++++++++++++++++++++ src/vm/wren_core.wren | 15 +++ src/vm/wren_core.wren.inc | 15 +++ src/vm/wren_value.c | 7 + src/vm/wren_value.h | 7 +- src/vm/wren_vm.c | 1 + src/vm/wren_vm.h | 1 + test/core/tuple/count.wren | 5 + test/core/tuple/equality.wren | 19 +++ test/core/tuple/filled.wren | 8 ++ test/core/tuple/filled_size_negative.wren | 1 + test/core/tuple/filled_size_not_int.wren | 1 + test/core/tuple/filled_size_not_num.wren | 1 + test/core/tuple/subscript.wren | 7 + test/core/tuple/subscript_setter.wren | 21 +++ test/core/tuple/to_string.wren | 10 ++ 16 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 test/core/tuple/count.wren create mode 100644 test/core/tuple/equality.wren create mode 100644 test/core/tuple/filled.wren create mode 100644 test/core/tuple/filled_size_negative.wren create mode 100644 test/core/tuple/filled_size_not_int.wren create mode 100644 test/core/tuple/filled_size_not_num.wren create mode 100644 test/core/tuple/subscript.wren create mode 100644 test/core/tuple/subscript_setter.wren create mode 100644 test/core/tuple/to_string.wren diff --git a/src/vm/wren_core.c b/src/vm/wren_core.c index d0a121f8c..334d79328 100644 --- a/src/vm/wren_core.c +++ b/src/vm/wren_core.c @@ -1197,6 +1197,134 @@ DEF_PRIMITIVE(string_toString) RETURN_VAL(args[0]); } +static bool tuple_new(WrenVM* vm, Value* args, int numArgs) +{ + RETURN_OBJ(wrenNewTuple(vm, &args[1], numArgs)); +} + +DEF_PRIMITIVE(tuple_filled) +{ + if (!validateInt(vm, args[1], "Size")) return false; + if (AS_NUM(args[1]) < 0) RETURN_ERROR("Size cannot be negative."); + + size_t size = (size_t)AS_NUM(args[1]); + + // FIXME: Add a wrenNewTupleFilled? + ObjMemorySegment* ms = wrenNewTuple(vm, NULL, size); + + // wrenNewTuple fills with `null` by default + if (IS_NULL(args[2])) RETURN_OBJ(ms); + + for (size_t i = 0; i < size; i++) + { + ms->data[i] = args[2]; + } + + RETURN_OBJ(ms); +} + +#define DEF_TUPLE_NEW(numArgs) \ + DEF_PRIMITIVE(tuple_new##numArgs) \ + { \ + return tuple_new(vm, args, numArgs); \ + } + +DEF_TUPLE_NEW(0) +DEF_TUPLE_NEW(1) +DEF_TUPLE_NEW(2) +DEF_TUPLE_NEW(3) +DEF_TUPLE_NEW(4) +DEF_TUPLE_NEW(5) +DEF_TUPLE_NEW(6) +DEF_TUPLE_NEW(7) +DEF_TUPLE_NEW(8) +DEF_TUPLE_NEW(9) +DEF_TUPLE_NEW(10) +DEF_TUPLE_NEW(11) +DEF_TUPLE_NEW(12) +DEF_TUPLE_NEW(13) +DEF_TUPLE_NEW(14) +DEF_TUPLE_NEW(15) +DEF_TUPLE_NEW(16) + +DEF_PRIMITIVE(tuple_count) +{ + ObjMemorySegment* ms = AS_MEMORYSEGMENT(args[0]); + + RETURN_NUM(ms->count); +} + +DEF_PRIMITIVE(tuple_subscript) +{ + ObjMemorySegment* ms = AS_MEMORYSEGMENT(args[0]); + + if (IS_NUM(args[1])) + { + uint32_t index = validateIndex(vm, args[1], ms->count, "Subscript"); + if (index == UINT32_MAX) return false; + + RETURN_VAL(ms->data[index]); + } + + if (!IS_RANGE(args[1])) + { + RETURN_ERROR("Subscript must be a number or a range."); + } + + int step; + uint32_t count = ms->count; + uint32_t start = calculateRange(vm, AS_RANGE(args[1]), &count, &step); + if (start == UINT32_MAX) return false; + + ObjMemorySegment* result = wrenNewTuple(vm, NULL, count); + for (size_t i = 0; i < count; i++) + { + result->data[i] = ms->data[start + i * step]; + } + + RETURN_OBJ(result); +} + +DEF_PRIMITIVE(tuple_subscriptSetter) +{ + ObjMemorySegment* ms = AS_MEMORYSEGMENT(args[0]); + uint32_t index = validateIndex(vm, args[1], ms->count, "Subscript"); + if (index == UINT32_MAX) return false; + + ms->data[index] = args[2]; + RETURN_VAL(args[2]); +} + +DEF_PRIMITIVE(tuple_iterate) +{ + ObjMemorySegment* ms = AS_MEMORYSEGMENT(args[0]); + + // If we're starting the iteration, return the first index. + if (IS_NULL(args[1])) + { + if (ms->count == 0) RETURN_FALSE; + RETURN_NUM(0); + } + + if (!validateInt(vm, args[1], "Iterator")) return false; + + // Stop if we're out of bounds. + double index = AS_NUM(args[1]); + if (index < 0 || index >= ms->count - 1) RETURN_FALSE; + + // Otherwise, move to the next index. + RETURN_NUM(index + 1); +} + +DEF_PRIMITIVE(tuple_iteratorValue) +{ + ObjMemorySegment* ms = AS_MEMORYSEGMENT(args[0]); + uint32_t index = validateIndex(vm, args[1], ms->count, "Iterator"); + if (index == UINT32_MAX) return false; + + RETURN_VAL(ms->data[index]); +} + DEF_PRIMITIVE(system_clock) { RETURN_NUM((double)clock() / CLOCKS_PER_SEC); @@ -1467,6 +1595,31 @@ void wrenInitializeCore(WrenVM* vm) PRIMITIVE(vm->rangeClass, "iteratorValue(_)", range_iteratorValue); PRIMITIVE(vm->rangeClass, "toString", range_toString); + vm->tupleClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Tuple")); + PRIMITIVE(vm->tupleClass->obj.classObj, "filled(_,_)", tuple_filled); + PRIMITIVE(vm->tupleClass->obj.classObj, "new()", tuple_new0); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_)", tuple_new1); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_)", tuple_new2); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_)", tuple_new3); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_,_)", tuple_new4); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_,_,_)", tuple_new5); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_,_,_,_)", tuple_new6); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_,_,_,_,_)", tuple_new7); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_,_,_,_,_,_)", tuple_new8); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_,_,_,_,_,_,_)", tuple_new9); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_,_,_,_,_,_,_,_)", tuple_new10); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_,_,_,_,_,_,_,_,_)", tuple_new11); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_,_,_,_,_,_,_,_,_,_)", tuple_new12); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_,_,_,_,_,_,_,_,_,_,_)", tuple_new13); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_,_,_,_,_,_,_,_,_,_,_,_)", tuple_new14); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", tuple_new15); + PRIMITIVE(vm->tupleClass->obj.classObj, "new(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", tuple_new16); + PRIMITIVE(vm->tupleClass, "count", tuple_count); + PRIMITIVE(vm->tupleClass, "[_]", tuple_subscript); + PRIMITIVE(vm->tupleClass, "[_]=(_)", tuple_subscriptSetter); + PRIMITIVE(vm->tupleClass, "iterate(_)", tuple_iterate); + PRIMITIVE(vm->tupleClass, "iteratorValue(_)", tuple_iteratorValue); + ObjClass* systemClass = AS_CLASS(wrenFindVariable(vm, coreModule, "System")); PRIMITIVE(systemClass->obj.classObj, "clock", system_clock); PRIMITIVE(systemClass->obj.classObj, "gc()", system_gc); diff --git a/src/vm/wren_core.wren b/src/vm/wren_core.wren index f073062c2..17f48ab43 100644 --- a/src/vm/wren_core.wren +++ b/src/vm/wren_core.wren @@ -437,6 +437,21 @@ class MapValueSequence is Sequence { class Range is Sequence {} +class Tuple is Sequence { + ==(rhs) { + if (Object.same(this, rhs)) return true + if (rhs.type != Tuple || count != rhs.count) return false + for (i in 0...count) { + if (this[i] == rhs[i]) continue + return false + } + return true + } + !=(rhs) {!(this == rhs)} + + toString { "(%(join(", ")))" } +} + class System { static print() { writeString_("\n") diff --git a/src/vm/wren_core.wren.inc b/src/vm/wren_core.wren.inc index be296cdf7..29cf06ad1 100644 --- a/src/vm/wren_core.wren.inc +++ b/src/vm/wren_core.wren.inc @@ -439,6 +439,21 @@ static const char* coreModuleSource = "\n" "class Range is Sequence {}\n" "\n" +"class Tuple is Sequence {\n" +" ==(rhs) {\n" +" if (Object.same(this, rhs)) return true\n" +" if (rhs.type != Tuple || count != rhs.count) return false\n" +" for (i in 0...count) {\n" +" if (this[i] == rhs[i]) continue\n" +" return false\n" +" }\n" +" return true\n" +" }\n" +" !=(rhs) {!(this == rhs)}\n" +"\n" +" toString { \"(%(join(\", \")))\" }\n" +"}\n" +"\n" "class System {\n" " static print() {\n" " writeString_(\"\\n\")\n" diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index 4ebcdbdd7..7075032cc 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -988,6 +988,12 @@ uint32_t wrenStringFind(ObjString* haystack, ObjString* needle, uint32_t start) return UINT32_MAX; } +ObjMemorySegment* wrenNewTuple(WrenVM* vm, Value* data, size_t count) +{ + return wrenNewMemorySegment(vm, OBJ_TUPLE, vm->tupleClass, + data, count, NULL, 0); +} + ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value) { ObjUpvalue* upvalue = ALLOCATE(vm, ObjUpvalue); @@ -1223,6 +1229,7 @@ static void blackenObject(WrenVM* vm, Obj* obj) case OBJ_MODULE: blackenModule( vm, (ObjModule*) obj); break; case OBJ_RANGE: blackenRange( vm, (ObjRange*) obj); break; case OBJ_STRING: blackenString( vm, (ObjString*) obj); break; + case OBJ_TUPLE: blackenMemorySegment(vm, (ObjMemorySegment*)obj); break; case OBJ_UPVALUE: blackenUpvalue( vm, (ObjUpvalue*) obj); break; } } diff --git a/src/vm/wren_value.h b/src/vm/wren_value.h index ae58668f3..7a4593340 100644 --- a/src/vm/wren_value.h +++ b/src/vm/wren_value.h @@ -79,9 +79,10 @@ #define IS_LIST(value) (wrenIsObjType(value, OBJ_LIST)) // ObjList #define IS_MAP(value) (wrenIsObjType(value, OBJ_MAP)) // ObjMap #define IS_MEMORYSEGMENT(value) /* ObjMemorySegment */ \ - (IS_FOREIGN(value) || IS_INSTANCE(value)) + (IS_FOREIGN(value) || IS_INSTANCE(value) || IS_TUPLE(value)) #define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange #define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString +#define IS_TUPLE(value) (wrenIsObjType(value, OBJ_TUPLE)) // ObjTuple // Creates a new string object from [text], which should be a bare C string // literal. This determines the length of the string automatically at compile @@ -101,6 +102,7 @@ typedef enum { OBJ_MODULE, OBJ_RANGE, OBJ_STRING, + OBJ_TUPLE, OBJ_UPVALUE } ObjType; @@ -797,6 +799,9 @@ static inline bool wrenStringEqualsCString(const ObjString* a, return a->length == length && memcmp(a->value, b, length) == 0; } +// Creates a new tuple. +ObjMemorySegment* wrenNewTuple(WrenVM* vm, Value* data, size_t count); + // Creates a new open upvalue pointing to [value] on the stack. ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value); diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index faf1f1b1f..2605ea3ec 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -526,6 +526,7 @@ static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue, superclass == vm->mapClass || superclass == vm->rangeClass || superclass == vm->stringClass || + superclass == vm->tupleClass || superclass == vm->boolClass || superclass == vm->nullClass || superclass == vm->numClass) diff --git a/src/vm/wren_vm.h b/src/vm/wren_vm.h index bb6f6af71..02e165b53 100644 --- a/src/vm/wren_vm.h +++ b/src/vm/wren_vm.h @@ -41,6 +41,7 @@ struct WrenVM ObjClass* objectClass; ObjClass* rangeClass; ObjClass* stringClass; + ObjClass* tupleClass; // The fiber that is currently running. ObjFiber* fiber; diff --git a/test/core/tuple/count.wren b/test/core/tuple/count.wren new file mode 100644 index 000000000..25380b45c --- /dev/null +++ b/test/core/tuple/count.wren @@ -0,0 +1,5 @@ +System.print(Tuple.new().count) // expect: 0 +System.print(Tuple.new(0).count) // expect: 1 +System.print(Tuple.new(0, 1).count) // expect: 2 +System.print(Tuple.new(0, 1, 2).count) // expect: 3 +System.print(Tuple.new(0, 1, 2, 3).count) // expect: 4 diff --git a/test/core/tuple/equality.wren b/test/core/tuple/equality.wren new file mode 100644 index 000000000..43c9568c8 --- /dev/null +++ b/test/core/tuple/equality.wren @@ -0,0 +1,19 @@ +var tuple0 = Tuple.new() +var tuple1 = Tuple.new(0) +var tuple2 = Tuple.new(0, 1) +var tuple3 = Tuple.new(0, 1, 2) + +System.print(tuple0 == tuple0) // expect: true +System.print(tuple0 == tuple3) // expect: false +System.print(tuple1 == tuple3) // expect: false +System.print(tuple2 == tuple3) // expect: false +System.print(tuple3 == tuple3) // expect: true + +System.print(tuple3 == Tuple.new(0, 1, 2)) // expect: true + +// Not equal to other types. +System.print(tuple0 == []) // expect: false +System.print(tuple1 == 1) // expect: false + +// Don't test all the path since `lhs != rhs` is trivially `!(lhs == rhs)` +System.print(tuple0 != tuple0) // expect: false diff --git a/test/core/tuple/filled.wren b/test/core/tuple/filled.wren new file mode 100644 index 000000000..d1c5e3f47 --- /dev/null +++ b/test/core/tuple/filled.wren @@ -0,0 +1,8 @@ +var tuple = Tuple.filled(3, "value") +System.print(tuple.count) // expect: 3 +System.print(tuple) // expect: (value, value, value) + +// Can create an empty tuple. +tuple = Tuple.filled(0, "value") +System.print(tuple.count) // expect: 0 +System.print(tuple) // expect: () diff --git a/test/core/tuple/filled_size_negative.wren b/test/core/tuple/filled_size_negative.wren new file mode 100644 index 000000000..0b454565b --- /dev/null +++ b/test/core/tuple/filled_size_negative.wren @@ -0,0 +1 @@ +Tuple.filled(-1, null) // expect runtime error: Size cannot be negative. diff --git a/test/core/tuple/filled_size_not_int.wren b/test/core/tuple/filled_size_not_int.wren new file mode 100644 index 000000000..dcd38e86d --- /dev/null +++ b/test/core/tuple/filled_size_not_int.wren @@ -0,0 +1 @@ +Tuple.filled(1.2, null) // expect runtime error: Size must be an integer. diff --git a/test/core/tuple/filled_size_not_num.wren b/test/core/tuple/filled_size_not_num.wren new file mode 100644 index 000000000..dadf8c94d --- /dev/null +++ b/test/core/tuple/filled_size_not_num.wren @@ -0,0 +1 @@ +Tuple.filled("not num", null) // expect runtime error: Size must be a number. diff --git a/test/core/tuple/subscript.wren b/test/core/tuple/subscript.wren new file mode 100644 index 000000000..a84bf9b3d --- /dev/null +++ b/test/core/tuple/subscript.wren @@ -0,0 +1,7 @@ + +var tuple = Tuple.new(0, 1, 2, 3) +System.print(tuple[0]) // expect: 0 +System.print(tuple[3]) // expect: 3 + +System.print(tuple[-1]) // expect: 3 +System.print(tuple[-4]) // expect: 0 diff --git a/test/core/tuple/subscript_setter.wren b/test/core/tuple/subscript_setter.wren new file mode 100644 index 000000000..23e990320 --- /dev/null +++ b/test/core/tuple/subscript_setter.wren @@ -0,0 +1,21 @@ + +var tuple = Tuple.new(0, 1, 2, 3) +System.print(tuple[0] = 3) // expect: 3 +System.print(tuple[1] = 2) // expect: 2 +System.print(tuple[2] = 1) // expect: 1 +System.print(tuple[3] = 0) // expect: 0 + +System.print(tuple[0]) // expect: 3 +System.print(tuple[1]) // expect: 2 +System.print(tuple[2]) // expect: 1 +System.print(tuple[3]) // expect: 0 + +System.print(tuple[-1] = 3) // expect: 3 +System.print(tuple[-2] = 2) // expect: 2 +System.print(tuple[-3] = 1) // expect: 1 +System.print(tuple[-4] = 0) // expect: 0 + +System.print(tuple[0]) // expect: 0 +System.print(tuple[1]) // expect: 1 +System.print(tuple[2]) // expect: 2 +System.print(tuple[3]) // expect: 3 diff --git a/test/core/tuple/to_string.wren b/test/core/tuple/to_string.wren new file mode 100644 index 000000000..a30f5ba72 --- /dev/null +++ b/test/core/tuple/to_string.wren @@ -0,0 +1,10 @@ +// Handle empty tuples. +System.print(Tuple.new().toString) // expect: () + +// Does not quote strings. +System.print(Tuple.new(1, "2", true).toString) // expect: (1, 2, true) + +// Nested tuples. +System.print(Tuple.new(1, Tuple.new(2, Tuple.new(3), 4), 5)) // expect: (1, (2, (3), 4), 5) + +// TODO: Handle tuples that contain themselves. From f7fa7ec92f38a69492cd4e5f154f39eb52f955b8 Mon Sep 17 00:00:00 2001 From: Michel Hermier Date: Thu, 9 Mar 2023 10:08:37 +0100 Subject: [PATCH 6/8] Add `List.toTuple` --- src/vm/wren_core.c | 8 ++++++++ test/core/list/to_tuple.wren | 7 +++++++ 2 files changed, 15 insertions(+) create mode 100644 test/core/list/to_tuple.wren diff --git a/src/vm/wren_core.c b/src/vm/wren_core.c index 334d79328..484c2a7f1 100644 --- a/src/vm/wren_core.c +++ b/src/vm/wren_core.c @@ -470,6 +470,13 @@ DEF_PRIMITIVE(list_subscriptSetter) RETURN_VAL(args[2]); } +DEF_PRIMITIVE(list_toTuple) +{ + ObjList* list = AS_LIST(args[0]); + + RETURN_OBJ(wrenNewTuple(vm, list->elements.data, list->elements.count)); +} + DEF_PRIMITIVE(map_new) { RETURN_OBJ(wrenNewMap(vm)); @@ -1571,6 +1578,7 @@ void wrenInitializeCore(WrenVM* vm) PRIMITIVE(vm->listClass, "remove(_)", list_removeValue); PRIMITIVE(vm->listClass, "indexOf(_)", list_indexOf); PRIMITIVE(vm->listClass, "swap(_,_)", list_swap); + PRIMITIVE(vm->listClass, "toTuple", list_toTuple); vm->mapClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Map")); PRIMITIVE(vm->mapClass->obj.classObj, "new()", map_new); diff --git a/test/core/list/to_tuple.wren b/test/core/list/to_tuple.wren new file mode 100644 index 000000000..6efbd3783 --- /dev/null +++ b/test/core/list/to_tuple.wren @@ -0,0 +1,7 @@ +var tuple = [0, 1, 2, 3].toTuple +System.print(tuple.type) // expect: Tuple +System.print(tuple.count) // expect: 4 +System.print(tuple[0]) // expect: 0 +System.print(tuple[1]) // expect: 1 +System.print(tuple[2]) // expect: 2 +System.print(tuple[3]) // expect: 3 From 3db038003bf254553307d1bbeb5ebdffe64521ee Mon Sep 17 00:00:00 2001 From: Michel Hermier Date: Fri, 10 Mar 2023 16:38:22 +0100 Subject: [PATCH 7/8] Add `Sequence.toTuple` --- src/vm/wren_core.wren | 2 ++ src/vm/wren_core.wren.inc | 2 ++ test/core/sequence/to_tuple.wren | 13 +++++++++++++ 3 files changed, 17 insertions(+) create mode 100644 test/core/sequence/to_tuple.wren diff --git a/src/vm/wren_core.wren b/src/vm/wren_core.wren index 17f48ab43..b0629bd47 100644 --- a/src/vm/wren_core.wren +++ b/src/vm/wren_core.wren @@ -116,6 +116,8 @@ class Sequence { } return result } + + toTuple { toList.toTuple } } class MapSequence is Sequence { diff --git a/src/vm/wren_core.wren.inc b/src/vm/wren_core.wren.inc index 29cf06ad1..b76687a5a 100644 --- a/src/vm/wren_core.wren.inc +++ b/src/vm/wren_core.wren.inc @@ -118,6 +118,8 @@ static const char* coreModuleSource = " }\n" " return result\n" " }\n" +"\n" +" toTuple { toList.toTuple }\n" "}\n" "\n" "class MapSequence is Sequence {\n" diff --git a/test/core/sequence/to_tuple.wren b/test/core/sequence/to_tuple.wren new file mode 100644 index 000000000..bcdc24c2c --- /dev/null +++ b/test/core/sequence/to_tuple.wren @@ -0,0 +1,13 @@ +class TestSequence is Sequence { + construct new() {} + + iterate(iterator) { + if (iterator == null) return 1 + if (iterator == 3) return false + return iterator + 1 + } + + iteratorValue(iterator) { iterator } +} + +System.print(TestSequence.new().toTuple) // expect: (1, 2, 3) From 72cc8b1c77f1a8b081fb4a6d2b31f57d0ee8414e Mon Sep 17 00:00:00 2001 From: Michel Hermier Date: Thu, 9 Mar 2023 10:01:35 +0100 Subject: [PATCH 8/8] lang: Add anonymous `Tuple` syntax --- src/vm/wren_compiler.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 92b16cac0..2bc6f54cc 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -1747,6 +1747,7 @@ typedef struct // Forward declarations since the grammar is recursive. static GrammarRule* getRule(TokenType type); static void expression(Compiler* compiler); +static void maybeTuple(Compiler* compiler); static void statement(Compiler* compiler); static void definition(Compiler* compiler); static void parsePrecedence(Compiler* compiler, Precedence precedence); @@ -2138,7 +2139,7 @@ static void loadCoreVariable(Compiler* compiler, const char* name) // A parenthesized expression. static void grouping(Compiler* compiler, bool canAssign) { - expression(compiler); + maybeTuple(compiler); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after expression."); } @@ -2857,6 +2858,16 @@ void expression(Compiler* compiler) parsePrecedence(compiler, PREC_LOWEST); } +void maybeTuple(Compiler* compiler) +{ + expression(compiler); + + if (peek(compiler) == TOKEN_COMMA) + { + error(compiler, "Cannot create a tuple."); + } +} + // Returns the number of bytes for the arguments to the instruction // at [ip] in [fn]'s bytecode. static int getByteCountForArguments(const uint8_t* bytecode,