pTk(3pm)

πŸ’– pTk β€” Tk2portableTk πŸ’–

okay bestie, welcome to the pTk(3pm) page β€” she's a Perl manual page and she's lowkey iconic. sit down, grab a boba πŸ§‹, and let's get into it.

✨ 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. 🫢 (fr fr, king behavior.)


🌈 DESCRIPTION 🌈

So 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. iykyk πŸ’…

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. slay ✨

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.

bestie, listen β€” Do not use this strategy to port your own code. that's the whole warning, she's serious πŸ’€

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. no cap πŸŽ€

For details on what munge can do, see "Translation of some TCL functions".

✨ PortableTk API ✨

πŸ¦‹ Checking what you are running under πŸ¦‹

PortableTk hands you a symbol called ????. If that symbol is defined, bestie, your source is getting compiled with it. That's the whole vibe check. πŸ’…


πŸŽ€ New types of configuration options πŸŽ€

PortableTk defines a whole lil squad of new configuration option types:

  • TK_CONFIG_CALLBACK
  • TK_CONFIG_LANGARG
  • TK_CONFIG_SCALARVAR
  • TK_CONFIG_HASHVAR
  • TK_CONFIG_ARRAYVAR
  • TK_CONFIG_IMAGE

You should reach for these instead of TK_CONFIG_STRING whenever it actually makes sense. Why? Because then your application gets a direct representation of the corresponding resource instead of the string-y version of it β€” if that's possible under the language you're in. Slay.

???? It looks like TK_CONFIG_IMAGE and TK_CONFIG_SCALARVAR set variables of type char*.

🌸 Language data 🌸

Here's the roster of data types, and yes they're all iconic in their own way:

  • Tcl_Obj * β€” this is the main datatype of the language. It's the type your C function receives pointers to for arguments when the matching Lang function gets called. The config type that goes with it is TK_CONFIG_LANGARG. It's also the type that keeps info about the contents of a Lang variable. main character energy fr.
  • Var β€” a stand-in for a char * that holds the name of a variable. Over in Lang, it's an object that contains a reference to another Lang variable.
  • LangResultSave β€” ????
  • LangCallback β€” LangCallback* is the substitute for a char * that holds a command to call. Its matching config type is TK_CONFIG_CALLBACK.
  • LangFreeProc β€” this is the type that Lang_SplitList sets. Before you call it, you declare:
Args *args;
LangFreeProc *freeProc = NULL;
...
code = Lang_SplitList(interp, value,
    &argc, &args, &freeProc);

And after you're done using the split values, you call:

if (args != NULL && freeProc) (*freeProc)(argc,args);
Heads up: it is not guaranteed that the args can survive deletion of value. 😭

πŸ’– Conversion πŸ’–

These are the macros and functions you use to convert back and forth between strings and the extra types:

LangCallback * LangMakeCallback(Tcl_Obj *)
Tcl_Obj * LangCallbackArg(LangCallback *)
char * LangString(Tcl_Obj *)

Once you've used the result of LangCallbackArg(), you should free it with the freeProc LANG_DYNAMIC. And listen β€” it is not guaranteed that any change of Tcl_Obj * won't be reflected in <LangCallback>, so you can't sneak a LangSet...() in between. You should also reset it to NULL if you want to do any further assignments to this Tcl_Obj *.

This next function returns the Tcl_Obj * that's a reference to Var:

Tcl_Obj * LangVarArg(Var)
???? It is very anti-intuitive, I hope the name is changed.

Then:

int LangCmpCallback(LangCallback *a,Tcl_Obj * b)

(currently only a stub, ngl), and at last:

LangCallback * LangCopyCallback(LangCallback *)

🫢 Callbacks 🫢

So earlier we met the new datatype LangCallback and its config option bestie TK_CONFIG_CALLBACK. Here are the functions you get for manipulating your LangCallbacks:

void LangFreeCallback(LangCallback *)
int LangDoCallback(Tcl_Interp *,LangCallback *,
       int result,int argc, char *format,...)

The format argument of LangDoCallback should hold a string that's suitable for sprintf, with optional arguments of LangDoCallback. And result should be false if the result of the callback isn't needed. Simple as that.

int LangMethodCall(Tcl_Interp *,Tcl_Obj *,char *method,
       int result,int argc,...)
????

Conceptually, LangCallback* is the substitute for that ubiquitous char * in TCL. So you should use LangFreeCallback instead of ckfree or free if appropriate. Periodt.


🧚 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)

Okay so those last two? Equivalent β€” unless s is an empty string. In that case LangSetDefault behaves like LangSetString with s==NULL, meaning 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 tell uninitialized data apart from initialized data by comparing data with NULL. So if you want to use your arg with all the functions above, the declaration for an Tcl_Obj * should look like:

