Skip to content

Commit

Permalink
Add Object.(freeze,isFrozen,setFrozen,validateIsFrozen).
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mhermier committed Mar 17, 2023
1 parent 9ac65ab commit 25da522
Show file tree
Hide file tree
Showing 33 changed files with 346 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/include/wren.h
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,19 @@ WREN_API void wrenEnsureSlots(WrenVM* vm, int numSlots);
// Gets the type of the object in [slot].
WREN_API WrenType wrenGetSlotType(WrenVM* vm, int slot);

// Try to freeze the object in [slot] for eternity.
WREN_API bool wrenFreezeSlot(WrenVM* vm, int slot);

// Test if the object in [slot] is frozen.
WREN_API bool wrenIsSlotFrozen(WrenVM* vm, int slot);

// Try to (un)freeze the object in [slot].
WREN_API bool wrenSetSlotFrozen(WrenVM* vm, int slot, bool isFrozen);

// Try to (un)freeze the object in [slot] using [secretSlot].
WREN_API bool wrenSetSlotFrozenWithSecret(WrenVM* vm, int slot, bool isFrozen,
int secretSlot);

// Reads a boolean value from [slot].
//
// It is an error to call this if the slot does not contain a boolean value.
Expand Down
59 changes: 59 additions & 0 deletions src/vm/wren_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ DEF_PRIMITIVE(list_new)

DEF_PRIMITIVE(list_add)
{
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);

wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]);
RETURN_VAL(args[1]);
}
Expand All @@ -330,6 +332,8 @@ DEF_PRIMITIVE(list_add)
// minimize stack churn.
DEF_PRIMITIVE(list_addCore)
{
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);

wrenValueBufferWrite(vm, &AS_LIST(args[0])->elements, args[1]);

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

DEF_PRIMITIVE(list_clear)
{
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);

wrenValueBufferClear(vm, &AS_LIST(args[0])->elements);
RETURN_NULL;
}
Expand All @@ -349,6 +355,8 @@ DEF_PRIMITIVE(list_count)

DEF_PRIMITIVE(list_insert)
{
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);

ObjList* list = AS_LIST(args[0]);

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

DEF_PRIMITIVE(list_removeAt)
{
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);

ObjList* list = AS_LIST(args[0]);
uint32_t index = validateIndex(vm, args[1], list->elements.count, "Index");
if (index == UINT32_MAX) return false;
Expand All @@ -400,6 +410,8 @@ DEF_PRIMITIVE(list_removeAt)
}

DEF_PRIMITIVE(list_removeValue) {
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);

ObjList* list = AS_LIST(args[0]);
int index = wrenListIndexOf(vm, list, args[1]);
if(index == -1) RETURN_NULL;
Expand All @@ -414,6 +426,8 @@ DEF_PRIMITIVE(list_indexOf)

DEF_PRIMITIVE(list_swap)
{
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);

ObjList* list = AS_LIST(args[0]);
uint32_t indexA = validateIndex(vm, args[1], list->elements.count, "Index 0");
if (indexA == UINT32_MAX) return false;
Expand Down Expand Up @@ -461,6 +475,8 @@ DEF_PRIMITIVE(list_subscript)

DEF_PRIMITIVE(list_subscriptSetter)
{
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);

ObjList* list = AS_LIST(args[0]);
uint32_t index = validateIndex(vm, args[1], list->elements.count,
"Subscript");
Expand Down Expand Up @@ -488,6 +504,8 @@ DEF_PRIMITIVE(map_subscript)

DEF_PRIMITIVE(map_subscriptSetter)
{
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);

if (!validateKey(vm, args[1])) return false;

wrenMapSet(vm, AS_MAP(args[0]), args[1], args[2]);
Expand All @@ -499,6 +517,8 @@ DEF_PRIMITIVE(map_subscriptSetter)
// minimize stack churn.
DEF_PRIMITIVE(map_addCore)
{
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);

if (!validateKey(vm, args[1])) return false;

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

DEF_PRIMITIVE(map_clear)
{
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);

