JVx and Lua, a proof of concept
We've found the time to look at something that was floating around the office for quite some time. A few of us had previous experiences with Lua, a simple scripting language, but nothing too concrete and while doing a prototype a question popped up: Would it be easy to create a JVx GUI in Lua? As it turns out, the answer is "yes".
Lua, a short tour
Lua is a lightweight, multi-paradigm programming language designed primarily for embedded systems and clients.
Lua was originally designed in 1993 as a language for extending software applications to meet the increasing demand for customization at the time. It provided the basic facilities of most procedural programming languages, but more complicated or domain-specific features were not included; rather, it included mechanisms for extending the language, allowing programmers to implement such features. As Lua was intended to be a general embeddable extension language, the designers of Lua focused on improving its speed, portability, extensibility, and ease-of-use in development.
That is what Wikipedia has to say about Lua, but personally I like to think about it as "Basic done right", no insults intended. Lua is easy to write, easy to read and allows to quickly write and edit scripts. There are quite a few implementations for different languages and systems available which makes it very versatile and usable from in nearly every environment.
The most simple Lua script is one that prints "Hello World":
-
print("Hello World")
Because it is a prototype based language, functions are first-class citizens, which can be easily created, passed around and invoked:
-
local call = function()
-
print("Hello World")
-
end
-
-
call()
Additionally, we can use tables to store state. They work like a simple key/value store:
-
local operation = {
-
method = function(string)
-
print(string)
-
end,
-
value = "Hello World"
-
}
-
-
operation.method(operation.value)
-
local operation = {}
-
operation.method = function(string)
-
print(string)
-
end
-
operation.value = "Hello World"
-
-
operation.method(operation.value)
Additionally, with some syntactic sugar, we can even emulate "real" objects. This is done by using a colon for invoking functions, which means that the table on which the function is invoked from will be provided as first parameter:
-
local operation = {
-
method = function(valueContainer, string)
-
print(valueContainer.value .. " " .. string)
-
end,
-
value = "Hello World"
-
}
-
-
operation:method("and others")
Last but not least, the rules about "new lines" and "end of statements" are very relaxed in Lua, we can either write everything on one line or use semicolons as statement end:
-
local call = function(value) return value + 5 end print(call(10))
-
-
local call = function(value)
-
return value + 5;
-
end
-
-
print(call(10));
But enough of the simple things, let's jump right to the case.
World, meet JVx.Lua
JVx.Lua is a proof of concept Java/Lua bridge, which allows to use the JVx classes in Lua scripts. Additionally, we've created a short demo application, JVx.Lua Live, which allows to directly write Lua code and see the output live in the application.
The example code should be self-explanatory and the API is as close to the Java one as is possible. If an exception is thrown by the Lua environment it will be displayed in the live preview.
This allows to quickly test out the Lua bindings and create a simple GUI in no time. But note that this simple demo application does not store what you've created, when you close it, it will be gone.
How does it work?
Glad you asked! The demo application is, of course, a simple GUI build with JVx, there are two core components which make it possible:
- RSyntaxTextArea, a Swing component for displaying and editing code.
- LuaJ, a Lua interpreter and compiler which allows to compile Lua directly to Java bytecode.
RSyntaxTextArea does not need to be further explained, it just works, and working very well it does. So does LuaJ, but that one has to be explained.
To create a new "Lua environment" one has to instance a new set of Globals and install the Lua-to-Lua-Bytecode and Lua-Bytecode-to-Java-Bytecode compilers into it.
-
Globals globals = new Globals();
-
LuaC.install(globals);
-
LuaJC.install(globals);
-
-
globals.load("print(\"Hello World\")");
And that's it! With this we can already execute Lua code directly in Java, and most importantly, at runtime.
By default, LuaJ does provide nothing for the Lua environment, which means that it is sandboxed by default. If we want to add functionality and libraries, we'll have to load it into the Globals as so called "libs". For example if we want to provide all functions which can be found inside the string table, we'll have to load the StringLib:
-
Globals globals = new Globals();
-
LuaC.install(globals);
-
LuaJC.install(globals);
-
-
globals.load(new StringLib());
-
-
globals.load("print(string.sub(\"Hello World\", 7))");
There are multiple libs provided with LuaJ which contain the standard library functions of Lua or provide access directly into the Java environment. For example we can coerce Java objects directly into Lua ones:
-
BigDecimal value = new BigDecimal("-5.1234");
-
-
globals.set("value", CoerceJavaToLua.coerce(value));
-
local absoluteValue = value:abs()
-
local squaredValue = absoluteValue:pow(2)
-
-
print(squaredValue:toString())
Which gives us all the power of Java at our fingertips in Lua.
JVx bindings for Lua
Armed with that knowledge, we can have a look at the bindings which make it possible to use the JVx classes. JVxLib and LuaUtil are the main classes which coerce a single class to be used by Lua, the procedure looks as follows:
- Create a LuaTable to hold the class and register it globally.
- Add all public static fields (constants) to it.
- Add all static methods to it.
- Add a single constructor with a vararg argument to it.
The most interesting point is the constructor call, we simply register a method called "new" on the table and give it a vararg argument, which means that it can be called with any number of arguments. When this function is invoked the arguments are processed and a fitting constructor for the object is chosen. The found constructor is invoked and the created object is coerced to a Lua object, that created Lua object is returned.
This allows us to use a clean syntax when it comes to accessing the static or instance state of the objects. Static methods and constants, including constructors, are always accessed using the "dot" notation, while everything related to the instance is accessed using the "colon" notation.
Downside, binding events
For events and event handlers we had to dig a little deeper into LuaJ. The main problem with our EventHandler is that it has two methods: addListener(L) and addListener(IRunnable), at runtime the first one is reduced to addListener(Object). Let's assume the following code:
-
button:eventAction():addListener(listener)
With such a construct LuaJ had a hard time finding the correct overload to use even when listener was a coerced IRunnable. This turned out to be undefined behavior, because the order of methods returned by a class object during runtime is undefined, sometimes LuaJ would choose the correct method and all other times it would use the addListener(Object) method. Which had "interesting" side effects, obviously, because an IRunnable object ended up in a list which should only hold objects of type L.
We've added a workaround so that functions with no parameters can be easily used, but for "full blown listener" support we'd have to invest quite some time. Which we might do at some point, but currently this is alright for a proof of concept.
Conclusion
Using Lua from Java is easy thanks to LuaJ, another possible option is Rembulan, which can not go unmentioned when one talks about Java and Lua. It does not only allow to quickly and easily write logic, but with the right bindings one can even create complete GUIs and applications in it and thanks to the ability to compile it directly to Java bytecode it is nearly as fast as the Java code. But, with the big upside that it can be easily changed at runtime, even by users.