=============
What is this?
=============
The main purpose of lua-convert is to allow development of Lua code using a more familiar and more powerful syntax.
Two things:
1. A language preprocessor. This is not entierly unlike the C preprocessor, but it is more powerful and robust.
2. A language converter. You write your code in JavaScript-like language. This code is converted to Lua and run by a Lua backend.
You can use either component or both as you desire. The preprocessing language and the "JavaScript-like"
language it translates from are both designed to be syntax-highlighter compatable with regular JavaScript.
=============
What's it do?
=============
Turns this JavaScript:
var x = 0;
for(var i = 0; i < 20; ++i) {
print(x += .1, i);
}
into this Lua:
local x = 0;
do
local i = 0;
local _t0 = false;
while i < 20 do
if _t0 then
i = i + 1;
else _t0 = true;
end
x = x + .1;
print(x, i);
end
end
=================
What is this not?
=================
Note that this system converts JS syntax to Lua syntax. It DOES NOT convert languages. / This does not turn functioning JavaScript code
into functioning Lua code.
Lua and JavaScript are two completely different languages, and the nuiances and paradigm differences are modest enough that simple conversion
is not enough. Such a converter could be written, but that is not the scope of this project.
That said if you are doing a one-time hand-conversion of code from JS to Lua, you may find lua-convert useful for a blind first-pass.
====
Why?
====
Lua syntax sucks. It's very simple and doesn't let you do complex statements (and I prefer the general syntax of JS to that of Lua).
Example:
JavaScript:
return a[(b+=6)][c++]('munchies') == 14 ? 'yes' : 'never';
The same thing in Lua:
b = b + 6;
local tmp = c;
c = c + 1;
if a[b][tmp]('munchies') then
return 'yes';
else
return 'never';
end
OWWWWWWWWCHHH!!!
My head hurts from all those extra lines. And a temporary variable. D-X
Sidenote 1: Did you just say "But we should force everyone to code like that! It's easier to understand!"?
I have some important information for you: You shouldn't be programming. Programming is a complex thing. If you can't
understand such syntax you shouldn't be programming. Seriously. I deal with the crap code people like you write at work. You
suck. Go away or study and get better.
Sidenote 2: There's a good reason Lua is simple like this. It's by design. A simpler syntax means Lua can have a simpler, more reliable
parser that takes up less space in your runtime. That doesn't mean we have to deal with it for daily coding, however. We can have the
best of both worlds!
==========
How to use
==========
Manually:
java -jar js-preprocess.jar someFile.js -o someFile.lua
java -jar js-preprocess.jar -i - < someFile.js > someFile.lua
java -jar js-preprocess.jar --slave #use to embed in other applications - run with the program with "--slaveHelp" for details
java -jar js-preprocess.jar --no-translate someFile.js #forgo language translation. Input language is not considered and may be anything.
java -jar js-preprocess.jar --no-preprocess someFile.js #forgo preprocessor, "$$" items are left alone.
As part of a build:
You can also add the conversion as part of your build. See your build system documentation for details. Use the CLI examples above for
examples.
Integrated into your program:
lua-convert was intended to be run by the developer, and was designed with that in mind. Ideally, use a build script or "developer
only" integration to do the conversion. When it comes to release, package the generated Lua, or the bytecode from the generated Lua.
That said, sometimes your end user IS a developer and you want them to be able to use the features of lua-convert on the fly.
You can integrate lua-convert into any program written in any language by spawning a sub-process and communicating wia stdin/stdout.
As a matter of simplicity (and to avoid dependency hell), lua-convert is written in Java and should run fine on any machine of any
archetecure with a decent JVM.
To use it, simply shell_exec/system/ProcessBuilder/etc. "java -jar js-preprocess.jar --slave". Then use the
simple piped binary text interface explained in `java -jar js-preprocess.jar --slaveHelp` to communicate.
================
The preprocessor
================
Everything starting with "$$" is a preprocessor command.
Lines like this:
$$UGLY_CONSTANT = "bobbleHeads";
doFoo(m.$$UGLY_CONSTANT);
$$magic = function(n) {
return "alpha" + n;
};
var alpha3 = "tastes good";
var i = "$$magic('bet') soup " + $$magic(3);
$$print("i += " + (3 + 9) + ";");
turn into this:
doFoo(m.bobbleHeads);
var alpha3 = "tastes good";
var i = "alphabet soup " + alpha3;
i += 12;
As long as the line starts with "$$", any valid (real) JavaScript may follow. The code is evaluated and variables
remembered for later use. You can also use other preprocessor functions such as $$print(value) to embed it into the
code at that point.
If the statement is too long for one line, the preprocessor will consume following lines until it can make a full JavaScript
statement out of it.
If the line does not begin with a "$$", you can only use single values or simple function calls such as:
bob = $$TIGER;
sam = $$makeAsciiArt("firstName", "lastName");
But not
sam = $$calculate("apples" + bananas);
Note that "$$" inside strings and comments are also parsed and replaced.
---------------------
Preprocessor Details:
---------------------
Processing steps:
For every line in the file:
If the line starts with "$$" everything that "appears" to be part of its syntax is removed from the file and evaluated.
For every "$$" that remains in the file (INCLUDING inside strings and comments):
A simple matching system parses out the refrence/function call and executes it, replacing the call with the returned value.
(Expansion, itself, is not recursive, but the expansion functions can call recursive functions if desired.)
The "simple matching system" mentioned above supports alphanumeric constants, and basic single line strings with no escaped values.
The preprocessor uses Mozilla's Rhino to evaluate the JS, consequently, you can use any of the standard Java shenanigans:
java.lang.System.out.println("I am a potato.");
This means, of course, that you can load up any external files or other resources in the $$ preprocessor,
and that you shouldn't run the preprocessor on any source code you don't trust as it could bork your computer and steal your credit card.
-----------------------
Preprocessor functions:
-----------------------
$$print('someString'):
Injects 'someString' into the line before the current one.
$$include('someFile.inc.js'):
Injects the contents of 'someFile.inc.js' into the line before the current one.
===============
The translator:
===============
The language read by this system is not JavaScript (though the documentation refers to
it as "JS" for brevity) nor is it ECMA Script and does not behave as it either. The syntax is a subset that is
designed to be syntax-highlighting compatible with ECMA Script.
Let's call it Jim Shoes (JS for short).
Primarily, however, it was designed to work as a Lua preprocessor to make the syntax more
"C"-like.
General notes:
Your whitespace will be reformatted, sorry.
Avoid using reserved words from any involved language as variable names.
Everything starting with "$" is reserved for used with the translator -
don't use '$' except as otherwise documented. (Remember: variable names with "$" in them aren't valid in most other languages.)
";" at the end of statements are required
Trailing commas in array/object declrations are fine
The order of operations is different, namely, the bitwise operators |, &, and ^ are the same level and are evaluated directly after
+ and - and before << and >>.
===========================
Notes on conversion to Lua:
===========================
-------------
Simple items:
-------------
Educated guesses would reason these behavors out:
nil === null
JS Array === JS Object === Lua Table
There's no difference between "==" and "===", same with "!=" and "!==".
In JS, "^" means XOR. If you want the converted code to use the native Lua exponent operator, use $pow(a, b).
Constructors: JS: x = new SomeObject(a, b) -> Lua: x = SomeObject.new(a, b)
Normal for(i = 0; i < 20; i += 2) loops will work fine.
Remember that Lua uses (broken) arrays that count from 1 instead of 0. Your code needs to cope with that.
Lua doesn't allow "statements without an effect". Things will break if you do that.
Lua can't handle anon. named functions: "a = function x(b) {return b ? x(b - 1) : b;}" won't work because Lua won't know what x is inside x.
Recall that Lua can't tolerate a return statement unless it is at the end of a block.
//"a ? b : c" translates to "a and b or c" - be wary, as c will be returned if b is false! TODO: this should be implemented in a pre-run if/else
------------
Workarounds:
------------
Some things in Lua are not possible with JS. Use these $ workarounds to generate code exactly how you want:
Any identifier named "this" is changed to "self". (i.e. just use "this" everywhere and it will work fine.)
(
You need to add "this" as a parameter to object methods:
a = { x: 3, n: function(this, w) { print(this.x + w); } };
vs.
a = { x: 3, n: function(w) { print(this.x + w); } };
)
A "method" call is always used when calling a function from a property:
a.someMethod(c, d); -> a:someMethod(c, d);
a.m().n.o(); -> a:m().n:o();
If you have a function (with no self parameter) that you want to call, and it happens to be stored in an object/table:
a.$someMethod(c, d); -> a.someMethod(c, d);//static function
In other words: we use ":" instead of "." ANYWHERE WE CAN. If you don't want that, prefix the name with a "$".
"a,b = 1,2" in JS means "a, (b=1), 2", in Lua it means "(a,b) = (1,2)". Use the magical shortcuts "$a$b = (1, 2)" or "$assign((a, b), (1, 2))"
to get the translated code of "a, b = 1, 2"
Metatables helpers:
a.$meta -> getmetatable(a) - creating and setting a blank one if there is none
a['$meta'] -> a['$meta']
a.$meta.__index = bob -> {local _ = getmetatable(a) or {}; _.__index = bob; setmetatable(a, _);}
//a.prototype = bob -> {local _ = getmetatable(a) or {}; _.__index = bob; setmetatable(a, _);}
Use "for(i in $for(0, 20, 2)) {...}" to get "for i = 0, 20, 2 do ... end" (loop from 0 to 20 in steps of 2)
Access Lua's (mostly-useless) length operator (#) with $len(someValue).
Ultimate ugly workaround: Use $lua("some = lua + code") to have the code injected literally during conversion.
"$varArgs" -> "..."
--------
Gotchas:
--------
Lua and JS are different languages, some things are not compatible. Under the following conditions your code may
not behave as initially expected:
Reordering: To allow you to use actions such as "++" and "echo( (n = (b+=6)) )" (which Lua *really* doesn't support), temporaries and other voodoo
are added to the line before and evaluated before the actual statement. If you find unusual side-effects or related oddities, take
a look at the generated code. You may need to break your line into multiple statements to get the desired result. (TODO: examples)
The translator uses variables starting with "_t" for these and other temporaries. You may want to refrain from starting any of your variables with this prefix.
The bitwise operators (~^&|) require http://bitop.luajit.org/ (included with LuaJIT).
You must use $concat(string1, string2, ...) to concatenate. Addition is always numeric.
Avoid side-effects in control block tests such as "while( (n = getNextBob()) )". Lua can't handle these side effects and it's better for you to
code around it than for me to silently emulate it.
-----
Tips:
-----
Don't forget the semicolons - remember they are required.
Also: http://www.luafaq.org/gotchas.html