Luaj
I was reading over the list of features so Curiodb lacks compared to Redis, that I ' d previously documented. It contained the item "No Lua scripting", which I ' d written confidently at the time, certain this I wouldn ' t possibly be a Ble to implement something so serious in my toy project. But then I thought to myself, ' Why not? ', and after a bit of the, I discovered the fantastic Luaj library, which TRA Nsformed the task from a significant engineering feat, to yet another case of merely gluing some libraries together.
Luaj provides a complete API for compiling and running LUA scripts, and exposing the JVM code to them as Lua functions. Luaj also makes a complete range of LUA data types available to your JVM code. I purposely refer to the JVM (Java Vsan) here rather than Java the language, as in my case, it ' s actually Sca La being used to code against LUAJ, which makes the overall accomplishment seem even more death-defying.
Obligatory Blog Snippets
Let's take a look. First up, we'll compile and run a snippet of Lua (sans imports and class scaffolding, which can pick up from the final result in Curiodb if wish):
The global namespace we ' ll run the script in, which we could preLoadVariablesinto if needed.val globals = Jseplatform.standardglobals ()//A tiny string of Lua Code-it returns the second string from a//tab Le called "names" (Lua tables use 1-based indexes). val script = " names = {' foo ', ' Bar ', ' Baz '}; return names[2] "//Running the script returns a Luavalue object, which from our lua//code above, we Know to bes a string, so we cast it to one and print//the value, which should be " bar ". val result = globals.< span class= "keyword" >load (script). call () println (result.asjstring)
As simple as. Now let's look at passing LUA variables from JVM land to a LUA script, and pulling Lua variables back out as JVM objects :
The same table declared above in Lua, but this time in scala.val names = Luavalue.listof (Array ("foo", "Bar", "Baz"). Map (luavalue.valueof)) //Create theGlobal namespace again,and pass the Luatable into it. Val globals = Jseplatform.standardglobals () globals. set ( "names", names)//Our Lua code is inline here, and just returns the variable " names ". val result = GL Obals. load ( "return names"). Call ()//The result is our original table back in Scala, where we can access//items by index, just as We previously did in lua.println (result. Get (2). asjstring)
There you has IT-JVM objects representing LUA variables, moving into and back out of LUA code. Now let's look at the reverse, calling JVM code from inside Lua. To does this, we implement a class representing a Lua function:
//simple function that takes a string, and prefixes it with "Hello". class sayhellofunction extends oneargfunction {override def call (name:luavalue) = Luavalue.valueof ( Span class= "string" > "Hello" + name.tojstring)}//Again, build the global namespace, this time addin G The Function//object to it with a given name, and calling it from within lua.val globals = Jsepla Tform.standardglobals () globals.set (new sayhellofunction ( ) Val result = Globals.load ( "return Say_hello (' Grandma ')"). Call () println (result.asjstring) span class= "comment" >//prints "Hello grandma".
That's it, the full round Trip-lua calling JVM code, and vice versa. With that working, the rest are up to your imagination. I ' ve only scratched the surface of what Luaj provides-all of the data types found in Lua, it standard library, and much More.
The final result is a little more involved than the above implies. Curiodb is carefully designed to be non-blocking, using-tell rather than ask, where actors only ever send messages Forwar DS, without expectation of a reply. The challenge here is introducing the synchronous redis.call
API into a fundamentally asynchronous system. The solution involved modelling the scripting API as a client actor, with a controlled amount of blocking, much like the W Ay TCP and HTTP connections is managed in the system.
Call-ception
A really fun and whacky side effect of implementing the possibly a little too correctly(for lack of a better ter m), is so it unintentionally allows the Lua API to recursively call itself:
$ redis-cli EVAL "return redis.call(‘EVAL‘, ‘return redis.call(\‘EVAL\‘, \‘return redis.call(\\\\\‘TIME\\\\\‘)\‘, 0)‘, 0)" 01) (integer) 2277342) (integer) 541653
Is this a feature, or a bug? The Scripting API in Redis specifically disallows this, and most likely for good reason.
Http://www.tuicool.com/articles/M36byiy
Http://stackoverflow.com/questions/32573748/multiple-return-values-in-luaj
http://levelup.sinaapp.com/
Http://www.luaj.org/luaj/3.0/README.html
Http://luaj.org/luaj/3.0/api/org/luaj/vm2/LuaValue.html
Http://stackoverflow.com/questions/12358047/how-can-i-pass-objects-to-an-exposed-luaj-function
Embedding Lua, in Scala, using Java (RPM)