Skip to content

Commit 25da522

Browse files
committed
Add Object.(freeze,isFrozen,setFrozen,validateIsFrozen).
Wren lacks the ability to declare variable constants. The foolwing patch allow to make objects immutable, giving a similar functionnality. NOTE: The implemetation while functionnal for `ObjectInstance` is opt-in for foreign and primitive methods.
1 parent 9ac65ab commit 25da522

33 files changed

+346
-0
lines changed

src/include/wren.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,19 @@ WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots);
401401
// Gets the type of the object in [slot].
402402
WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot);
403403

404+
// Try to freeze the object in [slot] for eternity.
405+
WREN_API bool wrenFreezeSlot(WrenVM* vm, int slot);
406+
407+
// Test if the object in [slot] is frozen.
408+
WREN_API bool wrenIsSlotFrozen(WrenVM* vm, int slot);
409+
410+
// Try to (un)freeze the object in [slot].
411+
WREN_API bool wrenSetSlotFrozen(WrenVM* vm, int slot, bool isFrozen);
412+
413+
// Try to (un)freeze the object in [slot] using [secretSlot].
414+
WREN_API bool wrenSetSlotFrozenWithSecret(WrenVM* vm, int slot, bool isFrozen,
415+
int secretSlot);
416+
404417
// Reads a boolean value from [slot].
405418
//
406419
// It is an error to call this if the slot does not contain a boolean value.

src/vm/wren_core.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@ DEF_PRIMITIVE(list_new)
321321

322322
DEF_PRIMITIVE(list_add)
323323
{
324+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
325+
324326
wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]);
325327
RETURN_VAL(args[1]);
326328
}
@@ -330,6 +332,8 @@ DEF_PRIMITIVE(list_add)
330332
// minimize stack churn.
331333
DEF_PRIMITIVE(list_addCore)
332334
{
335+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
336+
333337
wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]);
334338

335339
// Return the list.
@@ -338,6 +342,8 @@ DEF_PRIMITIVE(list_addCore)
338342

339343
DEF_PRIMITIVE(list_clear)
340344
{
345+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
346+
341347
wrenValueBufferClear(vm, &AS_LIST(args[0])->elements);
342348
RETURN_NULL;
343349
}
@@ -349,6 +355,8 @@ DEF_PRIMITIVE(list_count)
349355

