README_ltcl

Simple binding of the Tcl interpreter to Lua. Use by requiring “ltcl”.

Author: Gunnar Zötl gz@tset.de, 2010, 2011.
Released under MIT/X11 license. See file LICENSE for details.</gz@tset.de>

Constructor

Tcl extensions

ltcl registers a function ‘lua’ with Tcl that allows Tcl to call Lua functions by name. It is called from Tcl like this:

lua funcname [args]

Methods

ltcl methods are called using method syntax on the Tcl interpreter object returned by ltcl.new().

Conversion of values

Lua -> Tcl

The Lua types string and boolean are converted to equivalent Tcl types. Note however, that strings in Tcl are always Utf8 encoded!

The Lua number type is converted to the Tcl int type, if it is integral and fits into an int, and to double otherwise.

The Lua type table is converted to a Tcl list. Thus only the array part of a table is converted, from 1 to the last element of the table as returned by the # operator.

The Lua nil is converted to an empty string. There is no nil in Tcl (at least I have not yet found one).

Everything else creates an error.

Tcl -> Lua

The Tcl type boolean is converted to a Lua number. I initially had it converted to Lua boolean values, but between Tcl 8.4 and 8.5, something changed that caused booleans to be returned as integers from Tcl to Lua. So in order to minimise pains when developing for both Lua+Tcl8.4 and Lua+Tcl8.5, or migrating from the one to the other, I decided to always convert boolean values to numbers. Note that this means that when you receive a boolean value from Tcl, you will have to explicitly compare it to 0 or 1.

The Tcl types int and double are converted to a Lua number.

The Tcl types bytearray and string are converted to Lua strings. Note that Tcl strings are always Utf8 encoded!

The Tcl type list is converted to a Lua table, with the elements being stored at consecutive indices within the array part of the table.

Everything else, including unknown or unspecified types, is converted to a Lua string.

Note also that there is no special support for converting Tcl’s arrays. You must handle them using the setarray and getarray methods.

Note that the type for values in Tcl is often not known and thus values might be converted to string that you expect to be something else. This has to do with how and especially when Tcl determines the type of a value. Normally this should not pose significant problems as in an arithmetic context Lua treats a string containing a number just like a number, but for comparisons this does make a difference.

Beware

A conversion Lua -> Tcl -> Lua might not always yield the same result. The textual representation value should be the same, but the type may have changed. An example:

i = ltcl.new()
i:setvar("x", 1) -> type is number
v=i:getvar("x") -> type is number
v=i:eval("expr $x + 1") -> type is number
v=i:getvar("x") -> type is number
v=i:eval('expr $x') -> type is number
v=i:getvar("x") -> type is now string!

However, this does not always happen. This stems from how Tcl determines the types of values at runtime. Whenever the Tcl type is unknown, on return to Lua it will be converted to a string. So, if you expect a number from Tcl, it would be prudent to call tonumber() on what you received.

Getting and setting values

You access variables in the Tcl interpreter by the get* and set* methods. get and set work straightforward, you can read the value of a varible (get) or set it to a new value (set). The getarray and setarray methods deal with array values, the first parameter must refer to an array on the Tcl side. The second parameter is an index within this array.

For all these methods, there is an optional last parameter for flags.

Valid flags

Tracing Tcl variable access

ltcl provides a means to trace Tcl variable accesses. This is done with the method tcl:tracevar(name, idx, flags, func). name is the Name of the variable to trace. idx is an index into an array referenced by name. If idx is not nil, only accesses to that particular index within the array referred to va the variable name will be traced. Obviously, for this to work name must refer to a Tcl array if idx is not nil, otherwise if idx is nil, name can refer to any Tcl variable. flags are used to tell ltcl which accesses should be traced, see below for a list of valid flags. func is a Lua function that is called upon variable access. You can register multiple traces per variable. These will then be called in reverse order of registering, so the last registered trace function will be called first.

Valid flags for tracevar()

The trace function

The trace function is a Lua function with the signature

func(name, idx, flags) -> errmsg or nil This function is called whenever the variable is accessed in a way that is specified in the flags argument to the tracevar method. name is the name of the accessed variable, idx is an index into name if name is an array, or nil. flags specify how the variable was actually accessed. You can find out the exact method of access using the tcl:checkflags() method. flags can be any one of ltcl.TRACE_READ or ltcl.TRACE_WRITE, plus ltcl.TRACE ARRAY, if the access was made through the Tcl array function, and ltcl.GLOBAL_ONLY or ltcl.NAMESPACE_ONLY. If within your trace function you want to access the traced Tcl variable, you must pass these last two flags to that access.

Traces using ltcl.TRACE_ARRAY may trigger multiple calls to the trace function, for example

tcl:tracevar('z', nil, ltcl.TRACE_WRITE + ltcl.TRACE_ARRAY, tracefunc)
tcl:call('array', 'set', 'z', {'x', 1})

will first trigger a call for the creation of the array with idx set to nil, and then another call for the addition of the element 1 at index ‘x’.

The trace function should return nil, if there was no error. If an error occurred, the function can return a string, which will then be the message for the error raised by Tcl.

During the execution of the trace function, all traces on the variable that triggered the trace will temporarily suspended. So you do not need to worry about causing recursion by accessing the variable.

