itcl internals

apw 2008-10-04: This page is intended for collecting some of the internals of itcl for being able to discuss some wanted enhancements in Tcl core for use by itcl.

apw 2010-02-14: Updated some definitions of the wanted interfaces.

At the moment I am just dumping my ideas.

Classes in itcl are always handled as namespaces, that means every class in itcl corresponds to a namespace with the same name.

Example for classes used later on:

 ::itcl::class ::ns1::cl1 {
     # class commons
     public common c1pub c1pub
     protected common c1pro c1pro
     private common c1pri c1pri

     # class variables
     public variable v1pub v1pub
     protected variable v1pro v1pro
     private variable v1pri v1pri

     # class procs
     public proc p1pub {args} { puts stderr "p1pub" }
     protected proc p1pro {args} { puts stderr "p1pro" }
     private proc p1pri {args} { puts stderr "p1pri" }

     # class methods
     public method m1pub {args} { puts stderr "m1pub" }
     protected method m1pro {args} { puts stderr "m1pro" }
     private method m1pri {args} { puts stderr "m1pri" }
 }

 ::itcl::class cl2 {
    inherit ::ns1::cl1

     # class commons
     public common c2pub c2pub
     protected common c2pro c2pro
     private common c2pri c2pri

     # class variables
     public variable v2pub v2pub
     protected variable v2pro v2pro
     private variable v2pri v2pri

     # class procs
     public proc p2pub {args} { puts stderr "p2pub" }
     protected proc p2pro {args} { puts stderr "p2pro" }
     private proc p2pri {args} { puts stderr "p2pri" }

     # class methods
     public method m2pub {args} { puts stderr "m2pub" }
     protected method m2pro {args} { puts stderr "m2pro" }
     private method m2pri {args} { puts stderr "m2pri" }

 }

 ::itcl::class cl3 {
    inherit cl2

     # class commons
     public common c3pub c3pub
     protected common c3pro c3pro
     private common c3pri c3pri

     # class variables
     public variable v3pub v3pub
     protected variable v3pro v3pro
     private variable v3pri v3pri

     # class procs
     public proc p3pub {args} { puts stderr "p3pub" }
     protected proc p3pro {args} { puts stderr "p3pro" }
     private proc p3pri {args} { puts stderr "p3pri" }

     # class methods
     public method m3pub {args} { puts stderr "m3pub" }
     protected method m3pro {args} { puts stderr "m3pro" }
     private method m3pri {args} { puts stderr "m3pri" }

 }

 cl1 obja
 cl2 objb
 cl3 objc

The example code above will create namespaces (and classes) ::cl1, ::cl2 and ::cl3

The inherit command determines how the class hierarchy is built. In the above case cl3 inherits cl2 (has a "superclass" cl2) and cl2 inherits cl1.

So the hierarchy from base class to most specific class is: cl1 -> cl2 -> cl3.

Or to say it differently class cl2 is derived from class cl1 and class cl3 is derived from class cl2.

This is mostly equivalent with setting namespace path for class (namespace) cl3 to: [list cl3 cl2 cl1].

We have also created 3 itcl objects obja, objb, objc with the last 3 leines in the example code.

Methods in itcl classes are similar to procs with the difference, that they only can be called from an object created for a class and they have a protection level (see below).

It is also possible to have procs in a class, these procs can be called like procs in a namespace depending on the protection level.

Variables in itcl classes are similar to namespace variables with the difference, that they only can be accessed from an object created for a class when calling a method and they have a protection level (see below).

Commons in itcl classes are similar to namespace variables and they can be accessed like namespace variables depending on the protection level. So the lookup of methods and variables is done starting in namespace cl3, then cl2 and then cl1.

As every method in itcl is called in the namespace of its class lookup for variables and methods for a method in class cl2 starts in cl2 and continues in cl1.

Calling a method in cl3 starts lookup in cl3, then cl2 and then cl1.

Protection levels:

Itcl has 3 protection levels:

  • public
  • protected
  • private.

public protection level allows to access a variable from every method or proc tied to an itcl object and from the outside using the special methods configure and cget of an itcl object.

