daurnimator / lua.vm.js

The project is superceded by Fengari. See https://fengari.io/
MIT License
836 stars 101 forks source link

Call a node.js function from lua.vm #48

Closed joelpinheiro closed 8 years ago

joelpinheiro commented 8 years ago

Hi all,

Anyone know if it is possible to call a node.js function from lua.vm? If anyone, can you please put an example?

Thank you

daurnimator commented 8 years ago

Yes you can. Get a function from javascript, and then call it.

e.g

js.global.console:log("hello via JS console.log")

The function can come in via any means, e.g. Using js.global

L = require("./lua.vm.js").L;
L.execute('js.global.console:log("hello via JS console.log")')

or passed as an argument

L = require("./lua.vm.js").L;
L.execute('local console = ...; console:log("hello from lua")', console)

or via setting a lua global

L = require("./lua.vm.js").L;
L._G.set("console", console); // Add console global to lua
L.execute('console:log("hello")')

==> there are tonnes of ways to pass variables+functions back and forth.

joelpinheiro commented 8 years ago

In Lua code I did a require where I have my executeJQuery function: JQ = require("executeJQuery.js")

When I do L.execute I receive the following:

e:Lua.Error: [string "?"]:5: module 'executeJQuery' not found:
    no field package.preload['executeJQuery']
    no file '/usr/local/share/lua/5.2/executeJQuery.lua'
    no file '/usr/local/share/lua/5.2/executeJQuery/init.lua'
    no file '/usr/local/lib/lua/5.2/executeJQuery.lua'
    no file '/usr/local/lib/lua/5.2/executeJQuery/init.lua'
    no file './executeJQuery.lua'
    no file '/usr/local/lib/lua/5.2/executeJQuery.so'
    no file '/usr/local/lib/lua/5.2/loadall.so'
    no file './executeJQuery.so'

It seems like my import executeJQuery should be a Lua file but it isn't. In fact it is a simple JS function. Where should I put my function file? Am I doing it properly to call a JS function from lua.vm.js in node.js side?

Thanks in advance.

joelpinheiro commented 8 years ago

My purpose is to after that, be able to call: JQ.executeJQuery(url)

Where url is the data of the HTTP Request.

My executionJQuery code:

module.exports =  {

    executeJQuery : function(x) {

            console.log("executeJQuery");

            require("jsdom").env("", function(err, window) {
                if (err) {
                    console.error(err);
                    return;
                }

                var $ = require("jquery")(window);

                console.log("x: " + x);

                console.log("Resultado: \n\n")

                $.ajax({
                    type: "POST",
                    url: x,
                    success: function (result) {

                    console.log(result.message)

                    }
                });
            });
        }
};
daurnimator commented 8 years ago

My purpose is to after that, be able to call: JQ.executeJQuery(url)

Assuming you saved your code above as "JQ.js" you could do in lua:

local JQ = js.global:require"./JQ.js"
JQ:executeJQuery("http://example.com")
joelpinheiro commented 8 years ago

I updated my node_module from NPM: npm install lua.vm.js

My code:

local JQ = js.global:require"./executeJQuery.js"
JQ:executeJQuery(url)

Ex: e:Lua.Error: [string "?"]:5: attempt to call method 'require' (a nil value)

fabriziobertocci commented 8 years ago

I had the same problem. Apparently while all the JS globals are available using js.global, the function 'require' is not available.

In my case I just needed to access the filesystem with a require("fs"), so I just defined a global function in JS to read the file:

myReadFile = function(fileName) {
    return require("fs").readFileSync(fileName, "utf8");
}

Then from LUA I call myReadFile as:

  ...
  myFile.data = js.global:myReadFile(filename) 
  ..

You can do a similar trick, where you can wrap your executeJQuery.js inside a global function.

daurnimator commented 8 years ago

Apparently while all the JS globals are available using js.global, the function 'require' is not available.

I don't get the same behaviour locally:

$ node
> L = require("./dist/lua.vm.js").L;
{ _L: 5263368,
  _G: 
   { [Function: self]
     L: [Circular],
     ref: 2,
     invoke: [Function],
     push: [Function],
     free: [Function],
     toString: [Function],
     get: [Function],
     set: [Function] } }
