summaryrefslogtreecommitdiffstats
path: root/generic/tclOOProp.c
diff options
context:
space:
mode:
Diffstat (limited to 'generic/tclOOProp.c')
-rw-r--r--generic/tclOOProp.c1354
1 files changed, 1354 insertions, 0 deletions
diff --git a/generic/tclOOProp.c b/generic/tclOOProp.c
new file mode 100644
index 0000000..4cff300
--- /dev/null
+++ b/generic/tclOOProp.c
@@ -0,0 +1,1354 @@
+/*
+ * tclOOProp.c --
+ *
+ * This file contains implementations of the configurable property
+ * mecnanisms.
+ *
+ * Copyright © 2023-2024 Donal K. Fellows
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include "tclOOInt.h"
+
+/* Short-term cache for GetPropertyName(). */
+typedef struct GPNCache {
+ Tcl_Obj *listPtr; /* Holds references to names. */
+ char *names[TCLFLEXARRAY]; /* NULL-terminated table of names. */
+} GPNCache;
+
+enum GPNFlags {
+ GPN_WRITABLE = 1, /* Are we looking for a writable property? */
+ GPN_FALLING_BACK = 2 /* Are we doing a recursive call to determine
+ * if the property is of the other type? */
+};
+
+/*
+ * Shared bits for [property] declarations.
+ */
+enum PropOpt {
+ PROP_ALL, PROP_READABLE, PROP_WRITABLE
+};
+static const char *const propOptNames[] = {
+ "-all", "-readable", "-writable",
+ NULL
+};
+
+/*
+ * Forward declarations.
+ */
+
+static int Configurable_Getter(void *clientData,
+ Tcl_Interp *interp, Tcl_ObjectContext context,
+ int objc, Tcl_Obj *const *objv);
+static int Configurable_Setter(void *clientData,
+ Tcl_Interp *interp, Tcl_ObjectContext context,
+ int objc, Tcl_Obj *const *objv);
+static void DetailsDeleter(void *clientData);
+static int DetailsCloner(Tcl_Interp *, void *oldClientData,
+ void **newClientData);
+static void ImplementObjectProperty(Tcl_Object targetObject,
+ Tcl_Obj *propNamePtr, int installGetter,
+ int installSetter);
+static void ImplementClassProperty(Tcl_Class targetObject,
+ Tcl_Obj *propNamePtr, int installGetter,
+ int installSetter);
+
+/*
+ * Method descriptors
+ */
+
+static const Tcl_MethodType GetterType = {
+ TCL_OO_METHOD_VERSION_1,
+ "PropertyGetter",
+ Configurable_Getter,
+ DetailsDeleter,
+ DetailsCloner
+};
+
+static const Tcl_MethodType SetterType = {
+ TCL_OO_METHOD_VERSION_1,
+ "PropertySetter",
+ Configurable_Setter,
+ DetailsDeleter,
+ DetailsCloner
+};
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * TclOO_Configurable_Configure --
+ *
+ * Implementation of the oo::configurable->configure method.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+/*
+ * Ugly thunks to read and write a property by calling the right method in
+ * the right way. Note that we MUST be correct in holding references to Tcl_Obj
+ * values, as this is potentially a call into user code.
+ */
+static inline int
+ReadProperty(
+ Tcl_Interp *interp,
+ Object *oPtr,
+ const char *propName)
+{
+ Tcl_Obj *args[] = {
+ oPtr->fPtr->myName,
+ Tcl_ObjPrintf("<ReadProp%s>", propName)
+ };
+ int code;
+
+ Tcl_IncrRefCount(args[0]);
+ Tcl_IncrRefCount(args[1]);
+ code = TclOOPrivateObjectCmd(oPtr, interp, 2, args);
+ Tcl_DecrRefCount(args[0]);
+ Tcl_DecrRefCount(args[1]);
+ switch (code) {
+ case TCL_BREAK:
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "property getter for %s did a break", propName));
+ return TCL_ERROR;
+ case TCL_CONTINUE:
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "property getter for %s did a continue", propName));
+ return TCL_ERROR;
+ default:
+ return code;
+ }
+}
+
+static inline int
+WriteProperty(
+ Tcl_Interp *interp,
+ Object *oPtr,
+ const char *propName,
+ Tcl_Obj *valueObj)
+{
+ Tcl_Obj *args[] = {
+ oPtr->fPtr->myName,
+ Tcl_ObjPrintf("<WriteProp%s>", propName),
+ valueObj
+ };
+ int code;
+
+ Tcl_IncrRefCount(args[0]);
+ Tcl_IncrRefCount(args[1]);
+ Tcl_IncrRefCount(args[2]);
+ code = TclOOPrivateObjectCmd(oPtr, interp, 3, args);
+ Tcl_DecrRefCount(args[0]);
+ Tcl_DecrRefCount(args[1]);
+ Tcl_DecrRefCount(args[2]);
+ switch (code) {
+ case TCL_BREAK:
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "property setter for %s did a break", propName));
+ return TCL_ERROR;
+ case TCL_CONTINUE:
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "property setter for %s did a continue", propName));
+ return TCL_ERROR;
+ default:
+ return code;
+ }
+}
+
+/* Look up a property full name. */
+static Tcl_Obj *
+GetPropertyName(
+ Tcl_Interp *interp, /* Context and error reporting. */
+ Object *oPtr, /* Object to get property name from. */
+ int flags, /* Are we looking for a writable property?
+ * Can we do a fallback message?
+ * See GPNFlags for possible values */
+ Tcl_Obj *namePtr, /* The name supplied by the user. */
+ GPNCache **cachePtr) /* Where to cache the table, if the caller
+ * wants that. The contents are to be freed
+ * with Tcl_Free if the cache is used. */
+{
+ Tcl_Size objc, index, i;
+ Tcl_Obj *listPtr = TclOOGetAllObjectProperties(
+ oPtr, flags & GPN_WRITABLE);
+ Tcl_Obj **objv;
+ GPNCache *tablePtr;
+
+ (void) Tcl_ListObjGetElements(NULL, listPtr, &objc, &objv);
+ if (cachePtr && *cachePtr) {
+ tablePtr = *cachePtr;
+ } else {
+ tablePtr = (GPNCache *) TclStackAlloc(interp,
+ offsetof(GPNCache, names) + sizeof(char *) * (objc + 1));
+
+ for (i = 0; i < objc; i++) {
+ tablePtr->names[i] = TclGetString(objv[i]);
+ }
+ tablePtr->names[objc] = NULL;
+ if (cachePtr) {
+ /*
+ * Have a cache, but nothing in it so far.
+ *
+ * We cache the list here so it doesn't vanish from under our
+ * feet if a property implementation does something crazy like
+ * changing the set of properties. The type of copy this does
+ * means that the copy holds the references to the names in the
+ * table.
+ */
+ tablePtr->listPtr = TclListObjCopy(NULL, listPtr);
+ Tcl_IncrRefCount(tablePtr->listPtr);
+ *cachePtr = tablePtr;
+ } else {
+ tablePtr->listPtr = NULL;
+ }
+ }
+ int result = Tcl_GetIndexFromObjStruct(interp, namePtr, tablePtr->names,
+ sizeof(char *), "property", TCL_INDEX_TEMP_TABLE, &index);
+ if (result == TCL_ERROR && !(flags & GPN_FALLING_BACK)) {
+ /*
+ * If property can be accessed the other way, use a special message.
+ * We use a recursive call to look this up.
+ */
+
+ Tcl_InterpState foo = Tcl_SaveInterpState(interp, result);
+ Tcl_Obj *otherName = GetPropertyName(interp, oPtr,
+ flags ^ (GPN_WRITABLE | GPN_FALLING_BACK), namePtr, NULL);
+ result = Tcl_RestoreInterpState(interp, foo);
+ if (otherName != NULL) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "property \"%s\" is %s only",
+ TclGetString(otherName),
+ (flags & GPN_WRITABLE) ? "read" : "write"));
+ }
+ }
+ if (!cachePtr) {
+ TclStackFree(interp, tablePtr);
+ }
+ if (result != TCL_OK) {
+ return NULL;
+ }
+ return objv[index];
+}
+
+/* Release the cache made by GetPropertyName(). */
+static inline void
+ReleasePropertyNameCache(
+ Tcl_Interp *interp,
+ GPNCache **cachePtr)
+{
+ if (*cachePtr) {
+ GPNCache *tablePtr = *cachePtr;
+ if (tablePtr->listPtr) {
+ Tcl_DecrRefCount(tablePtr->listPtr);
+ }
+ TclStackFree(interp, tablePtr);
+ *cachePtr = NULL;
+ }
+}
+
+int
+TclOO_Configurable_Configure(
+ TCL_UNUSED(void *),
+ Tcl_Interp *interp, /* Interpreter used for the result, error
+ * reporting, etc. */
+ Tcl_ObjectContext context, /* The object/call context. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const *objv) /* The actual arguments. */
+{
+ Object *oPtr = (Object *) Tcl_ObjectContextObject(context);
+ Tcl_Size skip = Tcl_ObjectContextSkippedArgs(context);
+ Tcl_Obj *namePtr;
+ Tcl_Size i, namec;
+ int code = TCL_OK;
+
+ objc -= skip;
+ if ((objc & 1) && (objc != 1)) {
+ /*
+ * Bad (odd > 1) number of arguments.
+ */
+
+ Tcl_WrongNumArgs(interp, skip, objv, "?-option value ...?");
+ return TCL_ERROR;
+ }
+
+ objv += skip;
+ if (objc == 0) {
+ /*
+ * Read all properties.
+ */
+
+ Tcl_Obj *listPtr = TclOOGetAllObjectProperties(oPtr, 0);
+ Tcl_Obj *resultPtr = Tcl_NewObj(), **namev;
+
+ Tcl_IncrRefCount(listPtr);
+ ListObjGetElements(listPtr, namec, namev);
+
+ for (i = 0; i < namec; ) {
+ code = ReadProperty(interp, oPtr, TclGetString(namev[i]));
+ if (code != TCL_OK) {
+ Tcl_DecrRefCount(resultPtr);
+ break;
+ }
+ Tcl_DictObjPut(NULL, resultPtr, namev[i],
+ Tcl_GetObjResult(interp));
+ if (++i >= namec) {
+ Tcl_SetObjResult(interp, resultPtr);
+ break;
+ }
+ Tcl_SetObjResult(interp, Tcl_NewObj());
+ }
+ Tcl_DecrRefCount(listPtr);
+ return code;
+ } else if (objc == 1) {
+ /*
+ * Read a single named property.
+ */
+
+ namePtr = GetPropertyName(interp, oPtr, 0, objv[0], NULL);
+ if (namePtr == NULL) {
+ return TCL_ERROR;
+ }
+ return ReadProperty(interp, oPtr, TclGetString(namePtr));
+ } else if (objc == 2) {
+ /*
+ * Special case for writing to one property. Saves fiddling with the
+ * cache in this common case.
+ */
+
+ namePtr = GetPropertyName(interp, oPtr, GPN_WRITABLE, objv[0], NULL);
+ if (namePtr == NULL) {
+ return TCL_ERROR;
+ }
+ code = WriteProperty(interp, oPtr, TclGetString(namePtr), objv[1]);
+ if (code == TCL_OK) {
+ Tcl_ResetResult(interp);
+ }
+ return code;
+ } else {
+ /*
+ * Write properties. Slightly tricky because we want to cache the
+ * table of property names.
+ */
+ GPNCache *cache = NULL;
+
+ code = TCL_OK;
+ for (i = 0; i < objc; i += 2) {
+ namePtr = GetPropertyName(interp, oPtr, GPN_WRITABLE, objv[i],
+ &cache);
+ if (namePtr == NULL) {
+ code = TCL_ERROR;
+ break;
+ }
+ code = WriteProperty(interp, oPtr, TclGetString(namePtr),
+ objv[i + 1]);
+ if (code != TCL_OK) {
+ break;
+ }
+ }
+ if (code == TCL_OK) {
+ Tcl_ResetResult(interp);
+ }
+ ReleasePropertyNameCache(interp, &cache);
+ return code;
+ }
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * Configurable_Getter, Configurable_Setter --
+ *
+ * Standard property implementation. The clientData is a simple Tcl_Obj*
+ * that contains the name of the property.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+static int
+Configurable_Getter(
+ void *clientData, /* Which property to read. Actually a Tcl_Obj*
+ * reference that is the name of the variable
+ * in the cpntext object. */
+ Tcl_Interp *interp, /* Interpreter used for the result, error
+ * reporting, etc. */
+ Tcl_ObjectContext context, /* The object/call context. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const *objv) /* The actual arguments. */
+{
+ Tcl_Obj *propNamePtr = (Tcl_Obj *) clientData;
+ Tcl_Var varPtr, aryVar;
+ Tcl_Obj *valuePtr;
+
+ if ((int) Tcl_ObjectContextSkippedArgs(context) != objc) {
+ Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context),
+ objv, NULL);
+ return TCL_ERROR;
+ }
+
+ varPtr = TclOOLookupObjectVar(interp, Tcl_ObjectContextObject(context),
+ propNamePtr, &aryVar);
+ if (varPtr == NULL) {
+ return TCL_ERROR;
+ }
+
+ valuePtr = TclPtrGetVar(interp, varPtr, aryVar, propNamePtr, NULL,
+ TCL_NAMESPACE_ONLY | TCL_LEAVE_ERR_MSG);
+ if (valuePtr == NULL) {
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, valuePtr);
+ return TCL_OK;
+}
+
+static int
+Configurable_Setter(
+ void *clientData, /* Which property to write. Actually a Tcl_Obj*
+ * reference that is the name of the variable
+ * in the cpntext object. */
+ Tcl_Interp *interp, /* Interpreter used for the result, error
+ * reporting, etc. */
+ Tcl_ObjectContext context, /* The object/call context. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const *objv) /* The actual arguments. */
+{
+ Tcl_Obj *propNamePtr = (Tcl_Obj *) clientData;
+ Tcl_Var varPtr, aryVar;
+
+ if ((int) Tcl_ObjectContextSkippedArgs(context) + 1 != objc) {
+ Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context),
+ objv, "value");
+ return TCL_ERROR;
+ }
+
+ varPtr = TclOOLookupObjectVar(interp, Tcl_ObjectContextObject(context),
+ propNamePtr, &aryVar);
+ if (varPtr == NULL) {
+ return TCL_ERROR;
+ }
+
+ if (TclPtrSetVar(interp, varPtr, aryVar, propNamePtr, NULL,
+ objv[objc - 1], TCL_NAMESPACE_ONLY | TCL_LEAVE_ERR_MSG) == NULL) {
+ return TCL_ERROR;
+ }
+ return TCL_OK;
+}
+
+// Simple support functions
+static void
+DetailsDeleter(
+ void *clientData)
+{
+ // Just drop the reference count
+ Tcl_Obj *propNamePtr = (Tcl_Obj *) clientData;
+ Tcl_DecrRefCount(propNamePtr);
+}
+
+static int
+DetailsCloner(
+ TCL_UNUSED(Tcl_Interp *),
+ void *oldClientData,
+ void **newClientData)
+{
+ // Just add another reference to this name; easy!
+ Tcl_Obj *propNamePtr = (Tcl_Obj *) oldClientData;
+ Tcl_IncrRefCount(propNamePtr);
+ *newClientData = propNamePtr;
+ return TCL_OK;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * ImplementObjectProperty, ImplementClassProperty --
+ *
+ * Installs a basic property implementation for a property, either on
+ * an instance or on a class. It's up to the code that calls these
+ * to ensure that the property name is syntactically valid.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+void
+ImplementObjectProperty(
+ Tcl_Object targetObject, /* What to install into. */
+ Tcl_Obj *propNamePtr, /* Property name. */
+ int installGetter, /* Whether to install a standard getter. */
+ int installSetter) /* Whether to install a standard setter. */
+{
+ const char *propName = TclGetString(propNamePtr);
+
+ while (propName[0] == '-') {
+ propName++;
+ }
+ if (installGetter) {
+ Tcl_Obj *methodName = Tcl_ObjPrintf("<ReadProp-%s>", propName);
+ Tcl_IncrRefCount(propNamePtr); // Paired with DetailsDeleter
+ TclNewInstanceMethod(
+ NULL, targetObject, methodName, 0, &GetterType, propNamePtr);
+ Tcl_BounceRefCount(methodName);
+ }
+ if (installSetter) {
+ Tcl_Obj *methodName = Tcl_ObjPrintf("<WriteProp-%s>", propName);
+ Tcl_IncrRefCount(propNamePtr); // Paired with DetailsDeleter
+ TclNewInstanceMethod(
+ NULL, targetObject, methodName, 0, &SetterType, propNamePtr);
+ Tcl_BounceRefCount(methodName);
+ }
+}
+
+void
+ImplementClassProperty(
+ Tcl_Class targetClass, /* What to install into. */
+ Tcl_Obj *propNamePtr, /* Property name. */
+ int installGetter, /* Whether to install a standard getter. */
+ int installSetter) /* Whether to install a standard setter. */
+{
+ const char *propName = TclGetString(propNamePtr);
+
+ while (propName[0] == '-') {
+ propName++;
+ }
+ if (installGetter) {
+ Tcl_Obj *methodName = Tcl_ObjPrintf("<ReadProp-%s>", propName);
+ Tcl_IncrRefCount(propNamePtr); // Paired with DetailsDeleter
+ TclNewMethod(targetClass, methodName, 0, &GetterType, propNamePtr);
+ Tcl_BounceRefCount(methodName);
+ }
+ if (installSetter) {
+ Tcl_Obj *methodName = Tcl_ObjPrintf("<WriteProp-%s>", propName);
+ Tcl_IncrRefCount(propNamePtr); // Paired with DetailsDeleter
+ TclNewMethod(targetClass, methodName, 0, &SetterType, propNamePtr);
+ Tcl_BounceRefCount(methodName);
+ }
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * FindClassProps --
+ *
+ * Discover the properties known to a class and its superclasses.
+ * The property names become the keys in the accumulator hash table
+ * (which is used as a set).
+ *
+ * ----------------------------------------------------------------------
+ */
+
+static void
+FindClassProps(
+ Class *clsPtr, /* The object to inspect. Must exist. */
+ int writable, /* Whether we're after the readable or writable
+ * property set. */
+ Tcl_HashTable *accumulator) /* Where to gather the names. */
+{
+ int i, dummy;
+ Tcl_Obj *propName;
+ Class *mixin, *sup;
+
+ tailRecurse:
+ if (writable) {
+ FOREACH(propName, clsPtr->properties.writable) {
+ Tcl_CreateHashEntry(accumulator, (void *) propName, &dummy);
+ }
+ } else {
+ FOREACH(propName, clsPtr->properties.readable) {
+ Tcl_CreateHashEntry(accumulator, (void *) propName, &dummy);
+ }
+ }
+ if (clsPtr->thisPtr->flags & ROOT_OBJECT) {
+ /*
+ * We do *not* traverse upwards from the root!
+ */
+ return;
+ }
+ FOREACH(mixin, clsPtr->mixins) {
+ FindClassProps(mixin, writable, accumulator);
+ }
+ if (clsPtr->superclasses.num == 1) {
+ clsPtr = clsPtr->superclasses.list[0];
+ goto tailRecurse;
+ }
+ FOREACH(sup, clsPtr->superclasses) {
+ FindClassProps(sup, writable, accumulator);
+ }
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * FindObjectProps --
+ *
+ * Discover the properties known to an object and all its classes.
+ * The property names become the keys in the accumulator hash table
+ * (which is used as a set).
+ *
+ * ----------------------------------------------------------------------
+ */
+
+static void
+FindObjectProps(
+ Object *oPtr, /* The object to inspect. Must exist. */
+ int writable, /* Whether we're after the readable or writable
+ * property set. */
+ Tcl_HashTable *accumulator) /* Where to gather the names. */
+{
+ int i, dummy;
+ Tcl_Obj *propName;
+ Class *mixin;
+
+ if (writable) {
+ FOREACH(propName, oPtr->properties.writable) {
+ Tcl_CreateHashEntry(accumulator, (void *) propName, &dummy);
+ }
+ } else {
+ FOREACH(propName, oPtr->properties.readable) {
+ Tcl_CreateHashEntry(accumulator, (void *) propName, &dummy);
+ }
+ }
+ FOREACH(mixin, oPtr->mixins) {
+ FindClassProps(mixin, writable, accumulator);
+ }
+ FindClassProps(oPtr->selfCls, writable, accumulator);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * GetAllClassProperties --
+ *
+ * Get the list of all properties known to a class, including to its
+ * superclasses. Manages a cache so this operation is usually cheap.
+ * The order of properties in the resulting list is undefined.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+GetAllClassProperties(
+ Class *clsPtr, /* The class to inspect. Must exist. */
+ int writable, /* Whether to get writable properties. If
+ * false, readable properties will be returned
+ * instead. */
+ int *allocated) /* Address of variable to set to true if a
+ * Tcl_Obj was allocated and may be safely
+ * modified by the caller. */
+{
+ Tcl_HashTable hashTable;
+ FOREACH_HASH_DECLS;
+ Tcl_Obj *propName, *result;
+ void *dummy;
+
+ /*
+ * Look in the cache.
+ */
+
+ if (clsPtr->properties.epoch == clsPtr->thisPtr->fPtr->epoch) {
+ if (writable) {
+ if (clsPtr->properties.allWritableCache) {
+ *allocated = 0;
+ return clsPtr->properties.allWritableCache;
+ }
+ } else {
+ if (clsPtr->properties.allReadableCache) {
+ *allocated = 0;
+ return clsPtr->properties.allReadableCache;
+ }
+ }
+ }
+
+ /*
+ * Gather the information. Unsorted! (Caller will sort.)
+ */
+
+ *allocated = 1;
+ Tcl_InitObjHashTable(&hashTable);
+ FindClassProps(clsPtr, writable, &hashTable);
+ TclNewObj(result);
+ FOREACH_HASH(propName, dummy, &hashTable) {
+ Tcl_ListObjAppendElement(NULL, result, propName);
+ }
+ Tcl_DeleteHashTable(&hashTable);
+
+ /*
+ * Cache the information. Also purges the cache.
+ */
+
+ if (clsPtr->properties.epoch != clsPtr->thisPtr->fPtr->epoch) {
+ if (clsPtr->properties.allWritableCache) {
+ Tcl_DecrRefCount(clsPtr->properties.allWritableCache);
+ clsPtr->properties.allWritableCache = NULL;
+ }
+ if (clsPtr->properties.allReadableCache) {
+ Tcl_DecrRefCount(clsPtr->properties.allReadableCache);
+ clsPtr->properties.allReadableCache = NULL;
+ }
+ }
+ clsPtr->properties.epoch = clsPtr->thisPtr->fPtr->epoch;
+ if (writable) {
+ clsPtr->properties.allWritableCache = result;
+ } else {
+ clsPtr->properties.allReadableCache = result;
+ }
+ Tcl_IncrRefCount(result);
+ return result;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * SortPropList --
+ * Sort a list of names of properties. Simple support function. Assumes
+ * that the list Tcl_Obj is unshared and doesn't have a string
+ * representation.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+static int
+PropNameCompare(
+ const void *a,
+ const void *b)
+{
+ Tcl_Obj *first = *(Tcl_Obj **) a;
+ Tcl_Obj *second = *(Tcl_Obj **) b;
+
+ return TclStringCmp(first, second, 0, 0, TCL_INDEX_NONE);
+}
+
+static inline void
+SortPropList(
+ Tcl_Obj *list)
+{
+ Tcl_Size ec;
+ Tcl_Obj **ev;
+
+ if (Tcl_IsShared(list)) {
+ Tcl_Panic("shared property list cannot be sorted");
+ }
+ Tcl_ListObjGetElements(NULL, list, &ec, &ev);
+ TclInvalidateStringRep(list);
+ qsort(ev, ec, sizeof(Tcl_Obj *), PropNameCompare);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * TclOOGetAllObjectProperties --
+ *
+ * Get the sorted list of all properties known to a object, including to its
+ * its classes. Manages a cache so this operation is usually cheap.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+TclOOGetAllObjectProperties(
+ Object *oPtr, /* The object to inspect. Must exist. */
+ int writable) /* Whether to get writable properties. If
+ * false, readable properties will be returned
+ * instead. */
+{
+ Tcl_HashTable hashTable;
+ FOREACH_HASH_DECLS;
+ Tcl_Obj *propName, *result;
+ void *dummy;
+
+ /*
+ * Look in the cache.
+ */
+
+ if (oPtr->properties.epoch == oPtr->fPtr->epoch) {
+ if (writable) {
+ if (oPtr->properties.allWritableCache) {
+ return oPtr->properties.allWritableCache;
+ }
+ } else {
+ if (oPtr->properties.allReadableCache) {
+ return oPtr->properties.allReadableCache;
+ }
+ }
+ }
+
+ /*
+ * Gather the information. Unsorted! (Caller will sort.)
+ */
+
+ Tcl_InitObjHashTable(&hashTable);
+ FindObjectProps(oPtr, writable, &hashTable);
+ TclNewObj(result);
+ FOREACH_HASH(propName, dummy, &hashTable) {
+ Tcl_ListObjAppendElement(NULL, result, propName);
+ }
+ Tcl_DeleteHashTable(&hashTable);
+ SortPropList(result);
+
+ /*
+ * Cache the information.
+ */
+
+ if (oPtr->properties.epoch != oPtr->fPtr->epoch) {
+ if (oPtr->properties.allWritableCache) {
+ Tcl_DecrRefCount(oPtr->properties.allWritableCache);
+ oPtr->properties.allWritableCache = NULL;
+ }
+ if (oPtr->properties.allReadableCache) {
+ Tcl_DecrRefCount(oPtr->properties.allReadableCache);
+ oPtr->properties.allReadableCache = NULL;
+ }
+ }
+ oPtr->properties.epoch = oPtr->fPtr->epoch;
+ if (writable) {
+ oPtr->properties.allWritableCache = result;
+ } else {
+ oPtr->properties.allReadableCache = result;
+ }
+ Tcl_IncrRefCount(result);
+ return result;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * SetPropertyList --
+ *
+ * Helper for writing a property list (which is actually a set).
+ *
+ * ----------------------------------------------------------------------
+ */
+static inline void
+SetPropertyList(
+ PropertyList *propList, /* The property list to write. Replaces the
+ * property list's contents. */
+ Tcl_Size objc, /* Number of property names. */
+ Tcl_Obj *const objv[]) /* Property names. */
+{
+ Tcl_Size i, n;
+ Tcl_Obj *propObj;
+ int created;
+ Tcl_HashTable uniqueTable;
+
+ for (i=0 ; i<objc ; i++) {
+ Tcl_IncrRefCount(objv[i]);
+ }
+ FOREACH(propObj, *propList) {
+ Tcl_DecrRefCount(propObj);
+ }
+ if (i != objc) {
+ if (objc == 0) {
+ Tcl_Free(propList->list);
+ } else if (i) {
+ propList->list = (Tcl_Obj **)
+ Tcl_Realloc(propList->list, sizeof(Tcl_Obj *) * objc);
+ } else {
+ propList->list = (Tcl_Obj **)
+ Tcl_Alloc(sizeof(Tcl_Obj *) * objc);
+ }
+ }
+ propList->num = 0;
+ if (objc > 0) {
+ Tcl_InitObjHashTable(&uniqueTable);
+ for (i=n=0 ; i<objc ; i++) {
+ Tcl_CreateHashEntry(&uniqueTable, objv[i], &created);
+ if (created) {
+ propList->list[n++] = objv[i];
+ } else {
+ Tcl_DecrRefCount(objv[i]);
+ }
+ }
+ propList->num = n;
+
+ /*
+ * Shouldn't be necessary, but maintain num/list invariant.
+ */
+
+ if (n != objc) {
+ propList->list = (Tcl_Obj **)
+ Tcl_Realloc(propList->list, sizeof(Tcl_Obj *) * n);
+ }
+ Tcl_DeleteHashTable(&uniqueTable);
+ }
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * TclOOInstallReadableProperties --
+ *
+ * Helper for writing the readable property list (which is actually a set)
+ * that includes flushing the name cache.
+ *
+ * ----------------------------------------------------------------------
+ */
+void
+TclOOInstallReadableProperties(
+ PropertyStorage *props, /* Which property list to install into. */
+ Tcl_Size objc, /* Number of property names. */
+ Tcl_Obj *const objv[]) /* Property names. */
+{
+ if (props->allReadableCache) {
+ Tcl_DecrRefCount(props->allReadableCache);
+ props->allReadableCache = NULL;
+ }
+
+ SetPropertyList(&props->readable, objc, objv);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * TclOOInstallWritableProperties --
+ *
+ * Helper for writing the writable property list (which is actually a set)
+ * that includes flushing the name cache.
+ *
+ * ----------------------------------------------------------------------
+ */
+void
+TclOOInstallWritableProperties(
+ PropertyStorage *props, /* Which property list to install into. */
+ Tcl_Size objc, /* Number of property names. */
+ Tcl_Obj *const objv[]) /* Property names. */
+{
+ if (props->allWritableCache) {
+ Tcl_DecrRefCount(props->allWritableCache);
+ props->allWritableCache = NULL;
+ }
+
+ SetPropertyList(&props->writable, objc, objv);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * TclOOGetPropertyList --
+ *
+ * Helper for reading a property list.
+ *
+ * ----------------------------------------------------------------------
+ */
+Tcl_Obj *
+TclOOGetPropertyList(
+ PropertyList *propList) /* The property list to read. */
+{
+ Tcl_Obj *resultObj, *propNameObj;
+ Tcl_Size i;
+
+ TclNewObj(resultObj);
+ FOREACH(propNameObj, *propList) {
+ Tcl_ListObjAppendElement(NULL, resultObj, propNameObj);
+ }
+ return resultObj;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * TclOOInstallStdPropertyImpls --
+ *
+ * Validates a (dashless) property name, and installs implementation
+ * methods if asked to do so (readable and writable flags).
+ *
+ * ----------------------------------------------------------------------
+ */
+int
+TclOOInstallStdPropertyImpls(
+ void *useInstance,
+ Tcl_Interp *interp,
+ Tcl_Obj *propName,
+ int readable,
+ int writable)
+{
+ const char *name, *reason;
+ Tcl_Size len;
+ char flag = TCL_DONT_QUOTE_HASH;
+
+ /*
+ * Validate the property name. Note that just calling TclScanElement() is
+ * cheaper than actually formatting a list and comparing the string
+ * version of that with the original, as TclScanElement() is one of the
+ * core parts of doing that; this skips a whole load of irrelevant memory
+ * allocations!
+ */
+
+ name = Tcl_GetStringFromObj(propName, &len);
+ if (Tcl_StringMatch(name, "-*")) {
+ reason = "must not begin with -";
+ goto badProp;
+ }
+ if (TclScanElement(name, len, &flag) != len) {
+ reason = "must be a simple word";
+ goto badProp;
+ }
+ if (Tcl_StringMatch(name, "*::*")) {
+ reason = "must not contain namespace separators";
+ goto badProp;
+ }
+ if (Tcl_StringMatch(name, "*[()]*")) {
+ reason = "must not contain parentheses";
+ goto badProp;
+ }
+
+ /*
+ * Install the implementations... if asked to do so.
+ */
+
+ if (useInstance) {
+ Tcl_Object object = TclOOGetDefineCmdContext(interp);
+ if (!object) {
+ return TCL_ERROR;
+ }
+ ImplementObjectProperty(object, propName, readable, writable);
+ } else {
+ Tcl_Class cls = (Tcl_Class) TclOOGetClassDefineCmdContext(interp);
+ if (!cls) {
+ return TCL_ERROR;
+ }
+ ImplementClassProperty(cls, propName, readable, writable);
+ }
+ return TCL_OK;
+
+ badProp:
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad property name \"%s\": %s", name, reason));
+ Tcl_SetErrorCode(interp, "TCL", "OO", "PROPERTY_FORMAT", NULL);
+ return TCL_ERROR;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * TclOODefinePropertyCmd --
+ *
+ * Implementation of the "property" definition for classes and instances
+ * governed by the [oo::configurable] metaclass.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+int
+TclOODefinePropertyCmd(
+ void *useInstance, /* NULL for class, non-NULL for object. */
+ Tcl_Interp *interp, /* For error reporting and lookup. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const *objv) /* Arguments. */
+{
+ int i;
+ const char *const options[] = {
+ "-get", "-kind", "-set", NULL
+ };
+ enum Options {
+ OPT_GET, OPT_KIND, OPT_SET
+ };
+ const char *const kinds[] = {
+ "readable", "readwrite", "writable", NULL
+ };
+ enum Kinds {
+ KIND_RO, KIND_RW, KIND_WO
+ };
+ Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp);
+
+ if (oPtr == NULL) {
+ return TCL_ERROR;
+ }
+ if (!useInstance && !oPtr->classPtr) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "attempt to misuse API", -1));
+ Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL);
+ return TCL_ERROR;
+ }
+
+ for (i = 1; i < objc; i++) {
+ Tcl_Obj *propObj = objv[i], *nextObj, *argObj, *hyphenated;
+ Tcl_Obj *getterScript = NULL, *setterScript = NULL;
+
+ /*
+ * Parse the extra options for the property.
+ */
+
+ int kind = KIND_RW;
+ while (i + 1 < objc) {
+ int option;
+
+ nextObj = objv[i + 1];
+ if (TclGetString(nextObj)[0] != '-') {
+ break;
+ }
+ if (Tcl_GetIndexFromObj(interp, nextObj, options, "option", 0,
+ &option) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (i + 2 >= objc) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "missing %s to go with %s option",
+ (option == OPT_KIND ? "kind value" : "body"),
+ options[option]));
+ Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", NULL);
+ return TCL_ERROR;
+ }
+ argObj = objv[i + 2];
+ i += 2;
+ switch (option) {
+ case OPT_GET:
+ getterScript = argObj;
+ break;
+ case OPT_SET:
+ setterScript = argObj;
+ break;
+ case OPT_KIND:
+ if (Tcl_GetIndexFromObj(interp, argObj, kinds, "kind", 0,
+ &kind) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ break;
+ }
+ }
+
+ /*
+ * Install the property. Note that TclOOInstallStdPropertyImpls
+ * validates the property name as well.
+ */
+
+ if (TclOOInstallStdPropertyImpls(useInstance, interp, propObj,
+ kind != KIND_WO && getterScript == NULL,
+ kind != KIND_RO && setterScript == NULL) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ hyphenated = Tcl_ObjPrintf("-%s", TclGetString(propObj));
+ if (useInstance) {
+ TclOORegisterInstanceProperty(oPtr, hyphenated,
+ kind != KIND_WO, kind != KIND_RO);
+ } else {
+ TclOORegisterProperty(oPtr->classPtr, hyphenated,
+ kind != KIND_WO, kind != KIND_RO);
+ }
+ Tcl_BounceRefCount(hyphenated);
+
+ /*
+ * Create property implementation methods by using the right
+ * back-end API, but only if the user has given us the bodies of the
+ * methods we'll make.
+ */
+
+ if (getterScript != NULL) {
+ Tcl_Obj *getterName = Tcl_ObjPrintf("<ReadProp-%s>",
+ TclGetString(propObj));
+ Tcl_Obj *argsPtr = Tcl_NewObj();
+ Method *mPtr;
+
+ Tcl_IncrRefCount(getterScript);
+ if (useInstance) {
+ mPtr = TclOONewProcInstanceMethod(interp, oPtr, 0,
+ getterName, argsPtr, getterScript, NULL);
+ } else {
+ mPtr = TclOONewProcMethod(interp, oPtr->classPtr, 0,
+ getterName, argsPtr, getterScript, NULL);
+ }
+ Tcl_BounceRefCount(getterName);
+ Tcl_BounceRefCount(argsPtr);
+ Tcl_DecrRefCount(getterScript);
+ if (mPtr == NULL) {
+ return TCL_ERROR;
+ }
+ }
+ if (setterScript != NULL) {
+ Tcl_Obj *setterName = Tcl_ObjPrintf("<WriteProp-%s>",
+ TclGetString(propObj));
+ Tcl_Obj *argsPtr;
+ Method *mPtr;
+
+ TclNewLiteralStringObj(argsPtr, "value");
+ Tcl_IncrRefCount(setterScript);
+ if (useInstance) {
+ mPtr = TclOONewProcInstanceMethod(interp, oPtr, 0,
+ setterName, argsPtr, setterScript, NULL);
+ } else {
+ mPtr = TclOONewProcMethod(interp, oPtr->classPtr, 0,
+ setterName, argsPtr, setterScript, NULL);
+ }
+ Tcl_BounceRefCount(setterName);
+ Tcl_BounceRefCount(argsPtr);
+ Tcl_DecrRefCount(setterScript);
+ if (mPtr == NULL) {
+ return TCL_ERROR;
+ }
+ }
+ }
+ return TCL_OK;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * TclOOInfoClassPropCmd, TclOOInfoObjectPropCmd --
+ *
+ * Implements [info class properties $clsName ?$option...?] and
+ * [info object properties $objName ?$option...?]
+ *
+ * ----------------------------------------------------------------------
+ */
+
+int
+TclOOInfoClassPropCmd(
+ TCL_UNUSED(void *),
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ Class *clsPtr;
+ int i, idx, all = 0, writable = 0, allocated = 0;
+ Tcl_Obj *result;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "className ?options...?");
+ return TCL_ERROR;
+ }
+ clsPtr = TclOOGetClassFromObj(interp, objv[1]);
+ if (clsPtr == NULL) {
+ return TCL_ERROR;
+ }
+ for (i = 2; i < objc; i++) {
+ if (Tcl_GetIndexFromObj(interp, objv[i], propOptNames, "option", 0,
+ &idx) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ switch (idx) {
+ case PROP_ALL:
+ all = 1;
+ break;
+ case PROP_READABLE:
+ writable = 0;
+ break;
+ case PROP_WRITABLE:
+ writable = 1;
+ break;
+ }
+ }
+
+ /*
+ * Get the properties.
+ */
+
+ if (all) {
+ result = GetAllClassProperties(clsPtr, writable, &allocated);
+ if (allocated) {
+ SortPropList(result);
+ }
+ } else {
+ if (writable) {
+ result = TclOOGetPropertyList(&clsPtr->properties.writable);
+ } else {
+ result = TclOOGetPropertyList(&clsPtr->properties.readable);
+ }
+ SortPropList(result);
+ }
+ Tcl_SetObjResult(interp, result);
+ return TCL_OK;
+}
+
+int
+TclOOInfoObjectPropCmd(
+ TCL_UNUSED(void *),
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ Object *oPtr;
+ int i, idx, all = 0, writable = 0;
+ Tcl_Obj *result;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "objName ?options...?");
+ return TCL_ERROR;
+ }
+ oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]);
+ if (oPtr == NULL) {
+ return TCL_ERROR;
+ }
+ for (i = 2; i < objc; i++) {
+ if (Tcl_GetIndexFromObj(interp, objv[i], propOptNames, "option", 0,
+ &idx) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ switch (idx) {
+ case PROP_ALL:
+ all = 1;
+ break;
+ case PROP_READABLE:
+ writable = 0;
+ break;
+ case PROP_WRITABLE:
+ writable = 1;
+ break;
+ }
+ }
+
+ /*
+ * Get the properties.
+ */
+
+ if (all) {
+ result = TclOOGetAllObjectProperties(oPtr, writable);
+ } else {
+ if (writable) {
+ result = TclOOGetPropertyList(&oPtr->properties.writable);
+ } else {
+ result = TclOOGetPropertyList(&oPtr->properties.readable);
+ }
+ SortPropList(result);
+ }
+ Tcl_SetObjResult(interp, result);
+ return TCL_OK;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * TclOOReleasePropertyStorage --
+ *
+ * Delete the memory associated with a class or object's properties.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+static inline void
+ReleasePropertyList(
+ PropertyList *propList)
+{
+ Tcl_Obj *propertyObj;
+ Tcl_Size i;
+
+ FOREACH(propertyObj, *propList) {
+ Tcl_DecrRefCount(propertyObj);
+ }
+ Tcl_Free(propList->list);
+ propList->list = NULL;
+ propList->num = 0;
+}
+
+void
+TclOOReleasePropertyStorage(
+ PropertyStorage *propsPtr)
+{
+ if (propsPtr->allReadableCache) {
+ Tcl_DecrRefCount(propsPtr->allReadableCache);
+ }
+ if (propsPtr->allWritableCache) {
+ Tcl_DecrRefCount(propsPtr->allWritableCache);
+ }
+ if (propsPtr->readable.num) {
+ ReleasePropertyList(&propsPtr->readable);
+ }
+ if (propsPtr->writable.num) {
+ ReleasePropertyList(&propsPtr->writable);
+ }
+}
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-basic-offset: 4
+ * fill-column: 78
+ * End:
+ */