diff options
| -rw-r--r-- | generic/tclOO.c | 53 | ||||
| -rw-r--r-- | generic/tclOOCall.c | 200 | ||||
| -rw-r--r-- | generic/tclOODefineCmds.c | 475 | ||||
| -rw-r--r-- | generic/tclOOInfo.c | 183 | ||||
| -rw-r--r-- | generic/tclOOInt.h | 42 | ||||
| -rw-r--r-- | generic/tclOOScript.h | 114 | ||||
| -rw-r--r-- | tools/tclOOScript.tcl | 131 |
7 files changed, 1139 insertions, 59 deletions
diff --git a/generic/tclOO.c b/generic/tclOO.c index af5ea50..8710a89 100644 --- a/generic/tclOO.c +++ b/generic/tclOO.c @@ -3,7 +3,7 @@ * * This file contains the object-system core (NB: not Tcl_Obj, but ::oo) * - * Copyright (c) 2005-2012 by Donal K. Fellows + * Copyright (c) 2005-2019 by Donal K. Fellows * Copyright (c) 2017 by Nathan Coulter * * See the file "license.terms" for information on usage and redistribution of @@ -323,6 +323,7 @@ InitFoundation( DeletedObjdefNamespace); fPtr->helpersNs = Tcl_CreateNamespace(interp, "::oo::Helpers", fPtr, DeletedHelpersNamespace); + Tcl_CreateNamespace(interp, "::oo::configuresupport", NULL, NULL); fPtr->epoch = 0; fPtr->tsdPtr = tsdPtr; TclNewLiteralStringObj(fPtr->unknownMethodNameObj, "unknown"); @@ -961,7 +962,7 @@ TclOOReleaseClassContents( Class *clsPtr = oPtr->classPtr, *tmpClsPtr; Method *mPtr; Foundation *fPtr = oPtr->fPtr; - Tcl_Obj *variableObj; + Tcl_Obj *variableObj, *propertyObj; PrivateVariableMapping *privateVariable; /* @@ -1015,6 +1016,29 @@ TclOOReleaseClassContents( } /* + * Squelch the property lists. + */ + + if (clsPtr->properties.allReadableCache) { + Tcl_DecrRefCount(clsPtr->properties.allReadableCache); + } + if (clsPtr->properties.allWritableCache) { + Tcl_DecrRefCount(clsPtr->properties.allWritableCache); + } + if (clsPtr->properties.readable.num) { + FOREACH(propertyObj, clsPtr->properties.readable) { + Tcl_DecrRefCount(propertyObj); + } + ckfree(clsPtr->properties.readable.list); + } + if (clsPtr->properties.writable.num) { + FOREACH(propertyObj, clsPtr->properties.writable) { + Tcl_DecrRefCount(propertyObj); + } + ckfree(clsPtr->properties.writable.list); + } + + /* * Squelch our filter list. */ @@ -1115,7 +1139,7 @@ ObjectNamespaceDeleted( FOREACH_HASH_DECLS; Class *mixinPtr; Method *mPtr; - Tcl_Obj *filterObj, *variableObj; + Tcl_Obj *filterObj, *variableObj, *propertyObj; PrivateVariableMapping *privateVariable; Tcl_Interp *interp = oPtr->fPtr->interp; int i; @@ -1269,6 +1293,29 @@ ObjectNamespaceDeleted( } /* + * Squelch the property lists. + */ + + if (oPtr->properties.allReadableCache) { + Tcl_DecrRefCount(oPtr->properties.allReadableCache); + } + if (oPtr->properties.allWritableCache) { + Tcl_DecrRefCount(oPtr->properties.allWritableCache); + } + if (oPtr->properties.readable.num) { + FOREACH(propertyObj, oPtr->properties.readable) { + Tcl_DecrRefCount(propertyObj); + } + ckfree(oPtr->properties.readable.list); + } + if (oPtr->properties.writable.num) { + FOREACH(propertyObj, oPtr->properties.writable) { + Tcl_DecrRefCount(propertyObj); + } + ckfree(oPtr->properties.writable.list); + } + + /* * Because an object can be a class that is an instance of itself, the * class object's class structure should only be cleaned after most of * the cleanup on the object is done. diff --git a/generic/tclOOCall.c b/generic/tclOOCall.c index f3474b6..f647fb7 100644 --- a/generic/tclOOCall.c +++ b/generic/tclOOCall.c @@ -2,9 +2,10 @@ * tclOOCall.c -- * * This file contains the method call chain management code for the - * object-system core. + * object-system core. It also contains everything else that does + * inheritance hierarchy traversal. * - * Copyright (c) 2005-2012 by Donal K. Fellows + * Copyright (c) 2005-2019 by Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -2100,6 +2101,201 @@ AddDefinitionNamespaceToChain( definePtr->num++; } +static void +FindClassProps( + Class *clsPtr, + int writable, + Tcl_HashTable *accumulator) +{ + 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); + } +} + +static void +FindObjectProps( + Object *oPtr, + int writable, + Tcl_HashTable *accumulator) +{ + 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); +} + +Tcl_Obj * +TclOOGetAllClassProperties( + Class *clsPtr, + int writable, + int *allocated) +{ + 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); + result = Tcl_NewObj(); + 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; +} + +Tcl_Obj * +TclOOGetAllObjectProperties( + Object *oPtr, + int writable, + int *allocated) +{ + 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) { + *allocated = 0; + return oPtr->properties.allWritableCache; + } + } else { + if (oPtr->properties.allReadableCache) { + *allocated = 0; + return oPtr->properties.allReadableCache; + } + } + } + + /* + * Gather the information. Unsorted! (Caller will sort.) + */ + + *allocated = 1; + Tcl_InitObjHashTable(&hashTable); + FindObjectProps(oPtr, writable, &hashTable); + result = Tcl_NewObj(); + FOREACH_HASH(propName, dummy, &hashTable) { + Tcl_ListObjAppendElement(NULL, result, propName); + } + Tcl_DeleteHashTable(&hashTable); + + /* + * 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; +} + /* * Local Variables: * mode: c diff --git a/generic/tclOODefineCmds.c b/generic/tclOODefineCmds.c index f259954..7b70c79 100644 --- a/generic/tclOODefineCmds.c +++ b/generic/tclOODefineCmds.c @@ -4,7 +4,7 @@ * This file contains the implementation of the ::oo::define command, * part of the object-system core (NB: not Tcl_Obj, but ::oo). * - * Copyright (c) 2006-2013 by Donal K. Fellows + * Copyright (c) 2006-2019 by Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -78,51 +78,18 @@ static inline void RecomputeClassCacheFlag(Object *oPtr); static int RenameDeleteMethod(Tcl_Interp *interp, Object *oPtr, int useClass, Tcl_Obj *const fromPtr, Tcl_Obj *const toPtr); -static int ClassFilterGet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ClassFilterSet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ClassMixinGet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ClassMixinSet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ClassSuperGet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ClassSuperSet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ClassVarsGet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ClassVarsSet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ObjFilterGet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ObjFilterSet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ObjMixinGet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ObjMixinSet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ObjVarsGet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ObjVarsSet(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); -static int ResolveClass(ClientData clientData, - Tcl_Interp *interp, Tcl_ObjectContext context, - int objc, Tcl_Obj *const *objv); +static Tcl_MethodCallProc ClassFilterGet, ClassFilterSet; +static Tcl_MethodCallProc ClassMixinGet, ClassMixinSet; +static Tcl_MethodCallProc ClassRPropsGet, ClassRPropsSet; +static Tcl_MethodCallProc ClassSuperGet, ClassSuperSet; +static Tcl_MethodCallProc ClassVarsGet, ClassVarsSet; +static Tcl_MethodCallProc ClassWPropsGet, ClassWPropsSet; +static Tcl_MethodCallProc ObjFilterGet, ObjFilterSet; +static Tcl_MethodCallProc ObjMixinGet, ObjMixinSet; +static Tcl_MethodCallProc ObjRPropsGet, ObjRPropsSet; +static Tcl_MethodCallProc ObjVarsGet, ObjVarsSet; +static Tcl_MethodCallProc ObjWPropsGet, ObjWPropsSet; +static Tcl_MethodCallProc ResolveClass; /* * Now define the slots used in declarations. @@ -136,6 +103,14 @@ static const struct DeclaredSlot slots[] = { SLOT("objdefine::filter", ObjFilterGet, ObjFilterSet, NULL), SLOT("objdefine::mixin", ObjMixinGet, ObjMixinSet, ResolveClass), SLOT("objdefine::variable", ObjVarsGet, ObjVarsSet, NULL), + SLOT("configuresupport::readableproperties", + ClassRPropsGet, ClassRPropsSet, NULL), + SLOT("configuresupport::writableproperties", + ClassWPropsGet, ClassWPropsSet, NULL), + SLOT("configuresupport::objreadableproperties", + ObjRPropsGet, ObjRPropsSet, NULL), + SLOT("configuresupport::objwritableproperties", + ObjWPropsGet, ObjWPropsSet, NULL), {NULL, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}} }; @@ -201,13 +176,26 @@ BumpGlobalEpoch( if (classPtr->thisPtr->mixins.num > 0) { classPtr->thisPtr->epoch++; + + /* + * Invalidate the property caches directly. + */ + + if (classPtr->properties.allReadableCache) { + Tcl_DecrRefCount(classPtr->properties.allReadableCache); + classPtr->properties.allReadableCache = NULL; + } + if (classPtr->properties.allWritableCache) { + Tcl_DecrRefCount(classPtr->properties.allWritableCache); + classPtr->properties.allWritableCache = NULL; + } } return; } /* * Either there's no class (?!) or we're reconfiguring something that is - * in use. Force regeneration of call chains. + * in use. Force regeneration of call chains and properties. */ TclOOGetFoundation(interp)->epoch++; @@ -482,6 +470,7 @@ TclOOClassSetMixins( * * ---------------------------------------------------------------------- */ + static inline void InstallStandardVariableMapping( VariableNameList *vnlPtr, @@ -3080,6 +3069,398 @@ ResolveClass( } /* + * ---------------------------------------------------------------------- + * + * ClassRPropsGet, ClassRPropsSet, ObjRPropsGet, ObjRPropsSet -- + * + * Implementations of the "readableproperties" slot accessors for classes + * and instances. + * + * ---------------------------------------------------------------------- + */ + +static void +InstallReadableProps( + PropertyStorage *props, + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_Obj *propObj; + int i, n, created; + Tcl_HashTable uniqueTable; + + if (props->allReadableCache) { + Tcl_DecrRefCount(props->allReadableCache); + props->allReadableCache = NULL; + } + + for (i=0 ; i<objc ; i++) { + Tcl_IncrRefCount(objv[i]); + } + FOREACH(propObj, props->readable) { + Tcl_DecrRefCount(propObj); + } + if (i != objc) { + if (objc == 0) { + ckfree(props->readable.list); + } else if (i) { + props->readable.list = ckrealloc(props->readable.list, + sizeof(Tcl_Obj *) * objc); + } else { + props->readable.list = ckalloc(sizeof(Tcl_Obj *) * objc); + } + } + props->readable.num = 0; + if (objc > 0) { + Tcl_InitObjHashTable(&uniqueTable); + for (i=n=0 ; i<objc ; i++) { + Tcl_CreateHashEntry(&uniqueTable, objv[i], &created); + if (created) { + props->readable.list[n++] = objv[i]; + } else { + Tcl_DecrRefCount(objv[i]); + } + } + props->readable.num = n; + + /* + * Shouldn't be necessary, but maintain num/list invariant. + */ + + if (n != objc) { + props->readable.list = ckrealloc(props->readable.list, + sizeof(Tcl_Obj *) * n); + } + Tcl_DeleteHashTable(&uniqueTable); + } +} + +static int +ClassRPropsGet( + ClientData clientData, + Tcl_Interp *interp, + Tcl_ObjectContext context, + int objc, + Tcl_Obj *const *objv) +{ + Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); + Tcl_Obj *resultObj, *propNameObj; + int i; + + if (Tcl_ObjectContextSkippedArgs(context) != objc) { + Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, + NULL); + return TCL_ERROR; + } + if (oPtr == NULL) { + return TCL_ERROR; + } else if (!oPtr->classPtr) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "attempt to misuse API", -1)); + Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", NULL); + return TCL_ERROR; + } + + resultObj = Tcl_NewObj(); + FOREACH(propNameObj, oPtr->classPtr->properties.readable) { + Tcl_ListObjAppendElement(NULL, resultObj, propNameObj); + } + Tcl_SetObjResult(interp, resultObj); + return TCL_OK; +} + +static int +ClassRPropsSet( + ClientData clientData, + Tcl_Interp *interp, + Tcl_ObjectContext context, + int objc, + Tcl_Obj *const *objv) +{ + Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); + int varc; + Tcl_Obj **varv; + + if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { + Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, + "filterList"); + return TCL_ERROR; + } + objv += Tcl_ObjectContextSkippedArgs(context); + + if (oPtr == NULL) { + return TCL_ERROR; + } else if (!oPtr->classPtr) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "attempt to misuse API", -1)); + Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", NULL); + return TCL_ERROR; + } else if (Tcl_ListObjGetElements(interp, objv[0], &varc, + &varv) != TCL_OK) { + return TCL_ERROR; + } + + InstallReadableProps(&oPtr->classPtr->properties, varc, varv); + BumpGlobalEpoch(interp, oPtr->classPtr); + return TCL_OK; +} + +static int +ObjRPropsGet( + ClientData clientData, + Tcl_Interp *interp, + Tcl_ObjectContext context, + int objc, + Tcl_Obj *const *objv) +{ + Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); + Tcl_Obj *resultObj, *propNameObj; + int i; + + if (Tcl_ObjectContextSkippedArgs(context) != objc) { + Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, + NULL); + return TCL_ERROR; + } + if (oPtr == NULL) { + return TCL_ERROR; + } + + resultObj = Tcl_NewObj(); + FOREACH(propNameObj, oPtr->properties.readable) { + Tcl_ListObjAppendElement(NULL, resultObj, propNameObj); + } + Tcl_SetObjResult(interp, resultObj); + return TCL_OK; +} + +static int +ObjRPropsSet( + ClientData clientData, + Tcl_Interp *interp, + Tcl_ObjectContext context, + int objc, + Tcl_Obj *const *objv) +{ + Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); + int varc; + Tcl_Obj **varv; + + if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { + Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, + "filterList"); + return TCL_ERROR; + } + objv += Tcl_ObjectContextSkippedArgs(context); + + if (oPtr == NULL) { + return TCL_ERROR; + } else if (Tcl_ListObjGetElements(interp, objv[0], &varc, + &varv) != TCL_OK) { + return TCL_ERROR; + } + + InstallReadableProps(&oPtr->properties, varc, varv); + return TCL_OK; +} + +/* + * ---------------------------------------------------------------------- + * + * ClassWPropsGet, ClassWPropsSet, ObjWPropsGet, ObjWPropsSet -- + * + * Implementations of the "writableproperties" slot accessors for classes + * and instances. + * + * ---------------------------------------------------------------------- + */ + +static void +InstallWritableProps( + PropertyStorage *props, + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_Obj *propObj; + int i, n, created; + Tcl_HashTable uniqueTable; + + if (props->allWritableCache) { + Tcl_DecrRefCount(props->allWritableCache); + props->allWritableCache = NULL; + } + + for (i=0 ; i<objc ; i++) { + Tcl_IncrRefCount(objv[i]); + } + FOREACH(propObj, props->writable) { + Tcl_DecrRefCount(propObj); + } + if (i != objc) { + if (objc == 0) { + ckfree(props->writable.list); + } else if (i) { + props->writable.list = ckrealloc(props->writable.list, + sizeof(Tcl_Obj *) * objc); + } else { + props->writable.list = ckalloc(sizeof(Tcl_Obj *) * objc); + } + } + props->writable.num = 0; + if (objc > 0) { + Tcl_InitObjHashTable(&uniqueTable); + for (i=n=0 ; i<objc ; i++) { + Tcl_CreateHashEntry(&uniqueTable, objv[i], &created); + if (created) { + props->writable.list[n++] = objv[i]; + } else { + Tcl_DecrRefCount(objv[i]); + } + } + props->writable.num = n; + + /* + * Shouldn't be necessary, but maintain num/list invariant. + */ + + if (n != objc) { + props->writable.list = ckrealloc(props->writable.list, + sizeof(Tcl_Obj *) * n); + } + Tcl_DeleteHashTable(&uniqueTable); + } +} + +static int +ClassWPropsGet( + ClientData clientData, + Tcl_Interp *interp, + Tcl_ObjectContext context, + int objc, + Tcl_Obj *const *objv) +{ + Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); + Tcl_Obj *resultObj, *propNameObj; + int i; + + if (Tcl_ObjectContextSkippedArgs(context) != objc) { + Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, + NULL); + return TCL_ERROR; + } + if (oPtr == NULL) { + return TCL_ERROR; + } else if (!oPtr->classPtr) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "attempt to misuse API", -1)); + Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", NULL); + return TCL_ERROR; + } + + resultObj = Tcl_NewObj(); + FOREACH(propNameObj, oPtr->classPtr->properties.writable) { + Tcl_ListObjAppendElement(NULL, resultObj, propNameObj); + } + Tcl_SetObjResult(interp, resultObj); + return TCL_OK; +} + +static int +ClassWPropsSet( + ClientData clientData, + Tcl_Interp *interp, + Tcl_ObjectContext context, + int objc, + Tcl_Obj *const *objv) +{ + Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); + int varc; + Tcl_Obj **varv; + + if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { + Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, + "propertyList"); + return TCL_ERROR; + } + objv += Tcl_ObjectContextSkippedArgs(context); + + if (oPtr == NULL) { + return TCL_ERROR; + } else if (!oPtr->classPtr) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "attempt to misuse API", -1)); + Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", NULL); + return TCL_ERROR; + } else if (Tcl_ListObjGetElements(interp, objv[0], &varc, + &varv) != TCL_OK) { + return TCL_ERROR; + } + + InstallWritableProps(&oPtr->classPtr->properties, varc, varv); + BumpGlobalEpoch(interp, oPtr->classPtr); + return TCL_OK; +} + +static int +ObjWPropsGet( + ClientData clientData, + Tcl_Interp *interp, + Tcl_ObjectContext context, + int objc, + Tcl_Obj *const *objv) +{ + Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); + Tcl_Obj *resultObj, *propNameObj; + int i; + + if (Tcl_ObjectContextSkippedArgs(context) != objc) { + Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, + NULL); + return TCL_ERROR; + } + if (oPtr == NULL) { + return TCL_ERROR; + } + + resultObj = Tcl_NewObj(); + FOREACH(propNameObj, oPtr->properties.writable) { + Tcl_ListObjAppendElement(NULL, resultObj, propNameObj); + } + Tcl_SetObjResult(interp, resultObj); + return TCL_OK; +} + +static int +ObjWPropsSet( + ClientData clientData, + Tcl_Interp *interp, + Tcl_ObjectContext context, + int objc, + Tcl_Obj *const *objv) +{ + Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); + int varc; + Tcl_Obj **varv; + + if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { + Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, + "propertyList"); + return TCL_ERROR; + } + objv += Tcl_ObjectContextSkippedArgs(context); + + if (oPtr == NULL) { + return TCL_ERROR; + } else if (Tcl_ListObjGetElements(interp, objv[0], &varc, + &varv) != TCL_OK) { + return TCL_ERROR; + } + + InstallWritableProps(&oPtr->properties, varc, varv); + return TCL_OK; +} + +/* * Local Variables: * mode: c * c-basic-offset: 4 diff --git a/generic/tclOOInfo.c b/generic/tclOOInfo.c index 99918ae..ed44cc8 100644 --- a/generic/tclOOInfo.c +++ b/generic/tclOOInfo.c @@ -4,7 +4,7 @@ * This file contains the implementation of the ::oo-related [info] * subcommands. * - * Copyright (c) 2006-2011 by Donal K. Fellows + * Copyright (c) 2006-2019 by Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -17,6 +17,7 @@ #include "tclOOInt.h" static inline Class * GetClassFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr); +static void SortPropList(Tcl_Obj *list); static Tcl_ObjCmdProc InfoObjectCallCmd; static Tcl_ObjCmdProc InfoObjectClassCmd; static Tcl_ObjCmdProc InfoObjectDefnCmd; @@ -28,6 +29,7 @@ static Tcl_ObjCmdProc InfoObjectMethodsCmd; static Tcl_ObjCmdProc InfoObjectMethodTypeCmd; static Tcl_ObjCmdProc InfoObjectMixinsCmd; static Tcl_ObjCmdProc InfoObjectNsCmd; +static Tcl_ObjCmdProc InfoObjectPropCmd; static Tcl_ObjCmdProc InfoObjectVarsCmd; static Tcl_ObjCmdProc InfoObjectVariablesCmd; static Tcl_ObjCmdProc InfoClassCallCmd; @@ -41,6 +43,7 @@ static Tcl_ObjCmdProc InfoClassInstancesCmd; static Tcl_ObjCmdProc InfoClassMethodsCmd; static Tcl_ObjCmdProc InfoClassMethodTypeCmd; static Tcl_ObjCmdProc InfoClassMixinsCmd; +static Tcl_ObjCmdProc InfoClassPropCmd; static Tcl_ObjCmdProc InfoClassSubsCmd; static Tcl_ObjCmdProc InfoClassSupersCmd; static Tcl_ObjCmdProc InfoClassVariablesCmd; @@ -61,6 +64,7 @@ static const EnsembleImplMap infoObjectCmds[] = { {"methodtype", InfoObjectMethodTypeCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"mixins", InfoObjectMixinsCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"namespace", InfoObjectNsCmd, TclCompileInfoObjectNamespaceCmd, NULL, NULL, 0}, + {"property", InfoObjectPropCmd, TclCompileBasicMin1ArgCmd, NULL, NULL, 0}, {"variables", InfoObjectVariablesCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, {"vars", InfoObjectVarsCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, {NULL, NULL, NULL, NULL, NULL, 0} @@ -82,6 +86,7 @@ static const EnsembleImplMap infoClassCmds[] = { {"methods", InfoClassMethodsCmd, TclCompileBasicMin1ArgCmd, NULL, NULL, 0}, {"methodtype", InfoClassMethodTypeCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"mixins", InfoClassMixinsCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, + {"property", InfoClassPropCmd, TclCompileBasicMin1ArgCmd, NULL, NULL, 0}, {"subclasses", InfoClassSubsCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, {"superclasses", InfoClassSupersCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"variables", InfoClassVariablesCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, @@ -1714,6 +1719,182 @@ InfoClassCallCmd( } /* + * ---------------------------------------------------------------------- + * + * InfoClassPropCmd, InfoObjectPropCmd -- + * + * Implements [info class property $clsName ?$option...?] and + * [info object property $objName ?$option...?] + * + * ---------------------------------------------------------------------- + */ + +enum PropOpt { + PROP_ALL, PROP_READABLE, PROP_WRITABLE +}; +static const char *const propOptNames[] = { + "-all", "-readable", "-writable", + NULL +}; + +static int +InfoClassPropCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + Class *clsPtr; + int i, idx, all = 0, writable = 0, allocated = 0; + Tcl_Obj *result, *propObj; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "className ?options...?"); + return TCL_ERROR; + } + clsPtr = GetClassFromObj(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 = TclOOGetAllClassProperties(clsPtr, writable, &allocated); + if (allocated) { + SortPropList(result); + } + } else { + result = Tcl_NewObj(); + if (writable) { + FOREACH(propObj, clsPtr->properties.writable) { + Tcl_ListObjAppendElement(NULL, result, propObj); + } + } else { + FOREACH(propObj, clsPtr->properties.readable) { + Tcl_ListObjAppendElement(NULL, result, propObj); + } + } + SortPropList(result); + } + Tcl_SetObjResult(interp, result); + return TCL_OK; +} + +static int +InfoObjectPropCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + Object *oPtr; + int i, idx, all = 0, writable = 0, allocated = 0; + Tcl_Obj *result, *propObj; + + 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, &allocated); + if (allocated) { + SortPropList(result); + } + } else { + result = Tcl_NewObj(); + if (writable) { + FOREACH(propObj, oPtr->properties.writable) { + Tcl_ListObjAppendElement(NULL, result, propObj); + } + } else { + FOREACH(propObj, oPtr->properties.readable) { + Tcl_ListObjAppendElement(NULL, result, propObj); + } + } + SortPropList(result); + } + Tcl_SetObjResult(interp, result); + return TCL_OK; +} + +/* + * ---------------------------------------------------------------------- + * + * SortPropList -- + * Sort a list of names of properties. Simple support function. + * + * ---------------------------------------------------------------------- + */ + +static int +PropNameCompare( + const void *a, + const void *b) +{ + Tcl_Obj *first = *(Tcl_Obj **) a; + Tcl_Obj *second = *(Tcl_Obj **) b; + + return strcmp(Tcl_GetString(first), Tcl_GetString(second)); +} + +static void +SortPropList( + Tcl_Obj *list) +{ + int ec; + Tcl_Obj **ev; + + Tcl_ListObjGetElements(NULL, list, &ec, &ev); + qsort(ev, ec, sizeof(Tcl_Obj *), PropNameCompare); +} + +/* * Local Variables: * mode: c * c-basic-offset: 4 diff --git a/generic/tclOOInt.h b/generic/tclOOInt.h index ca984d0..e8b8f4a 100644 --- a/generic/tclOOInt.h +++ b/generic/tclOOInt.h @@ -161,6 +161,26 @@ typedef LIST_STATIC(Tcl_Obj *) VariableNameList; typedef LIST_STATIC(PrivateVariableMapping) PrivateVariableList; /* + * This type is used in various places. + */ + +typedef struct { + LIST_STATIC(Tcl_Obj *) readable; + /* The readable properties slot. */ + LIST_STATIC(Tcl_Obj *) writable; + /* The writable properties slot. */ + Tcl_Obj *allReadableCache; /* The cache of all readable properties + * exposed by this object or class (in its + * stereotypical instancs). Contains a sorted + * unique list if not NULL. */ + Tcl_Obj *allWritableCache; /* The cache of all writable properties + * exposed by this object or class (in its + * stereotypical instances). Contains a sorted + * unique list if not NULL. */ + int epoch; /* The epoch that the caches are valid for. */ +} PropertyStorage; + +/* * Now, the definition of what an object actually is. */ @@ -182,8 +202,8 @@ typedef struct Object { LIST_STATIC(Tcl_Obj *) filters; /* List of filter names. */ struct Class *classPtr; /* This is non-NULL for all classes, and NULL - * for everything else. It points to the class - * structure. */ + * for everything else. It points to the class + * structure. */ int refCount; /* Number of strong references to this object. * Note that there may be many more weak * references; this mechanism exists to @@ -211,12 +231,15 @@ typedef struct Object { * used inside methods. */ Tcl_Command myclassCommand; /* Reference to this object's class dispatcher * command. */ + PropertyStorage properties; /* Information relating to the lists of + * properties that this object *claims* to + * support. */ } Object; -#define OBJECT_DESTRUCTING 1 /* Indicates that an object is being or has - * been destroyed */ -#define DESTRUCTOR_CALLED 2 /* Indicates that evaluation of destructor script for the - object has began */ +#define OBJECT_DESTRUCTING 1 /* Indicates that an object is being or has + * been destroyed */ +#define DESTRUCTOR_CALLED 2 /* Indicates that evaluation of destructor + * script for the object has began */ #define OO_UNUSED_4 4 /* No longer used. */ #define ROOT_OBJECT 0x1000 /* Flag to say that this object is the root of * the class hierarchy and should be treated @@ -319,6 +342,9 @@ typedef struct Class { * namespace is defined but doesn't exist; we * also check at setting time but don't check * between times. */ + PropertyStorage properties; /* Information relating to the lists of + * properties that this class *claims* to + * support. */ } Class; /* @@ -568,6 +594,10 @@ MODULE_SCOPE void TclOODeleteContext(CallContext *contextPtr); MODULE_SCOPE void TclOODeleteDescendants(Tcl_Interp *interp, Object *oPtr); MODULE_SCOPE void TclOODelMethodRef(Method *method); +MODULE_SCOPE Tcl_Obj * TclOOGetAllClassProperties(Class *clsPtr, + int writable, int *allocated); +MODULE_SCOPE Tcl_Obj * TclOOGetAllObjectProperties(Object *oPtr, + int writable, int *allocated); MODULE_SCOPE CallContext *TclOOGetCallContext(Object *oPtr, Tcl_Obj *methodNameObj, int flags, Object *contextObjPtr, Class *contextClsPtr, diff --git a/generic/tclOOScript.h b/generic/tclOOScript.h index a1e4624..c8a79a9 100644 --- a/generic/tclOOScript.h +++ b/generic/tclOOScript.h @@ -248,6 +248,120 @@ static const char *tclOOSetupScript = "\t\tsuperclass class\n" "\t\tunexport create createWithNamespace new\n" "\t}\n" +"\tnamespace eval configuresupport {\n" +"\t\tproc property {readslot writeslot args} {\n" +"\t\t\tfor {set i 0} {$i < [llength $args]} {incr i} {\n" +"\t\t\t\tset prop [lindex $args $i]\n" +"\t\t\t\tif {[string match \"-*\" $prop]} {\n" +"\t\t\t\t\treturn -code error -errorcode {TCLOO PROPERTY_FORMAT} \\\n" +"\t\t\t\t\t\t\"bad property name \\\"$prop\\\"; must not begin with -\"\n" +"\t\t\t\t}\n" +"\t\t\t\tset realprop [string cat \"-\" $prop]\n" +"\t\t\t\tset getter [format {::set [my varname %s]} $prop]\n" +"\t\t\t\tset setter [format {::set [my varname %s] $value} $prop]\n" +"\t\t\t\tset kind readwrite\n" +"\t\t\t\twhile {[string match \"-*\" [set next [lindex $args [expr {$i + 1}]]]]} {\n" +"\t\t\t\t\tset arg [lindex $args [incr i 2]]\n" +"\t\t\t\t\tswitch [::tcl::prefix match {-get -kind -set} $next] {\n" +"\t\t\t\t\t\t-get {\n" +"\t\t\t\t\t\t\tif {$i >= [llength $args]} {\n" +"\t\t\t\t\t\t\t\treturn -code error -errorcode {TCL WRONGARGS} \\\n" +"\t\t\t\t\t\t\t\t\t\"missing body to go with -get option\"\n" +"\t\t\t\t\t\t\t}\n" +"\t\t\t\t\t\t\tset getter $arg\n" +"\t\t\t\t\t\t}\n" +"\t\t\t\t\t\t-set {\n" +"\t\t\t\t\t\t\tif {$i >= [llength $args]} {\n" +"\t\t\t\t\t\t\t\treturn -code error -errorcode {TCL WRONGARGS} \\\n" +"\t\t\t\t\t\t\t\t\t\"missing body to go with -set option\"\n" +"\t\t\t\t\t\t\t}\n" +"\t\t\t\t\t\t\tset getter $arg\n" +"\t\t\t\t\t\t}\n" +"\t\t\t\t\t\t-kind {\n" +"\t\t\t\t\t\t\tif {$i >= [llength $args]} {\n" +"\t\t\t\t\t\t\t\treturn -code error -errorcode {TCL WRONGARGS} \\\n" +"\t\t\t\t\t\t\t\t\t\"missing kind value to go with -kind option\"\n" +"\t\t\t\t\t\t\t}\n" +"\t\t\t\t\t\t\tset kind [::tcl::prefix match -message \"kind\" {\n" +"\t\t\t\t\t\t\t\treadable readwrite writable\n" +"\t\t\t\t\t\t\t} $arg]\n" +"\t\t\t\t\t\t}\n" +"\t\t\t\t\t}\n" +"\t\t\t\t}\n" +"\t\t\t\tswitch $kind {\n" +"\t\t\t\t\treadable {\n" +"\t\t\t\t\t\tuplevel 1 [list $readslot -append $realprop]\n" +"\t\t\t\t\t\tuplevel 1 [list method <ReadProp$realprop> {} $getter]\n" +"\t\t\t\t\t}\n" +"\t\t\t\t\twritable {\n" +"\t\t\t\t\t\tuplevel 1 [list $writeslot -append $realprop]\n" +"\t\t\t\t\t\tuplevel 1 [list method <WriteProp$realprop> {value} $setter]\n" +"\t\t\t\t\t}\n" +"\t\t\t\t\treadwrite {\n" +"\t\t\t\t\t\tuplevel 1 [list $readslot -append $realprop]\n" +"\t\t\t\t\t\tuplevel 1 [list $writeslot -append $realprop]\n" +"\t\t\t\t\t\tuplevel 1 [list method <ReadProp$realprop> {} $getter]\n" +"\t\t\t\t\t\tuplevel 1 [list method <WriteProp$realprop> {value} $setter]\n" +"\t\t\t\t\t}\n" +"\t\t\t\t}\n" +"\t\t\t}\n" +"\t\t}\n" +"\t\tnamespace eval configurableclass {\n" +"\t\t\tproc property args {\n" +"\t\t\t\ttailcall ::oo::configuresupport::property \\\n" +"\t\t\t\t\t::oo::configuresupport::readableproperties \\\n" +"\t\t\t\t\t::oo::configuresupport::writableproperties \\\n" +"\t\t\t\t\t{*}$args\n" +"\t\t\t}\n" +"\t\t\tnamespace path ::oo::define\n" +"\t\t}\n" +"\t\tnamespace eval configurableobject {\n" +"\t\t\tproc property args {\n" +"\t\t\t\ttailcall ::oo::configuresupport::property \\\n" +"\t\t\t\t\t::oo::configuresupport::objreadableproperties \\\n" +"\t\t\t\t\t::oo::configuresupport::objwritableproperties \\\n" +"\t\t\t\t\t{*}$args\n" +"\t\t\t}\n" +"\t\t\tnamespace path ::oo::objdefine\n" +"\t\t}\n" +"\t}\n" +"\tclass create configurable {\n" +"\t\tsuperclass class\n" +"\t\tconstructor {{definitionScript \"\"}} {\n" +"\t\t\tnext {mixin ::oo::configuresupport::configurable}\n" +"\t\t\tnext $definitionScript\n" +"\t\t}\n" +"\t\tdefinitionnamespace -class configuresupport::configurableclass\n" +"\t\tdefinitionnamespace -instance configuresupport::configurableobject\n" +"\t}\n" +"\tclass create configuresupport::configurable {\n" +"\t\tprivate method Configure:Match {prop kind} {\n" +"\t\t\tset props [info object property [self] -all $kind]\n" +"\t\t\t::tcl::prefix match -message \"property\" $props $prop\n" +"\t\t}\n" +"\t\tmethod configure args {\n" +"\t\t\tif {[llength $args] == 0} {\n" +"\t\t\t\tset result {}\n" +"\t\t\t\tforeach prop [info object property [self] -all -readable] {\n" +"\t\t\t\t\tdict set result $prop [my <ReadProp$prop>]\n" +"\t\t\t\t}\n" +"\t\t\t\treturn $result\n" +"\t\t\t} elseif {[llength $args] == 1} {\n" +"\t\t\t\tset prop [my Configure:Match [lindex $args 0] -readable]\n" +"\t\t\t\treturn [my <ReadProp$prop>]\n" +"\t\t\t} elseif {[llength $args] % 2 == 0} {\n" +"\t\t\t\tforeach {prop value} $args {\n" +"\t\t\t\t\tset prop [my Configure:Match $prop -writable]\n" +"\t\t\t\t\tmy <WriteProp$prop> $value\n" +"\t\t\t\t}\n" +"\t\t\t\treturn\n" +"\t\t\t} else {\n" +"\t\t\t\treturn -code error -errorcode {TCL WRONGARGS} \\\n" +"\t\t\t\t\t[format \"wrong # args: should be \\\"%s\\\"\" \\\n" +"\t\t\t\t\t\t \"[self] configure \?-option value ...\?\"]\n" +"\t\t\t}\n" +"\t\t}\n" +"\t}\n" "}\n" /* !END!: Do not edit above this line. */ ; diff --git a/tools/tclOOScript.tcl b/tools/tclOOScript.tcl index 5e0145f..8cc9627 100644 --- a/tools/tclOOScript.tcl +++ b/tools/tclOOScript.tcl @@ -447,6 +447,137 @@ superclass class unexport create createWithNamespace new } + + # ---------------------------------------------------------------------- + # + # oo::configurable -- + # + # A metaclass that is used to make classes that can be configured. Also + # its supporting classes and namespaces. + # + # ---------------------------------------------------------------------- + + namespace eval configuresupport { + proc property {readslot writeslot args} { + for {set i 0} {$i < [llength $args]} {incr i} { + # Parse the property name + set prop [lindex $args $i] + if {[string match "-*" $prop]} { + return -code error -errorcode {TCLOO PROPERTY_FORMAT} \ + "bad property name \"$prop\"; must not begin with -" + } + set realprop [string cat "-" $prop] + set getter [format {::set [my varname %s]} $prop] + set setter [format {::set [my varname %s] $value} $prop] + set kind readwrite + + # Parse the extra options + while {[string match "-*" [set next [lindex $args [expr {$i + 1}]]]]} { + set arg [lindex $args [incr i 2]] + switch [::tcl::prefix match {-get -kind -set} $next] { + -get { + if {$i >= [llength $args]} { + return -code error -errorcode {TCL WRONGARGS} \ + "missing body to go with -get option" + } + set getter $arg + } + -set { + if {$i >= [llength $args]} { + return -code error -errorcode {TCL WRONGARGS} \ + "missing body to go with -set option" + } + set getter $arg + } + -kind { + if {$i >= [llength $args]} { + return -code error -errorcode {TCL WRONGARGS} \ + "missing kind value to go with -kind option" + } + set kind [::tcl::prefix match -message "kind" { + readable readwrite writable + } $arg] + } + } + } + + # Install the option + switch $kind { + readable { + uplevel 1 [list $readslot -append $realprop] + uplevel 1 [list method <ReadProp$realprop> {} $getter] + } + writable { + uplevel 1 [list $writeslot -append $realprop] + uplevel 1 [list method <WriteProp$realprop> {value} $setter] + } + readwrite { + uplevel 1 [list $readslot -append $realprop] + uplevel 1 [list $writeslot -append $realprop] + uplevel 1 [list method <ReadProp$realprop> {} $getter] + uplevel 1 [list method <WriteProp$realprop> {value} $setter] + } + } + } + } + namespace eval configurableclass { + proc property args { + tailcall ::oo::configuresupport::property \ + ::oo::configuresupport::readableproperties \ + ::oo::configuresupport::writableproperties \ + {*}$args + } + namespace path ::oo::define + } + namespace eval configurableobject { + proc property args { + tailcall ::oo::configuresupport::property \ + ::oo::configuresupport::objreadableproperties \ + ::oo::configuresupport::objwritableproperties \ + {*}$args + } + namespace path ::oo::objdefine + } + } + + class create configurable { + superclass class + constructor {{definitionScript ""}} { + next {mixin ::oo::configuresupport::configurable} + next $definitionScript + } + definitionnamespace -class configuresupport::configurableclass + definitionnamespace -instance configuresupport::configurableobject + } + + class create configuresupport::configurable { + private method Configure:Match {prop kind} { + set props [info object property [self] -all $kind] + ::tcl::prefix match -message "property" $props $prop + } + method configure args { + if {[llength $args] == 0} { + set result {} + foreach prop [info object property [self] -all -readable] { + dict set result $prop [my <ReadProp$prop>] + } + return $result + } elseif {[llength $args] == 1} { + set prop [my Configure:Match [lindex $args 0] -readable] + return [my <ReadProp$prop>] + } elseif {[llength $args] % 2 == 0} { + foreach {prop value} $args { + set prop [my Configure:Match $prop -writable] + my <WriteProp$prop> $value + } + return + } else { + return -code error -errorcode {TCL WRONGARGS} \ + [format "wrong # args: should be \"%s\"" \ + "[self] configure ?-option value ...?"] + } + } + } } # Local Variables: |