protected protection level allows to access a variable from every method or proc tied to an itcl object in the class where it is defined and in all derived classes.

private protection level allows to access a variable from every method or proc tied to an itcl object only in the class where it is defined.

The same definition as for variables holds for commons with the difference, that instead of an object the class(namespace) must be used.

Lookup rules for itcl:

In general for looking up methods/procs and variables/commons so called "virtual tables" are used. Virtual tables exist on a class base for methods/procs and variables/commons.

In the following command means an itcl class method or proc and variable means an itcl class variable or common. Member is the command or variable name.

The current rule is: These tables store every possible name for each command/variable (member, class::member, namesp::class::member, etc.). Members in a derived class may shadow members with the same name in a base class. In that case, the simple name in the resolution table will point to the most-specific member.

Example for the class cl2 methods/procs entries in a virtual table:

  • p1pub
  • cl1::p1pub
  • ns1::cl1::p1pub
  • ::ns1::cl1::p1pub
  • p1pro
  • cl1::p1pro
  • ns1::cl1::p1pro
  • ::ns1::cl1::p1pro
  • p1pri
  • cl1::p1pri
  • ns1::cl1::p1pri
  • ::ns1::cl1::p1pri
  • m1pub
  • cl1::m1pub
  • ns1::cl1::m1pub
  • ::ns1::cl1::m1pub
  • m1pro
  • cl1::m1pro
  • ns1::cl1::m1pro
  • ::ns1::cl1::m1pro
  • m1pri
  • cl1::m1pri
  • ns1::cl1::m1pri
  • ::ns1::cl1::m1pri
  • p2pub
  • cl2::p2pub
  • ::cl2::p2pub
  • p2pro
  • cl2::p2pro
  • ::cl2::p2pro
  • p2pri
  • cl2::p2pri
  • ::cl2::p2pri
  • m2pub
  • cl2::m2pub
  • ::cl2::m2pub
  • m2pro
  • cl2::m2pro
  • ::cl2::m2pro
  • m2pri
  • cl2::m2pri
  • ::cl2::m2pri

Example for the class cl2 variables/commons entries in a virtual table:

  • c1pub
  • cl1::c1pub
  • ns1::cl1::c1pub
  • ::ns1::cl1::c1pub
  • c1pro
  • cl1::c1pro
  • ns1::cl1::c1pro
  • ::ns1::cl1::c1pro
  • c1pri
  • cl1::c1pri
  • ns1::cl1::c1pri
  • ::ns1::cl1::c1pri
  • v1pub
  • cl1::v1pub
  • ns1::cl1::v1pub
  • ::ns1::cl1::v1pub
  • v1pro
  • cl1::v1pro
  • ns1::cl1::v1pro
  • ::ns1::cl1::v1pro
  • v1pri
  • cl1::v1pri
  • ns1::cl1::v1pri
  • ::ns1::cl1::v1pri
  • c2pub
  • cl2::c2pub
  • ::cl2::c2pub
  • c2pro
  • cl2::c2pro
  • ::cl2::c2pro
  • c2pri
  • cl2::c2pri
  • ::cl2::c2pri
  • v2pub
  • cl2::v2pub
  • ::cl2::v2pub
  • v2pro
  • cl2::v2pro
  • ::cl2::v2pro
  • v2pri
  • cl2::v2pri
  • ::cl2::v2pri

Wanted lookup for variables (variables/commons):

The idea is to have an additional hash table (or whatever) for variables, which can be filled with a C-function like Tcl_RegisterItclVariable. There follows an example interface for such a functionality.

typedef int (Tcl_CheckItclVariableProtection)(Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *varName, ClientData clientData, ClientData objectPtr);

typedef void (*Tcl_ItclVariableDeleteProc)(ClientData clientData);

Tcl_Var Tcl_RegisterItclVariable(TclInterp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *varName, ClientData clientData, ClientData objectPtr, Tcl_Var varPtr, Tcl_CheckItclVariableProtection *fcnPtr);

int Tcl_CheckItclVariableProtection(Tcl_interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *varName, ClientData clientData, ClientData objectPtr)

