vpalos.com // milk, cookies, segfaults…

Lua2C, an updated version

by Valeriu Paloş on July 22, 2010

I know I have been “missing in action” lately but I am working furiously, and I seem to have too little time for my blog (very sad face). But, just for a breath of fresh air, I thought I’d share something with the world.

Entering lua2c.lua

Lately I became quite interested in Lua (a lot actually). It has phenomenal speed, exceptional interfacing with C and some features and libraries that just make my day (i.e. coroutines, lpeg, lua-ev and others), and since I needed to embed some Lua scripts (entirely) in a C project I’m currently working on, I ended up adapting Mike Edgar’s “bin2c.lua” script (which takes a Lua script and turns it into a C header file) to suit my needs.

Basic functionality

Specifically, this adaptation generates a function that takes a Lua state as the only argument and then runs the embedded Lua code in the given state after which it returns the status (as opposed to putting the code straight in the top-level scope of the generated file). This makes it easier to embed code in C and then invoke it, and also to apply the same code onto multiple Lua states (e.g. multiple threads).

Check further down for a short usage sample.

Options

  • -c compiles to Lua bytecode for (slightly) faster loading (does not work with LuaJIT)
  • -s minifies the source code before embedding to minimize wastes
  • -e applies a mild (XOR-based) obfuscation to prevent embedding of plain text
  • -u produces a (void) function that never fails and panics on errors

Download

So here it is, enjoy: lua2c.lua (MIT license, same as Lua)

Just to be clear

For example, given a lua script file called ‘test.lua’ (shown below) this is how we would go about embedding it into a simple C program called ‘test.c’.

-- test.lua: a small sign that we're up and running...
print("I am an embedded Lua script running inside a C binary! Hmm... cozy!")

And here is the C file (rather overly commented)…

/*
 * Include our embedded script.
 */
#include "test.h"

/*
 * Invoke the script.
 */
int main(int argc, char* argv[]) {

    // create a Lua state
    lua_State* L = lua_open();

    // load basic libraries into state
    luaL_openlibs(L);

    // invoke embedded script into state
    // by calling the generated function
    load_test_lua(L);

    // finish
    lua_close(L);
    return 0;
}

And here’s how we can compile all this into an executable. Note that I tested this on Ubuntu Lucid and the following packages had to be installed: lua5.1, liblua5.1-0-dev, liblua5.1-lpeg2, liblua5.1-bitop0. Your configuration may differ, and if you don’t have the pre-built Lua packages, you will have to install them manyally (along with the dependencies)!

# generate (unprotected, minified and encrypted) C code
lua lua2c.lua -seu test.lua > test.h

# compile C program against Lua library
gcc test.c -o test `pkg-config lua5.1 --libs --cflags`

# run program
./test
I am an embedded script running from inside a C binary! Hmm... cozy!

Let me know if it works fr you, or if you think of some cool improvement! 😀
Cheers!

3 thoughts on “Lua2C, an updated version

  1. Jeff Hill says:

    === modified file src/lua/utility/lua2c.lua
    — src/lua/utility/lua2c.lua 2014-05-08 16:14:52 +0000
    +++ src/lua/utility/lua2c.lua 2014-05-08 22:33:55 +0000
    @@ -224,7 +224,7 @@
    ok, so probably we dont open the output file in binary mode

    — Submit.
    if ( destination ) then
    – local outfile = assert ( io.open(destination, ‘wb’ ) )
    + local outfile = assert ( io.open(destination, ‘w’ ) )
    outfile:write ( output )
    outfile:close ()
    else

  2. Jeff Hill says:

    Also maybe a lua newbee can add an output file option

    === modified file src/lua/utility/lua2c.lua
    — src/lua/utility/lua2c.lua 2014-05-08 15:24:36 +0000
    +++ src/lua/utility/lua2c.lua 2014-05-08 15:53:29 +0000
    @@ -45,6 +45,9 @@
    result status, but instead terminates the program on error. This
    behaviour is desirable in many cases when it is unacceptable for
    an embedded file not to load successfully.
    +
    + -o
    + specifies the name of the output file

    Compiles a Lua source into a C header file which can be included into
    an existing C program via the ‘#include’ directive. This file defines
    @@ -70,20 +73,28 @@

    — Arguments.
    local source
    +local destination
    local do_help
    local do_compile
    local do_minify
    local do_encrypt
    local do_unsafe
    +local do_out_file
    for i, v in ipairs(arg or {}) do
    – if v:find(‘^-.+$’) then
    – do_help = v:find(‘h’, 1, true)
    – do_compile = v:find(‘c’, 1, true)
    – do_minify = v:find(‘s’, 1, true)
    – do_encrypt = v:find(‘e’, 1, true)
    – do_unsafe = v:find(‘u’, 1, true)
    – else
    – source = arg[i]
    + if ( do_out_file ) then
    + destination = arg[i]
    + do_out_file = false
    + else
    + if v:find(‘^-.+$’) then
    + do_help = v:find(‘h’, 1, true)
    + do_compile = v:find(‘c’, 1, true)
    + do_minify = v:find(‘s’, 1, true)
    + do_encrypt = v:find(‘e’, 1, true)
    + do_unsafe = v:find(‘u’, 1, true)
    + do_out_file = v:find(‘o’, 1, true)
    + else
    + source = arg[i]
    + end
    end
    end
    do_help = do_help or not source
    @@ -94,12 +105,12 @@
    return
    end

    — Read file.
    +– Read input file.
    local identity = source:lower():gsub(‘^.+/’, ”):gsub(‘[^%w_]’, ‘_’)
    -local file = assert(io.open(source, ‘rb’))
    -local content = file:read’*a’
    +local infile = assert(io.open(source, ‘rb’))
    +local content = infile:read’*a’
    local length = content:len()
    -file:close()
    +infile:close()

    — Minify.
    if do_minify then
    @@ -212,4 +223,10 @@
    identity, length, content, length, source)

    — Submit.
    -io.write(output)
    +if ( destination ) then
    + local outfile = assert ( io.open(destination, ‘wb’ ) )
    + outfile:write ( output )
    + outfile:close ()
    +else
    + io.write(output)
    +end

  3. Jeff Hill says:

    Hi,

    A lua newbee am I, but perhaps some changes may be needed for lua 5.2 and latest lpeg.

    Thanks,

    Jeff Hill

    === modified file src/lua/utility/lua2c.lua
    — src/lua/utility/lua2c.lua 2014-05-08 14:58:55 +0000
    +++ src/lua/utility/lua2c.lua 2014-05-08 15:00:30 +0000
    @@ -13,8 +13,8 @@

    — Requirements.
    -require’bit’
    -require’lpeg’
    +–require’bit’
    +local lpeg = require”lpeg”

    — Aliases.
    local P, R, V, S, C, Cs, Cb, Cg, Cmt =
    @@ -113,7 +113,7 @@
    level = assert(text:find(‘]’..level..’]’, start, true),
    ‘unclosed long brackets’)
    return level + 2
    – end,
    + end * 1,
    string = (‘”‘ * ((P’\\\\’ + P’\\”‘ + 1) – P'”‘)^0 * ‘”‘) +
    (“‘” * ((P’\\\\’ + P”\\'” + 1) – P”‘”)^0 * “‘”),
    strings = V’heredoc’ + V’string’,

Don't keep it to yourself!...