Skip to content

Commit 72d60f0

Browse files
committed
First commit
1 parent 1a0eff4 commit 72d60f0

14 files changed

+386
-1
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

.npmignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
example/
2+
.vscode/

.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"deno.enable": false
3+
}

README.md

+52-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,52 @@
1-
# lunode
1+
# demoon
2+
3+
You love Lua but the runtime API is very weak and you don't want to handle and learn a lot of different luarock libraries? You came at the right place! This project aims to offer the bests of **Lua** and **NodeJS** together.
4+
5+
## Usage
6+
7+
You don't need `lua` installed to run demoon, but you need `node` and npm as well, firstly, install demoon globally:
8+
9+
```sh
10+
$: npm i -g demoon
11+
```
12+
13+
Then run it passing your entry lua file:
14+
15+
```sh
16+
$: demoon app.lua
17+
```
18+
19+
## Example
20+
21+
This is a little sample code to demonstrate how demoon is powerful and bridges well with nodeJS:
22+
```lua
23+
-- you can require node modules (package.json/node_modules works as well)
24+
local http = require('http')
25+
26+
-- you can require js modules and lua files
27+
-- require('./myjsmodule.js')
28+
-- require('./myluamodule.lua')
29+
30+
local port = os.getenv('PORT') or 8080
31+
32+
function sleep(ms)
33+
-- you can use and create promises
34+
return Promise.create(function(resolve)
35+
setTimeout(resolve, ms)
36+
end)
37+
end
38+
39+
-- top level await works!
40+
sleep(1000):await()
41+
42+
http.createServer(async(function (req, res)
43+
-- you can await inside async bounded functions
44+
sleep(1000):await()
45+
46+
res:write('Hello World!');
47+
-- because end is a lua keyword you have to put the '_'
48+
res:_end();
49+
end)):listen(port);
50+
51+
print('Your server is running on port ' .. port .. '!')
52+
```

bin/demoon

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env node
2+
3+
const { start } = require('../src/index');
4+
5+
const snippets = process.argv.splice(2);
6+
const [entryFile] = snippets
7+
8+
start(entryFile)

example/main.lua

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-- you can require JS module files
2+
local jssleep = require('./sleep.js')
3+
-- you can require node modules
4+
local http = require('http')
5+
-- you can require Lua files
6+
local luasleep = require('sleep.lua')
7+
8+
local port = os.getenv('PORT') or 8080
9+
10+
-- yes, we have top level await
11+
luasleep(1000):await()
12+
13+
-- create a server object:
14+
http.createServer(async(function (req, res)
15+
-- you can await inside async bounded functions
16+
jssleep(1000):await()
17+
18+
res:write('Hello World!'); -- write a response to the client
19+
-- because end is a lua keyword you have to put the '_'
20+
res:_end(); -- end the response
21+
end)):listen(port); -- the server object listens on port 8080
22+
23+
print('Your server is running on port ' .. port .. '!')

example/sleep.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = ms => new Promise(resolve => setTimeout(resolve, ms))

example/sleep.lua

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
return function(ms)
2+
return Promise.create(function(resolve)
3+
setTimeout(resolve, ms)
4+
end)
5+
end

package-lock.json

+50
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "demoon",
3+
"version": "0.0.1",
4+
"description": "Lua + Node",
5+
"main": "src/index.js",
6+
"bin": {
7+
"demoon": "bin/demoon"
8+
},
9+
"author": "ceifa",
10+
"repository": "github:ceifa/demoon",
11+
"license": "ISC",
12+
"keywords": [
13+
"lua"
14+
],
15+
"dependencies": {
16+
"wasmoon": "1.5.0"
17+
}
18+
}

src/file.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const { resolve } = require('path');
2+
const { readdir } = require('fs').promises;
3+
4+
async function* walk(dir) {
5+
const dirents = await readdir(dir, { withFileTypes: true });
6+
for (const dirent of dirents) {
7+
const res = resolve(dir, dirent.name);
8+
if (dirent.isDirectory()) {
9+
yield* walk(res);
10+
} else {
11+
yield res;
12+
}
13+
}
14+
}
15+
16+
module.exports = { walk }

