C/Tcl Interface - Defining Methods in Tcl

Every object class autimatically has a predefined method named "method", which can be used to declare Tcl procedures to be methods of the object class. (This can also be done from C). Once a Tcl method is defined it can be used just as any other predefined method. This page demonstrates how you can write your own Tcl method.


Why Tcl-Defined Methods

Imagine you want to have a method that counts the phonemes in a Phones object. The procedure definition could look like this:
  proc count { pho } {
    return [llength [$pho]]
  }
This procedure expects as its first (and only) argument the name of a Phones object. What you can do is:
  %
  % Phones p
  p
  % p add a b c
  % proc count { pho } { return [llength [$pho]] }
  % count p
  3
  % 
What you would like to be able to do is:
  % p count
  3
  %
Why not just use count p? Because p count is consistent with the object oriented programming paradigm that is used in the rest of the Janus system, and because you can override C-defined methods with Tcl-scripts, and because JANUS will treat your Tcl-procedure like the predefined methods including help-text display, and because you might want to be able to change the behaviour of some method without having to change the scripts that use it, and because you can use the itfParseArgv procedure to parse your command line arguments in just the same way as it is done in C to make the usage of all methods appear the same, and because you don't want to care about how to enter your command, which is much easier if it is always "object first - method second".


Registering a Tcl-Procedure

For the above example you would do the following:
  % 
  % Phones method count count -text "count the phones of a Phones object"
   count "count the phones of a Phones object" 
  % Phones

  DESCRIPTION

  A 'Phones' object is an array of strings, each of which is a phoneme.

  METHODS

  puts               displays the contents of a phone-set
  add                add new phone(s) to a phone-set
  delete             delete phone(s) from a phone-set
  read               read a phone-set from a file
  write              write a phone-set into a file
  index              return index of named phone(s)
  name               return the name of indexed phone(s)

  TCL-DEFINED METHODS

  count              count the phones of a Phones object (count)
  %
You can see that JANUS has registered you method named count, and lists it together with the other methods. The registration has the following syntax:
  <objectClass> method <methodName> <procName> [-text <helpText>]
In our example we've used the same name for the method as for the Tcl-procedure that implements it. This is not necessary. Imagine you'd like that the count method of the Phones object class return its result in hex-code instead of decimal. You could redifine the method like this:
  %
  % proc countHex { pho } { return [format "%x" [llength [$pho]]] }
  % Phones method count countHex -text "hexcount the phones of a Phones object"
   countHex "hexcount the phones of a Phones object" 
  % p add q w e r t y u i o p 
  % p count
  d
  % 
The -text argument is optional, but you are strongly encouraged to use it. You can write an extra procedure for each object (actually for each 'object-hierarchy-path'), by appending an abgle-bracketed object name to the procedure's name:
  %
  % [Phones q] add q1 q2
  % [Phones r] add r1 r2 r3 r4
  % proc count<p> { pho } { return "set p has [llength [$pho]] phones" }
  % proc count<q> { pho } { return "set q has [llength [$pho]] phones" }
  % Phones method count count -text "count phones of a Phones object"
   count "count the phones of a Phones object" 
  % q count
  set q has 2 phones
  % p count
  set p has 13 phones
  % r count
  4
  % 
You can see that, for the objects p and q there are special procedures available, so they are used instead of the general one. For the object r there is no such special procedure, so the general one is used. This is especially usefull when you want a method to work only for one special object, and not for any other. E.g. you could define a feature set evaluation procedure which is specific for some feature set fs1, and another one for a different feature set fs2. You could use one procedure eval that does both and has some kind of if-statement at its beginning to decide what to do depending on the object with which it is called, or you could write two procedures, one called eval<fs1>, and the other one eval<fs2>. Please note once again, that the angle brackets are meant to be there. They are no metacharacters and they do not mean to mark non-terminal symbols.

The above example only illustrates the way, how a Tcl method is registered. The saple procedure, however, is a bad example. If you write your own methods, please use the itfParseArgv procedure for parsing the command line arguments. Please do so, even if your method doesn't need arguments, to allow the user to type -help and get some info.


Unregistering a Tcl-Procedure

If you want to remove the Tcl-defined method you use the string "destroy" instead of a Tcl-procedure name:
  %
  % Phones method count destroy
  % p count
  WARNING : itf.c(0541)        No method 'count' is defined for type Phones.
  1
  % 


Redifining Predefined Methods

If you define a Tcl method which uses the same name as a predefined one, you will get a warning message, but you can do it. In fact, you might badly want to do it. For example, you could get a dictionary which uses some non-standard format. Instead of messing around with your training scripts, you can simply write a new method named read for the object type Dictionary which does the job. The training scripts can remain unchanged, for any kind of dictionary format. Once a predefine method is redefined, it is still accessible. In the above example you could still use the original read method by typing the name _read. Overruled C-defined methods can be accessed with and underscore preceding their name. (The warning message will notify you about that, too).


Registering a Tcl-Procedure from C

There is a function in itf.c which is the actual implementation of the method-method.
extern int   itfTclMethod (TypeInfo* ti, int argc, char **argv);
It can be called from other modules. ti is a pointer to the TypeInfo structure of the object class, the argv argument contains the same strings that are needed when registering a method from Tcl. You will probably prefer to not call the C function but istead let Tcl do the job via Tcl_Eval().


How Is It Done?

Every object class can have its global Tcl-array whose name is the name of the class, extended by "Methods". I.e. PhonesMethods, FeatureSetMethods, etc. The keys of the arrays are the names of the methods, i.e. for example PhonesMethods(count). Every element of the array is a list whose first element is the name of the procedure that implements the method, and the second element is the help text.