Feather Command


Command objects

A 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 magic

Adding 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).

  1. 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.
  2. 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.
  3. 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.

Converting a Tcl object to a "cmdName" object. This is only done if the object is not already a "cmdName" object (which in the case of command objects it isn't) or if something has changed which could invalidate the cached information.

  1. 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.
  2. 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.

  1. 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.
  2. The Feather_Command structure is registered under the name of the object.
  3. A command resolver is added to the interpreter.
  4. 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.

Converting a Tcl command object to a "cmdName" object.

  1. Tcl_FindCommand calls the command resolver we have registered and passes it the string representation of the object.
  2. 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.
  3. The command resolver then calls the preservation function to ensure that the internal representation of the command object is not lost.
  4. Finally it returns a pointer to the Command structure that is embedded in the Feather_Command structure.
  5. The object is then converted to a "cmdName" object.

Invoking a command object.

  1. 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.
  2. 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.

At this point the command object is a "cmdName" and not the original type so the object needs fixing up. This has to be done by the command object's invoke function.

The invoke function is called either through the "feather::command" interface or through the above mechanism. The command object only needs fixing up in the latter case so the invoke function needs to distinguish between the two cases and it does that by looking at the clientData.

In the first case the clientData is NULL because objv [0] is still of the correct type and so the internal representation is available.

In the second case the clientData points to the internal representation of the original command object because objv [0] is now of a different type ("cmdName").

All that the invoke function needs to do is free the internal representation of objv [0], change its type and reset the internal representation. Remembering of course to take into account the effects of the preservation function.


Unfortunate side effects

There 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