1

I want to let a third-party develop a Lua script that runs with my C++ application. A script may contain many individual files. To make this safe I use Luau for sandboxing. My C++ application must compile and load the third-party Luau script files as outlined in the Roblox readme. I am not able to do this with more than one file.

Let us say that script file module1.luau looks like this:

module = require("module2")

print('started module 1')
print(module.hello())

Script file module2.luau looks like this:

local M = {}

function M.hello()
    return 'Hello'
end

print('started module 2')

return M

The simple C++ application (module.cpp) that loads the script files look like this:

#include <iostream>
#include <fstream>


#include <assert.h>
#include "/home/me/luau/VM/include/lua.h"
#include "/home/me/luau/VM/include/lualib.h"
#include "/home/me/luau/Compiler/include/luacode.h"

#include <gtk/gtk.h>

using namespace std;


int main() 
{  

    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    
    string content; 
    ifstream ifs;
    char* bytecode;
    size_t bytecodeSize = 0;
    
    
    
    ifs.open("module2.luau", ifstream::in);
    content.assign( (istreambuf_iterator<char>(ifs) ),
                        (istreambuf_iterator<char>()    ) );
                                     
    bytecode = luau_compile(content.c_str(), content.length(), NULL, &bytecodeSize);
    assert(luau_load(L, "foo2", bytecode, bytecodeSize, 0) == 0);
    free(bytecode); 
    
    lua_pcall(L, 0, 0, 0);
    
    
    ifs.open("module1.luau", ifstream::in);
    content.assign( (istreambuf_iterator<char>(ifs) ),
                        (istreambuf_iterator<char>()    ) );
                                     
    bytecode = luau_compile(content.c_str(), content.length(), NULL, &bytecodeSize);
    assert(luau_load(L, "foo1", bytecode, bytecodeSize, 0) == 0);
    free(bytecode); 
    
    lua_pcall(L, 0, 0, 0);
    
    
    

    lua_close(L);
    
    return 0;
    
}

Running the modules by themselves gives the expected result:

$ ./luau module1.luau
started module 2
started module 1
Hello

Running module.cpp gives this:

$ g++ -Wall -o module module.cpp -L/home/me/luau/build/release -lluauvm -lluaucompiler -lluauast -lisocline -lgtk-3 -lgio-2.0 -lgobject-2.0
$ ./module
started module 2

The second compile/load does not happen. The code is obviously wrong, but how?

I presume it is possible to load individual script files so that they work together with require in this manner. Note that there are a number of load C API functions that are available in Lua but not in Luau.

Rhett
  • 51
  • 1
  • 1
  • 3
  • OT: do not use absolute paths in `#include`s. use the `-I` argument to your gcc invocation instead. – Botje Mar 08 '23 at 12:29
  • The luau code looks fairly straightforward. Have you tried running your program in a debugger and stepping into the luau code? You may also want to base your code on the [runFile function of luau instead](https://github.com/Roblox/luau/blob/78798d46418f175767c4517fa5932111f0ca07f6/CLI/Repl.cpp#L604-L664) – Botje Mar 08 '23 at 13:53
  • I will get back to this. Thank you. – Rhett Mar 08 '23 at 16:59

2 Answers2

0

From the documentation on std::ifstream::open:

If the stream is already associated with a file (i.e., it is already open), calling this function fails.

So, your program fails on the attempt to open ifs twice.
You need to explicitly close the first instance before opening the second one.

ESkri
  • 1,461
  • 1
  • 1
  • 8
  • Thank you. A good point. I added a `ifs.close()` after both `content.assign` statements in module.cpp. Unfortunately I get the same output, that is only `started module 2`. – Rhett Mar 08 '23 at 16:53
  • Does the second part work correctly if you comment out the first part `ifs.open("module2.luau"...lua_pcall(L, 0, 0, 0);` ? – ESkri Mar 08 '23 at 16:57
  • No. If I comment out the first part (all that has to do with module2.luau) I get nothing. I know that even if an error message is not displayed the reason is that the statement `module = require("module2")` returns `invalid argument #1 to 'require'` – Rhett Mar 08 '23 at 17:05
  • This is because `require` in luau is not the same as in Lua. The standard `require` was removed, and new `require` introduced, now it expects a [ModuleScript](https://create.roblox.com/docs/reference/engine/classes/ModuleScript) object as argument (instead of a filename). – ESkri Mar 09 '23 at 00:20
  • I replaced `module = require("module2")` in **module1.luau** with `module = script and require(script.Parent.M)`. Now it prints _started module 2 started module 1_ as expected but not _Hello_. It seems it does not find the module with the given path script.Parent.M. I have tried using the chunkname _foo2_ and various other combinations with no luck. – Rhett Mar 09 '23 at 13:00
  • Probably you should install a "Module Script" somehow in the roblox studio GUI. :-) – ESkri Mar 09 '23 at 23:38
  • This is not a Roblox project in any way. It's totally independent of Roblox and their IDE. But thank you for pointing me in the right direction. I will continue to debug and gather information. – Rhett Mar 10 '23 at 10:31
0

I have found a solution of sorts. First of all I drop calling module2.luau after compiling and loading the script, that is no lua_pcall(L, 0, 0, 0). Instead I set the chunkname foo2 as a global. foo2 is actually a function. foo2() returns a table. This table holds the function hello.

module.cpp is now this:

#include <iostream>
#include <fstream>


#include <assert.h>
#include "lua.h"
#include "lualib.h"
#include "luacode.h"


using namespace std;


int main() 
{  

    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    
    string content; 
    ifstream ifs;
    char* bytecode;
    size_t bytecodeSize = 0;
    
    
    
    ifs.open("module2.luau", ifstream::in);
    content.assign( (istreambuf_iterator<char>(ifs) ),
                    (istreambuf_iterator<char>()    ) );
    ifs.close();
                                     
    bytecode = luau_compile(content.c_str(), content.length(), NULL, &bytecodeSize);
    assert(luau_load(L, "foo2", bytecode, bytecodeSize, 0) == 0);
    free(bytecode); 
    
    
    lua_setglobal(L, "foo2");
    
    
    
    ifs.open("module1.luau", ifstream::in);
    content.assign( (istreambuf_iterator<char>(ifs) ),
                    (istreambuf_iterator<char>()    ) );
    ifs.close();
                                     
    bytecode = luau_compile(content.c_str(), content.length(), NULL, &bytecodeSize);
    assert(luau_load(L, "foo1", bytecode, bytecodeSize, 0) == 0);
    free(bytecode); 
    
    lua_pcall(L, 0, 0, 0);
    
    
    

    lua_close(L);
    
    return 0;
    
}

module1.luau is now:

module = foo2()

print('started module 1')
print(module.hello())

module2.luau is as before:

local M = {}

function M.hello()
    return 'Hello'
end

print('started module 2')

return M

Running gives the expected result:

$ g++ -Wall -o module module.cpp -I/home/me/luau/VM/include -I/home/me/luau/Compiler/include -L/home/me/luau/build/release -lluauvm -lluaucompiler -lluauast -lisocline
$ ./module
started module 2
started module 1
Hello

I presume this can be generalized to include any number of Luau script files, not just two. Are there better ways of doing it?

Rhett
  • 51
  • 1
  • 1
  • 3