Lua

25 August 2019
One part of the job I recently took up is programming Lua, mainly as part of the OpenWRT Luci user-interface language. Lua has popped up onto my radar in the past but I had not previously made any attempt to see what the language was like, and so far it has constituted a small but significant portion of the work I do. It is a language that I have mixed feelings about, as although certainly light-weight it is/was that bit overzealous in features it did not include, and some things that ought to be simple are actually rather painful to do.

Although Lua has luarocks which is a package manager much like Python's pip, it left me with bad first-impressions. The version of nixio that it carried was five years old and attempting to build it fails with sizeof-pointer-memaccess warnings. I ended up building Nixio from source, which to be fair apart from needing minor tweaking due to Slackware's use of lib64 rather than lib was a simple make-install. Nevertheless it left me unimpressed with Lua's eco-system. I suspect that the Lua included with with OpenWRT, which is ultimately the version I needed to target, is an internally-maintained fork that has grown seperately to the standalone Lua.

Nice features of Lua

Having to use Lua meant coming across some features that are at least interesting, and in some cases the way I think things should be done. In the following section is a few that stuck in my mind.

Strings are bytes

For much of what I do Unicode is a massive pain and this reason alone is why I avoided using Python3 for several years. Lua sticks with ASCII strings which I considered a nice change from the almost-fundamentalist attitude towards Unicode that Python adopted, and for my use-cases is the unquestionably right thing to do.

ASCII code list to bytes

Quite often with network packets there are parts that are most conveniently specified as a list of hexadecimal byte values, whereas at other times ASCII tests is preferable. Lua makes it fairly easy to mix the two:

local packet = { string.char(0x08,0x01,0x27,0x00,0x00,0x00), string.char(0xc0,0xa0,0x00,0x01), "Data" } local bytes = table.concat(mac) fdSocket:send(bytes)

This also demonstrates what Lua calls ropes, which are list of strings that are later collapsed into a single immutable string. Python uses a similar strategy but does not have a special name for the intermediate list.

Indented output

Whereas with Python passing multiple parameters to print() will separate them with spaces, Lua on the other hand will separate them with tabs. This is convenient for the debugging purposes I tended to use printing for, as demonstrated in the following code snippet:

local listCodes = { ["One"] = 1, ["Two"] = 2, ["Three"] = 3, ["Far Too Long"] = 4 } for key,value in pairs(listCodes) do print(key,value) end

The resulting output of this code snippet is as follows:

One = 1 Far Too Long = 4 Three = 3 Two = 2

Admittedly there are scenarios where tabs are not the best choice, but it is nevertheless an interesting feature.

Easily extended

Although the nixio API for creating sockets lists raw as an accepted socket type, looking at the underlying code for sendto and bind reveals that actual raw socket writes are not implemented. As it turned out it was quite easy for me to fill the gaps, and in the process appreciated some of the simplicity in the design of the Lua interpretor.

Faults with the language

Lua has plenty of irritations, almost all of which stem from its endeavor to be extremely light-weight being taken to the extreme. There is not much of a standard library, but the minimalisam goes as far as requiring third-party libraries for things that are core parts of virtually every other practical language out there. Below is a selection of such omissions that have been headaches of varying intensity, and in some cases are likley counter-productive from a perfomance perspective.

No bit-wise operations

Lua prior to v5.3 (released early 2015) does not support bit-wise operations, which are instead provided by an external module. As a result, consider the the following function:

function shortToBytes(valInt) local hi = (valInt >> 8) & 0xff local lo = valInt & 0xff return string.char(hi,lo) end

This function will not compile, and instead it needs to be written as follows:

local bw = requre("bitwise") local hi = bw.band(bw.rshift(valInt,24),0xff) local lo = bw.band(valInt,0xff)

For good measure bit32 is also not a standard module, and it is just one of many that do a similar task and often can drop-in replace each other.

No continue statement

Supposedly as the result of some scoping issues, which to me look suspiciously like a design flaw, Lua loops do not support an equivalent of the continue statement. This annoys me a lot as my programming style makes heavy use of these, often to specifically avoid what has to be done in Lua: Nesting entire loop bodies inside conditional statements rather than do an early return-to-start. I think the parameter shadowing case cited in the StackOverflow answer feels too much like an excuse than a reason.

No string split

Lua does not have any function for splitting a string based on a seperator token, so this functionality has to be replicated using the functions that match patterns.

local str = "key,value" k,v = string:match("([^,]+),([^,]+)")

Overall

On the whole I am not impressed with Lua. While I think it is nicer than Bash shell-scripts for the scenarios I face professionally, the hassle involved makes me think that falling back on native C code is a better option if efficiency is really an overriding concern.