man Tk::pTk () - how to make your Tk source portable to other interpreted languages.
NAME
Tk2portableTk - how to make your Tk source portable to other interpreted languages.
Author
Ilya Zakharevich <ilya@math.ohio-state.edu> has contributed most of this document. Many thanks.
DESCRIPTION
PortableTk is an attempt to make Tk useful from other languages. Currently tk4.0 runs under Perl using this approach. Below, Lang is the notation for an external language to which PortableTk glues Tk code.
The main problem with using the code developed for TCL with different languages is the absence of data types: almost anything is CWchar*. It makes automatic translation hopeless. However, if you CWtypedef several new symbols to be CWchar*, you can still use your code in TCL, and it will make the automatic translation possible.
Another problem with the approach that everything is a string is impossibility to have a result that says NotApplicable without setting an error. Thus different Tk command return different string values that mean error happened, like CW"", CW" " or CW"??". Other languages can be more flexible, so in portableTk you should inform the compiler that what you want to return means error (see Setting variables).
Currently PortableTk uses several different approachs to simplify translation: several TCL functions that are especially dangerous to use are undefined, so you can easily find places that need to be updated to use Language-independent functions based on compiler warnings. Eventually a way to use these Language-independent functions under proper TCL will be also provided. The end of this document provides a starting point for such a project.
Structure of pTk, porting your code
pTk, that is a port of Tk, is very special with respect to porting of other code to portableTk. The problem is that currently there is very little hope to merge the modifications back into Tk, so a special strategy is needed to maintain this port. Do not use this strategy to port your own code.
pTk is produced from Tk via a two-step process: first, some manual editing (the result is in the subdirectory CWmTk), and second, automatic conversion by the CWmunge script (written in Perl). Thus the subdirectory CWpTk/mTk contains code with minimal possible difference from the virgin Tk code, so it is easier to merge(1) the differences between Tk versions into modified code.
It looks like the strategy for a portable code should be exactly opposite: starting from TCL-based code, apply CWmunge, and then hand-edit the resulting code. Probably it is also possible to target your code to portableTk from scratch, since this will make it possible to run it under a lot of Languages.
The only reason anyone would like to look into contents of CWpTk/mTk directory is to find out which constructs are not supported by CWmunge. On the other hand, CWpTk directory contains code that is conformant to portableTk, so you can look there to find example code.
CWmunge is the script that converts most common Tk constructs to their CWportableTk equivalent. For your code to qualify, you should follow Tk conventions on indentation and names of variables, in particular, the array of arguments for the CW...CmdProc should be called CWargv.
For details on what CWmunge can do, see Translation of some TCL functions.
PortableTk API
Checking what you are running under
PortableTk provides a symbol CW????. If this symbol is defined, your source is compiled with it.
New types of configuration options
PortableTk defines several new types of configuration options:
TK_CONFIG_CALLBACK TK_CONFIG_LANGARG TK_CONFIG_SCALARVAR TK_CONFIG_HASHVAR TK_CONFIG_ARRAYVAR TK_CONFIG_IMAGE
You should use them instead of TK_CONFIG_STRING whenever appropriate. This allows your application to receive a direct representation of the corresponding resource instead of the string representation, if this is possible under given language.
???? It looks like CWTK_CONFIG_IMAGE and CWTK_CONFIG_SCALARVAR set variables of type CWchar*.
Language data
The following data types are defined: is the main datatype of the language. This is a type that your C function gets pointers to for arguments when the corresponding Lang function is called. The corresponding config type is CWTK_CONFIG_LANGARG. This is also a type that keeps information about contents of Lang variable. Is a substitute for a CWchar * that contains name of variable. In Lang it is an object that contains reference to another Lang variable. ???? CWLangCallback* a substitute for a CWchar * that contains command to call. The corresponding config type is CWTK_CONFIG_CALLBACK. It is the type that the CWLang_SplitList sets. Before you call it, declare
Args *args; LangFreeProc *freeProc = NULL; ... code = Lang_SplitList(interp, value, &argc, &args, &freeProc);After you use the split values, call
if (args != NULL && freeProc) (*freeProc)(argc,args);It is not guaranteed that the CWargs can survive deletion of CWvalue.
Conversion
The following macros and functions are used for conversion between strings and the additional types:
LangCallback * LangMakeCallback(Tcl_Obj *) Tcl_Obj * LangCallbackArg(LangCallback *) char * LangString(Tcl_Obj *)
After you use the result of LangCallbackArg(), you should free it with CWfreeProc CWLANG_DYNAMIC (it is not guaranteed that any change of CWTcl_Obj * will not be reflected in <LangCallback>, so you cannot do LangSet...() in between, and you should reset it to CWNULL if you want to do any further assignments to this CWTcl_Obj *).
The following function returns the CWTcl_Obj * that is a reference to CWVar:
Tcl_Obj * LangVarArg(Var)
???? It is very anti-intuitive, I hope the name is changed.
int LangCmpCallback(LangCallback *a,Tcl_Obj * b)
(currently only a stub), and, at last,
LangCallback * LangCopyCallback(LangCallback *)
Callbacks
Above we have seen the new datatype CWLangCallback and the corresponding Config option CWTK_CONFIG_CALLBACK. The following functions are provided for manipulation of CWLangCallbacks:
void LangFreeCallback(LangCallback *) int LangDoCallback(Tcl_Interp *,LangCallback *, int result,int argc, char *format,...)
The argument CWformat of CWLangDoCallback should contain a string that is suitable for CWsprintf with optional arguments of CWLangDoCallback. CWresult should be false if result of callback is not needed.
int LangMethodCall(Tcl_Interp *,Tcl_Obj *,char *method, int result,int argc,...)
????
Conceptually, CWLangCallback* is a substitute for ubiquitous CWchar * in TCL. So you should use CWLangFreeCallback instead of CWckfree or CWfree if appropriate.
Setting variables
void LangFreeArg (Tcl_Obj *, Tcl_FreeProc *freeProc) Tcl_Obj * LangCopyArg (Tcl_Obj *); void Tcl_AppendArg (Tcl_Interp *interp, Tcl_Obj *) void LangSetString(Tcl_Obj * *, char *s) void LangSetDefault(Tcl_Obj * *, char *s)
These two are equivalent unless s is an empty string. In this case CWLangSetDefault behaves like CWLangSetString with CWs==NULL, i.e., it sets the current value of the Lang variable to be false.
void LangSetInt(Tcl_Obj * *,int) void LangSetDouble(Tcl_Obj * *,double)
The Lang functions separate uninitialized and initialized data comparing data with CWNULL. So the declaration for an CWTcl_Obj * should look like
Tcl_Obj * arg = NULL;
if you want to use this CWarg with the above functions. After you are done, you should use CWLangFreeArg with CWTCL_DYNAMIC as CWfreeProc.
Language functions
Use to check that an object is false; ???? to make a proper shutdown; to call Lang CWeval; currently stubs only; to save the structure CWarg into Lang variable CW*varPtr; to free the result; ???? unsupported????;
Another useful construction is
Tcl_Obj * variable = LangFindVar(interp, Tk_Window tkwin, char *name);
After using the above function, you should call
LangFreeVar(Var variable);
???? Note discrepancy in types!
If you want to find the value of a variable (of type CWTcl_Obj *) given the variable name, use CWTcl_GetVar(interp, varName, flags). If you are interested in the string value of this variable, use CWLangString(Tcl_GetVar(...)).
To get a C array of CWTcl_Obj * of length CWn, use
Tcl_Obj * *args = LangAllocVec(n); ... LangFreeVec(n,args);
You can set the values of the CWTcl_Obj *s using CWLangSet... functions, and get string value using CWLangString.
If you want to merge an array of CWTcl_Obj *s into one CWTcl_Obj * (that will be an array variable), use
result = Tcl_Merge(listLength, list);
Translation of some TCL functions
We mark items that can be dealt with by CWmunge by Autoconverted. does not take CW(char*)NULL, but CWNULL as delimiter. Autoconverted. CWTk_CreateWidget, CWTk_DeleteWidget, the second argument is the window itself, not the pathname. Autoconverted. CWTcl_IntResults(interp,4,0,...). Autoconverted. CWTcl_SetResult(interp,"1", TCL_STATIC). Autoconverted. CWTcl_GetResult(interp). Autoconverted. CWTk_WidgetResult(interp,textPtr->tkwin). Autoconverted. Use a single command
void Tcl_DoubleResults(Tcl_Interp *interp, int append, int argc,...);CWappend governs whether it is required to clear the result first. A similar command for CWint arguments is CWTcl_IntResults. Use CWLang_SplitList (see the description above).
Translation back to TCL
To use your portableTk program with TCL, put
#include "ptcl.h"
before inclusion of CWtk.h, and link the resulting code with CWptclGlue.c.
These files currently implement the following:
- Additional config types:
-
TK_CONFIG_CALLBACK TK_CONFIG_LANGARG TK_CONFIG_SCALARVAR TK_CONFIG_HASHVAR TK_CONFIG_ARRAYVAR TK_CONFIG_IMAGE
- Types:
-
Var, Tcl_Obj *, LangCallback, LangFreeProc.
- Functions and macros:
-
Lang_SplitList, LangString, LangSetString, LangSetDefault, LangSetInt, LangSetDouble Tcl_ArgResult, LangCallbackArg, LangSaveVar, LangFreeVar, LangFreeSplitProc, LangFreeArg, Tcl_DoubleResults, Tcl_IntResults, LangDoCallback, Tk_WidgetResult, Tcl_CreateCommand, Tcl_DeleteCommand, Tcl_GetResult.
Current implementation contains enough to make it possible to compile CWmTk/tkText*.[ch] with the virgin Tk.
New types of events ????
PortableTk defines following new types of events:
TK_EVENTTYPE_NONE TK_EVENTTYPE_STRING TK_EVENTTYPE_NUMBER TK_EVENTTYPE_WINDOW TK_EVENTTYPE_ATOM TK_EVENTTYPE_DISPLAY TK_EVENTTYPE_DATA
and a function
char * Tk_EventInfo(int letter, Tk_Window tkwin, XEvent *eventPtr, KeySym keySym, int *numPtr, int *isNum, int *type, int num_size, char *numStorage)
Checking for trouble
If you start with working TCL code, you can start convertion using the above hints. Good indication that you are doing is OK is absence of CWsprintf and CWsscanf in your code (at least in the part that is working with interpreter).
Additional API
What is described here is not included into base portableTk distribution. Currently it is coded in TCL and as Perl macros (core is coded as functions, so theoretically you can use the same object files with different interpreted languages). Dynamic arrays in TCL are used for two different purposes: to construct strings, and to construct lists. These two usages will have separate interfaces in other languages (since list is a different type from a string), so you should use a different interface in your code.
The type for construction of dynamic lists is CWListFactory. The API below is a counterpart of the API for construction of dynamic lists in TCL:
void ListFactoryInit(ListFactory *) void ListFactoryFinish(ListFactory *) void ListFactoryFree(ListFactory *) Tcl_Obj * * ListFactoryArg(ListFactory *) void ListFactoryAppend(ListFactory *, Tcl_Obj * *arg) void ListFactoryAppendCopy(ListFactory *, Tcl_Obj * *arg) ListFactory * ListFactoryNewLevel(ListFactory *) ListFactory * ListFactoryEndLevel(ListFactory *) void ListFactoryResult(Tcl_Interp *, ListFactory *)
The difference is that a call to CWListFactoryFinish should precede the actual usage of the value of CWListFactory, and there are two different ways to append an CWTcl_Obj * to a CWListFactory: ListFactoryAppendCopy() guarantees that the value of CWarg is copied to the list, but ListFactoryAppend() may append to the list a reference to the current value of CWarg. If you are not going to change the value of CWarg after appending, the call to ListFactoryAppend may be quicker.
As in TCL, the call to ListFactoryFree() does not free the CWListFactory, only the objects it references.
The functions ListFactoryNewLevel() and ListFactoryEndLevel() return a pointer to a CWListFactory to fill. The argument of ListFactoryEndLevel() cannot be used after a call to this function.
DStrings
Production of strings are still supported in portableTk. The following functions for getting a value of an CWTcl_Obj * may be provided:
double LangDouble(Tcl_Obj *) int LangInt(Tcl_Obj *) long LangLong(Tcl_Obj *) int LangIsList(Tcl_Obj * arg)
The function LangIsList() is supported only partially under TCL, since there is no data types. It checks whether there is a space inside the string CWarg. While LangSetDouble() and LangSetInt() are supported ways to assign numbers to assign an integer value to a variable, for the sake of efficiency under TCL it is supposed that the destination of these commands was massaged before the call so it contains a long enough string to sprintf() the numbers inside it. If you are going to immediately use the resulting CWTcl_Obj *, the best way to do this is to declare a buffer in the beginning of a block by
dArgBuffer;
and assign this buffer to the CWTcl_Obj * by
void LangSetDefaultBuffer(Tcl_Obj * *)
You can also create the buffer(s) manually and assign them using
void LangSetBuffer(Tcl_Obj * *, char *)
This is the only choice if you need to assign numeric values to several CWTcl_Obj *s simultaneously. The advantage of the first approach is that the above declarations can be made CWnops in different languages.
Note that if you apply CWLangSetDefaultBuffer to an CWTcl_Obj * that contains some value, you can create a leak if you do not free that CWTcl_Obj * first. This is a non-problem in real languages, but can be a trouble in CWTCL, unless you use only the above API. The API for creating a new CWTcl_Obj * is
void LangNewArg(Tcl_Obj * *, LangFreeProc *)
The API for creating a new CWTcl_Obj * is absent. Just initialize CWTcl_Obj * to be CWNULL, and apply one of CWLangSet... methods.
After you use this CWTcl_Obj *, it should be freed thusly:
CWLangFreeArg(arg, freeProc).
Evaluating a list
Use
int LangArgEval(Tcl_Interp *, Tcl_Obj * arg)
Here CWarg should be a list to evaluate, in particular, the first element should be a CWLangCallback massaged to be an CWTcl_Obj *. The arguments can be send to the subroutine by reference or by value in different languages. Use CWTcl_ArgResult. It is not guaranteed that result survives this operation, so the CWTcl_Obj * you get should be the only mean to access the data from this moment on. After you use this CWTcl_Obj *, you should free it with CWfreeProc CWLANG_DYNAMIC (you can do LangSet...() in between).