Tcl_Obj * arg = NULL;

And once you're done, you should use LangFreeArg with TCL_DYNAMIC as freeProc. Clean up after yourself, bestie πŸ’…


⭐ 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 the 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);

And after you use that function, you should call:

LangFreeVar(Var variable);
???? Note discrepancy in types!

If you wanna find the value of a variable (of type Tcl_Obj *) given the variable name, use Tcl_GetVar(interp, varName, flags). And if you're actually interested in the string value of that 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 those Tcl_Obj *s using the LangSet... functions, and get the string value using LangString.

And if you want to merge an array of Tcl_Obj *s into one single Tcl_Obj * (which will be an array variable), use:

result = Tcl_Merge(listLength, list);

🌈 Translation of some TCL functions 🌈

We mark the items that munge can deal with as Autoconverted. Cute, right?

  • Tcl_AppendResult β€” doesn't take (char*)NULL, but NULL as the delimiter. Autoconverted.
  • Tcl_CreateCommand, Tcl_DeleteCommand β€” becomes Tk_CreateWidget, Tk_DeleteWidget, and the second argument is the window itself, not the pathname. Autoconverted.
  • sprintf(interp->result, "%d %d %d %d",...) β€” becomes Tcl_IntResults(interp,4,0,...). Autoconverted.
  • interp->result = "1"; β€” becomes Tcl_SetResult(interp,"1", TCL_STATIC). Autoconverted.
  • Reading interp->result β€” Tcl_GetResult(interp). Autoconverted.
  • interp->result = Tk_PathName(textPtr->tkwin); β€” becomes Tk_WidgetResult(interp,textPtr->tkwin). Autoconverted.
  • The sequence Tcl_PrintDouble, Tcl_PrintDouble, ..., Tcl_AppendResult β€” use a single command instead:
void Tcl_DoubleResults(Tcl_Interp *interp, int append,
       int argc,...);

append governs whether it's required to clear the result first. And there's a similar command for int arguments: Tcl_IntResults. Two-for-one, we love to see it.

  • 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.

The current implementation contains enough to make it possible to compile mTk/tkText*.[ch] with the virgin Tk. πŸ’€ iconic


πŸ’€ New types of events ???? πŸ’€

PortableTk defines the 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're starting from working TCL code, you can kick off your conversion using all the hints above. And a good sign that you're doing it right? The absence of sprintf and sscanf in your code β€” at least in the part that's working with the interpreter. No cap. ✨

πŸ’– Additional API πŸ’–

okay so bestie, plot twist: none of this stuff ships with the base portableTk distribution πŸ™ˆ it's the extra credit section. Right now it's coded in TCL and as Perl macros β€” the core is coded as functions, so theoretically you could reuse the same object files with different interpreted languages 🧚 slay, portable.


πŸŽ€ "ListFactory" πŸŽ€

So dynamic arrays in TCL get used for two completely different jobs: building strings, and building lists. In other languages those are gonna get separate interfaces (because a list is a whole different type from a string, periodt), so you should use a different interface in your code. Don't be lazy and reuse one, iykyk.

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 β€” no cap, it's a real copy πŸ’…
  • ListFactoryAppend() may append to the list a reference to the current value of arg β€” so if you're not going to change the value of arg after appending, this call may be quicker ⚑

As in TCL, the call to ListFactoryFree() does not free the ListFactory β€” only the objects it references. she's giving "i cleaned my room but the room is still a mess" energy 😭

The functions ListFactoryNewLevel() and ListFactoryEndLevel() return a pointer to a ListFactory to fill. And listen: the argument of ListFactoryEndLevel() cannot be used after a call to this function 🚫 use it before, not after, bestie.


🌸 DStrings 🌸

Production of strings are still supported in portableTk. cute, iconic, still here 🫢


✨ 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. that's the whole vibe check, a space.


πŸ’… 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. prep work, bestie.

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 nops in different languages. zero cost, no thoughts, just vibes ✨

⚠️ WARNING bestie ⚠️ 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. free first, glow later πŸ’€

πŸ¦‹ 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). clean up after yourself, main character energy πŸ‘‘


🌈 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. dealer's choice, bestie 🎲


⭐ 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. one shot, one Tcl_Obj *, no takebacks 😭

After you use this Tcl_Obj *, you should free it with freeProc LANG_DYNAMIC (you can do LangSet...() in between). yes, in between is allowed. we checked. πŸ’–


and that's the whole page, bestie!! πŸŽ€βœ¨ This one was written by perl v5.40.1 on 2025-04-13 β€” go forth, free your Tcl_Obj *s, and don't leak πŸ’…πŸ¦‹πŸŒΈ

πŸ“¦ where this page comes from