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 "char*".
It makes automatic translation hopeless. However, if you "typedef"
several new symbols to be "char*", 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 "", " "
or "??". 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 "mTk"), and second,
automatic conversion by the "munge" script (written in Perl). Thus
the subdirectory "pTk/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 "munge", 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 "pTk/mTk"
directory is to find out which constructs are not supported by
"munge". On the other hand, "pTk" directory contains code
that is conformant to
portableTk, so you can look there to find example
code.
"munge" is the script that converts most common
Tk constructs
to their "portableTk" 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 "...CmdProc" should be
called "argv".
For details on what "munge" can do, see "Translation of some TCL
functions".
PortableTk API¶
Checking what you are running under¶
PortableTk provides a symbol "????". 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 "TK_CONFIG_IMAGE" and
"TK_CONFIG_SCALARVAR" set variables of type "char*".
Language data¶
The following data types are defined:
- "Tcl_Obj *"
- 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
"TK_CONFIG_LANGARG".
This is also a type that keeps information about contents of Lang
variable.
- "Var"
- Is a substitute for a "char *" that contains name
of variable. In Lang it is an object that contains reference to
another Lang variable.
- "LangResultSave"
- ????
- "LangCallback"
- "LangCallback*" a substitute for a "char
*" that contains command to call. The corresponding config type is
"TK_CONFIG_CALLBACK".
- "LangFreeProc"
- It is the type that the "Lang_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 "args" can survive deletion of
"value".
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
"freeProc" "LANG_DYNAMIC" (it is not guaranteed that any
change of "Tcl_Obj *" will not be reflected in <LangCallback>,
so you cannot do LangSet...() in between, and you should reset it to
"NULL" if you want to do any further assignments to this
"Tcl_Obj *").
The following function returns the "Tcl_Obj *" that is a reference to
"Var":
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 "LangCallback" and the
corresponding
Config option "TK_CONFIG_CALLBACK". The
following functions are provided for manipulation of
"LangCallback"s:
void LangFreeCallback(LangCallback *)
int LangDoCallback(Tcl_Interp *,LangCallback *,
int result,int argc, char *format,...)
The argument "format" of "LangDoCallback" should contain a
string that is suitable for "sprintf" with optional arguments of
"LangDoCallback". "result" should be false if result of
callback is not needed.
int LangMethodCall(Tcl_Interp *,Tcl_Obj *,char *method,
int result,int argc,...)
????
Conceptually, "LangCallback*" is a substitute for ubiquitous
"char *" in
TCL. So you should use
"LangFreeCallback" instead of "ckfree" or "free"
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
"LangSetDefault" behaves like "LangSetString" with
"s==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 "NULL". So the declaration for an "Tcl_Obj *"
should look like
Tcl_Obj * arg = NULL;
if you want to use this "arg" with the above functions. After you are
done, you should use "LangFreeArg" with "TCL_DYNAMIC" as
"freeProc".
Language functions¶
Use
- "int LangNull(Tcl_Obj *)"
- to check that an object is false;
- "int LangStringMatch(char *string, Tcl_Obj *
match)"
- ????
- "void LangExit(int)"
- to make a proper shutdown;
- "int LangEval(Tcl_Interp *interp, char *cmd, int
global)"
- to call Lang "eval";
- "void Lang_SetErrorCode(Tcl_Interp *interp,char
*code)"
- "char *Lang_GetErrorCode(Tcl_Interp
*interp)"
- "char *Lang_GetErrorInfo(Tcl_Interp
*interp)"
- "void LangCloseHandler(Tcl_Interp *interp,Tcl_Obj *
arg,FILE *f,Lang_FileCloseProc *proc)"
- currently stubs only;
- "int LangSaveVar(Tcl_Interp *,Tcl_Obj * arg,Var
*varPtr,int type)"
- to save the structure "arg" into Lang
variable *varPtr;
- "void LangFreeVar(Var var)"
- to free the result;
- "int LangEventCallback(Tcl_Interp *,LangCallback
*,XEvent *,KeySym)"
- ????
- "int LangEventHook(int flags)"
- "void LangBadFile(int fd)"
- "int LangCmpConfig(char *spec, char *arg, size_t
length)"
- unsupported????;
- "void Tcl_AppendArg (Tcl_Interp *interp, Tcl_Obj
*)"
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 "Tcl_Obj *")
given the variable name, use "Tcl_GetVar(interp, varName, flags)".
If you are interested in the string value of this variable, use
"LangString(Tcl_GetVar(...))".
To get a
C array of "Tcl_Obj *" of length "n", use
Tcl_Obj * *args = LangAllocVec(n);
...
LangFreeVec(n,args);
You can set the values of the "Tcl_Obj *"s using
"LangSet..." functions, and get string value using
"LangString".
If you want to merge an array of "Tcl_Obj *"s into one "Tcl_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 "munge" by
Autoconverted.
- "Tcl_AppendResult"
- does not take "(char*)NULL", but "NULL"
as delimiter. Autoconverted.
- "Tcl_CreateCommand",
"Tcl_DeleteCommand"
- "Tk_CreateWidget", "Tk_DeleteWidget",
the second argument is the window itself, not the pathname.
Autoconverted.
- "sprintf(interp->result, "%d %d %d
%d",...)"
- "Tcl_IntResults(interp,4,0,...)".
Autoconverted.
- "interp->result = "1";"
- "Tcl_SetResult(interp,"1",
TCL_STATIC)". Autoconverted.
- Reading "interp->result"
- "Tcl_GetResult(interp)".
Autoconverted.
- "interp->result =
Tk_PathName(textPtr->tkwin);"
- "Tk_WidgetResult(interp,textPtr->tkwin)".
Autoconverted.
- Sequence "Tcl_PrintDouble, Tcl_PrintDouble, ...,
Tcl_AppendResult"
- Use a single command
void Tcl_DoubleResults(Tcl_Interp *interp, int append,
int argc,...);
"append" governs whether it is required to clear the result first.
A similar command for "int" arguments is
"Tcl_IntResults".
- "Tcl_SplitList"
- Use "Lang_SplitList" (see the description
above).
Translation back to TCL¶
To use your
portableTk program with
TCL, put
#include "ptcl.h"
before inclusion of "tk.h", and link the resulting code with
"ptclGlue.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
"mTk/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
"sprintf" and "sscanf" 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).
"ListFactory"¶
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 "ListFactory". 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 "ListFactoryFinish" should precede
the actual usage of the value of "ListFactory", and there are two
different ways to append an "Tcl_Obj *" to a
"ListFactory":
ListFactoryAppendCopy() guarantees that the
value of "arg" is copied to the list, but
ListFactoryAppend()
may append to the list a reference to the current value of "arg". If
you are not going to change the value of "arg" after appending, the
call to ListFactoryAppend may be quicker.
As in
TCL, the call to
ListFactoryFree() does not free the
"ListFactory", only the objects it references.
The functions
ListFactoryNewLevel() and
ListFactoryEndLevel()
return a pointer to a "ListFactory" to fill. The argument of
ListFactoryEndLevel() cannot be used after a call to this function.
DStrings¶
Production of strings are still supported in
portableTk.
Accessing "Tcl_Obj *"s¶
The following functions for getting a value of an "Tcl_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 "arg".
Assigning numbers to "Tcl_Obj *"s¶
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 "Tcl_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 "Tcl_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
"Tcl_Obj *"s simultaneously. The advantage of the first approach is
that the above declarations can be made "nop"s in different
languages.
Note that if you apply "LangSetDefaultBuffer" to an "Tcl_Obj
*" that contains some value, you can create a leak if you do not free
that "Tcl_Obj *" first. This is a non-problem in real languages, but
can be a trouble in "TCL", unless you use only the above API.
Creating new "Tcl_Obj *"s¶
The API for creating a new "Tcl_Obj *" is
void LangNewArg(Tcl_Obj * *, LangFreeProc *)
The API for creating a new "Tcl_Obj *" is absent. Just initialize
"Tcl_Obj *" to be "NULL", and apply one of
"LangSet..." methods.
After you use this "Tcl_Obj *", it should be freed thusly:
"LangFreeArg(arg, freeProc)".
Evaluating a list¶
Use
int LangArgEval(Tcl_Interp *, Tcl_Obj * arg)
Here "arg" should be a list to evaluate, in particular, the first
element should be a "LangCallback" massaged to be an "Tcl_Obj
*". The arguments can be send to the subroutine by reference or by value
in different languages.
Getting result as "Tcl_Obj *"¶
Use "Tcl_ArgResult". It is not guaranteed that result survives this
operation, so the "Tcl_Obj *" you get should be the only mean to
access the data from this moment on. After you use this "Tcl_Obj *",
you should free it with "freeProc" "LANG_DYNAMIC" (you can
do LangSet...() in between).