350356
DEF_PRIMITIVE(list_insert)
351357
{
358+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
359+
352360
ObjList* list = AS_LIST(args[0]);
353361

354362
// count + 1 here so you can "insert" at the very end.
@@ -392,6 +400,8 @@ DEF_PRIMITIVE(list_iteratorValue)
392400

393401
DEF_PRIMITIVE(list_removeAt)
394402
{
403+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
404+
395405
ObjList* list = AS_LIST(args[0]);
396406
uint32_t index = validateIndex(vm, args[1], list->elements.count, "Index");
397407
if (index == UINT32_MAX) return false;
@@ -400,6 +410,8 @@ DEF_PRIMITIVE(list_removeAt)
400410
}
401411

402412
DEF_PRIMITIVE(list_removeValue) {
413+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
414+
403415
ObjList* list = AS_LIST(args[0]);
404416
int index = wrenListIndexOf(vm, list, args[1]);
405417
if(index == -1) RETURN_NULL;
@@ -414,6 +426,8 @@ DEF_PRIMITIVE(list_indexOf)
414426

415427
DEF_PRIMITIVE(list_swap)
416428
{
429+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
430+
417431
ObjList* list = AS_LIST(args[0]);
418432
uint32_t indexA = validateIndex(vm, args[1], list->elements.count, "Index 0");
419433
if (indexA == UINT32_MAX) return false;
@@ -461,6 +475,8 @@ DEF_PRIMITIVE(list_subscript)
461475

462476
DEF_PRIMITIVE(list_subscriptSetter)
463477
{
478+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
479+
464480
ObjList* list = AS_LIST(args[0]);
465481
uint32_t index = validateIndex(vm, args[1], list->elements.count,
466482
"Subscript");
@@ -488,6 +504,8 @@ DEF_PRIMITIVE(map_subscript)
488504

489505
DEF_PRIMITIVE(map_subscriptSetter)
490506
{
507+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
508+
491509
if (!validateKey(vm, args[1])) return false;
492510

493511
wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]);
@@ -499,6 +517,8 @@ DEF_PRIMITIVE(map_subscriptSetter)
499517
// minimize stack churn.
500518
DEF_PRIMITIVE(map_addCore)
501519
{
520+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
521+
502522
if (!validateKey(vm, args[1])) return false;
503523

504524
wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]);
@@ -509,6 +529,8 @@ DEF_PRIMITIVE(map_addCore)
509529

510530
DEF_PRIMITIVE(map_clear)
511531
{
532+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
533+
512534
wrenMapClear(vm, AS_MAP(args[0]));
513535
RETURN_NULL;
514536
}
@@ -560,6 +582,8 @@ DEF_PRIMITIVE(map_iterate)
560582

561583
DEF_PRIMITIVE(map_remove)
562584
{
585+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
586+
563587
if (!validateKey(vm, args[1])) return false;
564588

565589
RETURN_VAL(wrenMapRemoveKey(vm, AS_MAP(args[0]), args[1]));
@@ -885,6 +909,30 @@ DEF_PRIMITIVE(object_is)
885909
RETURN_BOOL(false);
886910
}
887911

912+
DEF_PRIMITIVE(object_freeze)
913+
{
914+
RETURN_BOOL(wrenFreeze(args[0]));
915+
}
916+
917+
DEF_PRIMITIVE(object_isFrozen)
918+
{
919+
RETURN_BOOL(wrenIsFrozen(args[0]));
920+
}
921+
922+
DEF_PRIMITIVE(object_setFrozen)
923+
{
924+
if (!validateBool(vm, args[1], "Value")) return false;
925+
926+
RETURN_BOOL(wrenSetFrozen(args[0], AS_BOOL(args[1])));
927+
}
928+
929+
DEF_PRIMITIVE(object_setFrozenWithSecret)
930+
{
931+
if (!validateBool(vm, args[1], "Value")) return false;
932+
933+
RETURN_BOOL(wrenSetFrozenWithSecret(args[0], AS_BOOL(args[1]), args[2]));
934+
}
935+
888936
DEF_PRIMITIVE(object_toString)
889937
{
890938
Obj* obj = AS_OBJ(args[0]);
@@ -897,6 +945,12 @@ DEF_PRIMITIVE(object_type)
897945
RETURN_OBJ(wrenGetClass(vm, args[0]));
898946
}
899947

948+
DEF_PRIMITIVE(object_validateIsNotFrozen)
949+
{
950+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
951+
RETURN_VAL(args[0]);
952+
}
953+
900954
DEF_PRIMITIVE(range_from)
901955
{
902956
RETURN_NUM(AS_RANGE(args[0])->from);
@@ -1247,9 +1301,14 @@ void wrenInitializeCore(WrenVM* vm)
12471301
PRIMITIVE(vm->objectClass, "!", object_not);
12481302
PRIMITIVE(vm->objectClass, "==(_)", object_eqeq);
12491303
PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq);
1304+
PRIMITIVE(vm->objectClass, "freeze()", object_freeze);
12501305
PRIMITIVE(vm->objectClass, "is(_)", object_is);
1306+
PRIMITIVE(vm->objectClass, "isFrozen", object_isFrozen);
1307+
PRIMITIVE(vm->objectClass, "setFrozen(_)", object_setFrozen);
1308+
PRIMITIVE(vm->objectClass, "setFrozen(_,_)", object_setFrozenWithSecret);
12511309
PRIMITIVE(vm->objectClass, "toString", object_toString);
12521310
PRIMITIVE(vm->objectClass, "type", object_type);
1311+
PRIMITIVE(vm->objectClass, "validateIsNotFrozen()", object_validateIsNotFrozen);
12531312

12541313
// Now we can define Class, which is a subclass of Object.
12551314
vm->classClass = defineClass(vm, coreModule, "Class");

src/vm/wren_core.wren

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ class MapEntry {
409409
construct new(key, value) {
410410
_key = key
411411
_value = value
412+
freeze()
412413
}
413414

414415
key { _key }

src/vm/wren_core.wren.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ static const char* coreModuleSource =
411411
" construct new(key, value) {\n"
412412
" _key = key\n"
413413
" _value = value\n"
414+
" freeze()\n"
414415
" }\n"
415416
"\n"
416417
" key { _key }\n"

src/vm/wren_primitive.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@
6363
return false; \
6464
} while (false)
6565

66+
#define ERROR_MSG_OBJECT_IS_FROZEN "Object is frozen"
67+
68+
#define VALIDATE_VALUE_IS_NOT_FROZEN(value) \
69+
do \
70+
{ \
71+
if (wrenIsFrozen(value)) \
72+
{ \
73+
RETURN_ERROR(ERROR_MSG_OBJECT_IS_FROZEN); \
74+
} \
75+
} while (false)
76+
6677
// Validates that the given [arg] is a bool. Returns true if it is. If not,
6778
// reports an error and returns false.
6879
bool validateBool(WrenVM* vm, Value arg, const char* argName);

src/vm/wren_value.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,19 @@ static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj)
3838
{
3939
obj->type = type;
4040
obj->isDark = false;
41+
obj->isFrozen = false;
42+
obj->frozenSecret = FREEZE_SECRET_DEFAULT;
4143
obj->classObj = classObj;
4244
obj->next = vm->first;
4345
vm->first = obj;
4446
}
4547

48+
static void freezeObj(Obj* obj)
49+
{
50+
obj->isFrozen = true;
51+
obj->frozenSecret = FREEZE_SECRET_ETERNAL;
52+
}
53+
4654
ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name)
4755
{
4856
ObjClass* classObj = ALLOCATE(vm, ObjClass);
@@ -664,6 +672,7 @@ Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive)
664672
{
665673
ObjRange* range = ALLOCATE(vm, ObjRange);
666674
initObj(vm, &range->obj, OBJ_RANGE, vm->rangeClass);
675+
freezeObj(&range->obj);
667676
range->from = from;
668677
range->to = to;
669678
range->isInclusive = isInclusive;
@@ -716,6 +725,7 @@ Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length)
716725
ASSERT(length == 0 || text != NULL, "Unexpected NULL string.");
717726

