Command objectsA command object is a Tcl object which can be invoked as a command and therefore has a "feather::command" interface. The closest example to this from a Tcl users point of view is a widget or an interp although these differ from command objects in that the widget or interp name is really just a handle which has to be explicitly freed when finished with.Feather has the following command objects.
- curried objects
- Feather CurryObj
- lambda objects
- Feather LambdaObj
- command objects
- Feather CommandObj
Black magicAdding support for command objects to Tcl involved a detailed analysis of how Tcl resolves commands and Tcl objects in general. Here is a description of how this process works.Executing a command line (which is basically an array of Tcl objects).
- Extract a pointer to a Command structure from objv [0] by first converting it to a "cmdName" type and then extracting the Command pointer from the internal representation.
- If the above step failed to produce a valid Command pointer then the unknown command is invoked which normally tries to auto load it or may even try and execute it. This step should not occur when invoking command objects as they cannot be auto loaded.
- Call the objProc member of the Command structure passing the objClientData member as the clientData and the rest of the Tcl objects that make up the command line as the arguments.
- The majority of the work is done by Tcl_FindCommand which calls the namespace resolver if present, then any interp resolvers and then if all else fails follows the standard Tcl resolution procedures.
- If Tcl_FindCommand returns a valid Tcl_Command / Command pointer it is stored in the internal representation of the "cmdName" object, otherwise the "cmdName" object has a NULL internal representation.
The main problem with implementing command objects is the fact that the internal representation of the object is freed when it is converted to a "cmdName" type.This is how I solved this problem.
- Every command object has an associated Feather_Command structure (which is an embedded Command structure plus a preservation function pointer). The objClientData member of the embedded Command structure is a pointer to the command object's internal representation and the objProc member is a pointer to the command object's invoke function.
- The Feather_Command structure is registered under the name of the object.
- A command resolver is added to the interpreter.
- The preservation function has to preserve the internal representation of the object even when the object has been converted to a "cmdName" object. This is normally done by incrementing a reference count.
- Tcl_FindCommand calls the command resolver we have registered and passes it the string representation of the object.
- The command resolver looks up the name in its table of registered command objects, if it doesn't find it it immediately returns and Tcl_FindCommand continues looking.
- The command resolver then calls the preservation function to ensure that the internal representation of the command object is not lost.
- Finally it returns a pointer to the Command structure that is embedded in the Feather_Command structure.
- The object is then converted to a "cmdName" object.
- Extract a pointer to a Command structure from objv [0] by first converting it to a "cmdName" type and then extracting the Command pointer from the internal representation.
- Call the objProc member of the Command structure passing the objClientData member as the clientData and the rest of the Tcl objects that make up the command line as the arguments.
Unfortunate side effectsThere is obviously a slight performance impact due to the extra command resolver function, however this is slight and would be removed if Tcl ever added this support natively.It is also possible to do the following because the interp resolver uses the name to look up the command. This usage is totally and utterly unsupported and I will prevent it if I can.
% set a [lambda {a} {puts $a}] <feather::lambda 0x20102028> % "<feather::lambda 0x20102028>" 95 95 % eval [list $a] 95 95