> L.execute('for k, v in pairs(js.global:require"./package.json") do print(k,v) end')
name    lua.vm.js
version 0.0.1
description The Lua VM, on the Web
main    dist/lua.vm.js
scripts [object Object]
repository  [object Object]
keywords    lua,emscripten
contributors    Alon Zakai,daurnimator <quae@daurnimator.com>
license MIT
bugs    [object Object]
homepage    http://kripken.github.io/lua.vm.js/lua.vm.js.html
fabriziobertocci commented 8 years ago

Ok, I might have identified my problem although it's not clear to me why. My problem is that at this point I don't understand what is the right object to use for the execute function. In your case you use the L from this:

L = require("./dist/lua.vm.js").L;

But in my case I have been doing:

luavm=require("./dist/lua.vm.js");
L=new temp.Lua.State();

Now the value of L appears to be the same in both cases (only the number of _L is different):

{ _L: 5295832,
  _G: 
   { [Function: self]
     L: [Circular],
     ref: 2,
     invoke: [Function],
     push: [Function],
     free: [Function],
     toString: [Function],
     get: [Function],
     set: [Function] } }

Unfortunately now in my case when I try to use require, here is what I get:

> L.execute('print(type(js.global:require))')
Lua.Error
    at /home/fabrizio/working/git/lua.vm.js/dist/lua.vm.js:16:18422
    at /home/fabrizio/working/git/lua.vm.js/dist/lua.vm.js:16:27153
    at Object.<anonymous> (/home/fabrizio/working/git/lua.vm.js/dist/lua.vm.js:21:35)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Module.require (module.js:365:17)
    at require (module.js:384:17)
    at repl:1:9
daurnimator commented 8 years ago

Ok, I might have identified my problem although it's not clear to me why. My problem is that I don't have it clear what is the right handle to use for the execute function. In your case you use the L from this:

L = require("./dist/lua.vm.js").L;

lua.vm.js currently creates an arbitrarily "main" state called L. It makes more sense in the browser than when run in node....

But in my case I have:

luavm=require("./dist/lua.vm.js");
L=new temp.Lua.State();

I guess you meant luavm not temp in this code sample? Also keep in mind that if you create your own lua state you need to run js.lua manually. (I should probably fix this?)

Now the value of L appears to be the same in both cases (only the number of _L is different)

Ignore the value of _L for anything but equivalence; it's an address to inside emscripten.


> L.execute('print(type(js.global:require))')
Lua.Error

type(js.global:require) isn't valid syntax. You probably wanted type(js.global.require). node doesn't strinify error messages well; do it yourself to get more information:

> try { L.execute('print(type(js.global:require))') } catch(e) { console.error(e.name, e.message) }
Lua.Error [string "?"]:1: function arguments expected near ')'
fabriziobertocci commented 8 years ago

I guess you meant luavm not temp in this code sample? Also keep in mind that if you create your own lua state you need to run js.lua manually. (I should probably fix this?)

Yes, I meant luavm, not temp... copy & paste after numerous tests...

type(js.global:require) isn't valid syntax. You probably wanted type(js.global.require).

Correct, that's another error that has nothing to do with this problem. I just wanted to print the type of the require object:

> L.execute('print(type(js.global.require))')
userdata

But bottom line is that my version of "L" any attempt to call requirefails.

> L.execute('for k, v in pairs(js.global:require"./package.json") do print(k,v) end')
Lua.Error
...

So in conclusion yes, I think the problem was that I did not know what was the correct way to get that L value.

Thanks!

daurnimator commented 8 years ago

But bottom line is that my version of "L" any attempt to call requirefails.

> L.execute('for k, v in pairs(js.global:require"./package.json") do print(k,v) end')
Lua.Error

I actually think that's pairs failing (without running js.lua proxy objects don't get a __pairs metamethod) You should use the try/catch I showed above to get the error message.

fabriziobertocci commented 8 years ago

Yes, it is pairs that is failing because it does not know how to iterate over a userdata object:

