/* * itclBase.c -- * * This file contains the C-implemented startup part of an * Itcl implemenatation * * Copyright (c) 2007 by Arnulf P. Wiedemann * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include #include "itclInt.h" static Tcl_NamespaceDeleteProc FreeItclObjectInfo; static Tcl_ObjCmdProc ItclSetHullWindowName; static Tcl_ObjCmdProc ItclCheckSetItclHull; MODULE_SCOPE const ItclStubs itclStubs; static int Initialize(Tcl_Interp *interp); static const char initScript[] = "namespace eval ::itcl {\n" " proc _find_init {} {\n" " global env tcl_library\n" " variable library\n" " variable patchLevel\n" " rename _find_init {}\n" " if {[info exists library]} {\n" " lappend dirs $library\n" " } else {\n" " set dirs {}\n" " if {[info exists env(ITCL_LIBRARY)]} {\n" " lappend dirs $env(ITCL_LIBRARY)\n" " }\n" " lappend dirs [file join [file dirname $tcl_library] itcl$patchLevel]\n" " set bindir [file dirname [info nameofexecutable]]\n" " lappend dirs [file join . library]\n" " lappend dirs [file join $bindir .. lib itcl$patchLevel]\n" " lappend dirs [file join $bindir .. library]\n" " lappend dirs [file join $bindir .. .. library]\n" " lappend dirs [file join $bindir .. .. itcl library]\n" " lappend dirs [file join $bindir .. .. .. itcl library]\n" " lappend dirs [file join $bindir .. .. itcl-ng itcl library]\n" " # On *nix, check the directories in the tcl_pkgPath\n" " # XXX JH - this looks unnecessary, maybe Darwin only?\n" " if {[string equal $::tcl_platform(platform) \"unix\"]} {\n" " foreach d $::tcl_pkgPath {\n" " lappend dirs $d\n" " lappend dirs [file join $d itcl$patchLevel]\n" " }\n" " }\n" " }\n" " foreach i $dirs {\n" " set library $i\n" " if {![catch {uplevel #0 [list source [file join $i itcl.tcl]]}]} {\n" " set library $i\n" " return\n" " }\n" " }\n" " set msg \"Can't find a usable itcl.tcl in the following directories:\n\"\n" " append msg \" $dirs\n\"\n" " append msg \"This probably means that Itcl/Tcl weren't installed properly.\n\"\n" " append msg \"If you know where the Itcl library directory was installed,\n\"\n" " append msg \"you can set the environment variable ITCL_LIBRARY to point\n\"\n" " append msg \"to the library directory.\n\"\n" " error $msg\n" " }\n" " _find_init\n" "}"; /* * The following script is used to initialize Itcl in a safe interpreter. */ static const char safeInitScript[] = "proc ::itcl::local {class name args} {\n" " set ptr [uplevel [list $class $name] $args]\n" " uplevel [list set itcl-local-$ptr $ptr]\n" " set cmd [uplevel namespace which -command $ptr]\n" " uplevel [list trace variable itcl-local-$ptr u \"::itcl::delete object $cmd; list\"]\n" " return $ptr\n" "}"; static const char *clazzClassScript = "::oo::class create ::itcl::clazz {\n" " superclass ::oo::class\n" " method unknown args {\n" " ::tailcall ::itcl::parser::handleClass [::lindex [::info level 0] 0] [self] {*}$args\n" " }\n" " unexport create new unknown\n" "}"; #define ITCL_IS_ENSEMBLE 0x1 #ifdef ITCL_DEBUG_C_INTERFACE extern void RegisterDebugCFunctions( Tcl_Interp * interp); #endif static Tcl_ObjectMetadataDeleteProc Demolition; static const Tcl_ObjectMetadataType canary = { TCL_OO_METADATA_VERSION_CURRENT, "Itcl Foundations", Demolition, NULL }; void Demolition( void *clientData) { ItclObjectInfo *infoPtr = (ItclObjectInfo *)clientData; infoPtr->clazzObjectPtr = NULL; infoPtr->clazzClassPtr = NULL; } static const Tcl_ObjectMetadataType objMDT = { TCL_OO_METADATA_VERSION_CURRENT, "ItclObject", ItclDeleteObjectMetadata, /* Not really used yet */ NULL }; static Tcl_MethodCallProc RootCallProc; const Tcl_MethodType itclRootMethodType = { TCL_OO_METHOD_VERSION_CURRENT, "itcl root method", RootCallProc, NULL, NULL }; static int RootCallProc( void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Tcl_Object oPtr = Tcl_ObjectContextObject(context); ItclObject *ioPtr = (ItclObject *)Tcl_ObjectGetMetadata(oPtr, &objMDT); ItclRootMethodProc *proc = (ItclRootMethodProc *)clientData; return (*proc)(ioPtr, interp, objc, objv); } /* * ------------------------------------------------------------------------ * Initialize() * * that is the starting point when loading the library * it initializes all internal stuff * * ------------------------------------------------------------------------ */ static int Initialize ( Tcl_Interp *interp) { Tcl_Namespace *nsPtr; Tcl_Namespace *itclNs; Tcl_HashEntry *hPtr; ItclObjectInfo *infoPtr; const char * ret; char *res_option; int opt; int isNew; Tcl_Class tclCls; Tcl_Object clazzObjectPtr, root; Tcl_Obj *objPtr, *resPtr; if (Tcl_InitStubs(interp, "8.6", 0) == NULL) { return TCL_ERROR; } ret = TclOOInitializeStubs(interp, "1.0"); if (ret == NULL) { return TCL_ERROR; } objPtr = Tcl_NewStringObj("::oo::class", -1); Tcl_IncrRefCount(objPtr); clazzObjectPtr = Tcl_GetObjectFromObj(interp, objPtr); if (!clazzObjectPtr || !(tclCls = Tcl_GetObjectAsClass(clazzObjectPtr))) { Tcl_DecrRefCount(objPtr); return TCL_ERROR; } Tcl_DecrRefCount(objPtr); infoPtr = (ItclObjectInfo*)Itcl_Alloc(sizeof(ItclObjectInfo)); nsPtr = Tcl_CreateNamespace(interp, ITCL_NAMESPACE, infoPtr, FreeItclObjectInfo); if (nsPtr == NULL) { Itcl_Free(infoPtr); Tcl_Panic("Itcl: cannot create namespace: \"%s\" \n", ITCL_NAMESPACE); } nsPtr = Tcl_CreateNamespace(interp, ITCL_INTDICTS_NAMESPACE, NULL, NULL); if (nsPtr == NULL) { Itcl_Free(infoPtr); Tcl_Panic("Itcl: cannot create namespace: \"%s::internal::dicts\" \n", ITCL_NAMESPACE); } /* * Create the top-level data structure for tracking objects. * Store this as "associated data" for easy access, but link * it to the itcl namespace for ownership. */ infoPtr->interp = interp; infoPtr->class_meta_type = (Tcl_ObjectMetadataType *)ckalloc( sizeof(Tcl_ObjectMetadataType)); infoPtr->class_meta_type->version = TCL_OO_METADATA_VERSION_CURRENT; infoPtr->class_meta_type->name = "ItclClass"; infoPtr->class_meta_type->deleteProc = ItclDeleteClassMetadata; infoPtr->class_meta_type->cloneProc = NULL; infoPtr->object_meta_type = &objMDT; Tcl_InitHashTable(&infoPtr->objects, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(&infoPtr->objectCmds, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(&infoPtr->classes, TCL_ONE_WORD_KEYS); Tcl_InitObjHashTable(&infoPtr->nameClasses); Tcl_InitHashTable(&infoPtr->namespaceClasses, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(&infoPtr->procMethods, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(&infoPtr->instances, TCL_STRING_KEYS); Tcl_InitHashTable(&infoPtr->frameContext, TCL_ONE_WORD_KEYS); Tcl_InitObjHashTable(&infoPtr->classTypes); infoPtr->ensembleInfo = (EnsembleInfo *)ckalloc(sizeof(EnsembleInfo)); memset(infoPtr->ensembleInfo, 0, sizeof(EnsembleInfo)); Tcl_InitHashTable(&infoPtr->ensembleInfo->ensembles, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(&infoPtr->ensembleInfo->subEnsembles, TCL_ONE_WORD_KEYS); infoPtr->ensembleInfo->numEnsembles = 0; infoPtr->protection = ITCL_DEFAULT_PROTECT; infoPtr->currClassFlags = 0; infoPtr->buildingWidget = 0; infoPtr->typeDestructorArgumentPtr = Tcl_NewStringObj("", -1); Tcl_IncrRefCount(infoPtr->typeDestructorArgumentPtr); infoPtr->lastIoPtr = NULL; Tcl_SetVar2(interp, ITCL_NAMESPACE"::internal::dicts::classes", NULL, "", 0); Tcl_SetVar2(interp, ITCL_NAMESPACE"::internal::dicts::objects", NULL, "", 0); Tcl_SetVar2(interp, ITCL_NAMESPACE"::internal::dicts::classOptions", NULL, "", 0); Tcl_SetVar2(interp, ITCL_NAMESPACE"::internal::dicts::classDelegatedOptions", NULL, "", 0); Tcl_SetVar2(interp, ITCL_NAMESPACE"::internal::dicts::classComponents", NULL, "", 0); Tcl_SetVar2(interp, ITCL_NAMESPACE"::internal::dicts::classVariables", NULL, "", 0); Tcl_SetVar2(interp, ITCL_NAMESPACE"::internal::dicts::classFunctions", NULL, "", 0); Tcl_SetVar2(interp, ITCL_NAMESPACE"::internal::dicts::classDelegatedFunctions", NULL, "", 0); hPtr = Tcl_CreateHashEntry(&infoPtr->classTypes, (char *)Tcl_NewStringObj("class", -1), &isNew); Tcl_SetHashValue(hPtr, ITCL_CLASS); hPtr = Tcl_CreateHashEntry(&infoPtr->classTypes, (char *)Tcl_NewStringObj("type", -1), &isNew); Tcl_SetHashValue(hPtr, ITCL_TYPE); hPtr = Tcl_CreateHashEntry(&infoPtr->classTypes, (char *)Tcl_NewStringObj("widget", -1), &isNew); Tcl_SetHashValue(hPtr, ITCL_WIDGET); hPtr = Tcl_CreateHashEntry(&infoPtr->classTypes, (char *)Tcl_NewStringObj("widgetadaptor", -1), &isNew); Tcl_SetHashValue(hPtr, ITCL_WIDGETADAPTOR); hPtr = Tcl_CreateHashEntry(&infoPtr->classTypes, (char *)Tcl_NewStringObj("extendedclass", -1), &isNew); Tcl_SetHashValue(hPtr, ITCL_ECLASS); res_option = getenv("ITCL_USE_OLD_RESOLVERS"); if (res_option == NULL) { opt = 1; } else { opt = atoi(res_option); } infoPtr->useOldResolvers = opt; Itcl_InitStack(&infoPtr->clsStack); Tcl_SetAssocData(interp, ITCL_INTERP_DATA, NULL, infoPtr); Itcl_PreserveData(infoPtr); root = Tcl_NewObjectInstance(interp, tclCls, "::itcl::Root", NULL, 0, NULL, 0); Tcl_NewMethod(interp, Tcl_GetObjectAsClass(root), Tcl_NewStringObj("unknown", -1), 0, &itclRootMethodType, (void *)ItclUnknownGuts); Tcl_NewMethod(interp, Tcl_GetObjectAsClass(root), Tcl_NewStringObj("ItclConstructBase", -1), 0, &itclRootMethodType, (void *)ItclConstructGuts); Tcl_NewMethod(interp, Tcl_GetObjectAsClass(root), Tcl_NewStringObj("info", -1), 1, &itclRootMethodType, (void *)ItclInfoGuts); /* first create the Itcl base class as root of itcl classes */ if (Tcl_EvalEx(interp, clazzClassScript, -1, 0) != TCL_OK) { Tcl_Panic("cannot create Itcl root class ::itcl::clazz"); } resPtr = Tcl_GetObjResult(interp); /* * Tcl_GetObjectFromObject can call Tcl_SetObjResult, so increment the * refcount first. */ Tcl_IncrRefCount(resPtr); clazzObjectPtr = Tcl_GetObjectFromObj(interp, resPtr); Tcl_DecrRefCount(resPtr); if (clazzObjectPtr == NULL) { Tcl_AppendResult(interp, "ITCL: cannot get Object for ::itcl::clazz for class \"", "::itcl::clazz", "\"", NULL); return TCL_ERROR; } Tcl_ObjectSetMetadata(clazzObjectPtr, &canary, infoPtr); infoPtr->clazzObjectPtr = clazzObjectPtr; infoPtr->clazzClassPtr = Tcl_GetObjectAsClass(clazzObjectPtr); /* * Initialize the ensemble package first, since we need this * for other parts of [incr Tcl]. */ if (Itcl_EnsembleInit(interp) != TCL_OK) { return TCL_ERROR; } Itcl_ParseInit(interp, infoPtr); /* * Create "itcl::builtin" namespace for commands that * are automatically built into class definitions. */ if (Itcl_BiInit(interp, infoPtr) != TCL_OK) { return TCL_ERROR; } /* * Export all commands in the "itcl" namespace so that they * can be imported with something like "namespace import itcl::*" */ itclNs = Tcl_FindNamespace(interp, "::itcl", NULL, TCL_LEAVE_ERR_MSG); /* * This was changed from a glob export (itcl::*) to explicit * command exports, so that the itcl::is command can *not* be * exported. This is done for concern that the itcl::is command * imported might be confusing ("is"). */ if (!itclNs || (Tcl_Export(interp, itclNs, "body", /* reset */ 1) != TCL_OK) || (Tcl_Export(interp, itclNs, "class", 0) != TCL_OK) || (Tcl_Export(interp, itclNs, "code", 0) != TCL_OK) || (Tcl_Export(interp, itclNs, "configbody", 0) != TCL_OK) || (Tcl_Export(interp, itclNs, "delete", 0) != TCL_OK) || (Tcl_Export(interp, itclNs, "delete_helper", 0) != TCL_OK) || (Tcl_Export(interp, itclNs, "ensemble", 0) != TCL_OK) || (Tcl_Export(interp, itclNs, "filter", 0) != TCL_OK) || (Tcl_Export(interp, itclNs, "find", 0) != TCL_OK) || (Tcl_Export(interp, itclNs, "forward", 0) != TCL_OK) || (Tcl_Export(interp, itclNs, "local", 0) != TCL_OK) || (Tcl_Export(interp, itclNs, "mixin", 0) != TCL_OK) || (Tcl_Export(interp, itclNs, "scope", 0) != TCL_OK)) { return TCL_ERROR; } Tcl_CreateObjCommand(interp, ITCL_NAMESPACE"::internal::commands::sethullwindowname", ItclSetHullWindowName, infoPtr, NULL); Tcl_CreateObjCommand(interp, ITCL_NAMESPACE"::internal::commands::checksetitclhull", ItclCheckSetItclHull, infoPtr, NULL); /* * Set up the variables containing version info. */ Tcl_SetVar2(interp, "::itcl::version", NULL, ITCL_VERSION, TCL_NAMESPACE_ONLY); Tcl_SetVar2(interp, "::itcl::patchLevel", NULL, ITCL_PATCH_LEVEL, TCL_NAMESPACE_ONLY); #ifdef ITCL_DEBUG_C_INTERFACE RegisterDebugCFunctions(interp); #endif /* * Package is now loaded. */ Tcl_PkgProvideEx(interp, "Itcl", ITCL_PATCH_LEVEL, &itclStubs); return Tcl_PkgProvideEx(interp, "itcl", ITCL_PATCH_LEVEL, &itclStubs); } /* * ------------------------------------------------------------------------ * Itcl_Init() * * Invoked whenever a new INTERPRETER is created to install the * [incr Tcl] package. Usually invoked within Tcl_AppInit() at * the start of execution. * * Creates the "::itcl" namespace and installs access commands for * creating classes and querying info. * * Returns TCL_OK on success, or TCL_ERROR (along with an error * message in the interpreter) if anything goes wrong. * ------------------------------------------------------------------------ */ int Itcl_Init ( Tcl_Interp *interp) { if (Initialize(interp) != TCL_OK) { return TCL_ERROR; } return Tcl_EvalEx(interp, initScript, -1, 0); } /* * ------------------------------------------------------------------------ * Itcl_SafeInit() * * Invoked whenever a new SAFE INTERPRETER is created to install * the [incr Tcl] package. * * Creates the "::itcl" namespace and installs access commands for * creating classes and querying info. * * Returns TCL_OK on success, or TCL_ERROR (along with an error * message in the interpreter) if anything goes wrong. * ------------------------------------------------------------------------ */ int Itcl_SafeInit ( Tcl_Interp *interp) { if (Initialize(interp) != TCL_OK) { return TCL_ERROR; } return Tcl_EvalEx(interp, safeInitScript, -1, 0); } /* * ------------------------------------------------------------------------ * ItclSetHullWindowName() * * * ------------------------------------------------------------------------ */ static int ItclSetHullWindowName( void *clientData, /* infoPtr */ Tcl_Interp *dummy, /* current interpreter */ int objc, /* number of arguments */ Tcl_Obj *const objv[]) /* argument objects */ { ItclObjectInfo *infoPtr; (void)dummy; infoPtr = (ItclObjectInfo *)clientData; if ((infoPtr->currIoPtr != NULL) && (objc > 1)) { infoPtr->currIoPtr->hullWindowNamePtr = objv[1]; Tcl_IncrRefCount(infoPtr->currIoPtr->hullWindowNamePtr); } return TCL_OK; } /* * ------------------------------------------------------------------------ * ItclCheckSetItclHull() * * * ------------------------------------------------------------------------ */ static int ItclCheckSetItclHull( void *clientData, /* infoPtr */ Tcl_Interp *interp, /* current interpreter */ int objc, /* number of arguments */ Tcl_Obj *const objv[]) /* argument objects */ { Tcl_HashEntry *hPtr; Tcl_Obj *objPtr; ItclObject *ioPtr; ItclVariable *ivPtr; ItclObjectInfo *infoPtr; const char *valueStr; if (objc < 3) { Tcl_AppendResult(interp, "ItclCheckSetItclHull wrong # args should be ", " ", NULL); return TCL_ERROR; } /* * This is an internal command, and is never called with an * objectName value other than the empty list. Check that with * an assertion so alternative handling can be removed. */ assert( strlen(Tcl_GetString(objv[1])) == 0); infoPtr = (ItclObjectInfo *)clientData; { ioPtr = infoPtr->currIoPtr; if (ioPtr == NULL) { Tcl_AppendResult(interp, "ItclCheckSetItclHull cannot find object", NULL); return TCL_ERROR; } } objPtr = Tcl_NewStringObj("itcl_hull", -1); hPtr = Tcl_FindHashEntry(&ioPtr->iclsPtr->variables, (char *)objPtr); Tcl_DecrRefCount(objPtr); if (hPtr == NULL) { Tcl_AppendResult(interp, "ItclCheckSetItclHull cannot find itcl_hull", " variable for object \"", Tcl_GetString(objv[1]), "\"", NULL); return TCL_ERROR; } ivPtr = (ItclVariable *)Tcl_GetHashValue(hPtr); valueStr = Tcl_GetString(objv[2]); if (strcmp(valueStr, "2") == 0) { ivPtr->initted = 2; } else { if (strcmp(valueStr, "0") == 0) { ivPtr->initted = 0; } else { Tcl_AppendResult(interp, "ItclCheckSetItclHull bad value \"", valueStr, "\"", NULL); return TCL_ERROR; } } return TCL_OK; } /* * ------------------------------------------------------------------------ * FreeItclObjectInfo() * * called when an interp is deleted to free up memory * * ------------------------------------------------------------------------ */ static void FreeItclObjectInfo( void *clientData) { ItclObjectInfo *infoPtr = (ItclObjectInfo *)clientData; Tcl_DeleteHashTable(&infoPtr->instances); Tcl_DeleteHashTable(&infoPtr->classTypes); Tcl_DeleteHashTable(&infoPtr->procMethods); Tcl_DeleteHashTable(&infoPtr->objectCmds); Tcl_DeleteHashTable(&infoPtr->classes); Tcl_DeleteHashTable(&infoPtr->nameClasses); Tcl_DeleteHashTable(&infoPtr->namespaceClasses); assert (infoPtr->infoVarsPtr == NULL); assert (infoPtr->infoVars4Ptr == NULL); if (infoPtr->typeDestructorArgumentPtr) { Tcl_DecrRefCount(infoPtr->typeDestructorArgumentPtr); infoPtr->typeDestructorArgumentPtr = NULL; } /* cleanup ensemble info */ if (infoPtr->ensembleInfo) { Tcl_DeleteHashTable(&infoPtr->ensembleInfo->ensembles); Tcl_DeleteHashTable(&infoPtr->ensembleInfo->subEnsembles); ItclFinishEnsemble(infoPtr); ckfree((char *)infoPtr->ensembleInfo); infoPtr->ensembleInfo = NULL; } if (infoPtr->class_meta_type) { ckfree((char *)infoPtr->class_meta_type); infoPtr->class_meta_type = NULL; } /* clean up list pool */ Itcl_FinishList(); Itcl_ReleaseData(infoPtr); }