xiangnanscu / js2lua

js2lua Writing LuaJIT with the expressiveness of JavaScript.
https://xiangnanscu.github.io/js2lua/
3 stars 0 forks source link

js2lua

js2lua Writing LuaJIT with the expressiveness of JavaScript.

Install

npm install @xiangnanscu/js2lua

Usage

command

Concat one or more js files and transform them to one lua string:

js2lua [options] file1, file2, ...

where options are:

const defaultOptions = {
  tagArrayExpression: true,
  importStatementHoisting: true,
  transform$SymbolToDollar: true,
  transformToString: true,
  transformString: true,
  transformJSONStringify: true,
  transformJSONParse: true,
  transformParseFloat: true,
  transformParseInt: true,
  transformNumber: true,
  transformIsArray: true,
  transformConsoleLog: true,
  moduleExportsToReturn: true,
  index0To1: true,
  tryTranslateClass: true,
  disableUpdateExpressionCallback: true,
  renameCatchErrorIfNeeded: true,
  disableClassCall: true,
};

examples

Basic:

js2lua foo.js > foo.lua

To disable a feature --no-[option]:

js2lua --no-transformToString foo.js

To enable a feature --[option]:

js2lua --debug foo.js

api

import { js2lua } from "js2lua";
js2lua(`let a = 1`, { importStatementHoisting: true });

see also

Features

(function() { print(2) })();

(() => { print(1) })() (function() { print(2) })();

