Lunacy is a fork of Lua 5.1.5 (why 5.1? Because it’s about 20% smaller than Lua 5.3 and because there’s a lot of code based on Lua 5.1: Roblox Luau, LuaJIT, Gopher Lua, Adobe Lightroom Classic, etc.) designed to be a tiny yet powerful stand alone scripting language.
The reason for this fork is to have a security hardened version of Lua 5.1.
The main differences from Lua 5.1 are:
pairs() is non-deterministicmath.random() is cryptographically stronglfs, bit32, and spawnerI have written a PDF book on how to program in Lunacy; while this doesn’t cover everything Lunacy can do, it covers the kind of text processing I used to do in Perl. Source files for this book are also available.
There is also a PDF reference manual which covers pretty much everything Lunacy can do. This manual is also available in other formats.
This can be compiled as a tiny Windows binary, and it also compiles and runs in Linux. Since Lunacy is written in pure C, it should be able to compile pretty much anywhere else, but this has not been tested.
Lunacy is available at GitHub, Sourcehut, and Codeberg.
Lunacy has a web page.
To compile, one needs a POSIX standard make program and a C compiler
with the name gcc. If one wishes to use another C compiler, edit the
file Makefile and change the line CC= gcc to use the compiler in
question.
To compile on a Linux or compatible system (e.g. Cygwin) with readline
support (so, when invoked from a terminal, one has arrow history),
enter the src/ directory and invoke the make command as follows:
make -f Makefile.readline
Note that the resulting binary will be GPL licensed. If this is not desired, and arrow history is wanted, Lunacy also has support for editline. To compile Lunacy with editline support, after installing editline:
make -f Makefile.editline
To compile this on a Mingw system:
make -f Makefile.mingw32
To compile this on another system:
make
The code is compatible with gcc (gcc 3.4.2 and gcc 11.3.0), clang (clang
8.0.1), and will probably compile in other compilers, including C++
compilers, without issue, e.g. it has been compiled and runs
with tcc (tcc 0.9.25 for Windows).
If using another name for the Makefile, e.g. Makefile.foo (which would
be invoked as make -f Makefile.foo), be sure to edit the Makefile used
and change the line which sets its MAKEFILE value.
bin/ folder.os.clock(),
os.date(), and os.difftime() have all been removed (If you want
to play with dates, use a 64-bit compile of Lua so we don’t
have headaches come 2038). os.time() is here (and is Y2038 compliant,
both as a 32-bit and 64-bit build: The Windows build uses the Y2038
compliant “FileTime” API, and the 32-bit *NIX build gives negative
timestamps post-Y2038 values), but only returns the current
system time.math.random() uses RadioGatún[32] instead of rand() to get higher
quality random numbers. math.random() works as usual, but there is
now math.rand16(), which generates 16-bit random numbers (i.e. a
random integer between 0 and 65,535) in a manner which allows one to
recreate RadioGatún[32]’s test vectors. math.randomseed() takes a
NULL-terminated string (not number) as a random seed (the seed can
not have ASCII NULLs in it). If a number is given to
math.randomseed(), Lua’s coercion converts it in to a string.math.random() routines can also be called
with rg32 aliases: rg32.randomseed(), rg32.random(), and
rg32.rand16(). This allows one to use Lunacy’s random number
generator via a third party library in stock Lua; Lunacy-compatible
libraries for stock Lua are at https://github.com/samboy/LUAlibsbit32 libs, based on the Lua 5.2 and Lua 5.3 API, are here for bit
manipulation.spawner is here so we can have a
version of Python’s old popen2 in Luancy. For users of stock Lua,
this library for Lua is available at https://github.com/samboy/LUAlibs0 and 9) or with (. This gives Lunacy
“desktop calculator” support, allowing one to easily use it to
perform numeric computations.2022-12-06 Editline support added. ( can now be first character
on lines to enable desktop calculator mode. Option to compile without
spawner and lfs. MaraDNS’s version of Lunacy is now based on the
2022-12-06 version of Lunacy.2022-11-04 Second part of HalfSiphash key is now correctly
32 bits instead of 64 bits. This does not affect how HalfSip runs,
except it will now run a little faster since a 64-bit number doesn’t
need to be converted in to a 32-bit one.2022-10-23 lunacy.today() returns nil if time_t is 32-bit.
This way, people will deal with the Y2038 issues now instead of
on January 19, 2038. This does not affect the 32-bit Windows build,
and it does not affect 32-bit implementations of Linux with a
64-bit time_t (Alpine Linux, etc.). This only affects the rare
32-bit Linux distribution subbornly holding on to the 32-bit time_t.2022-09-14 Add lunacy.today()2022-08-11 Bugfix: Make sure bit32.rrotate doesn’t ever engage in
undefined behavior2021-07-28 Bugfix: One line patch to fix CVE-2014-54612021-03-22 Bugfix: math.pi returns pi again.2021-03-21 When run in terminal mode, if the first character in an
expression is a number, we return the result of the expression. In
other words, Lunacy now is a “quick and simple” desktop calculator:
Type in “lunacy”, then type in a numeric math expression one wants to
solve, e.g. 2 ^ 35, and Lunacy will return the answer without needing
to have the line begin with =.2021-03-06 Fully document lfs (luafilesystem) in Lunacy manual.
Remove spawner.c *NIX compile-time warnings. Lunacy binary still at
2021-02-22.2021-02-27 Documentation update for Lunacy 2021-02-22. spawner now
has documentation; rg32 random number routines (also with math
aliases) are now fully documented. Basic documentation for lfs with
pointer to full documentation added. Lunacy code unchanged.2021-02-22 rg32.runmill added to code; Lunacy date updated.2021-02-21 Add rg32.randomseed, rg32.random, and rg32.rand16
aliases for math.random, math.randomseed, and math.rand16. This
allows one to write code using the same random number generator in
both Lunacy and stock Lua with the libs available at
https://github.com/samboy/LUAlibs2021-02-15 Add spawner routines that allow Lunacy to have two-way
pipes (both reading and writing) with child processes.2020-12-06 Restore simple os.time() (numeric *NIX timestamp) routine
which uses a Y2038-compatible Windows API (filetime), full Y2038 support
when time_t is 64-bit (i.e. most modern *NIX OSes), and support until
around 2100 if time_t is 32-bit (when time_t is negative, we add
2^32 seconds to the time).2020-08-12 Lunacy now uses HalfSipHash-1-3 for hash compression.Lunacy, by default, uses HalfSipHash-1-3 as its hash compression algorithm. This has reasonable security, while being very fast when Lunacy is compiled as a 32-bit or 64-bit binary.
As a result, pairs() is no longer deterministic. Let’s look at this
code
#!/usr/bin/env lunacy
foo = {a=1, b=2, c=3, d=4, e=5}
out = ""
for a in pairs(foo) do
out = out .. a .. " "
end
print(out)
In Lua 5.1, the above will generate the same output over and over (in Lua 5.4.4 and LuaJIT, multiple runs of the above code will generate different outputs); in Lunacy, each invocation of the above code will generate a different output.
In cases where we need a deterministic pairs(), see sPairs()
below.
If running on a 64-bit system, it may be desirable to
compile Lunacy to use 64-bit SipHash-1-3. To do so, edit
src/Makefile to add the flag -DFullSipHash, e.g.:
CFLAGS= -O3 -Wall $(MYCFLAGS) -DFullSipHash
To use 64 bit SipHash-2-4, likewise add -DSIP24:
CFLAGS= -O3 -Wall $(MYCFLAGS) -DFullSipHash -DSIP24
I have run a number of benchmarks with Lunacy, my fork of Lua 5.1, to see how much changing the SipHash variant used affects performance, for both 32-bit (386) and 64-bit (x86_64) binaries.
Conclusion: I will use HalfSipHash31 as the hash compression algorithm, for both 32-bit and 64-bit builds of Lunacy.
The binaries have been compiled using GCC 8.3.1, in CentOS 8, using an older Core Duo T8100 chip from 2008. The benchmark consisted of loading and processing a bunch of COVID-19 data in to large tables taking up 550 (32-bit) or 750 megs (64-bit) of memory. This real-world benchmark (it is the exact same code I use to build an entire COVID-19 tracking website) was done multiple times, to minimize speed fluctuations from outside factors, against the following setups:
And the following string hash compression functions:
Here are the results, where lower numbers are better (less time needed to run the benchmark):
lunacy64-noSipHash 197.801
lunacy64-sipHash13 203.457
lunacy64-SipHalf13 203.507
lunacy64-sipHash24 210.043
lunacy32-noSipHash 240.898
lunacy32-SipHalf13 246.995
lunacy32-sipHash13 265.916
lunacy32-sipHash24 270.226
HalfSipHash-1-3 is as fast as full SipHash-1-3 on 64-bit CPUs, while being quite a bit faster for 32-bit binaries compared to 64-bit sipHash.
HalfSipHash-1-3 is only 2.5% slower on 32-bit machines (compared to Lua’s “stock” hash); it is only 2.9% slower on 64-bit machines.
In Lunacy’s use case, HalfSipHash should provide an adequate security margin; as per what its designer has to say:
HalfSipHash takes its core function from Chaskey and uses the same construction as SipHash, so it should be secure. Nonetheless it hasn’t received the same amount of attention as 64-bit SipHash did. So I’m less confident about its security than about SipHash’s, but it obviously inspires a lot more confidence than non-crypto hashes.
Too, HalfSipHash only has a 64-bit key, not a 128-bit key like SipHash, so only use this as a mitigation for hash-flooding attacks, where the output of the hash function is never directly shown to the caller.
Since pairs() is non-deterministic, in cases where it’s desirable to
iterate through a table in a consistent manner, I have this public domain
Lua/Lunacy code to sort the elements of a table when iterating through
it:
function sPairs(inputTable,sFunc)
if not sFunc then
sFunc = function(a, b)
local ta = type(a)
local tb = type(b)
if(ta == tb)
then return a < b
end
return ta < tb
end
end
local keyList = {}
local index = 1
for k,_ in pairs(inputTable) do
table.insert(keyList,k)
end
table.sort(keyList,sFunc)
return function()
rvalue = keyList[index]
index = index + 1
return rvalue, inputTable[rvalue]
end
end
This code is run the same way one runs pairs(); for example:
foo = {a=1, b=2, c=3, d=4, e=5}
out = ""
for a in sPairs(foo) do
out = out .. a .. " "
end
print(out)
Some other languages based on Lua:
Other embedded languages:
LuaJIT is a high performance implementation of Lua 5.1.
Since development for LuaJIT has slowed down, there are some forks of it:
No AI was used in the development of Lunacy, nor to help with writing the documentation for Lunacy.