try { L.execute('for k, v in pairs(js.global:require"./package.json") do print(k,v) end') } catch(e) { console.error(e.name, e.message) }
Lua.Error [string "?"]:1: bad argument #1 to 'pairs' (table expected, got userdata)

So after those tests I went back to my project, and I have replaced my:

var luavm = require('lua.vm.js');
L = new luavm.Lua.State();

with:

L = require('lua.vm.js').L

And tried to use again require in my code, but it still fail. Now, I think the problem might be somewhere else. My real use case is a bit different than just executing require from inside a L.execute() call. In my case, I have some legacy LUA code that performs io.open to read a file. In my JS wrapper I re-define the LUA's io.open to read the file (through filesystem in JS) and return to LUA a table with a read and close function:

L.execute(" \
                io.open = function(filename, mode) \
                    if (mode ~= 'r') then \
                        print('Error: non-read mode not supported in io.open wrapper') \
                        return nil \
                    end \
                    local myFile = {} \
                    myFile.read = function() \
                        return myFile.data \
                    end \
                    myFile.close = function() \
                        myFile.data = nil \
                    end \
                    myFile.data = js.global:require('fs').readFileSync(filename, 'utf8') \
                    return myFile \
                end");

That code is executed before invoking my LUA's function that need to access the local filesystem. In this scenario, I am always getting this error:

Error executing LUA code: Lua.Error: [string "?"]:1: attempt to call method 'require' (a nil value)

no matter how I get L. But if define a JS global function to read a file:

myReadFile = function(fileName) {
    return require("fs").readFileSync(fileName, "utf8");
}

then I invoke it from my io.open wrapper:

                    ...
                    myFile.close = function() \
                        myFile.data = nil \
                    end \
                    myFile.data = js.global:myReadFile(filename) \
                    return myFile \
                end");

everything works well.

joelpinheiro commented 8 years ago

Hi,

I had tried as daurnimator said:

var L = require("lua.vm.js").L;
try {
L.execute(code);
} catch(e) {
console.log("e:" + e)
}
local JQ = js.global:require"./executeJQuery.js"
JQ:executeJQuery(url)

Same code of executeJQuery (see above).

And I'm receiving the same error again: e:Lua.Error: [string "?"]:XX: attempt to call method 'require' (a nil value)

joelpinheiro commented 8 years ago

If it put like you said fabriziobertocci: js.global:executeJQuery(url) I receive: e:Lua.Error: [string "?"]:XX: attempt to call method 'executeJQuery' (a nil value)

For both cases, I'm not sure If I am putting the executeJQuery.js file in the correct path. I have it on the same path I'm running L.execute and inside the node_module lua.vm.js.

joelpinheiro commented 8 years ago

For both cases, my executeJQuery.js (with an executeJQuery global) is the following:

module.exports =  {

    executeJQuery : function(x) {

            console.log("executeJQuery");

            require("jsdom").env("", function(err, window) {
                if (err) {
                    console.error(err);
                    return;
                }

                var $ = require("jquery")(window);

                console.log("x: " + x);

                console.log("Resultado: \n\n")

                $.ajax({
                    type: "POST",
                    url: x,
                    success: function (result) {

                        console.log(result.message)
                        //if (result.isOk == false) console.log(result.message);
                    }
                });
            });
        }
};
daurnimator commented 8 years ago

@joelpinheiro it works for me:

$ mkdir tmp
$ cd tmp
$ npm install lua.vm.js
/home/daurnimator/tmp
└── lua.vm.js@0.0.1 

npm WARN enoent ENOENT: no such file or directory, open '/home/daurnimator/tmp/package.json'
npm WARN tmp No description
npm WARN tmp No repository field.
npm WARN tmp No README data
npm WARN tmp No license field.
$ cat > test.js << EOF
> console.log("loaded");
> module.exports = { foo: function() { return "bar" } };
> EOF
$ node
> var luavm = require("./lua.vm.js");
undefined
> luavm.L.execute("return type(js.global.require)")
[ 'userdata' ]
> luavm.L.execute("local test = js.global:require('./test.js'); return test:foo()")
loaded
[ 'bar' ]
joelpinheiro commented 8 years ago