718727
ObjString* string = allocateString(vm, length);
728+
freezeObj(&string->obj);
719729

720730
// Copy the string (if given one).
721731
if (length > 0 && text != NULL) memcpy(string->value, text, length);
@@ -983,6 +993,8 @@ void wrenGrayObj(WrenVM* vm, Obj* obj)
983993
// It's been reached.
984994
obj->isDark = true;
985995

996+
wrenGrayValue(vm, obj->frozenSecret);
997+
986998
// Add it to the gray list so it can be recursively explored for
987999
// more marks later.
9881000
if (vm->grayCount >= vm->grayCapacity)

src/vm/wren_value.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ struct sObj
141141
ObjType type;
142142
bool isDark;
143143

144+
bool isFrozen;
145+
Value frozenSecret;
146+
144147
// The object's class.
145148
ObjClass* classObj;
146149

src/vm/wren_vm.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,9 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
885885
} \
886886
} while (false)
887887

888+
#define ERROR_ON_VALUE_IS_NOT_FROZEN(value) \
889+
ERROR_ON(wrenIsFrozen(value), ERROR_MSG_OBJECT_IS_FROZEN)
890+
888891
#if WREN_DEBUG_TRACE_INSTRUCTIONS
889892
// Prints the stack and instruction before each instruction is executed.
890893
#define DEBUG_TRACE_INSTRUCTIONS() \
@@ -1110,6 +1113,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
11101113

11111114
CASE_CODE(STORE_UPVALUE):
11121115
{
1116+
ERROR_ON_VALUE_IS_NOT_FROZEN(OBJ_VAL(frame->closure));
11131117
ObjUpvalue** upvalues = frame->closure->upvalues;
11141118
*upvalues[READ_BYTE()]->value = PEEK();
11151119
DISPATCH();
@@ -1128,6 +1132,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
11281132
uint8_t field = READ_BYTE();
11291133
Value receiver = stackStart[0];
11301134
ASSERT(IS_INSTANCE(receiver), "Receiver should be instance.");
1135+
ERROR_ON_VALUE_IS_NOT_FROZEN(receiver);
11311136
ObjInstance* instance = AS_INSTANCE(receiver);
11321137
ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field.");
11331138
instance->fields[field] = PEEK();
@@ -1150,6 +1155,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
11501155
uint8_t field = READ_BYTE();
11511156
Value receiver = POP();
11521157
ASSERT(IS_INSTANCE(receiver), "Receiver should be instance.");
1158+
ERROR_ON_VALUE_IS_NOT_FROZEN(receiver);
11531159
ObjInstance* instance = AS_INSTANCE(receiver);
11541160
ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field.");
11551161
instance->fields[field] = PEEK();
@@ -1683,6 +1689,35 @@ WrenType wrenGetSlotType(WrenVM* vm, int slot)
16831689
return WREN_TYPE_UNKNOWN;
16841690
}
16851691

1692+
bool wrenFreezeSlot(WrenVM* vm, int slot)
1693+
{
1694+
validateApiSlot(vm, slot);
1695+
1696+
return wrenFreeze(vm->apiStack[slot]);
1697+
}
1698+
1699+
bool wrenIsSlotFrozen(WrenVM* vm, int slot)
1700+
{
1701+
validateApiSlot(vm, slot);
1702+
1703+
return wrenIsFrozen(vm->apiStack[slot]);
1704+
}
1705+
1706+
bool wrenSetSlotFrozen(WrenVM* vm, int slot, bool isFrozen)
1707+
{
1708+
validateApiSlot(vm, slot);
1709+
1710+
return wrenSetFrozen(vm->apiStack[slot], isFrozen);
1711+
}
1712+
1713+
bool wrenSetSlotFrozenWithSecret(WrenVM* vm, int slot, bool isFrozen, int secretSlot)
1714+
{
1715+
validateApiSlot(vm, slot);
1716+
validateApiSlot(vm, secretSlot);
1717+
1718+
return wrenSetFrozenWithSecret(vm->apiStack[slot], isFrozen, vm->apiStack[secretSlot]);
1719+
}
1720+
16861721
bool wrenGetSlotBool(WrenVM* vm, int slot)
16871722
{
16881723
validateApiSlot(vm, slot);

0 commit comments

Comments
 (0)