Callback timing

On read accesses, the trace function is invoked just before the value is returned. It may set the variable to a different value, and this new value will be returned.

On write accesses, the trace function is called after the new value has been set but before it is returned. If it modifies the variable, the new value set by the trace function is returned. This may also be used to create read-only variables, but you will have to reset the variable to the original value in the trace function.

When array tracing has been specified, the trace function will be invoked at the beginning of the array command implementation, before any of the operations like get, set, or names have been invoked. The trace procedure can modify the array elements with the tcl:setvar and tcl:setarray methods.

When unset tracing has been specified, the trace function will be invoked whenever the variable is destroyed. The traces will be called after the variable has been completely unset.

Calling Tcl functions from Lua

There are 3 ways of calling into Tcl. The simplest way is to just pass a skript to the interpreter by means of the eval method. The Skript is then evaluated and the result is returned to Lua. This can in fact evaluate complete skripts, but only complete skripts, as there is no means to pass partial skripts to the interpreter from Lua. The optional flags parameter may take any or the sum of these values.

Another way that can only call one function is the method call. Its first argument (after the optional flags) is the name of he function to call, and must be a string. The following arguments are the arguments to pass to the function. In this case, the arguments are converted from their Lua values to Tcl values. If one of the arguments is an object returned by tcl:vals(), all of its members will be inserted into the argument list.

The third option is to use callt. This method receives the arguments to the function call in a table. Apart from that, it behaves just like tcl:call().

Tcl functions may receive option/value pairs as arguments. In order to provide for a neater way to specify these from Lua, the method tcl:makearglist() is provided. So, if you want to call a function reveiving options from Lua, you could do it like this:

tcl:callt(tcl:makearglist {option=value, ...})

Beware that positional elements of the argument table passed to tcl:makearglist will always be at the beginning of the resulting table. Also, only the positionl elements from 1 to #table will be inserted into the result table, so tcl:makearglist{1,2,3,[99]=4} will probably not do what you want.

Valid flags

Calling Lua functions from Tcl

There are also two ways to call back into Lua from Tcl. One is to register the function you want to call with Tcl. See the next section on how to do that.

The other way is an additional function Tcl registers with Tcl that allows for calls back to the Lua state. This allows Tcl to call Lua functions by name, passing args and receiving return values.The arguments are converted from Tcl to Lua types, and the return value is converted from Lua to Tcl type. The function is always looked up at the global level, that is, from the global variable table. Local functions can not be called in this manner. If you want to access local functions from Tcl, see the next section. Calling a Lua function from Tcl is as easy as

lua funcname [args]

for example:

lua print "Hello from Tcl"

Registering Lua functions

Lua functions can be registered as extensions with the Tcl interpreter using the tcl:register() method. When calling the function from Tcl, any arguments are converted to their respective Lua types, and on return the result is converted to a Tcl type. Just keep in mind that the types you get from Tcl may not necessarily be what you expect them to be. This is because value types in Tcl are mostly left unset on object creation, which will cause them to be converted to strings on the Lua side.

A Lua function to be registered with the Tcl interpreter may take any number of arguments, but can only return one result. It may of course return as much as it likes, but only the first return value is returned to Tcl. If the Lua function returns nothing, an empty Tcl object is returned to Tcl, just like Tcl functions do.

The registered Lua functions can be used from Tcl like any other Tcl function.

Error handling

All errors are propagated back to Lua. When an error occurs while doing anything with Tcl, a Lua error is thrown. You can catch such an error in the usual manner using pcall. The error message in such cases is the message Tcl generates, the location is the ltcl method called. This also holds when Tcl calls a Lua function that throws an error, this error will be propagated to Tcl, and from there back to Lua. If there is an error handler on the Tcl side, it will be able to catch such errors from Lua.

Misc stuff

The ltcl module exports 3 additional constants: _TCLVERSION, _VERSION and _REVISION. _TCLVERSION holds the version of the Tcl interpreter. _VERSION and _REVISION are version information for the ltcl module. As long as the _VERSION number is the same, there have been no changes to the module API. Bugfix releases only increment the _REVISION number. These constants are also available through any created Tcl interpreter object.

The binding should just work as expected, no magical surprises intended. Only 3 “magical” things happen:

For implementation details look at the source code.

Finally, some of the text in this document is shamelessly ripped from the Tcl docs.

References

I worked with Tcl/Tk 8.4 and 8.5. The relevant documentation is here:

for Tcl 8.4 at http://www.tcl.tk/man/tcl8.4/TclCmd/contents.htm
and its C API at http://www.tcl.tk/man/tcl8.4/TclLib/contents.htm
for Tcl 8.5 at http://www.tcl.tk/man/tcl8.4/TclCmd/contents.htm
and its C API at http://www.tcl.tk/man/tcl8.4/TclLib/contents.htm.

Also relevant is of course the Lua reference manual, available at
http://www.lua.org/manual/5.1/
and Programming in Lua, 2nd edition, available at your bookstore.

The complete Tcl/Tk documentation for all versions is available at
http://www.tcl.tk/doc/

Disclaimer

I don’t know sh*t about Tcl. Everything I know I found out while implementing this. I may be completely off at some points. If so, please let me know.