Implementing TkLua wasn't hard. Using the Tcl/Tk-C interface, we created a service provider and registered it to be accessed from Lua. The Lua code that implements the binding uses an object oriented approach, using the index fallback (see Lua reference manual). Each widget instance inherits from a class object. At the top of the hierarchy, there is the widget class. This class provides standard methods used by all widgets. The code bellow shows the definition of this generic class and, as an example, its method to set a the focus to a widget. It also shows the button class definition.
Widget = { } function Widget:focus() if self.tkname then tklua_setFocus(self.tkname) end end Button = { parent = Widget, tkwidget = "button" }
Each widget is created with a table constructor. The constructor sets the instance class, creates the widget, and stores it in a global array. However, we also use a small trick: instead of returning the new table, the constructor returns the widget location as a numeric id:
function button (self) self.parent = classButton tklua_ID = tklua_ID + 1 tklua_IDtable[tklua_ID] = self return tklua_ID end
Hence, when Lua encounters tries to index a widget, as in b.text, it calls a fallback, because numbers cannot be indexed. This trick gives us complete control over widget semantics. For instance, if b is a button (actually it stores an id) and you do b.text = "New text", then the fallback is responsible for calling the appropriate service command to update the widget.
The TkLua settable fallback function is shown bellow.
function setFB (id, f, v) local h = tklua_IDtable[id] if h == nil then old_setFB(id,f,v) return end if h.tkname and h:isAttrib(f) then tklua_configure(h.tkname,f,v) end h[f] = v end old_setFB = setfallback("settable",setFB)
This fallback will be called for every tries to index a non-table variable. So, we first check if the first parameter correspond to a valid widget id. If it represents a TkLua widget, we retrieve the widget table by accessing the global array. Otherwise, we dispatch the occurrence to the previous registered fallback.
The widget table has an internal field, tkname, that store the corresponding Tk widget name. This name is used to invoke Tk commands. We first check if there exist a corresponding Tk widget and if the indexing value is a valid Tk attribute. If so, we simply request the service provider to change the widget attribute (calling the registered C function tklua_configure). The assignment h[f] = v assures that we can use the widget table to store any other values, besides Tk attributes. The last code line above registers the fallback, storing the previous one registered.
The gettable fallback implementation is quite similar. Besides these two, the Tk/Lua binding also uses the index fallback to implement inheritance (see Lua manual for an example) and the function fallback to call widget commands or window manager commands, using a similar strategy as the one applied to control attributes.