### lua
```lua
(function()
  print(1)
end)();
(function()
  print(2)
end)();
(function()
  print(1)
end)()(function()
  print(2)
end)()

assignment

js

let u, v;
[u, v] = [1, 2]
const foo = 1
const object = { o1: 'o1', o2: 'o2' }, array = [1, 2]
// eslint-disable-next-line no-undef
const h1 = h2 = h3 = 'title'
const { a, b: bAlias, ...rest } = { a: 1, b: 2, c: 3, d: 4 }
const [x, y, ...others] = [1, 2, 3, 4]
const e = 1, f = 'a', { o1, o2: o22 } = object, [a1, a2] = array

lua

local u;
local v;
u, v = unpack(array{
  1,
  2
});
local foo = 1;
local object = {
  o1 = 'o1',
  o2 = 'o2'
};
local array = array{
  1,
  2
};
local h3 = 'title';
local h2 = h3;
local h1 = h2;
local a, bAlias, rest;
do
  local __tmp = {
    a = 1,
    b = 2,
    c = 3,
    d = 4
  };
  a = __tmp.a;
  bAlias = __tmp.b;
  rest = {};
  for k, v in pairs(__tmp) do
    if k ~= "a" and k ~= "b" then
      rest[k] = v
    end
  end
end;
local x, y, others;
do
  local __tmp = array{
    1,
    2,
    3,
    4
  };
  x = __tmp[1];
  y = __tmp[2];
  others = {};
  for __i = 3, # __tmp do
    others[# others + 1] = __tmp[__i]
  end
end;
local e = 1;
local f = 'a';
local o1, o22;
do
  local __tmp = object;
  o1 = __tmp.o1;
  o22 = __tmp.o2
end;
local a1, a2;
do
  local __tmp = array;
  a1 = __tmp[1];
  a2 = __tmp[2]
end

class

js

class BasePosition {
  say(word = 'base haha') {
    console.log(`Base say: ${word}`)
  }
}
class Position extends BasePosition {
  static insCount = 0
  start = 0
  end = 1;
  constructor(name, x = 1, y = 2, ...numbers) {
    super()
    Position.insCount++
    this.name = name
    this.x = x
    this.y = y
    this.numbers = numbers
  }
  static echoInsCount() {
    console.log(this.insCount)
  }
  echoPosition() {
    console.log(this.name, this.x, this.y)
  }
  echoNumbersLength() {
    console.log('numbers length:', this.name, this.numbers.length)
  }
  say(word = 'haha') {
    super.say(word)
    console.log(`${this.name} say: ${word}, first number is ${this.numbers[0]}`)
  }
}

const p1 = new Position('p1', 1, 2, 3, 4)
Position.echoInsCount()
const p2 = new Position('p2', 10, 20, 30, 40)
Position.echoInsCount()
p1.echoPosition()
p2.echoPosition()
p1.say('hello')
p1.say.call(p2)
p1.echoNumbersLength('a', 'b', 'c')
p1.echoNumbersLength.apply(p2, [1, 2])

lua

local BasePosition = setmetatable({}, {
  __call = function(t)
    local self = t:new();
    self:constructor();
    return self;
  end
})
BasePosition.__index = BasePosition
function BasePosition.new(cls)
  return setmetatable({}, cls)
end
function BasePosition:constructor()
end
function BasePosition:say(word)
  if word == nil then
    word = 'base haha'
  end
  print(string.format([=[Base say: %s]=], word))
end;
local Position = setmetatable({}, {
  __index = BasePosition,
  __call = function(t, name, x, y, ...)
    local self = t:new();
    self:constructor(name, x, y, ...);
    return self;
  end
})
Position.__index = Position
Position.insCount = 0
function Position.new(cls)
  return setmetatable({
    start = 0,
    ["end"] = 1
  }, cls)
end
function Position:constructor(name, x, y, ...)
  if x == nil then
    x = 1
  end;
  if y == nil then
    y = 2
  end
  local numbers = {
    ...
  };
  BasePosition.constructor(self);
  Position.insCount = Position.insCount + 1;
  self.name = name;
  self.x = x;
  self.y = y;
  self.numbers = numbers
end
function Position:echoInsCount()
  print(self.insCount)
end;
function Position:echoPosition()
  print(self.name, self.x, self.y)
end;
function Position:echoNumbersLength()
  print('numbers length:', self.name, # self.numbers)
end;
function Position:say(word)
  if word == nil then
    word = 'haha'
  end
  BasePosition.say(self, word);
  print(string.format([=[%s say: %s, first number is %s]=], self.name, word, self.numbers[1]))
end;
local p1 = Position('p1', 1, 2, 3, 4);
Position:echoInsCount();
local p2 = Position('p2', 10, 20, 30, 40);
Position:echoInsCount();
p1:echoPosition();
p2:echoPosition();
p1:say('hello');
p1.say(p2);
p1:echoNumbersLength('a', 'b', 'c');
p1.echoNumbersLength(p2, unpack(array{
  1,
  2
}))

export

js

module.exports.a = {}
module.exports['b'] = {}

const c = 1
const d = 2
const e = 3

export default { c }
export { d }
export { e as f }
export const g = 1, h = 2;
export function foo() { }

lua

local _M = {}
local c = 1;
local d = 2;
local e = 3;
local g = 1;
local h = 2;
local function foo()
end
_M.a = {};
_M['b'] = {};
_M.default = {
  c = c
};
_M.d = d;
_M.f = e;
_M.g = g;
_M.h = h;
_M.foo = foo
return _M

function

js

const foo = { bar() { } }
const bar = 'bar'
// translate foo.bar() => foo:bar()
foo.bar()
foo[bar]()
foo[1]()
foo['bar']()
foo['bar' || 'foo']();
[].concat([1, 2, 3]);
/a/.exec();
foo.map(e => { return e.name })
foo.map(e => e.name)
foo.map(function (e) { return e.name })
const func1 = (x = 1, y = 2, ...args) => [x, y, ...args]

function func2(x = [], y = {}, ...args) {
  return { x, y, ...args }
}

// first uppercase is treated as class
function Echo(x = 1, y = 2, ...args) {
  this.x = x
  this.y = y
  this.args = args
}
// xx.prototype.yy => xx:yy
Echo.prototype.echoX = function () {
  console.log(this.x)
}
Echo.prototype.echoY = function () {
  console.log(this.y)
}

lua

local foo = {
  bar = function ()
  end
};
local bar = 'bar';
foo:bar();
foo[bar](foo);
foo[1](foo);
foo['bar'](foo);
foo['bar' or 'foo'](foo);
(function()
  local __tmp = array{}
  return __tmp:concat(array{
    1,
    2,
    3
  })
end)();
(function()
  local __tmp = [=[a]=]
  return __tmp:exec()
end)();
foo:map(function(e)
  return e.name
end);
foo:map(function(e)
  return e.name
end);
foo:map(function(e)
  return e.name
end);
local func1 = function(x, y, ...)
  if x == nil then
    x = 1
  end;
  if y == nil then
    y = 2
  end
  local args = {
    ...
  };
  return (function()
    local __tmp = array{};
    __tmp[# __tmp + 1] = x;
    __tmp[# __tmp + 1] = y;
    for _, v in ipairs(args) do
      __tmp[# __tmp + 1] = v
    end
    return __tmp
  end)()
end;
local function func2(x, y, ...)
  if x == nil then
    x = array{}
  end;
  if y == nil then
    y = {}
  end
  local args = {
    ...
  };
  return (function()
    local __tmp = {};
    __tmp.x = x;
    __tmp.y = y;
    for k, v in pairs(args) do
      __tmp[k] = v
    end
    return __tmp
  end)()
end;
local Echo = setmetatable({}, {
  __call = function(t, x, y, ...)
    local self = t:new();
    self:constructor(x, y, ...);
    return self;
  end
})
Echo.__index = Echo
function Echo.new(cls)
  return setmetatable({}, cls)
end
function Echo:constructor(x, y, ...)
  if x == nil then
    x = 1
  end;
  if y == nil then
    y = 2
  end
  local args = {
    ...
  };
  self.x = x;
  self.y = y;
  self.args = args
end;
function Echo:echoX()
  print(self.x)
end;
function Echo:echoY()
  print(self.y)
end

if

js

const a = 1
if (a === 1) {
  print(a)
}
if (a) {
  print(a)
}
if (!a) {
  print(a)
}
if (a === 1) {
  print(a)
} else {
  print(2)
}

if (a === 1) {
  print(a)
} else if (a == 2) {
  print(2)
} else {
  print(3)
}

lua

local a = 1;
if a == 1 then
  print(a)
end;
if a then
  print(a)
end;
if not a then
  print(a)
end;
if a == 1 then
  print(a)
else
  print(2)
end;
if a == 1 then
  print(a)
else
  if a == 2 then
    print(2)
  else
    print(3)
  end
end

import

js

import g from "bar"
import { foo } from "bar"
import { a as b } from "bar"
import * as c from "bar"
import d, { e as eAlias, f } from "bar"

lua

local g = require("bar").default;
local foo = require("bar").foo;
local b = require("bar").a;
local c = require("bar");
local d, eAlias, f;
do
  local _esModule = require("bar")
  d = _esModule.default;
  eAlias = _esModule.e;
  f = _esModule.f
end

index0To1

js

const a = []
const i = a[0]

lua

local a = array{};
local i = a[1]

keywords

js

// lua keywords
const Obj = { and: 'real' }

Obj.prototype.f1 = function () { }
Obj.prototype.end = function () { }
print(Obj.end)

const arr = [true, false]

lua

local Obj = {
  ["and"] = 'real'
};
function Obj:f1()
end;
Obj["end"] = function(self)
end;
print(Obj["end"]);
local arr = array{
  true,
  false
}

loop

js

const arr = [];
for (let i = 0; i <= arr.length; i++) {
  print(i);
}
for (i = 0; i < 10; i = i + 2) {
  print(1);
}
for (const e of arr) {
  print(e);
  break;
}
for (const [a, b] of arr) {
  if (b === 1) {
    continue;
  }
  print(a);
}

for (const key in arr) {
  print(key);
}

while (1) {
  print("a");
}

for(;;) {

}

lua

local arr = array{};
do
  local i = 0
  while i <= # arr do
    print(i)
    i = i + 1
  end
end;
do
  i = 0
  while i < 10 do
    print(1)
    i = i + 2
  end
end;
for _, e in ipairs(arr) do
  print(e);
  break
end;
for _, esPairs in ipairs(arr) do
  local a, b = unpack(esPairs);
  if b == 1 then
    goto continue
  end;
  print(a)
  ::continue::
end;
for key, __ in pairs(arr) do
  print(key)
end;
while 1 do
  print("a")
end;
do
  while 1 do
  end
end

multipleReturnForLuaFunction

js

let [ok, res] = pcall(foo)
let [ok2, res2] = xpcall(foo)
let [e1, e2] = unpack(bar)
// comparision
let [ok3, res3] = pcall2(foo)
let [ok4, ...res4] = xpcall(foo)

lua

local ok, res = pcall(foo);
local ok2, res2 = xpcall(foo);
local e1, e2 = unpack(bar);
local ok3, res3;
do
  local __tmp = pcall2(foo);
  ok3 = __tmp[1];
  res3 = __tmp[2]
end;
local ok4, res4;
do
  local __tmp = xpcall(foo);
  ok4 = __tmp[1];
  res4 = {};
  for __i = 2, # __tmp do
    res4[# res4 + 1] = __tmp[__i]
  end
end

object

js

const foo = 'bar'
const d1 = {
  foo: 1,
  [foo]: 2,
  "k3": 3,
  [5]: 5,
  // "true":7,
  [true]: 6
}
const d2 = { end: 1, end1: 2 }
const d3 = { end: 1, end1: 2, ...d2, and: 3 }
const a = { ["true"]: 1, [true]: 2 }
const d = {
  ...d3,
  foo: 1,
  end() { },
  end2() { },
};

// local a = { ["true"] = 1, [true] = 2 }
// print(a['true'], a[true])

lua

local foo = 'bar';
local d1 = {
  foo = 1,
  [foo] = 2,
  ["k3"] = 3,
  [5] = 5,
  [true] = 6
};
local d2 = {
  ["end"] = 1,
  end1 = 2
};
local d3 = (function()
  local __tmp = {};
  __tmp["end"] = 1;
  __tmp.end1 = 2;
  for k, v in pairs(d2) do
    __tmp[k] = v
  end;
  __tmp["and"] = 3
  return __tmp
end)();
local a = {
  ["true"] = 1,
  [true] = 2
};
local d = (function()
  local __tmp = {};
  for k, v in pairs(d3) do
    __tmp[k] = v
  end;
  __tmp.foo = 1;
  __tmp ["end"] = function ()
  end;
  __tmp .end2 = function ()
  end
  return __tmp
end)()

operator

js

const obj = {}
let a = 1
typeof a
print(a instanceof obj)
const x = a ? 1 : 2
a = !!a
const k = obj?.b;
const o = a?.[k]?.['c']?.e;
obj.func?.(1, ...[1, 2, 3]);
a.b?.c?.();
a ?? 'hello';
obj.n ??= 100;
print(a && x || k)
print(a && (x || k))
a += 1
a -= 1
a *= 1
a /= 1
a %= 1
a &&= k
a ||= k
a >> 2
2 << a
a & 2
a &= 2
a | 2
a |= 2
a ^ 2
a ^= 2
a ** 2
a **= 2
~a

lua

local bit = require("bit")
local obj = {};
local a = 1;
type(a);
print(getmetatable(a) == obj);
local x = (function()
  if a then
    return 1;
  else
    return 2;
  end
end)();
a = not not a;
local k = (function()
  if obj == nil then
    return nil
  else
    return obj.b
  end
end)();
local o = (function()
  if a == nil then
    return nil
  elseif a[k] == nil then
    return nil
  elseif a[k]['c'] == nil then
    return nil
  else
    return a[k]['c'].e
  end
end)();
(function()
  local __tmp = obj.func
  if __tmp == nil then
    return nil
  elseif type(__tmp) ~= 'function' then
    error('obj.func is not a function')
  else
    return obj:func(1, unpack(array{
      1,
      2,
      3
    }))
  end
end)();
(function()
  local __tmp = (function()
    if a.b == nil then
      return nil
    else
      return a.b.c
    end
  end)()
  if __tmp == nil then
    return nil
  elseif type(__tmp) ~= 'function' then
    error('a.b.c is not a function')
  else
    return a.b.c()
  end
end)();
(function()
  if a == nil then
    return 'hello'
  else
    return a
  end
end)();
obj.n = (function()
  if obj.n == nil then
    return 100
  else
    return obj.n
  end
end)();
print(a and x or k);
print(a and (x or k));
a = a + 1;
a = a - 1;
a = a * 1;
a = a / 1;
a = a % 1;
a = a and k;
a = a or k;
bit.rshift(a, 2);
bit.lshift(2, a);
bit.band(a, 2);
a = bit.band(a, 2);
bit.bor(a, 2);
a = bit.bor(a, 2);
bit.bxor(a, 2);
a = bit.bxor(a, 2);
math.pow(a, 2);
a = math.pow(a, 2);
bit.bnot(a)

optionalNullish

js


// optional
const a = 1, b = 'foo', obj = {}, args = []

// basic
const m = a?.b;

//chain
const o = a?.[b]['c']?.e;

// optional call
obj.func?.(1, ...args);

// chain optional call
a.b?.c.d?.();

// nullish
a ?? 'hello';
a.b() ?? 'hello';
const d = {}
d.n ??= 100;

lua

local a = 1;
local b = 'foo';
local obj = {};
local args = array{};
local m = (function()
  if a == nil then
    return nil
  else
    return a.b
  end
end)();
local o = (function()
  if a == nil then
    return nil
  elseif a[b]['c'] == nil then
    return nil
  else
    return a[b]['c'].e
  end
end)();
(function()
  local __tmp = obj.func
  if __tmp == nil then
    return nil
  elseif type(__tmp) ~= 'function' then
    error('obj.func is not a function')
  else
    return obj:func(1, unpack(args))
  end
end)();
(function()
  local __tmp = (function()
    if a.b == nil then
      return nil
    else
      return a.b.c.d
    end
  end)()
  if __tmp == nil then
    return nil
  elseif type(__tmp) ~= 'function' then
    error('a.b.c.d is not a function')
  else
    return a.b.c.d()
  end
end)();
(function()
  if a == nil then
    return 'hello'
  else
    return a
  end
end)();
(function()
  local __tmp = a:b()
  if __tmp == nil then
    return 'hello'
  else
    return __tmp
  end
end)();
local d = {};
d.n = (function()
  if d.n == nil then
    return 100
  else
    return d.n
  end
end)()

others

js

const foo = {}
delete foo.bar
print(foo.length)
const URL_PATTERN = /^https?:\/\/.*?\//

const constraints = {
  'foo': 'baz',
  foo: 'bar',
  ...route.opts.constraints,
  [httpMethodStrategy.name]: route.method
}

lua

local foo = {};
foo.bar = nil;
print(# foo);
local URL_PATTERN = [=[^https?:\/\/.*?\/]=];
local constraints = (function()
  local __tmp = {};
  __tmp['foo'] = 'baz';
  __tmp.foo = 'bar';
  for k, v in pairs(route.opts.constraints) do
    __tmp[k] = v
  end;
  __tmp[httpMethodStrategy.name] = route.method
  return __tmp
end)()

smartAddition

js

const s1 = b + c
const s2 = b + 'c'
const s3 = 'c' + b
const s4 = a + b + 'c'
const s7 = 'a' + b + c
const s5 = a + b + 'c' + d + f
const s6 = a + b + c + d + f

lua

local s1 = b + c;
local s2 = b .. 'c';
local s3 = 'c' .. b;
local s4 = a .. b .. 'c';
local s7 = 'a' .. b .. c;
local s5 = a .. b .. 'c' .. d .. f;
local s6 = a + b + c + d + f

spread

js

const a = [1, 2, 3]
const d = { x: 1, y: 2, z: 3 }
const [v1, v2, ...v] = [4, 5, ...a]
const { x: k1, y: k2, ...k } = { ...d, foo: 'bar' }

lua

local a = array{
  1,
  2,
  3
};
local d = {
  x = 1,
  y = 2,
  z = 3
};
local v1, v2, v;
do
  local __tmp = (function()
    local __tmp = array{};
    __tmp[# __tmp + 1] = 4;
    __tmp[# __tmp + 1] = 5;
    for _, v in ipairs(a) do
      __tmp[# __tmp + 1] = v
    end
    return __tmp
  end)();
  v1 = __tmp[1];
  v2 = __tmp[2];
  v = {};
  for __i = 3, # __tmp do
    v[# v + 1] = __tmp[__i]
  end
end;
local k1, k2, k;
do
  local __tmp = (function()
    local __tmp = {};
    for k, v in pairs(d) do
      __tmp[k] = v
    end;
    __tmp.foo = 'bar'
    return __tmp
  end)();
  k1 = __tmp.x;
  k2 = __tmp.y;
  k = {};
  for k, v in pairs(__tmp) do
    if k ~= "x" and k ~= "y" then
      k[k] = v
    end
  end
end

stringTemplate

js

const foo = 5
const s = `1.${2}.3.${'4'}.${foo}`

lua

local foo = 5;
local s = string.format([=[1.%s.3.%s.%s]=], 2, '4', foo)

switch

js

const c = 'v2'

switch (c) {
  case 'v1':
    print(1)
    break;
  default:
    break;
}

switch (c) {
  case 'v1':
    print(1)
    break
  case 'v2':
  case 'v3':
    print(2)
    break
  default:
    break;
}

lua

local c = 'v2';
repeat
  local caseExp = c
  if caseExp == 'v1' then
    print(1);
    break
  else
    break
  end
until (false);
repeat
  local caseExp = c
  if caseExp == 'v1' then
    print(1);
    break
  elseif caseExp == 'v2' or caseExp == 'v3' then
    print(2);
    break
  else
    break
  end
until (false)

throw

js

const test = ''
if (test) {
  throw new Error('!')
}
if (test) {
  throw new CustomError({ message: "custom error" })
}
if (test) {
  throw '!!'
}
if (test) {
  throw { message: 'bare object error' }
}

lua

local test = '';
if test then
  error('!')
end;
if test then
  error({
    message = "custom error"
  })
end;
if test then
  error('!!')
end;
if test then
  error({
    message = 'bare object error'
  })
end

transform

js

const a = { b: '' }
String(1)
a.b.toString()
JSON.stringify({})
JSON.parse('{}')
Number('2')
parseInt('2')
parseFloat('1')
Array.isArray(1)

lua

local isarray = require("table.isarray");
local cjson = require("cjson")
local a = {
  b = ''
};
tostring(1);
tostring(a.b);
cjson.encode({});
cjson.decode('{}');
tonumber('2');
math.floor('2');
tonumber('1');
isarray(1)

trycatch

js

try {
  const res = parseInt('fooo')
  print(res)
} catch (error) {
  // rename error to _err to prevent shadow lua's error function name
  console.log(error)
}

try {
  const res = parseInt('fooo')
  print(res)
} catch (error1) {
  console.log(error1)
}

lua

local ok  , _err = pcall(function()
  local res = math.floor('fooo');
  print(res)
end);
if not ok then
  print(_err)
end;
local ok  , error1 = pcall(function()
  local res = math.floor('fooo');
  print(res)
end);
if not ok then
  print(error1)
end

updateExpression

js

// NOTE: both i++ and ++i means ++i to lua, don't use i++ in expression context!
// in statement context, use i = i ? 1
i++;
--i;
// otherwise use a callback
let a = --i
let b = i++
if (--i) {
  print(i)
}

lua

i = i + 1;
i = i - 1;
local a = (function ()
  i = i - 1;
  return i
end)();
local b = (function ()
  i = i + 1;
  return i
end)();
if (function ()
  i = i - 1;
  return i
end)() then
  print(i)
end