Skip to content

Commit 6bdcbe1

Browse files
committed
Add Object.freeze, Object.isFrozen and Object.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 a28ac44 commit 6bdcbe1

22 files changed

+189
-0
lines changed

src/include/wren.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,12 @@ 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].
405+
WREN_API bool wrenFreezeSlot(WrenVM* vm, int slot, bool isFrozen);
406+
407+
// Test if the object in [slot] is frozen.
408+
WREN_API bool wrenIsFrozenSlot(WrenVM* vm, int slot);
409+
404410
// Reads a boolean value from [slot].
405411
//
406412
// It is an error to call this if the slot does not contain a boolean value.

src/vm/wren_core.c

Lines changed: 43 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]));
@@ -863,6 +887,11 @@ DEF_PRIMITIVE(object_bangeq)
863887
RETURN_BOOL(!wrenValuesEqual(args[0], args[1]));
864888
}
865889

890+
DEF_PRIMITIVE(object_freeze)
891+
{
892+
RETURN_VAL(wrenFreeze(args[0], true));
893+
}
894+
866895
DEF_PRIMITIVE(object_is)
867896
{
868897
if (!IS_CLASS(args[1]))
@@ -885,6 +914,11 @@ DEF_PRIMITIVE(object_is)
885914
RETURN_BOOL(false);
886915
}
887916

917+
DEF_PRIMITIVE(object_isFrozen)
918+
{
919+
RETURN_BOOL(wrenIsFrozen(args[0]));
920+
}
921+
888922
DEF_PRIMITIVE(object_toString)
889923
{
890924
Obj* obj = AS_OBJ(args[0]);
@@ -897,6 +931,12 @@ DEF_PRIMITIVE(object_type)
897931
RETURN_OBJ(wrenGetClass(vm, args[0]));
898932
}
899933

934+
DEF_PRIMITIVE(object_validateIsNotFrozen)
935+
{
936+
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
937+
RETURN_VAL(args[0]);
938+
}
939+
900940
DEF_PRIMITIVE(range_from)
901941
{
902942
RETURN_NUM(AS_RANGE(args[0])->from);
@@ -1247,9 +1287,12 @@ void wrenInitializeCore(WrenVM* vm)
12471287
PRIMITIVE(vm->objectClass, "!", object_not);
12481288
PRIMITIVE(vm->objectClass, "==(_)", object_eqeq);
12491289
PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq);
1290+
PRIMITIVE(vm->objectClass, "freeze()", object_freeze);
12501291
PRIMITIVE(vm->objectClass, "is(_)", object_is);
1292+
PRIMITIVE(vm->objectClass, "isFrozen", object_isFrozen);
12511293
PRIMITIVE(vm->objectClass, "toString", object_toString);
12521294
PRIMITIVE(vm->objectClass, "type", object_type);
1295+
PRIMITIVE(vm->objectClass, "validateIsNotFrozen()", object_validateIsNotFrozen);
12531296

12541297
// Now we can define Class, which is a subclass of Object.
12551298
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 function. Returns true if it is. If not,
6778
// reports an error and returns false.
6879
bool validateFn(WrenVM* vm, Value arg, const char* argName);

src/vm/wren_value.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj)
3838
{
3939
obj->type = type;
4040
obj->isDark = false;
41+
obj->isFrozen = false;
4142
obj->classObj = classObj;
4243
obj->next = vm->first;
4344
vm->first = obj;
@@ -664,6 +665,7 @@ Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive)
664665
{
665666
ObjRange* range = ALLOCATE(vm, ObjRange);
666667
initObj(vm, &range->obj, OBJ_RANGE, vm->rangeClass);
668+
range->obj.isFrozen = true;
667669
range->from = from;
668670
range->to = to;
669671
range->isInclusive = isInclusive;
@@ -716,6 +718,7 @@ Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length)
716718
ASSERT(length == 0 || text != NULL, "Unexpected NULL string.");
717719

718720
ObjString* string = allocateString(vm, length);
721+
string->obj.isFrozen = true;
719722

720723
// Copy the string (if given one).
721724
if (length > 0 && text != NULL) memcpy(string->value, text, length);

src/vm/wren_value.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ struct sObj
110110
{
111111
ObjType type;
112112
bool isDark;
113+
bool isFrozen;
113114

114115
// The object's class.
115116
ObjClass* classObj;
@@ -829,6 +830,20 @@ static inline bool wrenIsBool(Value value)
829830
#endif
830831
}
831832

833+
static inline Value wrenFreeze(Value value, bool isFrozen)
834+
{
835+
if (IS_OBJ(value)) AS_OBJ(value)->isFrozen = isFrozen;
836+
837+
return value;
838+
}
839+
840+
static inline bool wrenIsFrozen(Value value)
841+
{
842+
if (IS_OBJ(value)) return AS_OBJ(value)->isFrozen;
843+
844+
return true;
845+
}
846+
832847
// Returns true if [value] is an object of type [type]. Do not call this
833848
// directly, instead use the [IS___] macro for the type in question.
834849
static inline bool wrenIsObjType(Value value, ObjType type)

src/vm/wren_vm.c

Lines changed: 20 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,20 @@ WrenType wrenGetSlotType(WrenVM* vm, int slot)
16831689
return WREN_TYPE_UNKNOWN;
16841690
}
16851691

1692+
bool wrenFreezeSlot(WrenVM* vm, int slot, bool isFrozen)
1693+
{
1694+
validateApiSlot(vm, slot);
1695+
1696+
return wrenIsFrozen(wrenFreeze(vm->apiStack[slot], isFrozen));
1697+
}
1698+
1699+
WREN_API bool wrenIsFrozenSlot(WrenVM* vm, int slot)
1700+
{
1701+
validateApiSlot(vm, slot);
1702+
1703+
return wrenIsFrozen(vm->apiStack[slot]);
1704+
}
1705+
16861706
bool wrenGetSlotBool(WrenVM* vm, int slot)
16871707
{
16881708
validateApiSlot(vm, slot);

test/core/bool/is_frozen.wren

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
System.print(false.isFrozen) // expect: true
3+
System.print(true.isFrozen) // expect: true

test/core/class/is_frozen.wren

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
class Foo {
3+
}
4+
5+
// Check core bootstrap classes
6+
System.print(Class.isFrozen) // expect: false
7+
System.print(Object.isFrozen) // expect: false
8+
System.print(Object.type.isFrozen) // expect: false
9+
10+
// Check a core class
11+
System.print(List.isFrozen) // expect: false
12+
System.print(List.type.isFrozen) // expect: false
13+
14+
// Check a user defined class
15+
System.print(Foo.isFrozen) // expect: false
16+
System.print(Foo.type.isFrozen) // expect: false

0 commit comments

Comments
 (0)