Luaj: How to Import or Require a Lua Function Library
Asked Answered
K

1

18

In the Java LuaJ library I would like to know how to require or import a lua script of functions in another lua script called by a lua closure through Java. For example this does not work:

public static LuaValue runInputStreamLua(InputStream inputStream) throws Exception {
    Prototype luaScriptPrototype = LuaC.instance.compile(inputStream, "");
    Globals luaScriptStandardGlobals = JsePlatform.standardGlobals();
    luaScriptStandardGlobals.loadfile("mycoolmathfunctions.lua");
    LuaClosure luaClosure = new LuaClosure(luaScriptPrototype, luaScriptStandardGlobals);
    return luaClosure.call();
}

And the input stream here refers to the contents of another lua:

import 'mycoolmathfunctions'
-- or maybe require mycoolmathfunctions ?

return sum({1, 2, 3})
-- or maybe mycoolmathfunctions.sum({1, 2, 3}) ?

How do I do this?

Knorr answered 16/9, 2015 at 0:12 Comment(0)
M
14

In the Java LuaJ library I would like to know how to require or import a lua script of functions in another lua script called by a lua closure through Java.

You can place your Lua libraries as resources in your Java packages. Then on your lua script that requires another lua script, you require them relative to your package path.

Here's an example:

enter image description here

Here's our import-me.lua:

-- make our sample module table global
my_imported = {}

function my_imported.printHello()
    print "Hello!"
end

return my_imported

Which is then imported in our sample-that-imports.lua:

require "com.example.import-me"

my_imported.printHello()

Then we run our sample-that-imports.lua in our SampleMain Java class:

package com.example;
...
public class SampleMain {

    public static void main(String[] args) {
        Globals globals = JsePlatform.standardGlobals();

        // Again, we load the lua scripts relative to our package path
        LuaValue chunk = globals.loadfile("com/example/sample-that-imports.lua");
        chunk.call();

        // We could even use our imported library here
        chunk = globals.load("my_imported.printHello()");
        chunk.call();
    }
}

Now to answer your other problems,

For example this does not work…

I've noticed on your Java code that you've assumed that calling loadfile() would automatically run your lua script. Furthermore, you've assumed that loadfile() is used for loading your lua modules. However, this isn't how it is supposed to be used.

Your loadfile() should be able to return a LuaValue that you need to call() to run the script itself. You could even safely cast it to a LuaClosure because this is what loadfile() actually returns.

To fix your Java code above, you can use this,

public static LuaValue runInputStreamLua(InputStream inputStream) throws Exception {
    Prototype luaScriptPrototype = LuaC.instance.compile(inputStream, "");
    Globals globals = JsePlatform.standardGlobals();
    LuaClosure luaClosure = new LuaClosure(luaScriptPrototype, globals);
    return luaClosure.call();
}

I will assume in the above code that you are already using require in the InputStream (containing a lua script) that you have passed with the above method. If not then, you can do the following changes:

public static LuaValue runInputStreamLua(InputStream inputStream) throws Exception {
    Prototype luaScriptPrototype = LuaC.instance.compile(inputStream, "");
    Globals globals = JsePlatform.standardGlobals();

    LuaValue chunk = globals.load("require 'com.example.import-me';");
    chunk.call();

    LuaClosure luaClosure = new LuaClosure(luaScriptPrototype, globals);
    return luaClosure.call();
}

In the above changes, I am assuming that your lua module (in our example import-me.lua) automatically creates a global space for itself (in our example, the my_imported table). If not, you could do this final touch:

...
LuaValue chunk = globals.load("my_imported = require 'com.example.import-me';");
...


You also should reuse your Globals (returned by JsePlatform.standardGlobals()) unless you really want to create a new Globals table every time you call your method. Furthermore, if you really don't need an InputStream, and simply wants to load the file itself from its file path (or resource path in your Java package path), you can simplify everything into this:

public static LuaValue runLuaFile(Globals globals, String luafile) {
    return globals.loadfile(luafile).call();
}

Or to ensure that our lua module is always imported (or has been require'd) by our lua script,

public static LuaValue runLuaFile(Globals globals, String luafile) {
    LuaValue chunk = globals.load("require 'com.example.import-me';");
    chunk.call();
    chunk = globals.loadfile(luafile);
    return chunk.call();
}

Again, you must specify the complete resource path for our lua file. Here's a sample Java snippet using our simplified method above:

Globals globals = JsePlatform.standardGlobals();
runLuaFile(globals, "com/example/sample-that-imports.lua");

I hope this helps!


Edit:

You've mentioned in the comments that you need to import lua modules from InputStreams. There are 2 ways to achieve that:

  1. The first one is to load and run the lua modules that you need, like simple lua scripts – and if case that the lua modules that you need are only compatible with lua's require mechanism, you'll have a lot of problems to face.
  2. The second, easiest, and most efficient way is to simply load the module, place it inside the lua table package.preload, mapped with a key as its name (to be used by require).

We'll use the second method above, as this is exactly what lua's require mechanism really intends. Here's how to implement it using LuaJ:

public static void preloadLuaModule(Globals globals, String modname, InputStream is) {
    LuaValue module = globals.load(is, modname, "bt", globals);
    globals.get("package").get("preload").set(modname, module);
}

The above utility method pre-loads an InputStream to be used by require. Here's an example usage:

Somewhere in the beginning of everything, we initialize stuffs:

...
preloadLuaModule(globals, "sample_module", sampleModuleInputStream);
...

And our sampleModuleInputStream above is a lua module with the following content:

-- make our sample module table global
sample_module = {}

function sample_module.printHi()
    print "Hi!"
end

return sample_module

Then we could simply use require "sample_module" anywhere we like, be it in a Lua script or in Java using LuaJ:

globals.get("require").call("sample_module");
Monumental answered 24/9, 2015 at 3:55 Comment(5)
Thanks Jason! Question, these lua scripts are not in our Java codebase, rather I would want to import them as text data from a database. Can I manually pass the script source code to the globals to make it available to other scripts (pass as input streams?)Knorr
@DavidWilliams Sure you can. But you have to make sure that you have a mechanism of loading them only once. The advantage of require is that it loads and runs the lua module only once. You could read the lua documentation for require, about installing custom loaders inside package.preload[] table. You could also do the same in LuaJ; see the LuaJ docs for the class PackageLib.Monumental
Hi Jason, what does "bt" mean in the preloadLuaModule function?Knorr
Oh I see binary text.Knorr
Thanks for this! The preloadLuaModule() function, in particular, was just what I needed. I got luaj working by copying the luaj folder from the Github repo elye/demo_android_with_lua_script into my project, but I couldn't figure out how to make json = require "json" resolve to the contents of a json.lua file included as an asset with my Android app. Your approach still works perfectly, 7 years after your original post!Anthracosilicosis

© 2022 - 2024 — McMap. All rights reserved.