void Tcl_UnregisterItclVariable(Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *varName, ClientData clientData, Tcl_ItclVariableDeleteProc delProc);

   * nsPtr is the class namespace to add the variable to or where the variable is declared
   * varName is the name of the variable like cl1::v1pri or v1pri
   * clientData some data to be used in Tcl_CheckItclVariableProtection
   * varPtr == NULL for itcl variables, so a new variable should be created. For itcl commons varPtr != NULL, in that case the varPtr should be used instead of creating a variable.
   * objectPtr the itcl object for which the class variables are registered


Wanted lookup for commands (methods/procs):

maybe similar to the handling of class variables. Prototype implementation only exists for variables right now.

typedef int (Tcl_CheckItclCommandProtection)(Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *cmdName, ClientData clientData, ClientData objectPtr);

typedef void (*Tcl_ItclCommandDeleteProc)(ClientData clientData);

Tcl_Command Tcl_RegisterItclCommand(TclInterp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *cmdName, Tcl_ObjCmdProc cmdPtr, ClientData clientData, ClientData objectPtr, Tcl_CheckItclCommandProtection *fcnPtr);

int Tcl_CheckItclCommandProtection(Tcl_interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *commandName, ClientData clientData, ClientData objectPtr)

void Tcl_UnregisterItclCommand(Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *cmdName, ClientData clientData, Tcl_ItclCommandDeleteProc delProc);

int Tcl_SetCallFrameObject(Tcl_Interp *interp, ClientData objectPtr);

The above interfaces are only an example how the interfaces could look like and also include some for commands, because they could be analogous.

Tcl_RegisterItclVariable makes the variables which are reachable in an Itcl class known to variable lookup per namespace (a namespace is an itcl class) and eventually creates physically variables for the Itcl class variables on a per (itcl) object base. The Tcl_HashTable or whatever is used for it has to be CallFrame dependent (calling a method must also provide that information in the CallFrame structure/environment.

Also a special CallFrame flag should be set when calling an itcl method (as side effect of using Tcl_SetCallFrameObject or by having CallFrame object != NULL), to make use of the mechanism above on a per CallFrame basis.

If the CallFrame flag is set, then this variable hash table, or however it is implemented, should be searched for the variable info first and if a reference is found, then the object variable table should be looked up with the reference information for the corresponding Tcl_Var. If it is set then a callback (like Tcl_CheckItclVariableProtection) should be called. The object variables should not be visible directly in a namespace! The check call allows the caller to do a check for protection/type specific stuff. The callback either returns TCL_OK or TCL_ERROR (in that case with possibly leaving an appropriate error message in the interpreter). If the variable is not found here or the special flag in the call frame is not set, then the normal variable lookup should be done.

That would allow it to make a fast variable lookup for itcl and leave all the details to Tcl core and would additionally allow this:

  • No need for variable/command namespace/interp resolvers for itcl any longer.

I have done a prototype implementation for variables (still using namespace resolvers, but in there using the described interfaces from above) for itcl-ng which is passing the tests as before.

Details about my prototype Implementation

I have used a straight forward approach, which was easy to implement.

All lookup info is at the moment in a structure available with Tcl_SetAssocData/Tcl_GetAssocData

Tcl_RegisterItclVariable fills a Tcl_HashTable with a struct containig the namespace as a pointer and the the objectPtr as the key (not necessary when implemented in Tcl core, there the info should be tied to the Namespace structure via an additional Tcl_HashTable). The value is a Tcl_HashTable for the variable infos. In that hash table there are entries with a struct containing varName and the objectPtr as a key and varPtr as the value.

If varPtr == NULL then a new variable is created and used for the varPtr. The variable is created at the moment in my implementation in a special per object namespace but better should be stored in a per object TclVarHashTable.

Lookup in the namespace resolvers fetches the lookup data structure, looks with namespace and varName for clientData, and if found calls Tcl_CheckItclVariableProtection and if TCL_OK is returned, it returns varPtr found via the way the variable is registered.

Similar the handling of commands should be done depending on the CallFrame flag. If the command is found in the additional hash table Tcl_CheckItclCommandProtection should be called and if that call returns TCL_OK the cmdPtr C function should be called with objc and objv set appropriately, and additionally the clientData from registering.