wrenMapClear(vm, AS_MAP(args[0]));
RETURN_NULL;
}
Expand Down Expand Up @@ -560,6 +582,8 @@ DEF_PRIMITIVE(map_iterate)

DEF_PRIMITIVE(map_remove)
{
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);

if (!validateKey(vm, args[1])) return false;

RETURN_VAL(wrenMapRemoveKey(vm, AS_MAP(args[0]), args[1]));
Expand Down Expand Up @@ -885,6 +909,30 @@ DEF_PRIMITIVE(object_is)
RETURN_BOOL(false);
}

DEF_PRIMITIVE(object_freeze)
{
RETURN_BOOL(wrenFreeze(args[0]));
}

DEF_PRIMITIVE(object_isFrozen)
{
RETURN_BOOL(wrenIsFrozen(args[0]));
}

DEF_PRIMITIVE(object_setFrozen)
{
if (!validateBool(vm, args[1], "Value")) return false;

RETURN_BOOL(wrenSetFrozen(args[0], AS_BOOL(args[1])));
}

DEF_PRIMITIVE(object_setFrozenWithSecret)
{
if (!validateBool(vm, args[1], "Value")) return false;

RETURN_BOOL(wrenSetFrozenWithSecret(args[0], AS_BOOL(args[1]), args[2]));
}

DEF_PRIMITIVE(object_toString)
{
Obj* obj = AS_OBJ(args[0]);
Expand All @@ -897,6 +945,12 @@ DEF_PRIMITIVE(object_type)
RETURN_OBJ(wrenGetClass(vm, args[0]));
}

DEF_PRIMITIVE(object_validateIsNotFrozen)
{
VALIDATE_VALUE_IS_NOT_FROZEN(args[0]);
RETURN_VAL(args[0]);
}

DEF_PRIMITIVE(range_from)
{
RETURN_NUM(AS_RANGE(args[0])->from);
Expand Down Expand Up @@ -1247,9 +1301,14 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->objectClass, "!", object_not);
PRIMITIVE(vm->objectClass, "==(_)", object_eqeq);
PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq);
PRIMITIVE(vm->objectClass, "freeze()", object_freeze);
PRIMITIVE(vm->objectClass, "is(_)", object_is);
PRIMITIVE(vm->objectClass, "isFrozen", object_isFrozen);
PRIMITIVE(vm->objectClass, "setFrozen(_)", object_setFrozen);
PRIMITIVE(vm->objectClass, "setFrozen(_,_)", object_setFrozenWithSecret);
PRIMITIVE(vm->objectClass, "toString", object_toString);
PRIMITIVE(vm->objectClass, "type", object_type);
PRIMITIVE(vm->objectClass, "validateIsNotFrozen()", object_validateIsNotFrozen);

// Now we can define Class, which is a subclass of Object.
vm->classClass = defineClass(vm, coreModule, "Class");
Expand Down
1 change: 1 addition & 0 deletions src/vm/wren_core.wren
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ class MapEntry {
construct new(key, value) {
_key = key
_value = value
freeze()
}

key { _key }
Expand Down
1 change: 1 addition & 0 deletions src/vm/wren_core.wren.inc
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ static const char* coreModuleSource =
" construct new(key, value) {\n"
" _key = key\n"
" _value = value\n"
" freeze()\n"
" }\n"
"\n"
" key { _key }\n"
Expand Down
11 changes: 11 additions & 0 deletions src/vm/wren_primitive.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@
return false; \
} while (false)

#define ERROR_MSG_OBJECT_IS_FROZEN "Object is frozen"

#define VALIDATE_VALUE_IS_NOT_FROZEN(value) \
do \
{ \
if (wrenIsFrozen(value)) \
{ \
RETURN_ERROR(ERROR_MSG_OBJECT_IS_FROZEN); \
} \
} while (false)

// Validates that the given [arg] is a bool. Returns true if it is. If not,
// reports an error and returns false.
bool validateBool(WrenVM* vm, Value arg, const char* argName);
Expand Down
12 changes: 12 additions & 0 deletions src/vm/wren_value.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,19 @@ static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj)
{
obj->type = type;
obj->isDark = false;
obj->isFrozen = false;
obj->frozenSecret = FREEZE_SECRET_DEFAULT;
obj->classObj = classObj;
obj->next = vm->first;
vm->first = obj;
}