@daurnimator your code worked for me in my node.js version: 4.2.1

But, I'm using electron (atom shell) that used its own node.js version: 4.2.2

JS:

var luavm = require("lua.vm.js"); -- this comes from node_modules (am I using the correct import?)
luavm.L.execute("print('type: ' .. type(js.global.require))")
luavm.L.execute("local test = js.global:require('./test.js'); print('foo: ' .. test:foo())")

Output:

type: nil

/Users/joelpinheiro/Dropbox/iTrading/electron_app/node_modules/lua.vm.js/dist/lua.vm.js:2
var Module;if(typeof Module==="undefined")Module={};if(!Module.expectedDataFileDownloads){Module.expectedDataFileDownloads=0;Module.finishedDataFileDownloads=0}Module.expectedDataFileDownloads++;((function(){var loadPackage=(function(metadata){function runWithFS(){var fileData0=[];fileData0.push.apply(fileData0,[45,45,32,77,97,107,101,32,119,105,110,100,111,119,32,111,98,106,101,99,116,32,97,32,103,108,111,98,97,108,10,119,105,110,100,111,119,32,61,32,106,115,46,103,108,111,98,97,108,59,10,10,100,111,32,45,45,32,67,114,101,97,116,101,32,106,115,46,105,112,97,105,114,115,32,97,110,100,32,106,115,46,112,97,105,114,115,32,102,117,110,99,116,105,111,110,115,46,32,97,116,116,97,99,104,32,97,115,32,95,95,112,97,105,114,115,32,97,110,100,32,95,95,105,112,97,105,114,115,32,111,110,32,74,83,32,117,115,101,114,100,97,116,97,32,111,98,106,101,99,116,115,46,10,9,108,111,99,97,108,32,95,80,82,79,88,89,95,77,84,32,61,32,100,101,98,1
Lua.Error
    at /Users/joelpinheiro/Dropbox/iTrading/electron_app/node_modules/lua.vm.js/dist/lua.vm.js:16:18422
    at /Users/joelpinheiro/Dropbox/iTrading/electron_app/node_modules/lua.vm.js/dist/lua.vm.js:16:27153
    at Object.<anonymous> (/Users/joelpinheiro/Dropbox/iTrading/electron_app/node_modules/lua.vm.js/dist/lua.vm.js:21:35)
    at Module._compile (module.js:434:26)
    at Object.Module._extensions..js (module.js:452:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Module.require (module.js:365:17)
    at require (module.js:384:17)
    at EventEmitter.<anonymous> (/Users/joelpinheiro/Dropbox/iTrading/electron_app/main.js:79:17)

Any reason for the different behaviour?

daurnimator commented 8 years ago

@daurnimator your code worked for me in my node.js version: 4.2.1

But, I'm using electron (atom shell) that used its own node.js version: 4.2.2

I tested in 5.9.0, so I doubt it's the version that's the issue.

Any reason for the different behaviour?

I assume that electron doesn't have a global.require for some reason. You'll have to ask the electron devs.

joelpinheiro commented 8 years ago

I didn't have any solution from them.

@daurnimator there is another way to invoke HTTP Request from lua.vm.js?

daurnimator commented 8 years ago

I didn't have any solution from them.

Where did you ask?

@daurnimator there is another way to invoke HTTP Request from lua.vm.js?

You can get a js value into lua using any of the options in https://github.com/kripken/lua.vm.js/issues/48#issuecomment-194073741

joelpinheiro commented 8 years ago

Where did you ask?

https://github.com/electron/electron/issues/4935#issuecomment-203883719

You can get a js value into lua using any of the options in #48 (comment)

The thing is, I need to require my js file executeJQuery.js and pass an argument to it on Lua. Since I can't require I'm not able to call my function on Lua.

Do you suggest another approach?

daurnimator commented 8 years ago

The thing is, I need to require my js file executeJQuery.js and pass an argument to it on Lua. Since I can't require I'm not able to call my function on Lua.

Do you suggest another approach?

Please try some of the other options I linked.