src/index.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const { LuaFactory, decorate } = require('wasmoon')
2+
const path = require('path')
3+
const { walk } = require('./file')
4+
const fs = require('fs').promises
5+
const proxy = require('./proxy')
6+
7+
const registerDirectory = async (factory, dir) => {
8+
for await (const file of walk(dir)) {
9+
await factory.mountFile(file, await fs.readFile(file))
10+
}
11+
}
12+
13+
const start = async (entryFile) => {
14+
const factory = new LuaFactory()
15+
16+
await factory.mountFile(path.resolve(process.cwd(), entryFile), await fs.readFile(entryFile))
17+
await registerDirectory(factory, path.resolve(__dirname, "std"))
18+
19+
const lua = await factory.createEngine({ injectObjects: true })
20+
21+
lua.global.set('new', constructor => new constructor)
22+
lua.global.set('global', decorate(global, {
23+
reference: true,
24+
metatable: proxy
25+
}))
26+
lua.global.set('mountFile', factory.mountFileSync.bind(factory))
27+
lua.global.set('jsRequire', (modulename, metaDirectory) => {
28+
if (modulename.startsWith('.')) {
29+
modulename = path.resolve(metaDirectory, '..', modulename)
30+
}
31+
32+
return decorate(require(modulename), {
33+
reference: true,
34+
metatable: proxy
35+
})
36+
})
37+
38+
const module = await factory.getModule()
39+
module.module.FS.chdir(process.cwd())
40+
41+
await lua.doFile(path.resolve(__dirname, "std/main.lua"))
42+
await lua.doFile(entryFile)
43+
}
44+
45+
module.exports = { start }

src/proxy.js

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
const { decorate } = require('wasmoon')
2+
3+
const keywords = [
4+
"and", "break", "do", "else", "elseif",
5+
"end", "false", "for", "function", "goto", "if",
6+
"in", "local", "nil", "not", "or", "repeat",
7+
"return", "then", "true", "until", "while"
8+
]
9+
10+
const proxy = {
11+
__index: (t, k) => {
12+
let value = t[k]
13+
14+
if (value === undefined && typeof k === 'string') {
15+
const possibleExpectedKey = k.substring(1)
16+
if (keywords.includes(possibleExpectedKey) && t[possibleExpectedKey]) {
17+
value = t[possibleExpectedKey]
18+
}
19+
}
20+
21+
if (['object', 'function'].includes(typeof value)) {
22+
if (typeof value === 'function') {
23+
value.__lua_self = t
24+
}
25+
26+
return decorate(value, { reference: true, metatable: proxy })
27+
}
28+
29+
return value
30+
},
31+
__newindex: (t, k, v) => {
32+
t[k] = v
33+
},
34+
__call: (t, ...args) => {
35+
// Called with the : syntax, let's bind this
36+
if (args[0] === t.__lua_self) {
37+
t = t.bind(t.__lua_self)
38+
delete args[0]
39+
args = args.slice(1)
40+
}
41+
42+
args = args.map(arg => {
43+
if (typeof arg === 'function') {
44+
return (...luaFunctionArgs) => {
45+
const fixedLuaFunctionArgs = luaFunctionArgs.map(luaFunctionArg => {
46+
if (['object', 'function'].includes(typeof luaFunctionArg)) {
47+
return decorate(luaFunctionArg, { reference: true, metatable: proxy })
48+
}
49+
50+
return luaFunctionArg
51+
})
52+
53+
return arg(...fixedLuaFunctionArgs)
54+
}
55+
}
56+
57+
return arg
58+
})
59+
60+
delete t.__lua_self
61+
62+
const value = t(...args)
63+
64+
if (['object', 'function'].includes(typeof value) && value !== Promise.resolve(value)) {
65+
return decorate(value, { reference: true, metatable: proxy })
66+
}
67+
68+
return value
69+
},
70+
__tostring: (t) => {
71+
const value = t
72+
return value.toString?.() ?? typeof value
73+
},
74+
__len: (t) => {
75+
return t.lenght ?? 0
76+
},
77+
__pairs: (t) => {
78+
const value = t
79+
const keys = Object.getOwnPropertyNames(value)
80+
let i = 0
81+
return new MultiReturn((ob, last) => {
82+
const k = keys[i]
83+
i = i + 1
84+
return k, ob[k]
85+
}, t, undefined)
86+
},
87+
__ipairs: (t) => {
88+
const value = t
89+
90+
const js_inext = (t, i) => {
91+
i = i + 1
92+
if (i >= value.length) {
93+
return undefined
94+
}
95+
return i, value[i]
96+
}
97+
98+
return js_inext, value, -1
99+
},
100+
__eq: (t1, t2) => {
101+
return t1 === t2
102+
},
103+
}
104+
105+
module.exports = proxy

0 commit comments

Comments
 (0)