static void freezeObj(Obj* obj)
{
obj->isFrozen = true;
obj->frozenSecret = FREEZE_SECRET_ETERNAL;
}

ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name)
{
ObjClass* classObj = ALLOCATE(vm, ObjClass);
Expand Down Expand Up @@ -664,6 +672,7 @@ Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive)
{
ObjRange* range = ALLOCATE(vm, ObjRange);
initObj(vm, &range->obj, OBJ_RANGE, vm->rangeClass);
freezeObj(&range->obj);
range->from = from;
range->to = to;
range->isInclusive = isInclusive;
Expand Down Expand Up @@ -716,6 +725,7 @@ Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length)
ASSERT(length == 0 || text != NULL, "Unexpected NULL string.");

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

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

wrenGrayValue(vm, obj->frozenSecret);

// Add it to the gray list so it can be recursively explored for
// more marks later.
if (vm->grayCount >= vm->grayCapacity)
Expand Down
3 changes: 3 additions & 0 deletions src/vm/wren_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ struct sObj
ObjType type;
bool isDark;

bool isFrozen;
Value frozenSecret;

// The object's class.
ObjClass* classObj;

Expand Down
35 changes: 35 additions & 0 deletions src/vm/wren_vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,9 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
} \
} while (false)

#define ERROR_ON_VALUE_IS_NOT_FROZEN(value) \
ERROR_ON(wrenIsFrozen(value), ERROR_MSG_OBJECT_IS_FROZEN)

#if WREN_DEBUG_TRACE_INSTRUCTIONS
// Prints the stack and instruction before each instruction is executed.
#define DEBUG_TRACE_INSTRUCTIONS() \
Expand Down Expand Up @@ -1110,6 +1113,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)

CASE_CODE(STORE_UPVALUE):
{
ERROR_ON_VALUE_IS_NOT_FROZEN(OBJ_VAL(frame->closure));
ObjUpvalue** upvalues = frame->closure->upvalues;
*upvalues[READ_BYTE()]->value = PEEK();
DISPATCH();
Expand All @@ -1128,6 +1132,7 @@ 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.");
ERROR_ON_VALUE_IS_NOT_FROZEN(receiver);
ObjInstance* instance = AS_INSTANCE(receiver);
ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field.");
instance->fields[field] = PEEK();
Expand All @@ -1150,6 +1155,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
uint8_t field = READ_BYTE();
Value receiver = POP();
ASSERT(IS_INSTANCE(receiver), "Receiver should be instance.");
ERROR_ON_VALUE_IS_NOT_FROZEN(receiver);
ObjInstance* instance = AS_INSTANCE(receiver);
ASSERT(field < instance->obj.classObj->numFields, "Out of bounds field.");
instance->fields[field] = PEEK();
Expand Down Expand Up @@ -1683,6 +1689,35 @@ WrenType wrenGetSlotType(WrenVM* vm, int slot)
return WREN_TYPE_UNKNOWN;
}

bool wrenFreezeSlot(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);

return wrenFreeze(vm->apiStack[slot]);
}

bool wrenIsSlotFrozen(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);

return wrenIsFrozen(vm->apiStack[slot]);
}

bool wrenSetSlotFrozen(WrenVM* vm, int slot, bool isFrozen)
{
validateApiSlot(vm, slot);

return wrenSetFrozen(vm->apiStack[slot], isFrozen);
}

bool wrenSetSlotFrozenWithSecret(WrenVM* vm, int slot, bool isFrozen, int secretSlot)
{
validateApiSlot(vm, slot);
validateApiSlot(vm, secretSlot);

return wrenSetFrozenWithSecret(vm->apiStack[slot], isFrozen, vm->apiStack[secretSlot]);
}

bool wrenGetSlotBool(WrenVM* vm, int slot)
{
validateApiSlot(vm, slot);
Expand Down
Loading

0 comments on commit 25da522

Please sign in to comment.